Фильтр Entity Framework "Выражение <Func <T, bool >>"

Я пытаюсь создать метод фильтра для списка объектов Entity и лучше понять Expression<Func<...

У меня есть тестовая функция, подобная этой.

public IQueryable<T> Filter<T>(IEnumerable<T> src, Expression<Func<T, bool>> pred)
{
    return src.AsQueryable().Where(pred);
}

и если я это сделаю:

context.Table.Filter(e => e.ID < 500);

или это:

context.Table.Filter(e => e.SubTable.Where(et => et.ID < 500).Count() > 0 && e.ID < 500);

все работает хорошо.

Но если я это сделаю:

context.Table.Filter(e => e.SubTable.Filter(et => et.ID < 500).Count() > 0 && e.ID < 500);

или это:

context.Table.Where(e => e.SubTable.Filter(et => et.ID < 500).Count() > 0 && e.ID < 500);

Я получаю одну ошибку. LINQ to Entities does not recognize the method ...Filter...

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

В первых двух случаях фильтр работает в базе данных правильно.

Ответ 1

Джон и Тим уже объяснили, почему это не работает.

Предполагая, что код фильтра внутри Filter не является тривиальным, вы можете изменить Filter так, чтобы он возвращал выражение, которое может преобразовать EF.

Предположим, что у вас есть этот код:

context.Table.Where(x => x.Name.Length > 500);

Теперь вы можете создать метод, который возвращает это выражение:

Expression<Func<YourEntity, bool>> FilterByNameLength(int length)
{
    return x => x.Name.Length > length;
}

Использование будет таким:

context.Table.Where(FilterByNameLength(500));

Выражение, созданное внутри FilterByNameLength, может быть сколь угодно сложным, если вы можете передать его непосредственно на Where.

Ответ 2

Полезно понять разницу между Expression<Func<>> и Func<>.

An Expression e => e.ID < 500 хранит информацию об этом выражении: там T e, что вы обращаетесь к свойству ID, вызывая оператор < со значением int 500. Когда EF смотрит на это, он может превратить его в нечто вроде [SomeTable].[ID] < 500.

A Func e => e.ID < 500 - это метод, эквивалентный:

static bool MyMethod(T e) { return e.ID < 500; }

Он скомпилирован как IL-код, который делает это; он не предназначен для "восстановления" SQL-запроса или чего-то еще, только запускается.

Когда EF принимает ваш Expression, он должен понимать каждую его часть, потому что она использует это для построения SQL-запроса. Он запрограммирован на то, что означает существующий метод Where. Он не знает, что означает ваш метод Filter, хотя это тривиальный метод, поэтому он просто отказывается.

Ответ 3

Почему он работает в одном случае, а не в сумматоре?

Поскольку EF действительно не знает о вашем методе Filter. Он не понимает, что он должен делать, поэтому он не знает, как перевести его в SQL. Сравните это с Where и т.д., Которое он понимает.

Версия, в которой вы вызываете ее непосредственно в исходной таблице, работает, потому что таким образом вы не получаете дерево выражений, содержащее вызов Filter - он просто вызывает Filter напрямую, что, в свою очередь, создает запрос... но тот, который понимает EF.

Я был бы очень удивлен, если бы вы могли решить, как заставить ваш метод Filter работать в EF-запросе... но вы уже сказали, что использование Where работает в любом случае, поэтому зачем использовать Filter вообще? Я бы использовал версию Where - или еще лучше, используйте перегрузку Any, которая берет предикат:

context.Table.Filter(e => e.SubTable.Any(et => et.ID < 500) && e.ID < 500);