Как выполнить соединение (JOIN) таблиц в Entity Framework?

Ответ

В Entity Framework Core соединения выполняются в основном через LINQ-запросы. Вот основные подходы с пояснениями, когда что использовать:

1. Неявное соединение через навигационные свойства (Самый частый и удобный способ): EF автоматически генерирует JOIN при обращении к связанным данным.

// EF сам построит JOIN между Products и Categories
var products = await _context.Products
    .Where(p => p.Category.Name == "Electronics")
    .ToListAsync();

2. Явное соединение с Join (Полезно при отсутствии навигационных свойств или для сложных ключей):

var query = from product in _context.Products
            join category in _context.Categories 
                on product.CategoryId equals category.Id
            select new { product.Name, CategoryName = category.Name };

3. Жадная загрузка связанных данных с Include и ThenInclude (Для загрузки связанных сущностей в одном запросе):

// Загружаем продукты вместе с их категориями и поставщиками
var products = await _context.Products
    .Include(p => p.Category)          // JOIN с Categories
    .ThenInclude(c => c.Supplier)      // JOIN с Suppliers (через Category)
    .ToListAsync();

4. Левое внешнее соединение (Left Join) с GroupJoin и SelectMany + DefaultIfEmpty:

var query = from category in _context.Categories
            join product in _context.Products 
                on category.Id equals product.CategoryId into productsGroup
            from product in productsGroup.DefaultIfEmpty() // Ключевой момент для LEFT JOIN
            select new { category.Name, ProductName = product.Name ?? "No products" };

Важные замечания:

  • Производительность: Всегда проверяйте сгенерированный SQL через ToQueryString() или профилировщик. Иногда Include может привести к избыточным данным (проблема N+1 или cartesian explosion) — в таких случаях используйте Select для проекции только нужных полей.
  • Метод синтаксиса: Аналогичные операции можно выполнять и в цепочке методов (Method Syntax).

Ответ 18+ 🔞

Ну слушай, про соединения в EF Core — тут вроде всё просто, но если не врубиться, можно наделать таких костылей, что потом самому противно будет смотреть. Объясняю на пальцах, как есть.

1. Неявное соединение через навигационные свойства (Это вообще святое, юзай везде, где можно) Ты просто пишешь LINQ, как будто объекты уже в памяти, а EF сам, хитрая жопа, на лету JOIN'ы в SQL превращает. Красота же!

// Смотри, тут мы фильтруем товары по названию категории. Навигационное свойство `p.Category` есть? Есть! Значит, EF сам JOIN между Products и Categories прилепит.
var products = await _context.Products
    .Where(p => p.Category.Name == "Electronics")
    .ToListAsync();

Вот честно, в 90% случаев этого хватает. Не выёбывайся, пиши так.

2. Явное соединение с Join (Когда навигашек нет или ключи — ёбушки-воробушки) Бывает, связь в модели не объявил, или ключи составные, или просто старый запрос переписываешь. Тогда вот так, по-старинке:

var query = from product in _context.Products
            join category in _context.Categories 
                on product.CategoryId equals category.Id // Важно: equals, а не ==, запомни нахуй!
            select new { product.Name, CategoryName = category.Name };

Работает, но выглядит грустнее. Используй, только если реально надо.

3. Жадная загрузка с Include/ThenInclude (Чтобы всё разом вытащить, а не дергать базу десять раз) Вот это мощь. Представь: тебе нужны товары, их категории, а ещё поставщики этих категорий. Без этого делал бы три запроса? А вот хуй!

var products = await _context.Products
    .Include(p => p.Category)          // Первый JOIN к категориям
    .ThenInclude(c => c.Supplier)      // Второй JOIN к поставщикам, уже через категорию!
    .ToListAsync();

Всё за один заход, одной здоровенной пачкой. Но, бля, осторожно! Если связей много, можно получить "взрыв Cartesian'а" — когда тебе вернётся овердохуища строк, и сервер сдохнет. Всегда смотри сгенерированный SQL!

4. Левое внешнее соединение (Left Join) — тот самый случай, когда "даже если нихуя нет" Нужно выбрать ВСЕ категории, даже те, где товаров вообще нет. В LINQ это немного мозголомно, но делается через GroupJoin и DefaultIfEmpty().

var query = from category in _context.Categories
            join product in _context.Products 
                on category.Id equals product.CategoryId into productsGroup
            from product in productsGroup.DefaultIfEmpty() // Вот это магическая палочка для LEFT JOIN!
            select new { category.Name, ProductName = product.Name ?? "No products" };

Выглядит страшновато, но работает как часы. Главное — не перепутай порядок.

И на последок, золотое правило: Не верь EF на слово, ёпта! Всегда смотри, какой SQL он там нагенерировал. Просто вызови .ToQueryString() у запроса (до ToListAsync) или включи логирование. А то можно написать красивый LINQ, а он там под капотом такую дичь запросит, что терпения ебать ноль. Особенно это касается Include на глубоких связях — иногда лучше вручную через Select выбрать только нужные поля, чем тащить целые объекты с кучей NULL'ов.

Короче, инструмент мощный, но думать головой всё равно надо. Удачи!