Поведінка
Припустимо, у вас є два списки:
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 до юридичних осіб:) IQueryable<T>
.
Join
синтаксис був би
from p in Parent
join c in Child on p.Id equals c.Id
select new { p.Value, c.ChildValue }
повернення an, 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 }
повернення an, 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.