使用表达式为Entity Framework构建Array.Contains

问题描述 投票:1回答:2

我想在Where-Contains和Select中都有一个变量字段。 “field_a”是我想要变量的人(有时我想要field_b或_c;它们是字符串)。下面的代码正确地构建了Select为Select(x => x.field_a)。我如何得到Where子句的第二部分,&& targetCodes.Contains(x.field_a)? [dbDbContextitemsArray是一个对象数组,有一个名为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();
    }
c# expression-trees
2个回答
2
投票

可能最困难的部分是找到MethodInfo方法的.Contains()。您可以使用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);

1
投票

有几种方法可以做到这一点。在这种特殊情况下,你甚至不需要处理表达式,因为你可以在Where之后使用简单的链Select(在最终查询中链接的Where条件与&&结合):

var query = db.myTables
    .Where(x => x.companyId == 1)
    .Select((Expression<Func<myTable, string>>)selector)
    .Where(v => targetCodes.Contains(v));

但回答你的问题如何构建表示targetCodes.Contains({field})的表达式,因为你需要的实际调用(没有扩展方法糖)是Enumerable.Contains<string>(targetCodes, {field}),最简单的是使用以下专门为“调用”静态提供的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);
© www.soinside.com 2019 - 2024. All rights reserved.