Повторное использование запроса LINQ приводит к другому запросу LINQ без повторного запроса к базе данных. - PullRequest
3 голосов
/ 28 сентября 2011

У меня есть ситуация, когда мое приложение создает динамический запрос LINQ с использованием PredicateBuilder на основе заданных пользователем критериев фильтра ( в стороне: посмотрите эту ссылку для лучшей реализации EF PredicateBuilder ),Проблема в том, что этот запрос обычно выполняется долго, и мне нужны результаты этого запроса для выполнения других запросов (т. Е. Объединения результатов с другими таблицами).Если бы я писал T-SQL, я бы поместил результаты первого запроса во временную таблицу или переменную таблицы, а затем написал бы другие мои запросы вокруг этого.Я думал о том, чтобы получить список идентификаторов (например, List<Int32> query1IDs) из первого запроса, а затем сделать что-то вроде этого:

var query2 = DbContext.TableName.Where(x => query1IDs.Contains(x.ID))

Это будет работатьтеоретически;однако число идентификаторов в query1IDs может быть в сотнях или тысячах (а выражение LINQ x => query1IDs.Contains(x.ID) преобразуется в оператор T-SQL "IN", которыйплохо по понятным причинам), а число строк в TableName составляет миллионов .У кого-нибудь есть какие-либо предложения относительно лучшего способа справиться с такой ситуацией?

Редактировать 1: Дополнительные пояснения относительно того, что я делаю.

ХорошоЯ создаю свой первый запрос (query1), который просто содержит идентификаторы, которые меня интересуют. По сути, я собираюсь использовать query1 для «фильтрации» других таблиц.Примечание: я не использую ToList() в конце оператора LINQ --- запрос выполняется , а не в настоящее время, и результаты no отправлено клиенту:

var query1 = DbContext.TableName1.Where(ComplexFilterLogic).Select(x => x.ID)

Затем я беру query1 и использую его для фильтрации другой таблицы (TableName2).Теперь я поставил ToList() в конце этого оператора, потому что я хочу выполнить его и довести результаты до клиента:

var query2 = (from a in DbContext.TableName2 join b in query1 on a.ID equals b.ID select new { b.Column1, b.column2, b.column3,...,b.columnM }).ToList();

Затем я беру query1 и повторно использую его для фильтрации еще одной таблицы (TableName3), выполняю ее и передаю результаты клиенту:

var query3 = (from a in DbContext.TableName3 join b in query1 on a.ID equals b.ID select new { b.Column1, b.column2, b.column3,...,b.columnM }).ToList();

Я могу продолжать делать это для любого количества запросов:

var queryN = (from a in DbContext.TableNameN join b in query1 on a.ID equals b.ID select new { b.Column1, b.column2, b.column3,...,b.columnM }).ToList();

Проблема: запрос1 занимает долго время для выполнения.Когда я выполняю query2, query3 ... queryN, query1 выполняется (N-1) раз ... это не очень эффективный способ работы (особенно, если query1 не меняется).Как я уже говорил ранее, если бы я писал T-SQL, я бы поместил результат query1 во временную таблицу, а затем использовал эту таблицу в последующих запросах.

Edit 2:

Я собираюсь поблагодарить Альбина Суннанбо за ответ на этот вопрос за его комментарий:

Когда у меня возникли подобные проблемы с тяжелым запросом, который я хотел повторно использовать в нескольких другихзапросы Я всегда возвращался к решению создания объединения в каждом запросе и прикладывал больше усилий для оптимизации выполнения запроса (в основном путем настройки моих индексов).

Я думаю, что это действительно лучшее, что можно сделатьделать с Entity Framework.В конце концов, если производительность станет действительно плохой, я, вероятно, соглашаюсь с предложением Джона Вули:

Это может быть ситуация, когда переход к собственному ADO с сохраненным процессом возвращает несколько результатов и используетВнутренняя временная таблица может быть лучшим вариантом для этой операции.Используйте EF для остальных 90% вашего приложения.

Спасибо всем, кто прокомментировал этот пост ... Я ценю вклад каждого!

Ответы [ 3 ]

2 голосов
/ 28 сентября 2011

Если размер TableName не слишком велик для загрузки всей используемой таблицы

var tableNameById = DbContext.TableName.ToDictionary(x => x.ID);

, чтобы получить всю таблицу и автоматически поместить ее в локальный Dictionary с ID в качестве ключа.

Другой способ - просто «форсировать» оценку LINQ с помощью .ToList(), в случае извлечения всей таблицы и выполнения части Where локально с Linq2Objects.

var query1Lookup = new Hashset<int>(query1IDs);
var query2 = DbContext.TableName.ToList().Where(x => query1IDs.Contains(x.ID));

Изменить:
Хранение списка идентификаторов из одного запроса в списке и использование этого списка в качестве фильтра в другом запросе обычно можно переписать как объединение.
Когда у меня возникали похожие проблемы с тяжелым запросом, который я хотел повторно использовать в нескольких других запросах, я всегда возвращался к решению создания объединения в каждом запросе и прилагал больше усилий для оптимизации выполнения запроса (в основном путем настройки моих индексов).

1 голос
/ 29 сентября 2011

Рассматривали ли вы составление запроса согласно этой статье (с использованием шаблона проектирования декоратора):

Составленные запросы LINQ с использованием шаблона декоратора

Предпосылка состоит в том, чтовместо перечисления вашего первого (очень постоянного) запроса вы в основном используете шаблон декоратора для создания цепочки IQueryable, являющейся результатом запроса 1 и запроса N. Таким образом, вы всегда выполняете отфильтрованную форму запроса.

Надеюсь, это поможет

1 голос
/ 28 сентября 2011

Поскольку вы выполняете последующий запрос на основе результатов, возьмите свой первый запрос и используйте его в качестве представления на вашем SQL Server, добавьте представление в свой контекст и создайте запросы LINQ для этого представления.

...