代码之家  ›  专栏  ›  技术社区  ›  Jonathan Wood

如何将表达式<>合并到实体框架查询中?

  •  0
  • Jonathan Wood  · 技术社区  · 4 年前

    我正在尝试编写一个类来帮助动态创建LINQ查询。

    protected Func<T, TColumn> GetColumn;
    
    public MyClass(Func<T, TColumn> getColumn)
    {
        GetColumn = getColumn;
    }
    
    public virtual IQueryable<T> ApplyFilter(IQueryable<T> query)
    {
        if (FilterMode == FilterModeMatchAny)
            return query.Where(x => FilterIds.Contains(GetColumn(x)));
        return query;
    }
    

    这个类的名称如下:

    MyClass<Location, string> myClass = new MyClass<Location, string>(l => l.State);
    
    var locations = myClass.ApplyFilter(DbContext.Locations);
    

    但是,上述代码失败:

    LINQ表达式'DbSet

    。任何(xx=>\u筛选器ID\u 1
    .Contains(调用(\u GetColumn\u 2,xx)
    https://go.microsoft.com/fwlink/?linkid=2101038 更多信息。

    问题似乎是我使用的方式 GetColumn 获取列 所以它现在是一个表达式。

    protected Expression<Func<T, TColumn>> GetColumn;
    

    传递给构造函数的同一个参数可以很容易地转换为这种类型。我只需要更改参数类型。

    但是现在我怎么用这个新的 在我的 ApplyFilter() 方法?

    更新:

    最后,我还需要对以下两个表达式执行相同的操作。

    // Expressions to incorporate
    protected Expression<Func<T, ICollection<TJoinTable>>> GetJoiningTables;
    protected new Expression<Func<TJoinTable, TColumn>> GetColumn;
    
    // Match any query
    return query.Where(x => GetJoiningTables(x).Any(xx => FilterIds.Contains(GetColumn(xx))));
    
    // Match all query
    return query.Where(x => GetJoiningTables(x).Count(xx => FilterIds.Contains(GetColumn(xx))) >= FilterIds.Count());
    
    1 回复  |  直到 4 年前
        1
  •  1
  •   Jonathan Wood    4 年前

    首先,需要将列保持为表达式,否则c#将编译lambda函数,EF将无法提取它是哪一列;

    protected Expression<Func<T, TColumn>> GetColumn;
    
    public MyClass(Expression<Func<T, TColumn>> getColumn)
    {
        GetColumn = getColumn;
    }
    

    Expression 手动构建整个筛选器表达式。但对你来说有一条捷径。因为可以重用输入表达式的参数和主体(例如 x => x.Column ),然后把它包在你的口袋里 Contains x => FilterIds.Contains(x.Column) ).

    编辑:

    FilterIds 是一个 IEnumerable<T> ,那么 FilterIds.Contains() 实际上是静态扩展方法 Enumerable.Contains()

    public virtual IQueryable<T> ApplyFilter(IQueryable<T> query)
    {
        if (FilterMode == FilterModeMatchAny)
            return query.Where(
                Expression.Lambda<Func<T, bool>>(
                    Expression.Call(
                        null,
                        new Func<IEnumerable<TColumn>,TColumn,bool>(Enumerable.Contains).Method,
                        Expression.Constant(FilterIds),
                        GetColumn.Body),
                    GetColumn.Parameters)
            );
        return query;
    }
    

    编辑:

    .Where(x => GetJoiningTables(x).Any(...

    好吧,那是一罐虫子。。。。我假设您在这里要做的是获取到其他表的导航集合&列,并对其应用筛选器?

    表达式 你想要达到的目标。我想你是在尝试建立这样的表达方式;

    Expression<Func<T,bool>> filter = t => 
        Enumerable.Any(t.Child1, c => FilterIds.Contains(c.ChildCol))
        || Enumerable.Any(t.Child2, c => FilterIds.Contains(c.OtherChildCol))
        ... ;
    
    Expression<Func<T,bool>> filter = t => 
        Enumerable.Count(t.Child1, c => FilterIds.Contains(c.ChildCol))
        + Enumerable.Count(t.Child2, c => FilterIds.Contains(c.OtherChildCol))
        ... ;
    

    我建议你写这段代码,让c编译器把它们变成 表达式 图形,并使用调试器查看这些图形的外观。

    如果可能的话,我建议你想办法“把凯撒的东西给塞萨尔”。把这些碎片 通过内联或转换一些模板 表达式 .