Как реализовать join в C# с использованием LINQ без двух foreach
Что проверяют
Знаете ли вы операторы соединения в LINQ и понимаете ли, что они делают под капотом, а не просто прячут вложенный цикл. Следом спрашивают про левое соединение и про то, что меняется, когда источник это IQueryable из EF Core.
Как отвечать
Базовый инструмент Join, в двух синтаксисах:
- query-синтаксис:
from u in users join o in orders on u.Id equals o.UserId select new { u.Name, o.Product }; - method-синтаксис:
users.Join(orders, u => u.Id, o => o.UserId, (u, o) => new { u.Name, o.Product }).
Главное: в LINQ to Objects Join строит хэш-таблицу по ключам внутренней последовательности и один раз проходит внешнюю. Сложность O(n + m), а два foreach дают O(n × m). Порядок сохраняется: внешние элементы идут как были, совпадения для каждого в порядке внутренней последовательности.
Варианты:
- левое соединение: классика
GroupJoinплюсSelectManyсDefaultIfEmpty(), в query-синтаксисеjoin ... into g from o in g.DefaultIfEmpty(). В .NET 10 появились готовыеLeftJoinиRightJoin; - группировка:
GroupJoinсразу отдаёт пользователя со всеми его заказами; - составной ключ: анонимный тип
new { a.X, a.Y } equals new { b.X, b.Y }, имена и типы свойств должны совпадать; - свой компаратор: перегрузка с
IEqualityComparer<TKey>, напримерStringComparer.OrdinalIgnoreCase.
Если данные в базе, EF Core транслирует запрос в SQL JOIN. Часто явный Join там не нужен: навигационное свойство u.Orders выражает то же самое понятнее.
Помните про отложенное выполнение: каждый новый foreach или Count() выполняет запрос заново. Нужен результат несколько раз, материализуйте его через ToList().
Где спотыкаются
Пишут users.Select(u => orders.Where(o => o.UserId == u.Id)). Выглядит как LINQ, но это те же вложенные циклы с квадратичной сложностью.
Путают стороны в equals: слева ключ внешней последовательности, справа внутренней, иначе код не скомпилируется.
Вызывают ToList() на DbSet до соединения. Обе таблицы целиком едут в память, и соединение, которое база сделала бы по индексу, выполняется в приложении.