Using the expression <Func <T, X >> in Linq contains the extension
Using the following example, I would like to use my Expression inside my Contains method, passing the request to the sql server using EF .
How can I configure this to work correctly?
void Main() { IQueryable<Person> qry = GetQueryableItemsFromDB(); var filtered = qry.Filter(p=>p.CompanyId); } public static class Ext { public static IQueryable<T> Filter<T>(this IQueryable<T> items, Expression<Func<T, int>> resolveCompanyIdExpression) { IEnumerable<int> validComps = GetCompanyIdsFromDataBase(); var exp = Expression.Lambda<Func<T, bool>>( Expression.Call(typeof(Queryable),"Contains", new[] { typeof(Company) }, Expression.Constant(validComps), resolveCompanyIdExpression.Body), resolveCompanyIdExpression.Parameters[0]); return items.Where(exp); } public static IQueryable<T> Filter<T>(this IQueryable<T> items, Expression<Func<T, IEnumerable<int>>> resolveCompanyIdExpression) { IEnumerable<int> validComps = GetCompanyIdsFromDataBase(); //No Idea what to do here ? } } public class Person { public int CompanyId {get;set;} } I know that I can pass the entire predicate, but I want the user to suggest how to allow the Company from the object in question.
UPDATE
I decided to allow companyId, not the whole essence of the company, I can get a list of identifiers in memory and they will not fuss if it is IQueryable or just a simple array / IEnumerable
However, I get some strange errors:
An exception occurred during the execution of 'Extent.Select (o => o). Where (p => (p.Hide = False)). Where (p => (p.Archived = False)). Where (item => System.Int32 []. Contains (item.Development.CompanyId)) '. See InnerException for more details.
Internal exception
The argument expression is invalid.
UPDATE 2
I edited the code to reflect what I really would like to do without having much luck finding a solution to this.
If I understand correctly, then what you want is a composition of expressions:
public static IQueryable<T> Filter<T>(IQueryable<T> query, Expression<Func<T, int>> getCompanyId) { IEnumerable<int> validCompanyIds = GetCompanyIdsFromDatabase(); Expression<Func<int, bool>> filterByCompanyId = id => validCompanyIds.Contains(id); // these generics will actually be inferred, I've just written them to be explicit Expression<Func<T, bool>> composed = filterByCompanyId.Compose<T, int, bool>(getCompanyId); return query.Where(composed); } The following is an implementation of the Compose () extension method in an expression:
/// <summary> /// Composes two lambda expressions f(y) and g(x), returning a new expression representing f(g(x)). /// This is useful for constructing expressions to pass to functions like Where(). If given x => x.Id and id => ids.Contains(id), /// for example, you can create the expression x => ids.Contains(x.Id), which could be passed to Where() for an IQueryable of x type /// </summary> /// <typeparam name="TIn">The input of g</typeparam> /// <typeparam name="TIntermediate">The output of g and the input of f</typeparam> /// <typeparam name="TOut">The output of f</typeparam> /// <param name="f">The outer function</param> /// <param name="g">The inner function</param> /// <returns>A new lambda expression</returns> public static Expression<Func<TIn, TOut>> Compose<TIn, TIntermediate, TOut>(this Expression<Func<TIntermediate, TOut>> f, Expression<Func<TIn, TIntermediate>> g) { // The implementation used here gets around EF inability to process Invoke expressions. Rather than invoking f with the output of g, we // effectively "inline" g by replacing all instances of f parameter with g body and creating a new lambda with the rebound body of f and // the parameters of g var map = f.Parameters.ToDictionary(p => p, p => g.Body); var reboundBody = ParameterRebinder.ReplaceParameters(map, f.Body); var lambda = Expression.Lambda<Func<TIn, TOut>>(reboundBody, g.Parameters); return lambda; } public class ParameterRebinder : ExpressionVisitor { private readonly Dictionary<ParameterExpression, Expression> Map; public ParameterRebinder(Dictionary<ParameterExpression, Expression> map) { this.Map = map ?? new Dictionary<ParameterExpression, Expression>(); } public static Expression ReplaceParameters(Dictionary<ParameterExpression, Expression> map, Expression exp) { return new ParameterRebinder(map).Visit(exp); } protected override Expression VisitParameter(ParameterExpression node) { Expression replacement; if (this.Map.TryGetValue(node, out replacement)) { return this.Visit(replacement); } return base.VisitParameter(node); } } try the Expression.Compile () method:
return items.Where(item => validComps.Contains(resolveCompanyExpression.Compile()(item))).AsQueryable(); Not items a IQueryable ? If yes, try the following:
public static IQueryable<T> FilterByCompany<T>(this IQueryable<T> items, Expression<Func<T, Company>> resolveCompanyExpression) where T : EntityBase { IQueryable<Company> validComps = GetCompaniesFromDataBase(); var exp = Expression.Lambda<Func<T, bool>>( Expression.Call( typeof(Queryable), "Contains", new[] { typeof(Company) }, Expression.Constant(validComps), resolveCompanyExpression.Body ), resolveCompanyExpression.Parameters[0] ); return items.Where(exp); }