Is it possible with Generics to get an object from my EntityFramework without knowing the type?
I'm thinking of something along the lines of:
public T GetObjectByID<T>(int id)
{
return (from i in myDatabase.T where i.ID == id select i);
}
Is that doable? Could I use Reflection to somehow take T.GetType().Name and use that for the table?
EDIT
Another hangup, is that not all tables available to me use "ID" as their unique column name.
I think the Find() method may be able to do what you're looking for (DbSet.Find Method).
var someEntity = dbSet.Find(keyValue);
You can define interface implemented by all your entities:
public interface IEntity
{
int Id { get; }
}
and method to retrieve your entity:
public T GetObjectById<T>(int id) where T : class, IEntity
{
return context.CreateObjectSet<T>().SingleOrDefault(e => e.Id == id);
}
You can also use similar approach to one provided in the linked question. You just have to use another method to get your entity:
public virtual T GetByKey<T>(int id) where T : class, IEntity
{
string containerName = context.DefaultContainerName;
string setName = context.CreateObjectSet<T>().EntitySet.Name;
// Build entity key
var entityKey = new EntityKey(containerName + "." + setName, "Id", id);
return (TEntity)Context.GetObjectByKey(entityKey);
}
The difference is that first method always query the database even if you have already loaded the instance to the context whereas second approach first checks if the instance is already loaded. The method is not so efficient because it builds these names over and over. Here is more general approach which can work with any key type and name and here is approach working with complex keys.
Neither of this method directly works with inheritance - you must provide base type to make it work.
It's hard to make a completely generic solution because Entities can have composite keys, but this will work for a simple single key case.
The key is to restrict T to be of type System.Data.Objects.DataClasses.EntityObject, which then has as EntityKey property (which represents the primary key).
static T GetById<T>(object id) where T : EntityObject
{
using (var context = new MyEntities())
{
return context.CreateObjectSet<T>()
.SingleOrDefault(t => t.EntityKey.EntityKeyValues[0].Value == id);
}
}
Finally solved the issue with this:
http://pastebin.com/kjXUKBNS
To call the code I use this:
// Get the id of the object we are saving
PropertyInfo prop = GetProperty<TEntity>(entity, entity.EntityKey.EntityKeyValues[0].Key);
string entityID = prop.GetValue(entity, null).ToString();
// Get the current version of this object
var originalEntity = GetEntity<TEntity>(PropertyEquals, entityID);
This makes the assumption that the Primary Key you are searching on is the first one in the list of Primary Keys.
An other way to get entity from id on entitiy framework
public T Get<T>(int id) where T : EntityObject;
{
var ObjectSet = _context.CreateObjectSet<T>();
var PropName = ObjectSet.EntitySet.ElementType.KeyMembers[0].ToString();
return ObjectSet.Where("it." + PropName + "=" + id).FirstOrDefault();
}
Result
SELECT TOP (1)
[Extent1].[CatId] AS [CatId],
[Extent1].[CatName] AS [CatName],
[Extent1].[CatType] AS [CatType],
FROM [dbo].[Categories] AS [Extent1]
WHERE [Extent1].[CatId] = 1
I think this could help
public static TEntity Find<TEntity>(this ObjectSet<TEntity> set, object id) where TEntity : EntityObject
{
using (var context = new MyObjectContext())
{
var entity = set.Context.CreateObjectSet<TEntity>();
string keyName = entity.FirstOrDefault().EntityKey.EntityKeyValues.FirstOrDefault().Key;
return entity.Where("it." + keyName + " = " + id).FirstOrDefault();
}
}
One thing I've done that has worked is to define a "KeyComparator" interface:
public interface IHasKeyComparator<TObject> {
Expression<Func<TObject, bool>> KeyComparator { get; }
}
And then you can implement it on some object even with a multi-valued key:
public sealed class MultiKeyedObject : IHasKeyComparator<MultiKeyedObject> {
public int ID1 { get; set; }
public string ID2 { get; set; }
readonly Expression<Func<MultiKeyedObject, bool>> mKeyComparator;
public Expression<Func<MultiKeyedObject, bool>> KeyComparator {
get { return mKeyComparator; }
}
public MultiKeyedObject() {
mKeyComparator = other => other.ID1 == ID1 && other.ID2 == ID2;
}
}
And can use it generically if you provide the constraint:
public TObject Refresh<TObject>(TObject pObject)
where TObject : IHasKeyComparator<TObject> {
return this.Set<TObject>().Single(pObject.KeyComparator);
}
Doing it this way requires you to have an instance of the object, though, although you can fill an empty one with key values and then use it to pull from the DB.
Here's the method I use to get an Entity object by ID.
public TEntity GetByKey<TEntity>(object keyValue) where TEntity : class
{
EntityKey key = GetEntityKey<TEntity>(keyValue);
object originalItem;
if (ObjectContext.TryGetObjectByKey(key, out originalItem))
{
return (TEntity)originalItem;
}
return default(TEntity);
}
You may find this a more robust solution than some of those provided above. I've used it as part of a generic repository in a lot of projects. Hope it helps you out.
Related
I have a standard EF Core data model with several one-many and many-to-many relationships.
I want to create a way to produce various data calculations or commonly run procedures. These should return values to be used throughout the application and not values to be stored in the database.
I'll give a fictional example here of a scenario:
Entity 1 - YearGroup
Entity 2 - Student
Relationship - One YearGroup to many Students
Now I understand you could simply write in the controller:
int student = _context.Students.Where(s => s.YearGroupId == ygId).Count()
But let's say I wanted to simplify this by creating a method somewhere which returns data such as this, so I don't have to specify the query every time I get the number of Students in a YearGroup. I appreciate this is a simple example, but others could be more complex.
I was thinking adding a field to yeargroup.cs model such as:
public int TotalStudents { //code here to get students from students table }
Which I could then use like:
#model.YearGroup.TotalStudents
But I don't know how to get this to include relational data, i.e. from the students table.
I would prefer not create random methods in separate, unrelated classes such as GetStudentsInYearGroup(ygId). It would be nice to include this in the object model if possible.
What would be the best practice way of acheiving this?
Note: I don't have a code editor nor a project setup so what I am going to write might not be compilable.
As simple as your fictional example
For any case as simple as your fictional example, if you just want to get the total students per a year group, and I assume their relationships are properly setup, you can just simply use the navigation property:
// Find the target year group
var yearGroup = _context.YearGroups
.SingleOrDefault(x => x.Id == ygId);
int totalStudents = 0;
if (yearGroup != null)
{
totalStudents = yearGroup.Students.Count();
}
Extension methods
The other way I can think of is to define whatever you need as extension methods of the entity:
public static class YearGroupExtensions
{
public static int GetTotalStudents(this YearGroup yearGroup)
{
if (yearGroup == null)
{
return 0;
}
return yearGroup.Students.Count();
}
public static int GetTotalStudents(this YearGroup yearGroup, Gender gender)
{
if (yearGroup == null)
{
return 0;
}
if (gender == null)
{
return yearGroup.GetTotalStudents();
}
return yearGroup
.Students
.Count(x => x.Gender == gender);
}
}
// usage
// YearGroup yearGroup = GetYearGroup(ygId);
// int totalStudents = yearGroup.GetTotalStudents();
Repository Pattern
If you find yourself repeating similar methods you need for most of your entities, it might be good to define a generic repository for them.
I am not here arguing whether this is just a wrapper of DbContext as that itself is already using repository pattern.
public interface IEntity { }
public interface IRepository<T> where T : IEntity
{
IEnumerable<T> GetAll();
T GetById(int id);
void Insert(T entity);
...
}
public abstract class RepositoryBase<T> : IRepository<T> where T : IEntity
{
protected readonly AppDbContext _dbContext;
protected DbSet<T> _entities;
private RepositoryBase(AppDbContext dbContext)
{
_dbContext = dbContext;
_entities = dbContext.Set<T>();
}
public virtual IEnumerable<T> GetAll()
{
return _entities.AsEnumerable();
}
...
}
public class YearGroupRepository : RepositoryBase<YearGroup>
{
...
}
Separate Persistence and Domain Model
This is my preference just because I'm a DDD guy, and I want to build anything from the domain first (what businesss problems you're trying to solve), without thinking its backend persistence.
The basic idea here is to have 2 sets of models. They could be similar, or totally different. 1 set of models is called the domain model, which reflects your business domain. The other set of models is called the persistence model. That could be your regular Entity Framework Entities.
More on this: https://stackoverflow.com/a/14042539/2410655
Given a DbContext, an entity and a navigation property, you can construct an IQueryable as follows;
public static IQueryable AsQueryable(DbContext context, object entity, string navigation){
var entry = context.Entry(entity);
if (entry.State == EntityState.Detatched)
return null;
var nav = entry.Navigation(navigation);
return nav.Query();
}
I feel like there should be an existing method for this, but I can't seem to find one right now.
You should then be able to use this method in a fairly generic way for any navigation property, without needing to replicate the foreign key criteria all over the place.
public int TotalStudents(DbContext context) =>
AsQueryable(context, this, nameof(Students))?.Count() ?? 0;
While this would add some minor performance overhead, you could extract the base entity and navigation property from a LamdaExpression, and write some extension methods;
public class QueryableVisitor : ExpressionVisitor
{
private object obj;
public object BaseObject { get; private set; }
public string Navigation { get; private set; }
protected override Expression VisitConstant(ConstantExpression node)
{
BaseObject = obj = node.Value;
return base.VisitConstant(node);
}
protected override Expression VisitMember(MemberExpression node)
{
Visit(node.Expression);
BaseObject = obj;
if (node.Member is PropertyInfo prop)
obj = prop.GetValue(obj);
else if (node.Member is FieldInfo field)
obj = field.GetValue(obj);
Navigation = node.Member.Name;
return node;
}
}
public static IQueryable<T> AsQueryable<T>(this DbContext context, Expression<Func<IEnumerable<T>>> expression)
{
var visitor = new QueryableVisitor();
visitor.Visit(expression);
var query = AsQueryable(context, visitor.BaseObject, visitor.Navigation);
return (IQueryable<T>)query;
}
public static int Count<T>(this DbContext context, Expression<Func<IEnumerable<T>>> expression) =>
AsQueryable(context, expression)?.Count() ?? 0;
Enabling strongly typed usage like the following;
public int TotalStudents(DbContext context) =>
context.Count(() => this.Students);
public int ActiveStudents(DbContext context) =>
context.AsQueryable(() => this.Students)?.Where(s => s.Active).Count() ?? 0;
Let's say I have a class:
public class Customer
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
And now I want to create a generic Get() method that might query Customer or any one of several other classes that also have a [key] field defined.
public T Get<T>(int id)
{
string json = DoSomething(); // <-- making it easy for this post
List<T> items = JsonConvert.DeserializeObject<List<T>>(json);
return items.FirstOrDefault(i => i. ????? = id);
}
I'm not sure how to use Linq to generically specify the [key] field.
Thanks!
Hope this helps:
public interface IBase
{
int Id { get; }
}
public class Customer : IBase
{
public string Name { get; set; }
public int Id { get ; set ; }
}
public T Get<T>(int id) where T : IBase
{
string json = DoSomething(); // <-- making it easy for this post
List<T> items = JsonConvert.DeserializeObject<List<T>>(json);
return items.FirstOrDefault(i => i.Id == id);
}
Just implement the interface IBase in all other classes.
For what is worth I think using contracts is a better way to solve this. But in case you or someone else actually need to check for the attribute here's the answer:
public static T Get<T>(int id)
{
string json = DoSomething(); // <-- making it easy for this post
List<T> items = JsonConvert.DeserializeObject<List<T>>(json);
return items.FirstOrDefault(
item => (int)item.GetType()
.GetProperties()
.FirstOrDefault(
p => Attribute.IsDefined(p, typeof(KeyAttribute))
).GetValue(item) == id
);
}
As far a this part of your question:
I'm not sure how to use Linq to generically specify the [key] field.
The attribute is KeyAttribute you can know that by navigating to the definition (pressing F12 if you're using VS or checking the docs in case your editor doesn't support this feature.
Things to consider:
this is using Reflection reasonably heavily, so it will never have the best performance. That being said you can cache the result from GetProperties() somewhere for faster lookups.
It's hardcoding the cast to int but it appears that's what you're after.
If the collection is null it'll throw an exception.
How do I find the biggest Id of a DbSet.Set<T>()?
Note: not DbSet<TEntity>.
I don't know the type at runtime.
Context: I have 20 tables/entities, which I'm using a generic method to do processing.
The process involves looking up the biggest Id of that table/entity and comparing it with the record at hand.
If the record's id is bigger than the database's, than it would be inserted into the database.
So far I've tried using reflection:
DbSet<T> table = DbContext.Set<T>();
var lastRecord = table.LastOrDefault(); // throws not supported error
var idProperty = lastRecord.GetType().GetProperties()
.FirstOrDefault(p => p.Name.Equals("Id");
int maxId = (int)idProperty.GetValue(lastRecord);
I've also tried using an interface cast:
interface ICommonEntity
{ // this interface allows the generic method
string StringId { get;} // to know how to handle entity Id's of
int? IntId { get; } // different types (string vs int).
}
var whatever = table.OrderByDescending(e => (e as ICommonEntity).IntId).FirstOrDefault();
int maxId = (whatever as ICommonEntity).IntId ?? 0;
But the above yields the following error:
The 'TypeAs' expression with an input of type xx is not supported. and a check of type yy. Only entity types and complex types are supported in LINQ to Entities queries
Additional data: All my entities have the column/property Id of type int.
Web searches that I've done mainly point to solutions that the type is known e.g. TEntity, db.Users.xxx() etc..
Update
In response to Ian's answer, I can't use Id directly. Why?
One of my entity has a field named Id, but is of type string.
class EntityStringId : ICommonEntity
{
public string Id { get; set; }
public string StringId => Id;
public int? IntId => null;
}
class EntityIntId : ICommonEntity
{
public int Id { get; set; }
public string StringId => null;
public int? IntId => Id;
}
And if I try to use IntId for ordering,
private void SomeMethod<T>(string file)
//where T : class // original
//where T : ICommonEntity // cannot. DbContext.Set<T>(); requires class
where T : class, ICommonEntity // throws exception
{
var table_T = DbContext.Set<T>();
var maxId = table_T.Max(e => e.IntId); // throws exception ↓
}
The specified type member 'IntId' is not supported in LINQ to Entities.
Only initializers, entity members, and entity navigation properties are supported.
For a better picture, my method's logic:
private void ProcessCsvToDb<T>(
DbSet<T> table,
T csvRecord) where T : class
{
var iRecord = csvRecord as ICommonEntity;
T dbRecord = null;
if (!string.IsNullOrEmpty(iRecord.StringId))
{
dbRecord = table.Find(iRecord.StringId);
}
else if (iRecord.IntId != null)
{
dbRecord = table.Find(iRecord.IntId);
}
}
In order to do this without a base class/interface, you will need to manually compose the expression:
public static IOrderedQueryable<int> OrderById(Type entityType)
{
var dbSet = context.Set(entityType);
var item = Expression.Parameter(entityType, "item");
var property = Expression.Property(item, "Id");
var lambda = Expression.Lambda<Func<T, int>>(property, item);
// the above generates:
// item => item.Id
return dbSet.OrderByDescending(lambda);
}
You can build expression to sort by Id, but DynamicQueryable class does it for you:
DbSet<T> table = assignFromSomeWhere();
var maxId = table.OrderBy("Id desc").FirstOrDefault();
DynamicQueryable also gives you different extension methods (dynamic Where, Select). Obviously it is bigger satisfaction to build expressions on your own, but sometimes it is very complicated and this library helps a lot.
If you have an interface, as discussed in comments, is there any reason you can't do this to avoid the cast:
public static int? GetMaxId<T>(DBSet<T> dbSet)
where T : ICommonEntity
{
return dbSet.OrderByDescending(e => e.Id).FirstOrDefault();
}
I'm trying to implement a generic repository pattern that implements CRUD operations of generic items in an IEnumerable. I'm having issue of generically finding an item that may already be in the IEnumerable.
I need to programmatically pass which properties make up the 'key' or distinct record and then using LINQ do a Enumerable.Any() on the defined properties so it would see if the object already exists in the IEnumerable.
Here is my code so far:
// Generic Method
public void AddItem(TEntity item)
{
var entities = GetAllItems().ToList(); // Method gets cached IEnumerable<TEntity>
if(true) // Generically see if TEntity is already in the list based of defined properties
{
entities.Add(item);
}
}
// Same function but non-generic
private void AddItem(MyObject object)
{
var objects = GetAllItems().ToList(); //Method gets cached IEnumerable<MyObject>
if(!objects.Any(a=> a.ID == MyObject.ID ))
{
objects.Add(object);
_cache.AddReplaceCache(objects);
}
}
NOTE: the keys can be any property or properties on the object MyObject
You can make your entities inherit from a common interface:
public interface IEntity
{
int ID { get; set; }
}
Then you can redefine your method like
public void AddItem<TEntity>(TEntity entity) where TEntity : IEntity
{
// Now you can access entity.ID
}
Now, if you don't always want to compare via ID, then you can add a predicate to your method:
public void AddItem<TEntity>(TEntity entity, Func<TEntity, bool> predicate)
{
var objects = GetAllItems().ToList();
// You might need some logic in the predicate to check for null
if(!objects.Any(a => predicate(a as TEntity))
{
objects.Add(entity);
_cache.AddReplaceCache(objects);
}
}
Then you would use your function as
repository.AddItem(entity, e => e.ID == entity.ID && e.OtherProperty == entity.OtherProperty);
If I understood you correctly your problem is TEntity doesn't have property ID. So make your entities to inherit common interface which have for example the ID column.
public interface IObject
{
int ID {get; set;}
//define all other properties which are shared between your Entities.
}
public class MyObject : IObject
{
public int ID {get; set;}
//other properties.
}
public void AddItem(TEntity item): where TEntity:IObject
{
var entities = GetAllItems().ToList(); //Method gets cached IEnumerable<TEntity>
if(!objects.Any(a=> a.ID == item.ID ))//Generically see if TEntity is already in the list based of defined properties
{
entities.Add(item);
}
}
Im quite new to generics and as a learning exercise Im trying to create a simple method that picks an entity from an entity set (table) with a specified ID.
public T GetEntity<T>(int id) where T : class
{
return db.GetTable<T>().SingleOrDefault(o => o.id == id);
}
The above obviously wont work because o in o.id is unknown and thus cannot access the property id - can someone help with how to achieve this?
If all of your entities contain an id property then you can define an interface
public interface IEntity
{
int id { get; }
}
implement it on all your classes and change your method to
public T GetEntity<T>(int id) where T : IEntity
{
return db.GetTable<T>().SingleOrDefault(o => o.id == id);
}
Such interface can be useful in all the places where you want to do something for all your entities, ie. Delete by id. You can add more properties to your interface, ie. timestamp.
If you cannot change your entity classes (for implementing a common interface), you could use a predicate function and pass it into the method
public T GetEntity<T>(int id, Func<T, int, bool> predicate) where T : class
{
return db.GetTable<T>().SingleOrDefault(o => predicate(o,id));
}
Another solution, not recommended for performance reasons, but i shall post it anyway.
public T GetEntity<T>(int id) where T : class
{
return db.GetTable<T>().AsEnumerable().SingleOrDefault(o => (int)o.GetType().GetProperty("Id").GetValue(o,null) == id);
}
You need to set a constrain on your T so the compiler can be sure there is a id property. Once way is to use interfaces, ie.
public interface IUnique
{
int id { get; set; }
}
Then you can say
public T GetEntity<T>(int id) where T : class, IUnique
{
return db.GetTable<T>().SingleOrDefault(o => o.id == id);
}