Использование выражений для построения Array.Contains для Entity Framework - PullRequest
0 голосов
/ 12 сентября 2018

Я хочу, чтобы в переменной 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();
    }

Ответы [ 2 ]

0 голосов
/ 12 сентября 2018

Есть несколько способов сделать это.В этом конкретном случае вам даже не нужно иметь дело с выражениями, потому что вы можете использовать просто цепочку 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);
0 голосов
/ 12 сентября 2018

Вероятно, самая трудная часть - найти 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);
...