[英]How to incorporate an Expression<> into an Entity Framework query?
我正在尝试编写一个 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;
}
这个 class 是这样调用的:
MyClass<Location, string> myClass = new MyClass<Location, string>(l => l.State);
var locations = myClass.ApplyFilter(DbContext.Locations);
但是,上面的代码失败了:
LINQ 表达式'DbSet
.Where(l => Invoke(__GetJoiningTables_0, l[Location])
.Any(xx => __FilterIds_1
.Contains(Invoke(__GetColumn_2, xx)
)))' 无法翻译。 以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。 有关详细信息,请参阅https://go.microsoft.com/fwlink/?linkid=2101038 。
问题似乎是我使用GetColumn
的方式。 所以我改变了GetColumn
的声明,使它现在是一个表达式。
protected Expression<Func<T, TColumn>> GetColumn;
传递给我的构造函数的相同参数可以很容易地转换为这种类型。 我只需要更改参数类型。
但是现在如何在我的ApplyFilter()
方法中使用这个新的GetColumn
呢?
更新:
最后,我还需要对以下两个表达式做同样的事情。
// 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());
首先,您需要将列保留为表达式,否则 c# 将编译 lambda function 并且 EF 将无法提取它是哪一列;
protected Expression<Func<T, TColumn>> GetColumn;
public MyClass(Expression<Func<T, TColumn>> getColumn)
{
GetColumn = getColumn;
}
现在,您可以使用Expression
的 static 方法手动构建整个过滤器表达式。 但在你的情况下,有一条捷径。 因为您可以重用输入表达式的参数和主体(例如x => x.Column
),并将其包装在您的Contains
调用中(例如x => FilterIds.Contains(x.Column)
)。
编辑:
既然您现在已经发现FilterIds
是一个IEnumerable<T>
,那么FilterIds.Contains()
实际上是 static 扩展方法Enumerable.Contains()
。 查找匹配的通用 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;
}
编辑:
.Where(x => GetJoiningTables(x).Any(...
好的,那是一罐蠕虫....我假设您在这里尝试做的是收集到其他表和列的导航集合,然后对其应用过滤器?
我发现它有助于构建一个您想要实现的示例Expression
。 我假设您正在尝试构建表达式,例如;
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# 编译器将它们转换为Expression
图,并使用调试器查看这些图的样子。
如果可能的话,我建议尝试找到一种方法来“将凯撒的东西交给凯撒”。 并通过内联或转换某些模板Expression
来组装Expression
的片段。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.