Как включить Expression <> в запрос Entity Framework? - PullRequest
0 голосов
/ 13 июля 2020

Я пытаюсь написать класс, который помогает динамически создавать 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;
}

Этот класс вызывается так:

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]) .Любое (xx => __FilterIds_1 .Contains (Invoke (__ GetColumn_2, xx). ))) 'не удалось перевести. Либо перепишите запрос в форме, которая может быть переведена, либо явно переключитесь на оценку клиента, вставив вызов либо AsEnumerable (), AsAsyncEnumerable (), ToList (), либо ToListAsyn c (). См. https://go.microsoft.com/fwlink/?linkid=2101038 для получения дополнительной информации.

Проблема, похоже, в том, как я использую GetColumn. Поэтому я изменил объявление GetColumn, так что теперь это выражение.

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

Тот же аргумент, который передается моему конструктору, можно легко преобразовать в этот тип. Мне нужно только изменить тип аргумента.

Но теперь, как я могу использовать этот новый GetColumn в моем ApplyFilter() методе?

Обновление:

В конце концов, мне нужно проделать то же самое со следующими двумя выражениями.

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

1 Ответ

1 голос
/ 13 июля 2020

Во-первых, вам нужно сохранить столбец как выражение, иначе c# скомпилирует лямбда-функцию, и EF не сможет извлечь, какой это столбец;

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

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

Теперь вы можете использовать методы stati c из Expression для создания полного выражения фильтра вручную. Но в вашем случае есть ярлык. Поскольку вы можете повторно использовать параметр и тело входного выражения (например, x => x.Column) и обернуть его в свой вызов Contains (например, x => FilterIds.Contains(x.Column)).

EDIT:

Поскольку теперь вы выяснили, что FilterIds - это IEnumerable<T>, то FilterIds.Contains() на самом деле является методом расширения stati c Enumerable.Contains(). Самый простой способ найти подходящий метод generi c stati c - создать соответствующий делегат.

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(...

Хорошо, это банка с червями ... Я предполагаю, что вы пытаетесь здесь взять набор переходов к другим таблицам и столбцам и применить к ним фильтр?

Я считаю, что это помогает построить пример 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 s путем встраивания или преобразования некоторого шаблона Expression.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...