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);
}
Related
i want to create a method that can be used with lambda in that way:
return Method<MyClass>(x => x.PropName1, x.PropName2,...);
inside it i have to use tha propName to eager load those reference field via nhibernate:
return session.Query<MyClass>()
.Fetch(c => c.PropName1)
.Fetch(c => c.PropName2).ToList();
i look into linq source code to find some similar and went here:
public static void ListEager<TEntity>(IEnumerable<Func<TEntity, TKey>> fields)
but it's simply not correct.
how can it be done?
You can do like this, implement IGeneric interface and Generic class, with generic method GetList, i use this generic method and working very well.
public interface IGenericDataRepository<T> where T : class
{
IList<T> GetList(Func<T, bool> where, params Expression<Func<T, object>>[] navigationProperties);
}
public class GenericDataRepository<T> : IGenericDataRepository<T> where T : class
{
public virtual IList<T> GetList(Func<T, bool> where,
params Expression<Func<T, object>>[] navigationProperties)
{
List<T> list;
using (var dbQuery = new session.Query<T>())
{
//Apply eager loading
foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
dbQuery = dbQuery.Fetch<T, object>(navigationProperty);
list = dbQuery
.AsNoTracking()
.Where(where)
.ToList<T>();
}
return list;
}
}
To use it you need create repository class for any entity, here is example with my ProductRepository class
public interface IProductRepository:IGenericDataRepository<Product>
{
////
}
public class ProductRepository:GenericDataRepository<Product>,IProductRepository
{
////
}
i switch to queryover to get more power :D
public IEnumerable<TEntity> List(params Expression<Func<TEntity, object>>[] eagerFields)
{
var query = _session.QueryOver<TEntity>();
query = AddReferenceFetch(query, eagerFields);
return query.TransformUsing(Transformers.DistinctRootEntity).List();
}
private IQueryOver<TEntity, TEntity> AddReferenceFetch(IQueryOver<TEntity, TEntity> query, params Expression<Func<TEntity, object>>[] eagerFields)
{
foreach (Expression<Func<TEntity, object>> field in eagerFields)
query = query.Fetch(field).Eager;
return query;
}
in this way i can manage reference or hasmany without problem
i left #mww as accepted answer because the main idea is his
I have this method in my repository exposing EF6 DbContext.
public IList<T> GetEntities<T>(Func<T, bool> predicate) where T : class
{
return db.Set<T>().Where(predicate).ToList<T>();
}
When I watch this method execute in SQL Profiler, the predicate is executed in memory. The SQL statement contains no where clause.
Any ideas?
.Where accepts one of two things, either Func<T, bool> or Expression<Func<T, bool>>. If you pass in Expression<Func<T, bool>>, then your EF query should work properly.
public IList<T> GetEntities<T>(Expression<Func<T, bool>> predicate) where T : class
You'd call it the same way:
GetEntities(x => x.Id == 34)
When you pass in Func<T, bool>, the IEnumerable<T> implementation executes, which uses Linq-to-Objects rather than Linq-to-Entities.
Your predicate should be an Expression so that Entity Framework can actually use it to generate SQL instead of just executing it. If you pass in a Func you are actually calling the Enumerable.Where method instead of Queryable.Where:
public IList<T> GetEntities<T>(Expression<Func<T, bool>> predicate) where T : class
{
return db.Set<T>().Where(predicate).ToList<T>();
}
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;
}
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 });
I have an abstract class which gets implemented for all of my repositories - within there I have a property which returns a collection:
protected readonly IDbSet<T> _dbSet;
public virtual IEnumerable<T> GetSelection(Func<T, bool> where)
{
return _dbSet.Where(where);
}
Repo:
public class PostcodeDataRepository : EntityRepositoryBase<PostcodeData>, IPostcodeDataRepository
{
// Constructor in here
}
Now this works great:
var postcodeData = postcodeRespoistory.GetSelection(x=>x.Postcode == "SW1A 2TT");
(Yes it's uk postcode data, and yes there are nearly 2m rows in the table)
This works great but my problem is returning all of the data only for the application to then filter it is causing some performance issues (as you would expect!). I used from MiniProfiler and EFProf to confirm that it is effectively doing a select * from PostcodeData which isn't what I want it to do.
Has anyone any ideas how I can resolve this?
You need to use the Expression<Func<TSource, bool>> predicate:
public IEnumerable<T> GetSelection(Expression<Func<T, Boolean>> predicate)
{
return _dbSet.Where(where);
}