Linq to Entities присоединяется к groupjoin

182

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

duyn9uyen
источник
Согласно MSDN, групповое объединение - это предложение объединения с выражением in. Предложение join содержит больше информации и примеров кода. По сути, это внутреннее соединение (если ни один элемент справа не соответствует ни одному элементу слева, вы получите нулевой результат); однако результат организован в группы.
Тим

Ответы:

375

Поведение

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

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.

Герт Арнольд
источник
Таким образом, в GroupJoin дочерние значения будут содержать объекты, которые содержат связанные значения?
duyn9uyen
Как вы сказали, GroupJoin походит на внешнее соединение, но этот синтаксис (чисто linq для группового соединения) говорит, что это не похоже на внешнее соединение, а оставлено внешним соединением.
Имад
2
Я думаю, что я бы уточнил, что «Плоское внешнее соединение» является левым внешним соединением.
NetMage
1
Объяснил отлично, теперь я понимаю
peterincumbria
19

Согласно eduLINQ :

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

Единственная разница в заявлении:

Присоединиться :

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]); 
} 

Узнайте больше здесь:

MarcinJuraszek
источник