Entity Framework 核心代码优先:多对多关系上的级联删除
Entity Framework Core Code-First: Cascade delete on a many-to-many relationship
我正在使用 SQL Server 2012 express DB 支持的 Entity-Framework Core(版本 "EntityFramework.Core": "7.0.0-rc1-final"
)开发一个 ASP.NET MVC 6 项目。
我需要为 Person
实体和 Address
实体之间的多对多关系建模。
根据 this 指南,我使用 PersonAddress
join-table 实体对其进行建模,因为这样我可以存储一些额外的信息。
我的目标 是这样设置我的系统:
- 如果删除一个
Person
实例,则必须删除所有相关的PersonAddress
实例。他们引用的所有 Address
个实例也必须删除,前提是它们与其他 PersonAddress
个实例无关。
- 如果
PersonAddress
个实例被删除,只有当它与其他 PersonAddress
个实例无关时,它所关联的 Address
个实例才必须被删除。所有 Person
个实例必须存在。
- 如果删除
Address
个实例,则必须删除所有相关的 PersonAddress
个实例。所有 Person
个实例必须存在。
我想大部分工作肯定是在Person
和Address
之间的多对多关系中完成的,但我希望也写一些逻辑。我将把这部分排除在这个问题之外。我感兴趣的是如何配置我的多对多关系。
这是目前的情况。
这是 Person
实体。请注意,该实体与其他二级实体之间存在一对多关系。
public class Person
{
public int Id {get; set; } //PK
public virtual ICollection<Telephone> Telephones { get; set; } //navigation property
public virtual ICollection<PersonAddress> Addresses { get; set; } //navigation property for the many-to-many relationship
}
这是 Address
实体。
public class Address
{
public int Id { get; set; } //PK
public int CityId { get; set; } //FK
public City City { get; set; } //navigation property
public virtual ICollection<PersonAddress> People { get; set; } //navigation property
}
这是 PersonAddress
实体。
public class PersonAddress
{
//PK: PersonId + AddressId
public int PersonId { get; set; } //FK
public Person Person {get; set; } //navigation property
public int AddressId { get; set; } //FK
public Address Address {get; set; } //navigation property
//other info removed for simplicity
}
这是 DatabaseContext
实体,其中描述了所有关系。
public class DataBaseContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Address> Addresses { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
//All the telephones must be deleteded alongside a Person.
//Deleting a telephone must not delete the person it refers to.
builder.Entity<Person>()
.HasMany(p => p.Telephones)
.WithOne(p => p.Person);
//I don't want to delete the City when I delete an Address
builder.Entity<Address>()
.HasOne(p => p.City)
.WithMany(p => p.Addresses)
.IsRequired().OnDelete(Microsoft.Data.Entity.Metadata.DeleteBehavior.Restrict);
//PK for the join entity
builder.Entity<PersonAddress>()
.HasKey(x => new { x.AddressId, x.PersonId });
builder.Entity<PersonAddress>()
.HasOne(p => p.Person)
.WithMany(p => p.Addresses)
.IsRequired();
builder.Entity<PersonAddress>()
.HasOne(p => p.Address)
.WithMany(p => p.People)
.IsRequired();
}
}
为了简单起见,Telephone
和 City
实体都已删除。
这是删除 Person
的代码。
Person person = await _context.People.SingleAsync(m => m.Id == id);
try
{
_context.People.Remove(person);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
}
至于我的阅读,避免 .Include()
会让数据库负责最终的 CASCADE
删除。抱歉,我不记得澄清这个概念的 SO 问题。
如果我 运行 这段代码,我可以使用 this workaround 为数据库播种。当我想用上面的代码测试删除一个 Person
实体时,我得到了这个异常:
The DELETE statement conflicted with the REFERENCE constraint "FK_PersonAddress_Person_PersonId". The conflict occurred in database "<dbName>", table "<dbo>.PersonAddress", column 'PersonId'.
The statement has been terminated.
我在 DatabaseContext.OnModelCreating
方法中测试了几个关系设置,但没有任何运气。
最后,这是我的问题。根据之前描述的 goal,我应该如何配置我的多对多关系,以便从我的应用程序中正确删除 Person
及其相关实体?
谢谢大家
首先我看到你设置了 City 和 Address 与 DeleteBehavior.Restrict
的关系并且你说:
'//我不想在删除地址的同时删除城市'.
但是这里不需要Restrict,因为即使有DeleteBehavior.Cascade
City也不会被删除。
你从错误的一面看。
Cascade
在这里做的是当一个城市被删除时,所有属于它的地址也被删除。
这种行为是合乎逻辑的。
其次,你们的many-to-many关系很好。
当删除 Person 时,其来自 PersonAddress Table 的链接将因级联而自动删除。
如果您还想删除仅连接到那个人的地址,您将必须手动执行。
实际上,您必须在删除 Person 之前删除那些 Addresses 才能知道要删除什么。
所以逻辑应该如下:
1.查询PersonAddress的所有记录where PersonId = person.Id
;
2. 其中只取在 PersonAddress table 中出现 AddressId 一次的那些,并将它们从 Person table.
中删除
3. 现在删除人物。
您可以直接在代码中执行此操作,或者如果您希望数据库为您执行此操作,可以为步骤 2 创建触发器,函数如下:
当 PersonAddress 中的行即将被删除时,检查该 PersonAddress table 中是否不再有具有相同 AddressId 的行,在这种情况下,将其从 Address table.
中删除
更多信息在这里:
How to cascade delete over many to many table
How do I delete from multiple tables using INNER JOIN in SQL server
我正在使用 SQL Server 2012 express DB 支持的 Entity-Framework Core(版本 "EntityFramework.Core": "7.0.0-rc1-final"
)开发一个 ASP.NET MVC 6 项目。
我需要为 Person
实体和 Address
实体之间的多对多关系建模。
根据 this 指南,我使用 PersonAddress
join-table 实体对其进行建模,因为这样我可以存储一些额外的信息。
我的目标 是这样设置我的系统:
- 如果删除一个
Person
实例,则必须删除所有相关的PersonAddress
实例。他们引用的所有Address
个实例也必须删除,前提是它们与其他PersonAddress
个实例无关。 - 如果
PersonAddress
个实例被删除,只有当它与其他PersonAddress
个实例无关时,它所关联的Address
个实例才必须被删除。所有Person
个实例必须存在。 - 如果删除
Address
个实例,则必须删除所有相关的PersonAddress
个实例。所有Person
个实例必须存在。
我想大部分工作肯定是在Person
和Address
之间的多对多关系中完成的,但我希望也写一些逻辑。我将把这部分排除在这个问题之外。我感兴趣的是如何配置我的多对多关系。
这是目前的情况。
这是 Person
实体。请注意,该实体与其他二级实体之间存在一对多关系。
public class Person
{
public int Id {get; set; } //PK
public virtual ICollection<Telephone> Telephones { get; set; } //navigation property
public virtual ICollection<PersonAddress> Addresses { get; set; } //navigation property for the many-to-many relationship
}
这是 Address
实体。
public class Address
{
public int Id { get; set; } //PK
public int CityId { get; set; } //FK
public City City { get; set; } //navigation property
public virtual ICollection<PersonAddress> People { get; set; } //navigation property
}
这是 PersonAddress
实体。
public class PersonAddress
{
//PK: PersonId + AddressId
public int PersonId { get; set; } //FK
public Person Person {get; set; } //navigation property
public int AddressId { get; set; } //FK
public Address Address {get; set; } //navigation property
//other info removed for simplicity
}
这是 DatabaseContext
实体,其中描述了所有关系。
public class DataBaseContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Address> Addresses { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
//All the telephones must be deleteded alongside a Person.
//Deleting a telephone must not delete the person it refers to.
builder.Entity<Person>()
.HasMany(p => p.Telephones)
.WithOne(p => p.Person);
//I don't want to delete the City when I delete an Address
builder.Entity<Address>()
.HasOne(p => p.City)
.WithMany(p => p.Addresses)
.IsRequired().OnDelete(Microsoft.Data.Entity.Metadata.DeleteBehavior.Restrict);
//PK for the join entity
builder.Entity<PersonAddress>()
.HasKey(x => new { x.AddressId, x.PersonId });
builder.Entity<PersonAddress>()
.HasOne(p => p.Person)
.WithMany(p => p.Addresses)
.IsRequired();
builder.Entity<PersonAddress>()
.HasOne(p => p.Address)
.WithMany(p => p.People)
.IsRequired();
}
}
为了简单起见,Telephone
和 City
实体都已删除。
这是删除 Person
的代码。
Person person = await _context.People.SingleAsync(m => m.Id == id);
try
{
_context.People.Remove(person);
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
}
至于我的阅读,避免 .Include()
会让数据库负责最终的 CASCADE
删除。抱歉,我不记得澄清这个概念的 SO 问题。
如果我 运行 这段代码,我可以使用 this workaround 为数据库播种。当我想用上面的代码测试删除一个 Person
实体时,我得到了这个异常:
The DELETE statement conflicted with the REFERENCE constraint "FK_PersonAddress_Person_PersonId". The conflict occurred in database "<dbName>", table "<dbo>.PersonAddress", column 'PersonId'.
The statement has been terminated.
我在 DatabaseContext.OnModelCreating
方法中测试了几个关系设置,但没有任何运气。
最后,这是我的问题。根据之前描述的 goal,我应该如何配置我的多对多关系,以便从我的应用程序中正确删除 Person
及其相关实体?
谢谢大家
首先我看到你设置了 City 和 Address 与 DeleteBehavior.Restrict
的关系并且你说:
'//我不想在删除地址的同时删除城市'.
但是这里不需要Restrict,因为即使有DeleteBehavior.Cascade
City也不会被删除。
你从错误的一面看。
Cascade
在这里做的是当一个城市被删除时,所有属于它的地址也被删除。
这种行为是合乎逻辑的。
其次,你们的many-to-many关系很好。
当删除 Person 时,其来自 PersonAddress Table 的链接将因级联而自动删除。
如果您还想删除仅连接到那个人的地址,您将必须手动执行。
实际上,您必须在删除 Person 之前删除那些 Addresses 才能知道要删除什么。
所以逻辑应该如下:
1.查询PersonAddress的所有记录where PersonId = person.Id
;
2. 其中只取在 PersonAddress table 中出现 AddressId 一次的那些,并将它们从 Person table.
中删除
3. 现在删除人物。
您可以直接在代码中执行此操作,或者如果您希望数据库为您执行此操作,可以为步骤 2 创建触发器,函数如下: 当 PersonAddress 中的行即将被删除时,检查该 PersonAddress table 中是否不再有具有相同 AddressId 的行,在这种情况下,将其从 Address table.
中删除更多信息在这里:
How to cascade delete over many to many table
How do I delete from multiple tables using INNER JOIN in SQL server