Данное руководство устарело. Актуальное руководство: Руководство по Entity Framework Core 7
Каскадное удаление представляет автоматическое удаление зависимой сущности после удаления главной.
По умолчанию для сущностей применяется каскадное удаление, если наличие связанной сущности обязательно. Например:
public class Company { public int Id { get; set; } public string Name { get; set; } // название компании public List<User> Users { get; set; } } public class User { public int Id { get; set; } public string Name { get; set; } public int CompanyId { get; set; } // внешний ключ public Company Company { get; set; } // навигационное свойство } public class ApplicationContext : DbContext { public DbSet<Company> Companies { get; set; } public DbSet<User> Users { get; set; } public ApplicationContext() { Database.EnsureDeleted(); Database.EnsureCreated(); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=relationsdb;Trusted_Connection=True;"); } }
Здесь свойство внешнего ключа имеет тип int
, оно не допускает значения null и требует наличия конкретного значения - id связанного объекта Company.
То есть для объекта User обязательно необходимо наличия связанного объекта Company. Поэтому сгенерированная таблица Users будет иметь код:
CREATE TABLE [dbo].[Users] ( [Id] INT IDENTITY (1, 1) NOT NULL, [Name] NVARCHAR (MAX) NULL, [CompanyId] INT NOT NULL, CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [FK_Users_Companies_CompanyId] FOREIGN KEY ([CompanyId]) REFERENCES [dbo].[Companies] ([Id]) ON DELETE CASCADE );
В определении внешнего ключа устанавливается каскадное удаление: ON DELETE CASCADE
Например, добавим в базу данных 2 компании и 4 связанных с ними пользователей и затем удалим одну из компаний:
using (ApplicationContext db = new ApplicationContext()) { // добавляем начальные данные Company microsoft = new Company { Name = "Microsoft" }; Company google = new Company { Name = "Google" }; db.Companies.AddRange(microsoft, google); db.SaveChanges(); User tom = new User { Name = "Tom", Company = microsoft }; User bob = new User { Name = "Bob", Company = google }; User alice = new User { Name = "Alice", Company = microsoft }; User kate = new User { Name = "Kate", Company = google }; db.Users.AddRange(tom, bob, alice, kate); db.SaveChanges(); // получаем пользователей var users = db.Users.ToList(); foreach (var user in users) Console.WriteLine($"{user.Name}"); // Удаляем первую компанию var comp = db.Companies.FirstOrDefault(); db.Companies.Remove(comp); db.SaveChanges(); Console.WriteLine("\nСписок пользователей после удаления компании"); // снова получаем пользователей users = db.Users.ToList(); foreach (var user in users) Console.WriteLine($"{user.Name}"); }
Консольный вывод программы:
Bob Tom Alice Kate Список пользователей после удаления компании Bob Kate
Удаление главной сущности - компании привело к удалению двух зависимых сущностей - пользователей.
Теперь изменим модели, указав необязательность наличия объекта Company:
public class Company { public int Id { get; set; } public string Name { get; set; } // название компании public List<User> Users { get; set; } } public class User { public int Id { get; set; } public string Name { get; set; } public int? CompanyId { get; set; } // внешний ключ public Company Company { get; set; } // навигационное свойство }
Теперь внешний ключ имеет тип Nullable<int>, то есть он допускает значение null. Когда пользователь не будет принадлежать ни одной компании, это свойство будет иметь значение null. И в этом случае скрипт таблицы Users будет выглядеть следующим образом:
CREATE TABLE [dbo].[Users] ( [Id] INT IDENTITY (1, 1) NOT NULL, [Name] NVARCHAR (MAX) NULL, [CompanyId] INT NULL, CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [FK_Users_Companies_CompanyId] FOREIGN KEY ([CompanyId]) REFERENCES [dbo].[Companies] ([Id]) );
Если мы запустим ту же самую программу, то получим уже другой консольный вывод:
Bob Tom Alice Kate Список пользователей после удаления компании Bob Tom Alice Kate
В Fluent API доступны три разных сценария, которые управляют поведением зависимой сущности в случае удаления главной сущности:
Cascade: зависимая сущность удаляется вместе с главной
SetNull: свойство-внешний ключ в зависимой сущности получает значение null
Restrict: зависимая сущность никак не изменяется при удалении главной сущности
Например, установим каскадное удаление, даже если по умолчанию оно не предусматривается:
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using System.Collections.Generic; public class ApplicationContext : DbContext { public DbSet<Company> Companies { get; set; } public DbSet<User> Users { get; set; } public ApplicationContext() { Database.EnsureDeleted(); Database.EnsureCreated(); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=relationsdb;Trusted_Connection=True;"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<User>() .HasOne(p => p.Company) .WithMany(t => t.Users) .OnDelete(DeleteBehavior.Cascade); } } public class Company { public int Id { get; set; } public string Name { get; set; } public List<User> Users { get; set; } } public class User { public int Id { get; set; } public string Name { get; set; } public int? CompanyId { get; set; } public Company Company { get; set; } }
Соответственно чтобы отключить каскадное удаление, нам надо использовать вызов OnDelete(DeleteBehavior.SetNull)
.