使用表达式为 Entity Framework 构建 Array.Contains
Using Expressions to build Array.Contains for Entity Framework
我想在 Where-Contains 和 Select 中都有一个变量字段。 "field_a" 是我想要可变的人(有时我想要 field_b 或 _c;它们是字符串)。下面的代码正确地将 Select 构建为 Select(x => x.field_a)
。如何让 Where 子句的第二部分说 && targetCodes.Contains(x.field_a)
? [db
是一个 DbContext
,itemsArray
是一个包含名为 Code
的字符串 属性 的对象数组。]
using (var db = dbFactory.CreateInstance())
{
var parameter = Expression.Parameter(typeof(myTable), "x");
var field = Expression.Property(parameter, "field_a");
var selector = Expression.Lambda(field, parameter);
var targetCodes = itemsArray.Select(i => i.Code).ToArray();
var query = db.myTables
.Where(x => x.companyId == 1 && targetCodes.Contains(x.field_a))
.Select((Expression<Func<myTable, string>>)selector);
return await query.ToArrayAsync();
}
可能最困难的部分是找到.Contains()
方法的MethodInfo
。您可以使用 typeof(IEnumerable<string>).GetMethod(...).Where(...)
,但对于具有多个重载的泛型方法,通常很难做到正确。这是一个小技巧,它使用 C# 编译器通过创建临时表达式为您找到正确的重载:
Expression<Func<IEnumerable<string>, bool>> containsExpr = (IEnumerable<string> q) => q.Contains((string)null);
var containsMethod = (containsExpr.Body as MethodCallExpression).Method;
// containsMethod should resolve to this overload:
// System.Linq.Enumerable.Contains<string>(IEnumerable<string>, string)
程序的其余部分只是通过调用适当的 Expression.XYZ()
方法来构建表达式:
var companyIdEquals1 = Expression.Equal(
Expression.Property(parameter, nameof(myTable.companyId)),
Expression.Constant(1));
var targetCodesContains = Expression.Call(
containsMethod,
Expression.Constant(targetCodes),
field/*reuses expression you already have*/);
var andExpr = Expression.And(companyIdEquals1, targetCodesContains);
var whereExpr = (Expression<Func<myTable, bool>>)Expression.Lambda(andExpr, parameter);
var query = db//.myTables
.Where(whereExpr)
.Select((Expression<Func<myTable, string>>)selector);
有几种方法可以做到这一点。在这种特殊情况下,您甚至不需要处理表达式,因为您可以简单地使用 chain Where
after the Select
(chained Where
条件在最终查询中与 &&
组合):
var query = db.myTables
.Where(x => x.companyId == 1)
.Select((Expression<Func<myTable, string>>)selector)
.Where(v => targetCodes.Contains(v));
但是要回答你的问题如何构建表示targetCodes.Contains({field})
的表达式,因为你需要的实际调用(w/o扩展方法糖)是Enumerable.Contains<string>(targetCodes, {field})
,最简单的是使用专门为 "calling" 静态(通用和非通用)方法提供的以下 Expression.Call
方法重载:
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments
);
在你的情况下,它可以这样使用:
var containsCall = Expression.Call(
typeof(Enumerable), nameof(Enumerable.Contains), new [] { typeof(string) },
Expression.Constant(targetCodes), field);
我想在 Where-Contains 和 Select 中都有一个变量字段。 "field_a" 是我想要可变的人(有时我想要 field_b 或 _c;它们是字符串)。下面的代码正确地将 Select 构建为 Select(x => x.field_a)
。如何让 Where 子句的第二部分说 && targetCodes.Contains(x.field_a)
? [db
是一个 DbContext
,itemsArray
是一个包含名为 Code
的字符串 属性 的对象数组。]
using (var db = dbFactory.CreateInstance())
{
var parameter = Expression.Parameter(typeof(myTable), "x");
var field = Expression.Property(parameter, "field_a");
var selector = Expression.Lambda(field, parameter);
var targetCodes = itemsArray.Select(i => i.Code).ToArray();
var query = db.myTables
.Where(x => x.companyId == 1 && targetCodes.Contains(x.field_a))
.Select((Expression<Func<myTable, string>>)selector);
return await query.ToArrayAsync();
}
可能最困难的部分是找到.Contains()
方法的MethodInfo
。您可以使用 typeof(IEnumerable<string>).GetMethod(...).Where(...)
,但对于具有多个重载的泛型方法,通常很难做到正确。这是一个小技巧,它使用 C# 编译器通过创建临时表达式为您找到正确的重载:
Expression<Func<IEnumerable<string>, bool>> containsExpr = (IEnumerable<string> q) => q.Contains((string)null);
var containsMethod = (containsExpr.Body as MethodCallExpression).Method;
// containsMethod should resolve to this overload:
// System.Linq.Enumerable.Contains<string>(IEnumerable<string>, string)
程序的其余部分只是通过调用适当的 Expression.XYZ()
方法来构建表达式:
var companyIdEquals1 = Expression.Equal(
Expression.Property(parameter, nameof(myTable.companyId)),
Expression.Constant(1));
var targetCodesContains = Expression.Call(
containsMethod,
Expression.Constant(targetCodes),
field/*reuses expression you already have*/);
var andExpr = Expression.And(companyIdEquals1, targetCodesContains);
var whereExpr = (Expression<Func<myTable, bool>>)Expression.Lambda(andExpr, parameter);
var query = db//.myTables
.Where(whereExpr)
.Select((Expression<Func<myTable, string>>)selector);
有几种方法可以做到这一点。在这种特殊情况下,您甚至不需要处理表达式,因为您可以简单地使用 chain Where
after the Select
(chained Where
条件在最终查询中与 &&
组合):
var query = db.myTables
.Where(x => x.companyId == 1)
.Select((Expression<Func<myTable, string>>)selector)
.Where(v => targetCodes.Contains(v));
但是要回答你的问题如何构建表示targetCodes.Contains({field})
的表达式,因为你需要的实际调用(w/o扩展方法糖)是Enumerable.Contains<string>(targetCodes, {field})
,最简单的是使用专门为 "calling" 静态(通用和非通用)方法提供的以下 Expression.Call
方法重载:
public static MethodCallExpression Call(
Type type,
string methodName,
Type[] typeArguments,
params Expression[] arguments
);
在你的情况下,它可以这样使用:
var containsCall = Expression.Call(
typeof(Enumerable), nameof(Enumerable.Contains), new [] { typeof(string) },
Expression.Constant(targetCodes), field);