Неэффективная генерация SQL запросов при использовании выражений с LINQ - PullRequest
0 голосов
/ 05 мая 2020

Рассмотрим следующий код, где dbContext - это SQL контекст базы данных сервера, а Examples - DbSet:

this.dbContext.Examples.Take(5).ToList();
Enumerable.Take(this.dbContext.Examples, 5).ToList();

Первая строка работает должным образом и преобразуется в SQL следующим образом:

SELECT TOP(5) * FROM Examples

Однако вторая строка сначала извлекает все строки, а затем применяет оператор Take. Почему?

Поскольку я использую выражения для построения динамической c лямбды, мне приходится использовать второй подход (Enumerable.Take):

var call = Expression.Call(
    typeof(Enumerable),
    "Take",
    new[]{ typeof(Examples) },
    contextParam,
    Expression.Constant(5)
);

К сожалению, первый подход не работает при работе с выражениями, а текущая архитектура программы вынуждает меня динамически строить лямбда.

Почему второй подход извлекает все строки и как я могу предотвратить это, чтобы эффективно использовать его в выражениях ?

Ответы [ 3 ]

3 голосов
/ 05 мая 2020

Вы вызываете не тот же метод. Первая строка вызывает Queryable.Take, а не Enumerable.Take.

Поскольку DbSet реализует как IQueryable<>, так и IEnumerable<>, но IQueryable<> реализует IEnumerable<>, компилятор обрабатывает IQueryable<> как более конкретный тип c. Поэтому, когда он разрешает метод расширения Take для вызова, он определяет, что Queryable.Take(...) является правильным, потому что для него требуется IQueryable<> в качестве первого параметра.

Это важно, потому что IQueryable<> интерфейс - это то, что позволяет строить запросы LINQ как деревья выражений, которые вычисляются как SQL. В тот момент, когда вы переключаетесь на обработку IQueryable<> как IEnumerable<>, вы теряете это поведение и переключаетесь только на возможность перебора результатов любого запроса, который был создан до этого.

Попробуйте следующее:

Queryable.Take(this.dbContext.Examples, 5).ToList();

или так:

var call = Expression.Call(
    typeof(Queryable),
    "Take",
    new[]{ typeof(Examples) },
    contextParam,
    Expression.Constant(5)
);
1 голос
/ 05 мая 2020

Это работает, потому что в первом операторе

dbContext.Examples.Take(5).ToList(); - вы вызываете .Take (5) на интерфейсе IQuerable, на котором поставщик LINQ to SQL может выполнить правильный оператор SQL против базы данных.

Если вам нужно, чтобы запрос выполнялся на стороне базы данных, вы должны построить запрос на экземпляре интерфейса IQuerable.

Enumerable.Take - ссылка IEnumarble, выполнение Take произойдет в памяти после того, как вы получите все данные из db

0 голосов
/ 05 мая 2020

"this.dbContext.Examples" получает все данные, затем Enumerable. Возьмите фильтр и возьмите из него первые 5.

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