Имеет ли значение позиция "где" в запросе LINQ при объединении в памяти? - PullRequest
0 голосов
/ 18 декабря 2018

Ситуация: Скажем, мы выполняем запрос LINQ, который объединяет два списка в памяти (поэтому не требуется генерация DbSets или SQL-запроса), и этот запрос также содержит предложение where.Этот where фильтрует только свойства, включенные в исходный набор (часть запроса from).

Вопрос: Оптимизирует ли интерпретатор запросов linq этот запрос, поскольку он сначала выполняет where до того, как он выполняет join, независимо от того, пишу ли я where до илипосле join?- поэтому он не должен выполнять объединение элементов, которые в любом случае не будут включены позже.

Пример: Например, у меня есть список categories, к которому я хочу присоединиться с products список.Тем не менее, меня просто интересует category с ID 1. Выполняет ли интерпретатор linq внутри себя те же самые операции независимо от того, пишу ли я:

from category in categories
join prod in products on category.ID equals prod.CategoryID
where category.ID == 1 // <------ below join
select new { Category = category.Name, Product = prod.Name };

или

from category in categories
where category.ID == 1 // <------ above join
join prod in products on category.ID equals prod.CategoryID
select new { Category = category.Name, Product = prod.Name };

Предыдущее исследование: Я уже видел этот вопрос , но автор ОП заявил , что его / ее вопрос касается только случаев, не связанных с памятьюс сгенерированным SQL.Я явно заинтересован в том, чтобы LINQ выполнил объединение двух списков в памяти.

Обновление: это не дубликат "Выполнения заказа по запросу цепочки linq" , поскольку этот вопрос явно упоминаетсяотносится к dbset, и мой вопрос явно касался сценария не-db.(Более того, хотя это и похоже, я не спрашиваю здесь о включениях, основанных на навигационных свойствах, а о «соединениях».)

Обновление 2: хотя и очень похоже, оно также не является дубликатом «Это порядокпредикат важен при использовании LINQ? ", так как я явно спрашиваю о ситуациях в памяти, и я не могу видеть ссылочный вопрос, явно обращающийся к этому случаю.Более того, этот вопрос устарел, и я на самом деле заинтересован в linq в контексте .NET Core (которого не было в 2012 году), поэтому я обновил тег этого вопроса, чтобы отразить этот второй момент.

Обратите внимание: С этим вопросом я нацелен на то, будет ли интерпретатор запросов linq каким-либо образом оптимизировать этот запрос в фоновом режиме, и надеюсь получить ссылку на фрагмент документации или исходный код, который показывает, как это происходитсделано linq.Я не заинтересован в таких ответах, как "это не имеет значения, поскольку производительность обоих запросов примерно одинакова".

Ответы [ 2 ]

0 голосов
/ 18 декабря 2018

Для списков в памяти (IEnumerables) оптимизация не применяется, и выполнение запросов выполняется в цепочечном порядке для списков в памяти.

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

Я сделал быстрый тест для этого случая.

Console.WriteLine($"List Row Count = {list.Count()}"); 
Console.WriteLine($"JoinList Row Count = {joinList.Count()}"); 

var watch = Stopwatch.StartNew();
var result = list.Join(joinList, l => l.Prop3, i=> i.Prop3, (lst, inner) => new {lst, inner})
   .Where(t => t.inner.Prop3 == "Prop13")
   .Select(t => new { t.inner.Prop4, t.lst.Prop2}); 
result.Dump();
watch.Stop();

Console.WriteLine($"Result1 Elapsed = {watch.ElapsedTicks}");

watch.Restart();
var result2 = list
   .Where(t => t.Prop3 == "Prop13")
   .Join(joinList, l => l.Prop3, i=> i.Prop3, (lst, inner) => new {lst, inner})
   .Select(t => new { t.inner.Prop4, t.lst.Prop2});

result2.Dump();
watch.Stop();
Console.WriteLine($"Result2 Elapsed = {watch.ElapsedTicks}"); 

watch.Restart();
var result3 = list.AsQueryable().Join(joinList, l => l.Prop3, i=> i.Prop3, (lst, inner) => new {lst, inner})
   .Where(t => t.inner.Prop3 == "Prop13")
   .Select(t => new { t.inner.Prop4, t.lst.Prop2}); 
result3.Dump();
watch.Stop();
Console.WriteLine($"Result3 Elapsed = {watch.ElapsedTicks}"); 

Выводы:

List Count = 100
JoinList Count = 10
Result1 Elapsed = 27
Result2 Elapsed = 17
Result3 Elapsed = 591

List Count = 1000
JoinList Count = 10
Result1 Elapsed = 20
Result2 Elapsed = 12
Result3 Elapsed = 586

List Count = 100000
JoinList Count = 10
Result1 Elapsed = 603
Result2 Elapsed = 19
Result3 Elapsed = 1277

List Count = 1000000
JoinList Count = 10
Result1 Elapsed = 1469
Result2 Elapsed = 88
Result3 Elapsed = 3219
0 голосов
/ 18 декабря 2018

Синтаксис запроса LINQ будет скомпилирован в цепочку методов.Подробнее см., Например, в этом вопросе .

Первый запрос LINQ будет скомпилирован в следующую цепочку методов:

categories
    .Join(
        products,
        category => category.ID,
        prod => prod.CategoryID,
        (category, prod) => new { category, prod })
    .Where(t => t.category.ID == 1)
    .Select(t => new { Category = t.category.Name, Product = t.prod.Name });

Второй:

categories
    .Where(category => category.ID == 1)
    .Join(
        products,
        category => category.ID,
        prod => prod.CategoryID,
        (category, prod) => new { Category = category.Name, Product = prod.Name });

Как видите, второй запрос приведет к меньшему количеству выделенных ресурсов (обратите внимание только на один анонимный тип против 2 в первом запросе и обратите внимание, сколько экземпляров этих анонимных типов будет создано при выполнении запроса).

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

В случае LINQ-to дополнительная оптимизация запроса не будет-объекты запросов.

Так что вторая версия предпочтительнее.

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