I'm looking for a way to use reflection in my linq queries to increase code reusability. for example, i want to change this method :
private Orders GetObjectGraph(MyDbContext dbContext, int orderID)
{
return dbContext.Orders.Include(o => o.OrderDetails).AsNoTracking().FirstOrDefault(o => o.OrderID == orderID);
}
To something like this :
private object GetObjectGraph(MyDbContext dbContext, string masterEntityName, string detailsEntityName, string masterFieldName, object masterFieldValue)
{
return dbContext[masterEntityName].Include(o => o[detailsEntityName]).AsNoTracking().FirstOrDefault(o => o.[masterFieldName] == masterFieldValue);
}
Can anybody help me how to accomplish this task ?
Thanks in advance
You can try something like this (if you are not getting strings from somewhere outside in runtime, if you do than this solution can be adapted to build corresponding Expression Trees from strings):
private T GetObjectGraph<T, TDetails, TField>(
MyDbContext dbContext,
Expression<Func<T, TDetails>> details,
Expression<Func<T, TField>> field,
TField value) where T: class
{
var p = field.Parameters[0];
var eq = Expression.Equal(field.Body, Expression.Constant(value));
var expr = Expression.Lambda<Func<T, bool>>(eq, p);
return dbContext
.Set<T>()
.Include(details)
.AsNoTracking()
.FirstOrDefault(expr);
}
And usage:
GetObjectGraph(dbContext, (Order o) => o.OrderDetails, o => o.OrderID, orderID)
If you don't need to parse strings and so on this could be simplified to:
public static class Ext
{
public static T GetObjectGraph<T, TDetails>(
this DbSet<T> set,
Expression<Func<T, TDetails>> details,
Expression<Func<T, bool>> filter) where T : class
{
return set
.Include(details)
.AsNoTracking()
.FirstOrDefault(filter);
}
}
And usage:
dbContext.Orders(o => o.OrderDetails, o => o.OrderID == orderID);
And for more insight how to handle strings you can look at this question.
Related
What I'm trying to do
I have a repository function that I want to be able support searching by string Equals, Contains, StartsWith, EndsWith. I've created a simple extension method that wraps around these string functions, but EFCore seems unable to translate this.
Are there are any alternative, reusable approaches similar to this?
How I'm trying to do it
public enum StringComparisonType
{
Equals,
Contains,
BeginsWith,
EndsWith
}
public static bool CompareTo(this string inputText, string comparisonText, StringComparisonType comparisonType) => comparisonType switch
{
StringComparisonType.Equals => inputText.Equals(comparisonText),
StringComparisonType.BeginsWith => inputText.StartsWith(comparisonText),
StringComparisonType.Contains => inputText.Contains(comparisonText),
StringComparisonType.EndsWith => inputText.EndsWith(comparisonText),
_ => throw new NotImplementedException($"{nameof(StringComparisonType)} {comparisonType} not currently supported.")
};
var searchText = "hello";
var comparison = StringComparisonType.BeginsWith;
_context.Records.Where(r => r.Text.CompareTo(searchText, comparison))
The problem with the approach
This throws an error along the lines of:
The LINQ expression could not be translated
Alternative approach
The only alternative I've found that works is just inlining the logic to determine the type of comparison to apply, but this is horrible to read, horrible to write, and is not reusable, e.g.
_context.Records
.Where(r => comparison == StringComparisonType.Equals
? r.Text.Equals(searchText)
: comparison == StringComparisonType.BeginsWith
? r.Text.StartsWith(searchText)
: comparison == StringComparisonType.EndsWith
? r.Text.EndsWith(searchText)
: r.Text.Contains(searchText))
I'm currently using EFCore 7.
If you use it on predefined type (be Record in example) try something like this:
public static IQueryable<Record> WhereCompare(this IQueryable<Record> query, string comparisonText, StringComparisonType comparisonType) => comparisonType switch
{
StringComparisonType.Equals => query.Where(r => r.Text.Equals(comparisonText)),
StringComparisonType.BeginsWith => query.Where(r => r.Text.StartsWith(comparisonText)),
StringComparisonType.Contains => query.Where(r => r.Text.Contains(comparisonText)),
StringComparisonType.EndsWith => query.Where(r => r.Text.EndsWith(comparisonText)),
_ => throw new NotImplementedException($"{nameof(StringComparisonType)}
{comparisonType} not currently supported.")
}
And then use it like this:
var result = _context.Records.WhereCompare(searchText, comparison).ToList();
Generic method
Maybe this can be done easier, but let's do this. First implement Compose function (combine sequence of expressions into one):
using System;
using System.Linq;
using System.Linq.Expressions;
...
private static Expression<Func<TSource, TResult>> Compose<TSource, TIntermediate, TResult>(
this Expression<Func<TSource, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TSource));
var intermediateValue = first.Body.ReplaceParameter(first.Parameters[0], param);
var body = second.Body.ReplaceParameter(second.Parameters[0], intermediateValue);
return Expression.Lambda<Func<TSource, TResult>>(body, param);
}
private static Expression ReplaceParameter(this Expression expression,
ParameterExpression toReplace,
Expression newExpression)
{
return new ParameterReplaceVisitor(toReplace, newExpression)
.Visit(expression);
}
private class ParameterReplaceVisitor : ExpressionVisitor
{
private ParameterExpression from;
private Expression to;
public ParameterReplaceVisitor(ParameterExpression from, Expression to)
{
this.from = from;
this.to = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == from ? to : node;
}
}
With this methods we can now create this:
public static IQueryable<T> WhereCompare<T>(this IQueryable<T> query, Expression<Func<T, string>> selector, string comparisonText, StringComparisonType comparisonType)
{
var filter = Compose<T, string, bool>(selector, WhereCompareSelector<T>(comparisonText, comparisonType));
return query.Where(filter);
}
public static Expression<Func<string, bool>> WhereCompareSelector<T>(string comparisonText, StringComparisonType comparisonType) => comparisonType switch
{
StringComparisonType.Equals => r => r.Equals(comparisonText),
StringComparisonType.BeginsWith => r => r.StartsWith(comparisonText),
StringComparisonType.Contains => r => r.Contains(comparisonText),
StringComparisonType.EndsWith => r => r.EndsWith(comparisonText),
_ => throw new NotImplementedException($"{nameof(StringComparisonType)}{comparisonType} not currently supported.")
};
Usage:
var result = _context.Records.WhereCompare(t => t.Text, searchText, comparison).ToList();
I have an extension method that lets you generically include data in EF:
public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query, params Expression<Func<T, object>>[] includes)
where T : class
{
if (includes != null)
{
query = includes.Aggregate(query, (current, include) => current.Include(include));
}
return query;
}
This allows me to have methods in my repository like this:
public Patient GetById(int id, params Expression<Func<Patient, object>>[] includes)
{
return context.Patients
.IncludeMultiple(includes)
.FirstOrDefault(x => x.PatientId == id);
}
I believe the extension method worked before EF Core, but now including "children" is done like this:
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author);
Is there a way to alter my generic extension method to support EF Core's new ThenInclude() practice?
As said in comments by other, you can take EF6 code to parse your expressions and apply the relevant Include/ThenInclude calls. It does not look that hard after all, but as this was not my idea, I would rather not put an answer with the code for it.
You may instead change your pattern for exposing some interface allowing you to specify your includes from the caller without letting it accessing the underlying queryable.
This would result in something like:
using YourProject.ExtensionNamespace;
// ...
patientRepository.GetById(0, ip => ip
.Include(p => p.Addresses)
.ThenInclude(a=> a.Country));
The using on namespace must match the namespace name containing the extension methods defined in the last code block.
GetById would be now:
public static Patient GetById(int id,
Func<IIncludable<Patient>, IIncludable> includes)
{
return context.Patients
.IncludeMultiple(includes)
.FirstOrDefault(x => x.EndDayID == id);
}
The extension method IncludeMultiple:
public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query,
Func<IIncludable<T>, IIncludable> includes)
where T : class
{
if (includes == null)
return query;
var includable = (Includable<T>)includes(new Includable<T>(query));
return includable.Input;
}
Includable classes & interfaces, which are simple "placeholders" on which additional extensions methods will do the work of mimicking EF Include and ThenInclude methods:
public interface IIncludable { }
public interface IIncludable<out TEntity> : IIncludable { }
public interface IIncludable<out TEntity, out TProperty> : IIncludable<TEntity> { }
internal class Includable<TEntity> : IIncludable<TEntity> where TEntity : class
{
internal IQueryable<TEntity> Input { get; }
internal Includable(IQueryable<TEntity> queryable)
{
// C# 7 syntax, just rewrite it "old style" if you do not have Visual Studio 2017
Input = queryable ?? throw new ArgumentNullException(nameof(queryable));
}
}
internal class Includable<TEntity, TProperty> :
Includable<TEntity>, IIncludable<TEntity, TProperty>
where TEntity : class
{
internal IIncludableQueryable<TEntity, TProperty> IncludableInput { get; }
internal Includable(IIncludableQueryable<TEntity, TProperty> queryable) :
base(queryable)
{
IncludableInput = queryable;
}
}
IIncludable extension methods:
using Microsoft.EntityFrameworkCore;
// others using ommitted
namespace YourProject.ExtensionNamespace
{
public static class IncludableExtensions
{
public static IIncludable<TEntity, TProperty> Include<TEntity, TProperty>(
this IIncludable<TEntity> includes,
Expression<Func<TEntity, TProperty>> propertySelector)
where TEntity : class
{
var result = ((Includable<TEntity>)includes).Input
.Include(propertySelector);
return new Includable<TEntity, TProperty>(result);
}
public static IIncludable<TEntity, TOtherProperty>
ThenInclude<TEntity, TOtherProperty, TProperty>(
this IIncludable<TEntity, TProperty> includes,
Expression<Func<TProperty, TOtherProperty>> propertySelector)
where TEntity : class
{
var result = ((Includable<TEntity, TProperty>)includes)
.IncludableInput.ThenInclude(propertySelector);
return new Includable<TEntity, TOtherProperty>(result);
}
public static IIncludable<TEntity, TOtherProperty>
ThenInclude<TEntity, TOtherProperty, TProperty>(
this IIncludable<TEntity, IEnumerable<TProperty>> includes,
Expression<Func<TProperty, TOtherProperty>> propertySelector)
where TEntity : class
{
var result = ((Includable<TEntity, IEnumerable<TProperty>>)includes)
.IncludableInput.ThenInclude(propertySelector);
return new Includable<TEntity, TOtherProperty>(result);
}
}
}
IIncludable<TEntity, TProperty> is almost like IIncludableQueryable<TEntity, TProperty> from EF, but it does not extend IQueryable and does not allow reshaping the query.
Of course if the caller is in the same assembly, it can still cast the IIncludable to Includable and start fiddling with the queryable. But well, if someone wants to get it wrong, there is no way we would prevent him doing so (reflection allows anything). What does matter is the exposed contract.
Now if you do not care about exposing IQueryable to the caller (which I doubt), obviously just change your params argument for a Func<Queryable<T>, Queryable<T>> addIncludes argument, and avoid coding all those things above.
And the best for the end: I have not tested this, I do not use Entity Framework currently!
For posterity, another less eloquent, but simpler solution that makes use of the Include() overload that uses navigationPropertyPath:
public static class BlogIncludes
{
public const string Posts = "Posts";
public const string Author = "Posts.Author";
}
internal static class DataAccessExtensions
{
internal static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query,
params string[] includes) where T : class
{
if (includes != null)
{
query = includes.Aggregate(query, (current, include) => current.Include(include));
}
return query;
}
}
public Blog GetById(int ID, params string[] includes)
{
var blog = context.Blogs
.Where(x => x.BlogId == id)
.IncludeMultiple(includes)
.FirstOrDefault();
return blog;
}
And the repository call is:
var blog = blogRepository.GetById(id, BlogIncludes.Posts, BlogIncludes.Author);
You can do something like this:
public Patient GetById(int id, Func<IQueryable<Patient>, IIncludableQueryable<Patient, object>> includes = null)
{
IQueryable<Patient> queryable = context.Patients;
if (includes != null)
{
queryable = includes(queryable);
}
return queryable.FirstOrDefault(x => x.PatientId == id);
}
var patient = GetById(1, includes: source => source.Include(x => x.Relationship1).ThenInclude(x => x.Relationship2));
I made this method to do the dynamic includes. This way the "Select" command can be used in lambda to include just as it was in the past.
The call works like this:
repository.IncludeQuery(query, a => a.First.Second.Select(b => b.Third), a => a.Fourth);
private IQueryable<TCall> IncludeQuery<TCall>(
params Expression<Func<TCall, object>>[] includeProperties) where TCall : class
{
IQueryable<TCall> query;
query = context.Set<TCall>();
foreach (var property in includeProperties)
{
if (!(property.Body is MethodCallExpression))
query = query.Include(property);
else
{
var expression = property.Body as MethodCallExpression;
var include = GenerateInclude(expression);
query = query.Include(include);
}
}
return query;
}
private string GenerateInclude(MethodCallExpression expression)
{
var result = default(string);
foreach (var argument in expression.Arguments)
{
if (argument is MethodCallExpression)
result += GenerateInclude(argument as MethodCallExpression) + ".";
else if (argument is MemberExpression)
result += ((MemberExpression)argument).Member.Name + ".";
else if (argument is LambdaExpression)
result += ((MemberExpression)(argument as LambdaExpression).Body).Member.Name + ".";
}
return result.TrimEnd('.');
}
Ofcourse there is,
you could traverse the Expression tree of original params, and any nested includes, add them as
.Include(entity => entity.NavigationProperty)
.ThenInclude(navigationProperty.NestedNavigationProperty)
But its not trivial, but definitely very doable, please share if you do, as it can defintiely be reused!
public Task<List<TEntity>> GetAll()
{
var query = _Db.Set<TEntity>().AsQueryable();
foreach (var property in _Db.Model.FindEntityType(typeof(TEntity)).GetNavigations())
query = query.Include(property.Name);
return query.ToListAsync();
}
I adhere the simpler solution that makes use of the Include() overload that uses string navigationPropertyPath. The simplest that I can write is this extension method below.
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace MGame.Data.Helpers
{
public static class IncludeBuilder
{
public static IQueryable<TSource> Include<TSource>(this IQueryable<TSource> queryable, params string[] navigations) where TSource : class
{
if (navigations == null || navigations.Length == 0) return queryable;
return navigations.Aggregate(queryable, EntityFrameworkQueryableExtensions.Include); // EntityFrameworkQueryableExtensions.Include method requires the constraint where TSource : class
}
}
}
public TEntity GetByIdLoadFull(string id, List<string> navigatonProoperties)
{
if (id.isNullOrEmpty())
{
return null;
}
IQueryable<TEntity> query = dbSet;
if (navigationProperties != null)
{
foreach (var navigationProperty in navigationProperties)
{
query = query.Include(navigationProperty.Name);
}
}
return query.SingleOrDefault(x => x.Id == id);
}
Here is a much simpler solution, idea is to cast the dbset to iqueryable and then recursively include properties
I handle it with like that;
I have Article entity. It includes ArticleCategory entity. And also ArticleCategory entity includes Category entity.
So: Article -> ArticleCategory -> Category
In My Generic Repository;
public virtual IQueryable<T> GetIncluded(params Func<IQueryable<T>, IIncludableQueryable<T, object>>[] include)
{
IQueryable<T> query = Entities; // <- this equals = protected virtual DbSet<T> Entities => _entities ?? (_entities = _context.Set<T>());
if (include is not null)
{
foreach (var i in include)
{
query = i(query);
}
}
return query;
}
And I can use it like that;
var query = _articleReadRepository.GetIncluded(
i => i.Include(s => s.ArticleCategories).ThenInclude(s => s.Category),
i => i.Include(s => s.User)
);
We have implemented a security layer around our NHibernate persistence layer in a way that hopes to prevent a user from even receiving an object back from the database if he shouldn't have access to it. That security layer looks like this:
public static IQueryable<T> Secure<T>(this Queryable<T> query){
//if T does not implement ISecurable, then return query
//else
return query.Where(expressionFactory.GetExpression(securityKey));
}
We essentially restrict access to our ISession by wrapping it with a decorator that calls ISession.Query().Secure().
So we have numerous types that return an Expression<Func<T, bool>>, such that we can pass it to Where():
public class DocumentSecurityExpressionFactory : ISecurityExpressionFactory<Document> {
public Expression<Func<Document, bool>> GetExpression(SecurityKey key) {
return doc => doc.MasterDocument.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup) //Look at non-access group compartments for access
.All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
&& (
//person has to be either NTK
doc.MasterDocument.NeedToKnowAccessList.Count() == 0
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
);
}
}
public class DocumentSummarySecurityExpressionFactory : ISecurityExpressionFactory<DocumentSummary> {
public Expression<Func<DocumentSummary, bool>> GetExpression(SecurityKey key) {
return doc => doc.MasterDocument.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup)
.All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
&& (
doc.MasterDocument.NeedToKnowAccessList.Count() == 0
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
);
}
}
public class LatestDocumentVersionSecurityExpressionFactory : ISecurityExpressionFactory<LatestDocumentVersion> {
public Expression<Func<LatestDocumentVersion, bool>> GetExpression(SecurityKey key) {
return version => version.BaseDocument.MasterDocument.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup)
.All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
&& (
version.BaseDocument.MasterDocument.NeedToKnowAccessList.Count() == 0
|| version.BaseDocument.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
|| version.BaseDocument.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
);
}
}
And there are actually several more for different types that look just like this.
The problem here should be clear: each of our entities that does this is essentially the same. They each have a reference to a MasterDocument object, on which all the logic is done. Repeating this code totally sucks (and it all sits in one file so they can all change together if they ever do).
I feel like I should be able to just tell a method how to get the MasterDocument from type T, and then have a generalized method that builds the expression. Something like this:
public static class ExpressionFactory {
public static Expression<Func<T, bool>> Get<T>(Expression<Func<T, MasterDocument>> mdSource, SecurityKey key) {
return t => {
var md = mdSource.Compile()(t);
return md.Compartments.Where(c => c.AssociatedCompartment)...
};
}
}
And call it like so:
public class DocumentSecurityExpressionFactory : ISecurityExpressionFactory<Document> {
public Expression<Func<Document, bool>> GetExpression(SecurityKey key) {
return ExpressionFactory.Get<Document>(doc => doc.MasterDocument, key);
}
}
Now, I understand why this code doesn't work. What I can't figure out is how to build up this expression tree correctly in order to vastly simplify our code. I imagine I could pass in the Expression<Func<T, MasterDocument>> mdSource like that and then use the Expression API to build it out with MemberAccessExpressions and such, but I'm anticipating the mess that would look like, and I'm not sure what would be the lesser evil.
Any help is greatly appreciated.
What you can do is use a Compose method that can compose one expression with another:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Which uses the following method to replace all instances of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Now you can write:
public static class ExpressionFactory
{
public static Expression<Func<T, bool>> Get<T>(
Expression<Func<T, MasterDocument>> mdSource, SecurityKey key)
{
return mdSource.Compose(document =>
document.Compartments.Where(c => c.AssociatedCompartment.Type != ProgramTypes.AccessGroup)
.All(c => key.Compartments.Contains(c.AssociatedCompartment.ID))
&& (
doc.MasterDocument.NeedToKnowAccessList.Count() == 0
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => p.PersonID == key.PersonID)
|| doc.MasterDocument.NeedToKnowAccessList.Any(p => key.AccessGroups.Contains(p.CompartmentID))
);
}
}
I have a very simple code of what I am trying to achieve here.
Basically the idea here would be to be able to do a simple FindBy(x => x.<the_column_name>, <the value>); therefore I wouldn't have to copy-paste the same query by changing only the column name.
For now I keep getting an exception from LINQ saying LINQ to Entities does not recognize the method 'System.String Invoke(Spunky.Entities.User)' method, and this method cannot be translated into a store expression
Is that something possible ? Or maybe we're not yet there with EF 6.1?
public class UsersRepository
{
private Lazy<IDataContext> context;
public UsersRepository()
: this(() => new DataContext())
{
}
public UsersRepository(Func<IDataContext> context)
{
this.context = new Lazy<IDataContext>(context);
}
public IQueryable<User> Find(int id)
{
return FindBy(u => u.Id, id);
}
public IQueryable<User> Find(string username)
{
return FindBy(u => u.Username, username);
}
public IQueryable<User> FindBy<T>(Func<User, T> property, T value)
{
return context.Value
.Users
.Where(u => property.Invoke(u).Equals(value));
}
}
If you change the signature
IQueryable<User> FindBy(Expression<Func<User, bool>> predicate)
{
return context.Users.Where(predicate);
}
you can call
return FindBy(u => u.Username == username);
The code hardly changes, but you don't need to manufacture expressions.
You have to enter an expression (not a Func) because expressions can be translated into SQL. A Func is just a .Net delegate, for which no SQL equivalent exists.
You need to construct an expression tree linq to entities can translate to sql:
public IQueryable<T> FindBy<TKey>(Expression<Func<T,TKey>> memberExpression, object value)
{
var parameterVisitor = new ParameterVisitor(memberExpression);
var parameter = parameterVisitor.Parameter;
var constant = Expression.Constant(value);
var equal = Expression.Equal(memberExpression,constant);
var predicate = Expression.Lambda(equal, parameter);
return context.Value.Users.Where(predicate);
}
public class ParameterVisitor : ExpressionVisitor
{
public ParameterExpression Parameter { get;set;}
public ParameterVisitor(Expression expr)
{
this.Visit(expr);
}
protected override VisitParameter(ParameterExpression parameter)
{
Parameter = parameter;
return parameter;
}
}
Still untested from the top of my head.
Edit: Code corrected.
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();
}