简体   繁体   English

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

[英]How to incorporate an Expression<> into an Entity Framework query?

I'm trying to write a class that helps create a LINQ query dynamically.我正在尝试编写一个 class 来帮助动态创建 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;
}

This class is called like this:这个 class 是这样调用的:

MyClass<Location, string> myClass = new MyClass<Location, string>(l => l.State);

var locations = myClass.ApplyFilter(DbContext.Locations);

However, the code above fails:但是,上面的代码失败了:

The LINQ expression 'DbSet LINQ 表达式'DbSet
.Where(l => Invoke(__GetJoiningTables_0, l[Location]) .Where(l => Invoke(__GetJoiningTables_0, l[Location])
.Any(xx => __FilterIds_1 .Any(xx => __FilterIds_1
.Contains(Invoke(__GetColumn_2, xx) .Contains(Invoke(__GetColumn_2, xx)
)))' could not be translated. )))' 无法翻译。 Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。 See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.有关详细信息,请参阅https://go.microsoft.com/fwlink/?linkid=2101038

The trouble appears to be the way I'm using GetColumn .问题似乎是我使用GetColumn的方式。 So I changed the declaration of GetColumn so that it is now an expression.所以我改变了GetColumn的声明,使它现在是一个表达式。

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

The same argument being passed to my constructor can easily be converted to this type.传递给我的构造函数的相同参数可以很容易地转换为这种类型。 I Only need to change the argument type.我只需要更改参数类型。

But now how can I use this new GetColumn in my ApplyFilter() method?但是现在如何在我的ApplyFilter()方法中使用这个新的GetColumn呢?

Update:更新:

Eventually, I also need to do the same thing to the following two expressions.最后,我还需要对以下两个表达式做同样的事情。

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

Firstly, you need to keep the column as an expression, or c# will compile the lambda function and EF will be unable to extract which column it is;首先,您需要将列保留为表达式,否则 c# 将编译 lambda function 并且 EF 将无法提取它是哪一列;

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

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

Now, you could use the static methods of Expression to build an entire filter expression by hand.现在,您可以使用Expression的 static 方法手动构建整个过滤器表达式。 But in your case there's a shortcut.但在你的情况下,有一条捷径。 Since you can reuse the parameter and body of the input expression (eg x => x.Column ), and wrap it in your Contains call (eg x => FilterIds.Contains(x.Column) ).因为您可以重用输入表达式的参数和主体(例如x => x.Column ),并将其包装在您的Contains调用中(例如x => FilterIds.Contains(x.Column) )。

EDIT:编辑:

Since you've now revealed that FilterIds is an IEnumerable<T> , then FilterIds.Contains() is actually the static extension method Enumerable.Contains() .既然您现在已经发现FilterIds是一个IEnumerable<T> ,那么FilterIds.Contains()实际上是 static 扩展方法Enumerable.Contains() The simplest way to find a matching generic static method is to create a matching delegate.查找匹配的通用 static 方法的最简单方法是创建匹配的委托。

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

EDIT:编辑:

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

Ok that's a can of worms.... I assume that what you're trying to do here is take a collection of navigations to other tables & columns, and apply a filter to that?好的,那是一罐蠕虫....我假设您在这里尝试做的是收集到其他表和列的导航集合,然后对其应用过滤器?

I find it helps to build an example Expression that you're trying to achieve.我发现它有助于构建一个您想要实现的示例Expression I assume you're trying to build expressions like;我假设您正在尝试构建表达式,例如;

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

I suggest that you write this code, let the c# compiler turn them into Expression graphs, and use the debugger to see what those graphs look like.我建议您编写此代码,让 c# 编译器将它们转换为Expression图,并使用调试器查看这些图的样子。

If possible, I'd recommend trying to find a way to "give to cesar what is caesar's".如果可能的话,我建议尝试找到一种方法来“将凯撒的东西交给凯撒”。 And assemble fragments of Expression s by inlining or transforming some template Expression .并通过内联或转换某些模板Expression来组装Expression的片段。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM