use Expression> in Linq contains extension

后端 未结 3 1691
终归单人心
终归单人心 2020-12-16 04:17

Using the following example i would like to use my Expression inside my Contains method, having it pass the query onto sql server using the EF.

相关标签:
3条回答
  • 2020-12-16 05:03

    If I'm understanding correctly, what you want is expression composition:

    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);
    }
    

    Below is the implementation of the Compose() extension method on 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's 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's inability to process Invoke expressions. Rather than invoking f with the output of g, we
            // effectively "inline" g by replacing all instances of f's parameter with g's 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);
                }
            }
    
    0 讨论(0)
  • 2020-12-16 05:05

    try Expression.Compile() method:

    return items.Where(item => validComps.Contains(resolveCompanyExpression.Compile()(item))).AsQueryable();
    
    0 讨论(0)
  • 2020-12-16 05:12

    Isn't the items parameter an IQueryable? If so, try this:

    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);
    }
    
    0 讨论(0)
提交回复
热议问题