Generic Repository GetByID method with "Including" .... - c#

I am trying to add a method to my generic repository that will allow me to query an entity by it's PrimaryKey (ID) and select what navigation properties to return. The kicker is, that in order to be truly generic I do not know the data type or field name of the Primary Key (Not supporting compound keys ... yet).
So here's what I started with ... the below snippet gets me the TEntity based on key regardless of the TYPE of the key ...
public virtual TEntity GetByID(Object entityID)
{
TEntity ret = DbSet.Find(entityID);
return ret;
}
The next step was to get records back, but including whatever navigation fields I chose to include ... This worked out great for that ... for multiple records.
public virtual IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] includedProperties)
{
var query = DbSet.AsQueryable();
foreach (var prop in includedProperties)
{
query = query.Include(prop);
}
return query;
}
//
// example usage gets ALL books including Author and Publisher information
var repo = uow.GetRepository<Book>();
repo.GetAllIncluding(e=>e.Author, e=>e.Publisher);
So now I need to mix the two ... here's the rub. In order to dynamically add the "including" expressions I need to convert the DbSet to an IQueryable ... however in order to leverage the type-agnostic key lookup I need to use the "Find()" method on the DbSet ...
So in theory I need to convert the DbSet to an IQueryable, add the Include expressions then convert the IQueryable BACK to a DbSet so that I can access the type-agnostic "Find()" method ... this does not work ...
Anyone got any ideas on how this can be done ... ??
Here's some pseudo-code for what I am trying to get to work ...
public virtual TEntity GetByID(Object entityID, params Expression<Func<TEntity, object>>[] includedProperties)
{
var query = DbSet.AsQueryable();
foreach (var prop in includedProperties)
{
query = query.Include(prop);
}
TEntity ret = ((DbSet)query).Find(entityID);
return ret;
}

Related

How do I make a generic method to return an Expression according to the type?

I want to create a generic function to insert or update a record in Entity Framework. The problem is the Id property is not in the base class but in each of the specific types. I had the idea to create a function that will return the Expression to check for that Id.
Example:
public void InsertOrUpdateRecord<T>(T record) where T : ModelBase
{
var record = sourceContext.Set<T>().FirstOrDefault(GetIdQuery(record));
if(record == null)
{
//insert
}
else
{
//update
}
}
private Expression<Func<T, bool>> GetIdQuery<T>(T record) where T : ModelBase
{
if (typeof(T) == typeof(PoiModel))
{
//here is the problem
}
}
private Expression<Func<PoiModel, bool>> GetIdQuery(PoiModel record)
{
return p => p.PoiId == record.PoiId;
}
How do I return an expression that checks the Id for that specific type?
Can I convert? Also tried to do with methods with overload parameters but, as far as I know, if it's generic the compiler will always go for the generic function.
I've found that using dynamic for dynamic overload resolution like this is immensely useful:
void Main()
{
InsertOrUpdateRecord(new PoiModel()); // Prints p => p.PoiId == record.PoiId
InsertOrUpdateRecord(new AnotherModel()); // Prints a => a.AnotherId == record.AnotherId
InsertOrUpdateRecord("Hi!"); // throws NotSupportedException
}
class PoiModel { public int PoiId; }
class AnotherModel { public int AnotherId; }
public void InsertOrUpdateRecord<T>(T record)
{
GetIdQuery(record).Dump(); // Print out the expression
}
private Expression<Func<T, bool>> GetIdQuery<T>(T record)
{
return GetIdQueryInternal((dynamic)record);
}
private Expression<Func<PoiModel, bool>> GetIdQueryInternal(PoiModel record)
{
return p => p.PoiId == record.PoiId;
}
private Expression<Func<AnotherModel, bool>> GetIdQueryInternal(AnotherModel record)
{
return a => a.AnotherId == record.AnotherId;
}
private Expression<Func<T, bool>> GetIdQueryInternal<T>(T record)
{
// Return whatever fallback, or throw an exception, whatever suits you
throw new NotSupportedException();
}
You can add as many GetIdQueryInternal methods as you like. The dynamic overload resolution will always try to find the most specific arguments possible, so in this case, PoiModel drops to the PoiModel overload, while "Hi!" drops to the fallback, and throws an exception.
Well, you can write such method, but it will be rather complex in the general case.
The concept is:
get EDM metadata for given entity type;
detect primary key properties for this type;
get current values for the primary key properties;
build an expression to check primary key existence in database;
run appropriate extension method, using that expression.
Note, that there are at least two pitfalls, which could affect code:
entity type could have composite primary key;
entity types could participate in some inheritance hierarchy.
Here's the sample for entity types, whose primary key consists from single property, and these types are roots of hierarchy (that is, they are not derived from another entity type):
static class MyContextExtensions
{
public static bool Exists<T>(this DbContext context, T entity)
where T : class
{
// we need underlying object context to access EF model metadata
var objContext = ((IObjectContextAdapter)context).ObjectContext;
// this is the model metadata container
var workspace = objContext.MetadataWorkspace;
// this is metadata of particular CLR entity type
var edmType = workspace.GetType(typeof(T).Name, typeof(T).Namespace, DataSpace.OSpace);
// this is primary key metadata;
// we need them to get primary key properties
var primaryKey = (ReadOnlyMetadataCollection<EdmMember>)edmType.MetadataProperties.Single(_ => _.Name == "KeyMembers").Value;
// let's build expression, that checks primary key value;
// this is _CLR_ metatadata of primary key (don't confuse with EF metadata)
var primaryKeyProperty = typeof(T).GetProperty(primaryKey[0].Name);
// then, we need to get primary key value for passed entity
var primaryKeyValue = primaryKeyProperty.GetValue(entity);
// the expression:
var parameter = Expression.Parameter(typeof(T));
var expression = Expression.Lambda<Func<T, bool>>(Expression.Equal(Expression.MakeMemberAccess(parameter, primaryKeyProperty), Expression.Constant(primaryKeyValue)), parameter);
return context.Set<T>().Any(expression);
}
}
Of course, some intermediate results in this code could be cached to improve performance.
P.S. Are you sure, that you don't want to re-design your model? :)
You can create generic Upsert extension which will look for entity in database by entity key value and then add entity or update it:
public static class DbSetExtensions
{
private static Dictionary<Type, PropertyInfo> keys = new Dictionary<Type, PropertyInfo>();
public static T Upsert<T>(this DbSet<T> set, T entity)
where T : class
{
DbContext db = set.GetContext();
Type entityType = typeof(T);
PropertyInfo keyProperty;
if (!keys.TryGetValue(entityType, out keyProperty))
{
keyProperty = entityType.GetProperty(GetKeyName<T>(db));
keys.Add(entityType, keyProperty);
}
T entityFromDb = set.Find(keyProperty.GetValue(entity));
if (entityFromDb == null)
return set.Add(entity);
db.Entry(entityFromDb).State = EntityState.Detached;
db.Entry(entity).State = EntityState.Modified;
return entity;
}
// other methods explained below
}
This method uses entity set metadata to get key property name. You can use any type of configuration here - xml, attributes or fluent API. After set is loaded into memory Entity Framework knows which propery is a key. Of course there could be composite keys, but current implementation do not support this case. You can extend it:
private static string GetKeyName<T>(DbContext db)
where T : class
{
ObjectContext objectContext = ((IObjectContextAdapter)db).ObjectContext;
ObjectSet<T> objectSet = objectContext.CreateObjectSet<T>();
var keyNames = objectSet.EntitySet.ElementType.KeyProperties
.Select(p => p.Name).ToArray();
if (keyNames.Length > 1)
throw new NotSupportedException("Composite keys not supported");
return keyNames[0];
}
To avoid this metadata search you can use caching in keys Dictionary. Thus each entity type will be examined only once.
Unfortunately EF 6 do not expose context via DbSet. Which is not very convenient. But you can use reflection to get context instance:
public static DbContext GetContext<TEntity>(this DbSet<TEntity> set)
where TEntity : class
{
object internalSet = set.GetType()
.GetField("_internalSet", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(set);
object internalContext = internalSet.GetType().BaseType
.GetField("_internalContext", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(internalSet);
return (DbContext)internalContext.GetType()
.GetProperty("Owner", BindingFlags.Instance | BindingFlags.Public)
.GetValue(internalContext, null);
}
Usage is pretty simple:
var db = new AmazonContext();
var john = new Customer {
SSN = "123121234", // configured as modelBuilder.Entity<Customer>().HasKey(c => c.SSN)
FirstName = "John",
LastName = "Snow"
};
db.Customers.Upsert(john);
db.SaveChanges();
Further optimization: you can avoid reflecting DbContext if you'll create Upsert method as member of your context class. Usage will look like
db.Upsert(john)

Can a generic method return the property I specify

Yesterday Ognyan helped me a great deal write this method:
public static class DbSetExtensions
{
public static T AddIfNotExists<T>(this DbSet<T> dbSet, T entity, Expression<Func<T, bool>> predicate = null) where T : class, new()
{
var exists = predicate != null ? dbSet.Any(predicate) : dbSet.Any();
return !exists ? dbSet.Add(entity) : null;
}
}
When this does do an Add of my entity, I get back the new ID of the entity, but if it exists it returns null.
There will be times that I would like to get back the ID of an entity that already exists. However, the Key ID property of my entities will be different depending on the model. For instance, Address model's key is AddressId, Profile's key is ProfileId.
So, I'd like to modify this query (or make another version of it) to accept the Id property name as a parameter. (Or use EF to recognize the Primary key.) And do something like this:
public static class DbSetExtensions
{
public static T AddIfNotExists<T>(this DbSet<T> dbSet,
T entity,
Expression<Func<T, bool>> predicate = null,
Expression<Func<T, TId>> keyColumnName) where T : class, new()
{
var exists = predicate != null ? dbSet.Where(predicate).Select(e => e.keyColumnName) : dbSet.Any();
return !exists ? dbSet.Add(entity) : exists ;
}
}
I'm not sure if that is the correct way to define the property I want to use. I also realize this may not be as fast as just doing an Any(), but at times it may be necessary for us to get the ID.
I would also like to understand better what all of these items mean and how they work together. I've been all over and gotten bits and pieces, but haven't been able to put the whole puzzle together.
Well maybe there is a better solution, but you can try this:
public static TResult AddIfNotExists<T,TResult>(this DbSet<T> dbSet,
T entity,
Expression<Func<T, bool>> predicate,
Expression<Func<T, TResult>> columns
) where T : class, new()
{
if (predicate==null || columns==null)
throw new Exception();
//Find if already exist the entity and select its key column(s)
var result = dbSet.Where(predicate).Select(columns).FirstOrDefault();
//the result could be a reference type (string or anonymous type) or a value type
if (result!=null && !result.Equals(default(TResult)))
return result;
var newElement = dbSet.Add(entity);
//Compile the Expresion to get the Func
var func = columns.Compile();
//To select the new element key(s), add the element to an array (or List) to apply the Select method and get the keys
var r = new[]{newElement};
return r.Select(func).FirstOrDefault();
}
As you can see, I always expect the parameters predicate to check if the element already exist and columns to select the key or keys in case you have an entity with composite PK (but notice you can select all the properties you want from that entity, not just the keys). Then, I apply the expressions to filter and select the key(s) in case the element exist. If it doesn't exist, I add the entity to its DbSet<T>, but there is a problem. How can you get the new element key(s)? Well, the only solution I found to this was adding that element to an array, compiling the expression to get a Func<T,TResult>, and apply that Func<T,TResult> to the array, (I know this may not be the best solution but it should work).
To call this extension method, for example, for an entity that have a composite PK, you can do this:
.AddIfNotExists(element, e => e.UserName== "admin", e => new{ e.Id1,e.Id2});
The more simple in your case should be:
public static class DbSetExtensions
{
public static T AddIfNotExists<T>(this DbSet<T> dbSet, T entity,
Expression<Func<T, bool>> predicate = null) where T : class, new()
{
T exists = predicate != null ?
dbSet.Where(predicate).FirstOrDefault():
dbSet.FirstOrDefault();
return exists == null ?
dbSet.Add(entity):
exists;
}
}
then, as you seem to know the PK property, you can check it from the returned value.

Pass lambda expressions of object properties to method to Select columns in EF

I am currently using one of the many repository patterns available online to perform CRUD operations with EF6. I am happy to use it as is but I have recently been handed a few legacy projects that have database tables with a very high number of columns. I would like a way to make my application as well as future applications smoother by devising a way to select only a subset of columns.
Current method.
public virtual TEntity Get(Expression<Func<TEntity, bool>> where,
params Expression<Func<TEntity, object>>[] navigationProperties)
{
TEntity item = null;
IQueryable<TEntity> dbQuery = this.Context.Set<TEntity>();
//Apply eager loading
foreach (Expression<Func<TEntity, object>> navigationProperty in navigationProperties)
dbQuery = dbQuery.Include<TEntity, object>(navigationProperty);
item = dbQuery
.AsNoTracking() //Don't track any changes for the selected item
.FirstOrDefault(where); //Apply where clause
return item;
}
I would like to enhance that method to retrieve only the columns I require but still return TEntity.
I do know I have to inject a Select after the '.AsNoTracking()' but I am unsure as to how I could pass the properties in as I am only starting out with Expression Trees.
In essence I would like to be able to do this.
public class Employee
{
public int EmployeeId { get;set; }
public string EmployeeRole { get;set; }
public string EmployeeFirstName { get;set; }
public string EmployeeLastName { get;set; }
public string DOB { get;set; }
...
}
Employee employee = EmployeeRepository.Get(where: e => e.EmployeeRole == "Developer",
columns: x => x.EmployeeFirstName, x => x.EmployeeLastName,
navigationProperties: null);
Where columns is a list of expressions specifying the columns to be added to the Select clause.
Any help would be appreciated.
Thanks in advance...
Update.
I ended up with using a DTO to do the necessary querying and extraction as I couldn't find an elegant way to perform it generically. There was a solution developed by a colleague of mine but it made the repository far too complex and would have been hard to manage in the future.
So I create a StaffBasicInformation class to hold the subset of columns I use regularly. I also created an interface for it if I needed in the future. The below code sample shows the final implementation of retrieving data for the DTO.
public virtual IStaffBasicInformation GetStaffBasicInformation<TEntity2>(Expression<Func<TEntity2, bool>> where)
where TEntity2 : ActiveStaffMember
{
TEntity2 item = null;
StaffBasicInformation resultItem = null;
IQueryable<TEntity2> dbQuery = this.Context.Set<TEntity2>();
resultItem =
dbQuery.Where(where)
.Select(x => new StaffBasicInformation
{
GivenName = x.GivenName,
Department = x.Department,
Description = x.Description,
DisplayName = x.DisplayName,
Gender = x.Gender,
IID = x.IID,
Mail = x.Mail,
Title = x.Title,
ID = x.Id
})
.FirstOrDefault();
return resultItem;
}
Your return value will not be of type TEntity anymore after you have done the projection, it will be an anonymous type. You have to decide, if you want to map this anonymous type to an instance of TEntity, including mapping all navigationproperties, or return dynamic or object from your repository. Both choices are not very pleasant. Mapping would include a huge amount of reflection, which will not be very fast. By returning a dynamic type you loose all type safety. You have seen this problem allready i assume.
That said: You will need to build this expression manually. Based on this answer you can modify the
public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<string> fieldNames)
to
public static IQueryable SelectDynamic(this IQueryable source, IEnumerable<Expression> fieldNames)
end extract the property names from the expressions. I would suggest to use an ExpressionVisitor for that, but you could also use the code from this answer
For the mapping you can compile the expressions and use the returned Func to retrieve the value from the anonymous type. After that you would need to use expression and find the hosting type for the selected property by using an ExpressionVisitor again. Then you will need to create a object of type of TEntity via Activator.CreateType(), and objects for every hosting type. Assign the value from Func(AnonymousType) to the created object of the hosting type using the property name from the expression. After that you have to determin the relationship between TEntity and the hosting type and build it up.
I will try to post some code tomorrow for this scenario, although i am quite sure there is a better and faster way.

Entity Framework cleaner way of multiple table includes

I have a large model that I'm trying to obtain from the database and am using the .Include(string) extension method to load all the entities that I need. It is getting very messy and I now have 18 rows that are also long in horizontal length where I have to chain them together.
Example:
var myModel = repository.Queryable()
.Include("Entity1")
.Include("Entity1.Entity2")
.Include("Entity1.Entity2.Entity3")
.Include("Entity1.Entity2.Entity3.Entity4")
.Include("Entity1.Entity2.Entity3.Entity4.Entity5")
and so on!
There must be a better way of doing this? I'm struggling to find any help on a better way. I also then have a fair few condition I need to apply on each table for example removed flags on tables need to be checked. I'm wondering whether it would just be easier to get this from the database using another method.
If you write .Include("Entity1.Entity2.Entity3.Entity4.Entity5") all related entities are eager loaded, not just the last. So you can write just
repository.Queryable().Include("Entity1.Entity2.Entity3.Entity4.Entity5");
and you will have loaded Entity3 as well as Entity5. Check http://msdn.microsoft.com/en-gb/data/jj574232#eagerLevels for more details, especially
Note that it is not currently possible to filter which related entities are loaded. Include will always being in all related entities.
Also I think that much better is type safe variant of extension method Include. It is more robust to properties renaming etc. than string variant.
repository.Queryable().Include(x => x.Entity1.Entity2.Entity3.Entity4.Entity5);
Here is an example of generic repository with the option to include the navigation properties of an entity :
public class Repository<T> : IRepository<T> where T : class, IEntity
{
...
public virtual T GetOne(Expression<Func<T, bool>> predicate = null, Expression<Func<T, object>>[] includeProperties = null)
{
var set = SetWithIncludes(includeProperties);
return predicate != null ? set.FirstOrDefault(predicate) : set.FirstOrDefault();
}
protected IQueryable<T> SetWithIncludes(IEnumerable<Expression<Func<T, object>>> includes)
{
IQueryable<T> set = DbSet;
if (includes != null)
{
foreach (var include in includes)
{
set = set.Include(include);
}
}
return set;
}
}
and the actual usage :
_entityRepository.GetOne(c => c.Id == id, new Expression<Func<Entity, object>>[] { c => c.SubEntityOrEntityCollection.SubSubEntityOrEntityCollection });

Multiple includes using Entity Framework and Repository Pattern

I am using Entity Framework and a Repository Pattern for all my data access, when using table navigation I have noticed that 2 queries are being run when I got the first object and reference a field in a navigation object. As I have lots of relationships in the database using this technique for my navigation properties may cause performance overheads.
I have looked into the Include(string tableName) method and this will work really well (if I was not using a generic RP) but this only takes one table name. I have managed to replicate this in my repository pattern for one include by changing my where from classs to EntityObject but how can I have multiple includes in one query using a repository pattern??
here is my code:
public class GenericRepository<T> : IRepository<T> where T : EntityObject, new()
{
private Entities _Context;
private ObjectSet<T> _ObjectSet;
public IQueryable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate, string include)
{
// This works OK
return this._ObjectSet.Include(include).Where(predicate);
}
public IQueryable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate, param string[] include)
{
// This will not work but is what I am trying to do
return this._ObjectSet.Include(include).Where(predicate);
}
}
You can chain your includes:
public IQueryable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate, param string[] include)
{
IQueryable<T> query = this._ObjectSet;
foreach(string inc in include)
{
query = query.Include(inc);
}
return query.Where(predicate);
}

Categories