0Pricing
C# Academy · Урок

Связи: один-ко-многим и многие-ко-многим

Моделируйте и запрашивайте связи «один-ко-многим» и «многие-ко-многим» с помощью свойств навигации и Fluent API.

«Связи: один-ко-многим и многие-ко-многим» — бесплатный урок C# Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Связи в EF Core

EF Core моделирует три типа связей между сущностями: один к одному, один ко многим и многие ко многим. Каждая связь выражается навигационными свойствами и при необходимости настраивается с помощью Fluent API.

Один ко многим: настройка сущностей

У Blog есть много Post. Зависимая сущность (Post) содержит внешний ключ. Навигационные свойства с обеих сторон позволяют перемещаться по связи в любом направлении.

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public ICollection<Post> Posts { get; set; } = new List<Post>();
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int BlogId { get; set; }        // FK
    public Blog Blog { get; set; } = null!; // nav property
}

Fluent API: один ко многим

EF Core определяет связь по соглашению, но для большей ясности и настройки каскадного удаления её можно явно задать с помощью Fluent API.

protected override void OnModelCreating(ModelBuilder mb)
{
    mb.Entity<Blog>()
      .HasMany(b => b.Posts)
      .WithOne(p => p.Blog)
      .HasForeignKey(p => p.BlogId)
      .OnDelete(DeleteBehavior.Cascade);
}

Запросы с Include (жадная загрузка)

Используйте Include, чтобы загрузить связанные данные в рамках того же запроса. Используйте ThenInclude, чтобы перейти глубже по графу навигации.

// Load blog with all its posts
var blog = await _db.Blogs
    .Include(b => b.Posts)
    .FirstOrDefaultAsync(b => b.Id == 1);

// Load posts with their comments too
var blogs = await _db.Blogs
    .Include(b => b.Posts)
        .ThenInclude(p => p.Comments)
    .ToListAsync();

Многие ко многим без сущности-связки

EF Core 5+ поддерживает связи многие ко многим без явно заданной сущности-связки. Он автоматически создаёт таблицу связей, используя имена типов сущностей.

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public ICollection<Course> Courses { get; set; } = new List<Course>();
}

public class Course
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public ICollection<Student> Students { get; set; } = new List<Student>();
}
// EF creates StudentCourse join table automatically

Многие ко многим с сущностью-связкой

Если таблице связей нужны дополнительные столбцы, например дата зачисления или оценка, создайте явную сущность-связку с составным первичным ключом.

public class Enrollment
{
    public int StudentId { get; set; }
    public Student Student { get; set; } = null!;
    public int CourseId { get; set; }
    public Course Course { get; set; } = null!;
    public DateTime EnrolledAt { get; set; }
    public decimal? Grade { get; set; }
}

// Fluent API config:
mb.Entity<Enrollment>().HasKey(e => new { e.StudentId, e.CourseId });
mb.Entity<Enrollment>().HasOne(e => e.Student).WithMany(s => s.Enrollments);
mb.Entity<Enrollment>().HasOne(e => e.Course).WithMany(c => c.Enrollments);

Добавление связанных данных

Добавьте связанные сущности в коллекцию родительской сущности перед вызовом SaveChangesAsync. EF Core автоматически обработает значения FK.

var blog = new Blog { Name = "Dev Notes" };
blog.Posts.Add(new Post { Title = "Hello EF Core" });
blog.Posts.Add(new Post { Title = "Migrations Deep Dive" });

_db.Blogs.Add(blog);
await _db.SaveChangesAsync();
// Inserts Blog + 2 Posts with correct BlogId FK

Фильтрация связанных данных

В LINQ можно фильтровать данные по навигационным свойствам. EF Core автоматически преобразует такие запросы в SQL JOIN.

// Blogs that have at least one published post
var popularBlogs = await _db.Blogs
    .Where(b => b.Posts.Any(p => p.IsPublished))
    .Select(b => new { b.Name, PostCount = b.Posts.Count(p => p.IsPublished) })
    .OrderByDescending(b => b.PostCount)
    .ToListAsync();

Каскадное удаление

При использовании DeleteBehavior.Cascade удаление главной сущности (Blog) автоматически удаляет все зависимые сущности (Post). Другие варианты: SetNull, Restrict, NoAction.

// Delete a blog and all its posts
var blog = await _db.Blogs.FindAsync(1);
_db.Blogs.Remove(blog!);
await _db.SaveChangesAsync();
// Cascade: all Posts with BlogId=1 are also deleted

Сущности-владельцы для объектов-значений

Типы сущностей-владельцев отлично подходят для объектов-значений, например Address. Они хранятся в той же таблице, что и владелец, и не имеют независимого идентификатора.

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public Address ShippingAddress { get; set; } = new();
}

public class Address
{
    public string Street { get; set; } = "";
    public string City { get; set; } = "";
    public string Country { get; set; } = "";
}

// Fluent API:
mb.Entity<Customer>().OwnsOne(c => c.ShippingAddress);

Практика: заказ с позициями

Полный шаблон для интернет-магазина: Order — главная сущность со множеством OrderLines — зависимых сущностей, каждая из которых ссылается на Product.

var order = await _db.Orders
    .Include(o => o.Lines)
        .ThenInclude(l => l.Product)
    .FirstOrDefaultAsync(o => o.Id == orderId);

if (order is null) return Results.NotFound();

var summary = new
{
    OrderId = order.Id,
    Total   = order.Lines.Sum(l => l.Quantity * l.Product.Price),
    Items   = order.Lines.Select(l => new
    {
        l.Product.Name,
        l.Quantity,
        LineTotal = l.Quantity * l.Product.Price
    })
};
return Results.Ok(summary);

Быстрая проверка

Когда EF Core автоматически создаёт таблицу связей без явно заданной сущности-связки?

Итоги: связи в EF Core

Главное:

  • Один ко многим: FK находится у зависимой сущности, навигационные свойства есть с обеих сторон; используйте Include для загрузки
  • Многие ко многим (простой вариант): две навигации ICollection — EF Core автоматически создаёт таблицу связей
  • Многие ко многим (с дополнительными данными): явная сущность-связка с составным PK
  • Каскадное удаление определяет, что происходит с зависимыми сущностями при удалении главной
  • Сущности-владельцы моделируют объекты-значения в той же таблице, что и владелец

Часто задаваемые вопросы

Урок «Связи: один-ко-многим и многие-ко-многим» бесплатный?

Да — полный текст урока «Связи: один-ко-многим и многие-ко-многим» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Связи: один-ко-многим и многие-ко-многим»?

Моделируйте и запрашивайте связи «один-ко-многим» и «многие-ко-многим» с помощью свойств навигации и Fluent API. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Связи: один-ко-многим и многие-ко-многим»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Основы DbContext и DbSet
  2. Миграции и управление схемой
  3. Операции CRUD с EF Core
  4. Связи: один-ко-многим и многие-ко-многим
← Назад к C# Academy