EntityFramework Generic Repository, multiple include? - c#

I am trying to change my generic retrieve method from my generic repository. But I want instead to pass a string for the includeproperties, to pass this: params Expression<Func<TEntity, object>>[] includeProperties = null
The thing is when I call this method:
public virtual IEnumerable<TEntity> Retrieve(Expression<Func<TEntity, bool>> filter = null, params Expression<Func<TEntity, object>>[] includeProperties = null)
I want for example: TEntityExample.Retrieve(filter: c=>c.Id=Id, includeProperties:c=> c.propertynav1, e=> e.propertynav1.propertynav3, e=> e.Prop4)
Or just if dont need navigation properties TEntityExample.Retrieve(filter: c=>c.Id=Id)
But dont know why the includeProperties: is not working, is not accepted, anyone know why, or if I am doing something wrong. I want the possibility to dont pass the includeProperties or to pass it by specifying the includeProperties:
public virtual IEnumerable<TEntity> Retrieve(Expression<Func<TEntity, bool>> filter = null, string includeProperties = "")
{
IQueryable<TEntity> query = _dbSet;
if (filter != null)
{
query = query.Where(filter);
}
if (!string.IsNullOrEmpty(includeProperties))
{
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
}
return query.ToList();
}

I do something similar, but instead use expressions to eagerly load. Maybe this will help:
public TEntity Item(Expression<Func<TEntity, bool>> wherePredicate, params Expression<Func<TEntity, object>>[] includeProperties)
{
foreach (var property in includeProperties)
{
_dbSet.Include(property);
}
return _dbSet.Where(wherePredicate).FirstOrDefault();
}

Related

EF Core filter the child entities after include

I am working with EF Core and I created a generic repository. I have a method which returns all the entities with their child entities. Here is my method:
public Repository(DevbAERPContext dbContext)
{
_dbContext = dbContext;
Table = _dbContext.Set<T>();
}
public async Task<IEnumerable<T>> GetAllWithInclude(Expression<Func<T, bool>> where, string[] includeProperties)
{
var result = includeProperties.Aggregate(Table.Where(where), (query, path) => query.Include(path)).AsNoTracking();
return await result.ToListAsync();
}
While using this method I don't want to get the soft deleted data. I can filter the parent entity by writing where expression but I also want to do the same for the child entities. Currently I can handle the issue in controller like this:
var roles = await _roleRepository.GetAllWithInclude(x => !x.IsDeleted, new string[] { "RoleTemplateSkills", "RoleTemplateSkills.Skill" }).ConfigureAwait(false);
var mappedRoles = _mapper.Map<List<RoleTemplateViewModel>>(roles);
foreach(var mappedRole in mappedRoles)
{
mappedRole.RoleTemplateSkills = mappedRole.RoleTemplateSkills.Where(x => !x.IsDeleted).ToList();
}
What I want is to do this filtering in my generic repository method. Is there any way to do it?
I'm not sure if I got your problem exactly. But here below is a sample code I used in one generic repository may help you.
public TEntity FindByInclude(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includeProperties)
{
IQueryable<TEntity> result = dbSet.Where(predicate);
if (includeProperties.Any())
{
foreach (var includeProperty in includeProperties)
{
result = result.Include(includeProperty);
}
}
var firstResult = result.FirstOrDefault(predicate);
if (firstResult != null)
dbSet.Attach(firstResult);
return firstResult;
}
public IEnumerable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] includeProperties)
{
IQueryable<TEntity> result = dbSet.AsNoTracking();
return includeProperties.Aggregate(result, (current, includeProperty) => current.Include(includeProperty));
}
However, In the constructor, I didn't use Table. Instead I used as the following
public class FitnessRepository<TEntity> : IFitnessRepository<TEntity> where TEntity :class {
private readonly FitnessDbContextt fitnessDbContext;
private readonly DbSet<TEntity> dbSet;
public FitnessRepository (FitnessDbContextt context) {
fitnessDbContext = context;
dbSet = context.Set<TEntity> ();
}
}
I hope it helps.
Have you tried to add a filter to the Entity it self in the Entity designer? so you can skip adding the condition here.
Thanks

passing dynamic expression to order by in code first EF repository

we have written a Generic function to get the records from EF code first in a repository pattern. Rest seems to be ok but when passing an Integer to the dynamic order by , it says Cannot cast System.Int32 to System.Object
the expression is as follows:
Expression<Func<HeadOffice, object>> orderByFunc = o => o.Id;
if (options.sort == "Id")
{
// this is an Integer
orderByFunc = o => o.Id;
}
if (options.sort =="Name")
{
// string
orderByFunc = o => o.Name;
}
if (options.sort == "Code")
{
orderByFunc = o => o.Code;
}
the generic method is as follows:
public virtual IEnumerable<TEntity> GetSorted<TSortedBy>(
Expression<Func<TEntity, object>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = dbSet;
foreach (var include in includes)
{
query = dbSet.Include(include);
}
IEnumerable<TEntity> data = query.OrderBy(order).Skip(skip).Take(take).ToList();
return data;
}
if we convert Expression<Func<TEntity, object>> to Expression<Func<TEntity, int>> then it seems to work fine with integer but consequently not with strings
any help appreciated.
Maybe if you change the type of that parameter for this Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy, it could make your life easier:
public virtual IEnumerable<TEntity> GetSorted(Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy,...)
{
IQueryable<TEntity> query = dbSet;
//...
if (orderBy != null)
{
query = orderBy(query);
}
//...
}
This way you can pass an Func like this:
Func<IQueryable<HeadOffice>, IOrderedQueryable<HeadOffice>> orderBy=null;
if (options.sort == "Id")
{
orderBy= query=>query.OrderBy(o => o.Id);
}
//...
Update
Another thing that I notice now is you are not using the TSortedBy generic parameter, so, you could also do this:
public virtual IEnumerable<TEntity> GetSorted<TSortedBy>(Expression<Func<TEntity, TSortedBy>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
}
But anyway I think is better use the first option and remove that generic parameter.
Create a Sorter class. We also need a property-type-neutral base class:
public class SorterBase<TEntity>
{
public abstract IEnumerable<TEntity> GetSorted( // Note, no order argument here
int skip, int take,
params Expression<Func<TEntity, object>>[] includes);
}
public class Sorter<TEntity, TSortProp> : SorterBase<TEntity>
{
private Expression<Func<TEntity, TSortProp>> _order;
public Sorter(Expression<Func<TEntity, TSortProp>> order)
{
_order = order;
}
public override IEnumerable<TEntity> GetSorted(...)
{
// Use _order here ...
}
}
Now change the sort decision to:
SorterBase<HeadOffice> sorter;
if (options.sort == "Id") {
sorter = new Sorter<HeadOffice, int>(o => o.Id);
} else if (options.sort == "Name") {
sorter = new Sorter<HeadOffice, string>(o => o.Name);
}
...
var result = sorter.GetSorted(skip, take, includes);
One solution is to have two overloaded methods, one takes
Expression<Func<TEntity, int>>
and one takes
Expression<Func<TEntity, string>>
To minimize code duplication, extract common code (for example the query initialization statement and the for loop) to a shared method, and just let the two methods call this shared method and then invoke OrderBy on the result.
If none of the answers work for you and you must have the order expression as:
Expression<Func<TEntity,object>>
then try the following solution:
public class ExpressionHelper : ExpressionVisitor
{
private MemberExpression m_MemberExpression;
public MemberExpression GetPropertyAccessExpression(Expression expression)
{
m_MemberExpression = null;
Visit(expression);
return m_MemberExpression;
}
protected override Expression VisitMember(MemberExpression node)
{
var property = node.Member as PropertyInfo;
if (property != null)
{
m_MemberExpression = node;
}
return base.VisitMember(node);
}
}
public class DataClass<TEntity>
{
private readonly IQueryable<TEntity> m_Queryable;
public DataClass(IQueryable<TEntity> queryable)
{
m_Queryable = queryable;
}
public virtual IEnumerable<TEntity> GetSorted(
Expression<Func<TEntity, object>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
var property_access_expression = new ExpressionHelper().GetPropertyAccessExpression(order);
if(property_access_expression == null)
throw new Exception("Expression is not a property access expression");
var property_info = (PropertyInfo) property_access_expression.Member;
var covert_method = this.GetType().GetMethod("Convert").MakeGenericMethod(property_info.PropertyType);
var new_expression = covert_method.Invoke(this, new object[] {property_access_expression, order.Parameters });
var get_sorted_method = this.GetType()
.GetMethod("GetSortedSpecific")
.MakeGenericMethod(property_info.PropertyType);
return (IEnumerable<TEntity>)get_sorted_method.Invoke(this, new object[] { new_expression, skip, take, includes });
}
public virtual IEnumerable<TEntity> GetSortedSpecific<TSortedBy>(
Expression<Func<TEntity, TSortedBy>> order,
int skip, int take,
params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = m_Queryable;
//Here do your include logic and any other logic
IEnumerable<TEntity> data = query.OrderBy(order).Skip(skip).Take(take).ToList();
return data;
}
public Expression<Func<TEntity, TNewKey>> Convert<TNewKey>(
MemberExpression expression, ReadOnlyCollection<ParameterExpression> parameter_expressions )
{
return Expression.Lambda<Func<TEntity, TNewKey>>(expression, parameter_expressions);
}
}
Here is how I tested this:
void Test()
{
Expression<Func<Entity, object>> exp = (x) => x.Text;
List<Entity> entities = new List<Entity>();
entities.Add(new Entity()
{
Id = 1,
Text = "yacoub"
});
entities.Add(new Entity()
{
Id = 2,
Text = "adam"
});
DataClass<Entity> data = new DataClass<Entity>(entities.AsQueryable());
var result = data.GetSorted(exp, 0, 5, null);
}
I tested this with both integer and string properties.
Please note that this only works for simple property access expressions.
You can enhance the GetSorted method to make this work for more complex cases.

"Include" lambda in generic repository

I follow repository pattern and I have the following method in my generic repository class:
public virtual T Get(Expression<Func<T, bool>> where)
{
return dbset.Where(where).FirstOrDefault<T>();
}
I would like to add the a lambda expression for including navigation properties. Is it possible?
Here's one way you could do it
public virtual IQueryable<T> Include(this IQueryable<T> qry, params string[] includes)
{
foreach(var inc in includes)
qry = qry.Include(inc);
return qry;
}
Example usage:
var list = context.Products.Where(x => x.ProductID == 1).Include("Items", "Person").Single();
There's my generic repo Get method:
public virtual IQueryable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> Query = DbSet;
if (filter != null)
{
Query = Query.Where(filter);
}
if (!string.IsNullOrEmpty(includeProperties))
{
foreach (string IncludeProperty in includeProperties.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries))
{
Query = Query.Include(IncludeProperty);
}
}
if (orderBy != null)
{
return orderBy(Query);
}
return Query;
}

Generic repository, function explanation / c#

I have found some interesting generic repository, but I can't figure out what the function does: PerformInclusions(includeProperties, query);
Call to PerformInclusions,
public T Single(Expression<Func<T, bool>> where, string includeProperties)
{
try
{
IQueryable<T> query = IDbSet;
query = PerformInclusions(includeProperties, query);
return query.Single(where);
}
catch (InvalidOperationException ex)
{
return null;
}
}
PerformInclusions
private static IQueryable<T> PerformInclusions(string includeProperties,
IQueryable<T> query)
{
if (includeProperties != null && includeProperties.Length > 0)
{
foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
}
return query;
}
I can use the single function without the second parameter.
Euser test = Adapter.EuserRepository.Single(u => u.EuserEmail.Equals(user.EuserEmail), "");
So i have 2 questions what does the function PerformInclusions() do and can someone give me an example what the includeproperties would be in the call to the single() function.
Thanks in advance
as #Andrei says this is allowing you to use familiar EF include syntax to eager load navigation properties. The other alternative for nav property eager loading is via lambdas as below:
public T GetBy(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes)
{
var result = GetAll();
if (includes.Any())
{
foreach (var include in includes)
{
result = result.Include(include);
}
}
return result.FirstOrDefault(predicate);
}
If you have an entity with a navigation property as below:
public class Test{
public int Id {get;set;}
public SomethingElse Thing {get;set;}
}
_repo.Single(t => t.Id == 1, ""); OR _repo.GetBy(t => t.Id == 1);
will return an entity where Thing is null
_repo.Single(t => t.Id == 1, "Thing"); OR _repo.GetBy(t => t.Id == 1, t=>t.Thing);
will return an entity with Thing populated via the foreign key
For some more details on what Navigation properties in EF are check out my blog http://blog.staticvoid.co.nz/2012/7/17/entity_framework-navigation_property_basics_with_code_first

Use Include() method in repository

I have the following with EF 5:
var a = context.Posts.Include(x => x.Pack).Select(x => x.Pack.Id).ToList();
This works. Then I tried to replicate this in my generic repository:
public IQueryable<T> Include<T>(Expression<Func<T, Boolean>> criteria) where T : class
{
return _context.Set<T>().Include(criteria);
}
But in this case I am not able to do the following:
var b = repository.Include<Post>(x => x.Pack).Select(x => x.Pack.Id).ToList();
I get the error:
Cannot implicitly convert type 'Data.Entities.Pack' to 'bool'
How can I solve this?
What should I change in my Include() method?
Try:
Change
Expression<Func<T, Boolean>> criteria
To
Expression<Func<T, object>> criteria
Edit:
To Include multiple entities, you need to add an "include" extension:
public static class IncludeExtension
{
public static IQueryable<TEntity> Include<TEntity>(this IDbSet<TEntity> dbSet,
params Expression<Func<TEntity, object>>[] includes)
where TEntity : class
{
IQueryable<TEntity> query = null;
foreach (var include in includes)
{
query = dbSet.Include(include);
}
return query == null ? dbSet : query;
}
}
Then you can use it like this:
repository.Include(x => x.Pack, x => x.Pack.Roles, ...).Select(x => x.Pack.Id).ToList();
Just make sure "repository" return a "DbSet" Object.
I used the accepted answer but had to modify it a bit for EntityFramework Core.
In my experience I had to keep the chain going or the previous query references were overwritten.
public IQueryable<TEntity> Include(params Expression<Func<TEntity, object>>[] includes)
{
IIncludableQueryable<TEntity, object> query = null;
if(includes.Length > 0)
{
query = _dbSet.Include(includes[0]);
}
for (int queryIndex = 1; queryIndex < includes.Length; ++queryIndex)
{
query = query.Include(includes[queryIndex]);
}
return query == null ? _dbSet : (IQueryable<TEntity>)query;
}
This is what we ended by doing in EF 6
public IEnumerable<T> GetIncludes(params Expression<Func<T, Object>>[] includes)
{
IQueryable<T> query = table.Include(includes[0]);
foreach (var include in includes.Skip(1))
{
query = query.Include(include);
}
return query.ToList();
}

Categories

Resources