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


182

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


Согласно 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]); 
} 

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.