【问题标题】:use Expression<Func<T,X>> in Linq contains extension在 Linq 中使用 Expression<Func<T,X>> 包含扩展
【发布时间】:2012-04-10 14:01:03
【问题描述】:

使用以下示例,我想在我的 Contains 方法中使用我的 Expression,让它使用 EF 将查询传递到 sql server。

我怎样才能让它正常工作?

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

我知道我可以传入整个谓词,但我只希望用户提供如何从相关实体解析 Company。

更新

我决定解析 companyId 而不是整个公司实体,我可以获取内存中的 ids 列表,如果它是 IQueryable 或只是一个普通数组/IEnumerable,我不会大惊小怪

但是我得到了一些奇怪的错误:

'执行过程中发生异常 Extent.Select(o => o).Where(p => (p.Hide = False)).Where(p => (p.Archived = False)).Where(item => System.Int32[].Contains(item.Development.CompanyId))'。看 InnerException 了解更多详情。

内部异常是

参数表达式无效

更新 2

我已经编辑了代码以反映我真正想做的事情,但在找到解决方案时运气不佳。

【问题讨论】:

  • 即使您按照自己的方式使用它,您也会为列表中的每个项目执行单独的往返,因为您的查询基于对象列表。您需要将查询更改为以validComps 开头,或者从数据库中获取IQueryable&lt;T&gt; 并从那里开始。您还需要将“包含”基于对象的唯一 ID,而不是整个对象。
  • 好吧,这是我的错字,应该是IQueryable&lt;T&gt;

标签: c# .net linq lambda linq-expressions


【解决方案1】:

如果我理解正确,你想要的是表达式组合:

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

下面是表达式上 Compose() 扩展方法的实现:

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

【讨论】:

    【解决方案2】:

    尝试 Expression.Compile() 方法:

    return items.Where(item => validComps.Contains(resolveCompanyExpression.Compile()(item))).AsQueryable();
    

    【讨论】:

    【解决方案3】:

    items 参数不是IQueryable 吗?如果是这样,试试这个:

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

    【讨论】:

    • 在 OP 的例子中,this 是一个 List.
    猜你喜欢
    • 2019-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多