I have a generic repository an I am trying to add a GetById method as shown here
C# LINQ to SQL: Refactoring this Generic GetByID method
The problem is my repository does not use System.Data.Linq.DataContext
instead I use System.Data.Entity.DbContext
So I get errors where I try to use
Mapping.GetMetaType
and
return _set.Where( whereExpression).Single();
How can I implement a generic GetById method in CTP5? Should I be using System.Data.Entity.DbContext in my Repository.
Here is the start of my repository class
public class BaseRepository<T> where T : class
{
private DbContext _context;
private readonly DbSet<T> _set;
public BaseRepository()
{
_context = new MyDBContext();
_set = _context.Set<T>();
}
The most basic approach is simply
public T GetById(params object[] keys)
{
_set.Find(keys);
}
If you know that all your entities have primary key called Id (it doesn't have to be called Id in DB but it must be mapped to property Id) of defined type you can use simply this:
public interface IEntity
{
int Id { get; }
}
public class BaseRepository<T> where T : class, IEntity
{
...
public T GetById(int id)
{
_set.Find(id);
}
}
If data type is not always the same you can use:
public interface IEntity<TKey>
{
TKey Id { get; }
}
public class BaseRepository<TEntity, TKey> where TEntity : class, IEntity<TKey>
{
...
public TEntity GetById(TKey id)
{
_set.Find(id);
}
}
You can also simply use:
public class BaseRepository<TEntity, TKey> where TEntity : class
{
...
public TEntity GetById(TKey id)
{
_set.Find(id);
}
}
try this
public virtual T GetByID(object id)
{
// Define the entity key values.
IEnumerable<KeyValuePair<string, object>> entityKeyValues =
new KeyValuePair<string, object>[] {
new KeyValuePair<string, object>("Id", id) };
string qualifiedEntitySetName = _context.DefaultContainerName + "." + typeof(T).Name;
EntityKey key = new EntityKey(qualifiedEntitySetName, entityKeyValues);
return (T)_context.GetObjectByKey(key);
}
Related
I am using entity frame work in my mvc core application. I am also using dependency injection technique. Now i want to get the value of new record identity column. I am using the code as..
public interface IGenericRepositoryStudent<T> where T : class
{
void Add(T item);
}
public class GenericRepositoryStudent<T> : IGenericRepositoryStudent<T> where T : class
{
private eerp_studentContext _context;
private DbSet<T> _dbSet;
public GenericRepositoryStudent(eerp_studentContext context)
{
_context = context;
_dbSet = _context.Set<T>();
}
public void Add(T item)
{
_dbSet.Add(item);
_context.SaveChanges();
}
}
Controller:
public class StudentController : Controller
{
private IStudentRepository _dbSet;
public StudentController(IStudentRepository dbSet)
{
_dbSet = dbSet;
}
public long Add([FromBody]Student _student)
{
if (ModelState.IsValid)
{
_dbSet.Add(acJournal);
long _id = _student.id; // value of _id is always 0.
return _id;
}
}
}
Apparently every T that you want to Add has a Primary Key. You want the value of this primary Key after the T has been added and changes are saved.
This means, that you can't add objects of every class, you can only add classes that have a Primary Key.
The easiest, and a very type safe way (checked by compiler) is to allow only adding of object that have a primary key.
// interface of objects that have Id (primary key)
public interface IID
{
public long Id {get; set;}
}
If your database uses another type for primary keys like int, or GUID, use this other type as return type
public interface IGenericRepositoryStudent<T> where T : IID
{
T Add(T item);
}
public class GenericRepositoryStudent<T> : IGenericRepositoryStudent<T> where T : IID
{
...
public T Add(T itemToAdd)
{
T addedItem = _dbSet.Add(item);
_context.SaveChanges();
return addedItem
}
}
Usage:
class Student : IID
{
public long Id {get; set;} // primary key
...
}
public class StudentController : Controller
{
...
public T Add([FromBody]Student _student)
{
if (ModelState.IsValid)
{
return _dbSet.Add(_student);
}
}
}
I chose to return the complete T instead of only the Id, because if you need some of the properties of the added item, you don't need to fetch the item you just added. Besides this is just the return value of DbSet.Add(T)
If you really want, just return the Student's Id.
I'm trying to implement a generic item filter using Entity Framework based on a lambda expression telling me the id field. The following code compiles but of course does not work since EF does not understand the generic function:
public static IQueryable<T> Authorize<T>(this IQueryable<T> items, Func<T, Guid> idGetter) where T : class
{
return items.Where(i => idGetter(i) == new Guid("4A6FE5AF-AB63-4BB3-9D32-88766CF242CC"));
}
var result = context.Items.Authorize(i => i.Id);
How to do this using EF? How to use the expression tree to tell him what field to compare? How to use a generic name in a query that can be handled by Entity Framework?
If your id field is always of type Guid (as your code is suggesting), your classes could implement an interface with a member call Id of type Guid:
public interface MyInterface
{
Guid Id { get; set; }
}
public class Item : MyInterface
{
public Guid Id { get; set; }
// ...
}
Then your Authorize method could look like this:
public static IQueryable<T> Authorize<T>(this IQueryable<T> items) where T : MyInterface
{
return items.Where(i => i.Id == new Guid("4A6FE5AF-AB63-4BB3-9D32-88766CF242CC"));
}
And you can use this as follows:
var result = items.AsQueryable().Authorize();
Based on your comments and using reflection this is my approach:
First, your (simple) Attribute to decorate your "id fields":
[AttributeUsage(AttributeTargets.Property)]
public class IdFieldAttribute : Attribute
{
public IdFieldAttribute()
{
}
}
Then your classes could look like this:
public class Item
{
[IdFieldAttribute]
public Guid YourIdField{ get; set; }
// ...
}
A Reflection helper class for getting your desired field:
public static class ReflectionHelper
{
public static PropertyInfo GetPropertyInfoByAttribute<T>(T o, Type attributeType) where T : class
{
var type = o.GetType();
var propertyInfo = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(pi => Attribute.IsDefined(pi, attributeType));
return propertyInfo;
}
}
Once you have this, your Authorize method could look like this:
public static IQueryable<T> Authorize<T>(this IQueryable<T> items) where T : class
{
return items.Where(i => (Guid)ReflectionHelper.GetPropertyInfoByAttribute(i, typeof(IdFieldAttribute)).GetValue(i) == new Guid("4A6FE5AF-AB63-4BB3-9D32-88766CF242CC"));
}
And you can use it as follows:
var result = items.AsQueryable().Authorize();
I don't know if what you're looking for is something like this. Maybe this approach could help you.
I want to implement business logic in DbSet derived classes. I like the idea of not having services and DAL abstractions and think this could be a good way. For this to work I need to inject objects into my DbSet but I don't know how. Here some sample code which does not work, because the EF Framework can't create an object of the DbSet. Maybe someone can point me in the right direction?
public class LongTermBookingDbSet : DbSet<LongTermBooking>
{
DbContext _dbContext { get; set; }
public LongTermBookingDbSet(DbContext dbContext )
{
this._dbContext = _bContext ;
}
public override LongTermBooking Add(LongTermBooking entity)
{
return this.Add(entity, false);
}
public LongTermBooking Add(LongTermBooking entity, bool SendMails)
{
var dbSet = base.Add(entity);
//do something with the _dbContext
return dbSet;
}
}
One of the options is to aggregate real DbSet, not derive it:
public class PersonSet : IDbSet<Person>
{
private readonly DbSet<Person> _dbSet;
public PersonSet(DbSet<Person> dbSet)
{
_dbSet = dbSet;
}
}
public class MyDbContext: DbContext
{
public PersonSet PersonSet {...}
}
Inherits from DbSet<T> with the purposes to add property
Is there a way to inherits from DbSet? I want to add some new properties, like this:
public class PersonSet : DbSet<Person>
{
public int MyProperty { get; set; }
}
But I don't know how to instantiate it in my DbContext
public partial MyContext : DbContext
{
private PersonSet _personSet;
public PersonSet PersonSet
{
get
{
_personSet = Set<Person>(); // Cast Error here
_personSet.MyProperty = 10;
return _personSet;
}
}
}
How can I achieve this?
I have found an answer that works for me. I declare my DbSet properties as my derived interface in my context, e.g.:
IDerivedDbSet<Customer> Customers { get; set; }
IDerivedDbSet<CustomerOrder> CustomerOrders { get; set; }
My implementation includes a private IDbSet which which is assigned in the constructor e.g.:
public class DerivedDbSet<T> : IDerivedDbSet<T> where T : class
{
private readonly IDbSet<T> _dbSet;
public DerivedDbSet(IDbSet<T> dbSet)
{
this._dbSet = dbSet;
}
...
}
My implementation of a derived DbContext interface hides the Set<>() method like so:
new public IDerivedSet<TEntity> Set<TEntity>() where TEntity : class
{
//Instantiate _dbSets if required
if (this._dbSets == null)
{
this._dbSets = new Dictionary<Type, object>();
}
//If already resolved, return stored reference
if (this._dbSets.ContainsKey(typeof (TEntity)))
{
return (IDerivedSet<TEntity>) this._dbSets[typeof (TEntity)];
}
//Otherwise resolve, store reference and return
var resolvedSet = new GlqcSet<TEntity>(base.Set<TEntity>());
this._dbSets.Add(typeof(TEntity), resolvedSet);
return resolvedSet;
}
The derived DbContext returns a newly constructed IDerivedSet or picks it's reference cached in a Dictionary. In the derived DbContext I call a method from the constructor which uses type reflection to go through the DbContexts properties and assigns a value/reference using it's own Set method. See here:
private void AssignDerivedSets()
{
var properties = this.GetType().GetProperties();
var iDerivedSets =
properties.Where(p =>
p.PropertyType.IsInterface &&
p.PropertyType.IsGenericType &&
p.PropertyType.Name.StartsWith("IDerivedSet") &&
p.PropertyType.GetGenericArguments().Count() == 1).ToList();
foreach (var iDerivedSet in iDerivedSets)
{
var entityType = iDerivedSet.PropertyType.GetGenericArguments().FirstOrDefault();
if (entityType != null)
{
var genericSet = this.GetType().GetMethods().FirstOrDefault(m =>
m.IsGenericMethod &&
m.Name.StartsWith("Set") &&
m.GetGenericArguments().Count() == 1);
if (genericSet != null)
{
var setMethod = genericSet.MakeGenericMethod(entityType);
iDerivedSet.SetValue(this, setMethod.Invoke(this, null));
}
}
}
}
Works a treat for me. My context class has navigable set properties of my set type that implements a derived interface inheriting IDbSet. This means I can include query methods on my set type, so that queries are unit testable, instead of using the static extensions from the Queryable class. (The Queryable methods are invoked directly by my own methods).
One solution is to create a class that implements IDbSet and delegates all operations to a real DbSet instance, so you can store state.
public class PersonSet : IDbSet<Person>
{
private readonly DbSet<Person> _dbSet;
public PersonSet(DbSet<Person> dbSet)
{
_dbSet = dbSet;
}
public int MyProperty { get; set; }
#region implementation of IDbSet<Person>
public Person Add(Person entity)
{
return _dbSet.Add(entity);
}
public Person Remove(Person entity)
{
return _dbSet.Remove(entity);
}
/* etc */
#endregion
}
Then in your DbContext, put a getter for your Custom DbSet:
public class MyDbContext: DbContext
{
public DbSet<Person> People { get; set; }
private PersonSet _personSet;
public PersonSet PersonSet
{
get
{
if (_personSet == null)
_personSet = new PersonSet( Set<Person>() );
_personSet.MyProperty = 10;
return _personSet;
}
set
{
_personSet = value;
}
}
}
I solved this using another variable to instantiate the "regular" DbSet.
private DbSet<Person> _persons { get; set; }
public PersonDbSet<Person> Persons { get { return new PersonDbSet(_persons); } }
This way entityframework recognizes the Entity but I can still use my own DbSet class.
I know this is really old and the OP has probably moved on but I was just wondering the same thing myself. EF populates the DbSets inside your MyContext at run time.
I just created MyDbSet<T> that inherits from DbSet<T> and the replaced all references to DbSet<T> with my derived class in MyContext. Running my program failed to instantiate any of the properties.
Next I tried setting the properties to IDbSet<T> since DbSet<T> implements this interface. This DOES work.
Investigating further, the constructors for DbSet are protected and internal (the protected one calls the internal one anyway). So MS have made it pretty hard to roll your own version. You may be able to access the internal constructors through reflection but chances are that EF will not construct your derived class anyway.
I would suggest writing an extension method to plug the functionality into the DbSet object, however you're stuck if you want to store state.
Here is the generic insert method. I need your suggestion to return the ID of the inserted record.
public static void Create<T>(T entity) where T : class
{
using (var context = new InformasoftEntities())
{
DbSet dbSet = context.Set<T>();
dbSet.Add(entity);
context.SaveChanges();
}
}
Arturo Martinex is correct in his comment.
Entity framework fixes up the ID's during SaveChanges so it's already updated in the entity you passed in to the method.
To do specifically what you ask you could change your generic constraint from class to a new abstract class that all your entities inherit, which defines the key in that class.
public static int Create<T>(T entity) where T : BaseEntity
{
using (var context = new InformasoftEntities())
{
DbSet dbSet = context.Set<T>();
dbSet.Add(entity);
context.SaveChanges();
return entity.Id;
}
}
public abstract class BaseEntity
{
int Id { get; set;}
}
This technique is more useful in an InsertOrUpdate method
Another way to work with keys inside generic methods is to interrogate the MetaData as described here:
The key to AddOrUpdate
You need a little modification:
You need to create an IHasAutoID that implemented by Entity
public interface IHasAutoID {
int getAutoId();
}
In Entity Class
public class EntityA : IHasAutoID {
public int getAutoId() {
return pk; // Return -1 If the entity has NO Auto ID
}
}
In your Create function
public static int Create<T>(T entity) where T : class
{
using (var context = new InformasoftEntities())
{
DbSet dbSet = context.Set<T>();
dbSet.Add(entity);
context.SaveChanges();
if (entity is IHasAutoID) {
return ((IHasAutoID)entity).getAutoId();
}
return -1; // entity is NOT IHasAutoID)
}
}
NOTES:
If you are sure all tables have Auto ID with named "Id". You don't need to create Interface IHasAutoID. In Create function, after SaveChanges, You use REFLECTION to get value of Id property, but this way is not recommended!
public async Task<int> Add(TEntity entity)
{
await _context.Set<TEntity>().AddAsync(entity);
await Save();
return Task.FromResult(entity).Id;
}