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

Данное руководство устарело. Актуальное руководство: Руководство по Entity Framework Core 7

Последнее обновление: 16.11.2020

Каскадное удаление представляет автоматическое удаление зависимой сущности после удаления главной.

По умолчанию для сущностей применяется каскадное удаление, если наличие связанной сущности обязательно. Например:

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

В 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).

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850