Linq to Entities соединяются с groupjoin

У меня есть веб-поиск, но я все еще не могу найти простой ответ. Может кто-нибудь объяснить (на простом английском языке), что такое GroupJoin? Чем он отличается от обычного внутреннего Join? Используется ли он обычно? Это только для синтаксиса метода? Как насчет синтаксиса запроса? Пример кода С# был бы приятным.

Ответ 1

Поведение

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

Id  Value
1   A
2   B
3   C

Id  ChildValue
1   a1
1   a2
1   a3
2   b1
2   b2

Когда вы Join два списка в поле Id, результат будет:

Value ChildValue
A     a1
A     a2
A     a3
B     b1
B     b2

Когда вы GroupJoin два списка в поле Id, результат будет:

Value  ChildValues
A      [a1, a2, a3]
B      [b1, b2]
C      []

Итак, Join создает плоский (табличный) результат родительских и дочерних значений.
GroupJoin создает список записей в первом списке, каждый из которых имеет группу объединенных записей во втором списке.

Вот почему Join является эквивалентом INNER JOIN в SQL: для C нет записей. Пока GroupJoin является эквивалентом OUTER JOIN: C находится в наборе результатов, но с пустым списком соответствующих записей (в наборе результатов SQL будет строка C - null).

Синтаксис

Итак, пусть два списка будут IEnumerable<Parent> и IEnumerable<Child> соответственно. (В случае Linq to Entities: IQueryable<T>).

Join синтаксис будет

from p in Parent
join c in Child on p.Id equals c.Id
select new { p.Value, c.ChildValue }

возвращает IEnumerable<X>, где X является анонимным типом с двумя свойствами, Value и ChildValue. Этот синтаксис запроса использует метод Join под капотом.

GroupJoin синтаксис будет

from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }

возвращает IEnumerable<Y>, где Y - анонимный тип, состоящий из одного свойства типа Parent и свойства типа IEnumerable<Child>. Этот синтаксис запроса использует метод GroupJoin под капотом.

Мы могли бы просто сделать select g в последнем запросе, который выберет IEnumerable<IEnumerable<Child>>, скажем, список списков. Во многих случаях выбор с включенным родителем является более полезным.

Некоторые примеры использования

1. Изготовление плоского внешнего соединения.

Как сказано, утверждение...

from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }

... создает список родителей с дочерними группами. Это можно превратить в плоский список пар родитель-ребенок двумя небольшими дополнениями:

from p in parents
join c in children on p.Id equals c.Id into g // <= into
from c in g.DefaultIfEmpty()               // <= flattens the groups
select new { Parent = p.Value, Child = c?.ChildValue }

Результат похож на

Value Child
A     a1
A     a2
A     a3
B     b1
B     b2
C     (null)

Обратите внимание, что переменная диапазона C повторно используется в приведенном выше описании. Для этого любой оператор Join можно просто преобразовать в OUTER JOIN, добавив эквивалент into g from c in g.DefaultIfEmpty() в существующий оператор Join.

Здесь синтаксис синтаксиса запроса (или полного). Синтаксис метода (или свободного) показывает, что на самом деле происходит, но его трудно написать:

parents.GroupJoin(children, p => p.Id, c => c.Id, (p, c) => new { p, c })
       .SelectMany(x => x.c.DefaultIfEmpty(), (x,c) => new { x.p.Value, c?.ChildValue } )

Итак, плоская OUTER JOIN в LINQ - это GroupJoin, сплющенная SelectMany.

2. Заказ на сохранение

Предположим, что список родителей немного длиннее. В некоторых пользовательских интерфейсах создается список выбранных родителей как Id в фиксированном порядке. Позвольте использовать:

var ids = new[] { 3,7,2,4 };

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

Если мы делаем...

var result = parents.Where(p => ids.Contains(p.Id));

... порядок parents определит результат. Если родители заказаны Id, результатом будут родители 2, 3, 4, 7. Нехорошо. Однако мы можем использовать Join для фильтрации списка. И используя ids в качестве первого списка, порядок будет сохранен:

from id in ids
join p in parents on id equals p.Id
select p

В результате родители 3, 7, 2, 4.

Ответ 2

Согласно eduLINQ:

Лучший способ справиться с тем, что делает GroupJoin, - это думать о Присоединиться. Там общая идея заключалась в том, что мы просмотрели "внешний", входной последовательности, нашел все соответствующие элементы из "внутренней" последовательности (основанный на ключевой проекции на каждую последовательность), а затем дал пары соответствующие элементы. GroupJoin похож, за исключением того, что вместо получая пары элементов, он дает единственный результат для каждого "внешнего" элемент на основе этого элемента и последовательность совпадающих "внутренних" элементов.

Единственное различие заключается в операторе return:

Join

var lookup = inner.ToLookup(innerKeySelector, comparer); 
foreach (var outerElement in outer) 
{ 
    var key = outerKeySelector(outerElement); 
    foreach (var innerElement in lookup[key]) 
    { 
        yield return resultSelector(outerElement, innerElement); 
    } 
} 

GroupJoin

var lookup = inner.ToLookup(innerKeySelector, comparer); 
foreach (var outerElement in outer) 
{ 
    var key = outerKeySelector(outerElement); 
    yield return resultSelector(outerElement, lookup[key]); 
} 

Подробнее здесь: