代码优先 TPT 和级联删除

Posted

技术标签:

【中文标题】代码优先 TPT 和级联删除【英文标题】:Code first TPT and cascade on delete 【发布时间】:2011-07-19 12:02:16 【问题描述】:

我正在使用 EF4.1 和代码优先和 TPT(每个类型的表)继承。我有这样的结构

public class Customer 

    public virtual ICollection<Product> Products get; set;


public class Product

   [Required]
   public int Id  get; set; 

   [Required]
   public virtual Customer get; set;

   public decimal Price  get; set; 


public class SpecializedProduct : Product

   public string SpecialAttribute  get; set; 

当我删除客户时,我希望删除与该客户关联的所有产品。我可以在客户和产品之间指定 WillCascadeOnDelete(true):

modelBuilder.Entity<Customer>().HasMany(e => e.Products).WithRequired(p => p.Customer).WillCascadeOnDelete(true);

但由于 SpecializedProduct 和 Product 之间存在外键关系,因此当我尝试删除客户时出现异常:

DELETE 语句与 REFERENCE 约束“SpecializedProduct _TypeConstraint_From_Product_To_SpecializedProduct”冲突。冲突发生在数据库“Test”、表“dbo.SpecializedProduct”、列“Id”中。声明已终止。

如果我在 SpecializedProduct _TypeConstraint_From_Product_To_SpecializedProduct 约束上手动设置删除级联,它可以工作,但我希望能够使用模型构建器或代码中的其他方式指定它。这可能吗?

提前致谢!

最好的问候

西蒙

【问题讨论】:

【参考方案1】:

当涉及到数据库时,TPT inheritance 是在基类(例如 Product)和所有派生类(例如 SpecializedProduct)之间通过Shared Primary Key Association 实现的。现在,当您删除 Customer 对象而不获取其 Products 属性时,EF 不知道该 Customer 有一堆产品也需要根据您的要求删除。如果您通过根据需要标记您的客户-产品关联来启用级联删除,则 database 将负责从产品表中删除子记录,但如果此子记录是 SpecializedProduct,则相关的SpecializedProduct 上的行不会被删除,因此会出现异常。所以基本上下面的代码是行不通的:

// This works only if customer's products are not SpecializedProduct
Customer customer = context.Customers.Single(c => c.CustomerId == 1);
context.Customers.Remove(customer);
context.SaveChanges();    

此代码将导致 EF 向数据库提交以下 SQL:

exec sp_executesql N'delete [dbo].[Customer] where ([CustomerId] = @0)',N'@0 int',@0=1

也就是说,没有办法在 Product 和 SpecializedProduct 表之间启用级联删除,这就是 EF Code First 实现 TPT 继承的方式,您不能覆盖它。

那么解决办法是什么?

一种方法是您已经想出的方法,手动切换 Product 和 SpecializedProduct 表之间的级联,以避免在使用 SpecializedProducts 删除客户时出现异常。

第二种方法是在您移除客户时让 EF 处理客户的 SpecializedProducts。就像我之前说的,发生这种情况是因为客户对象没有被正确获取,并且 EF 不知道客户的 SpecializedProducts 这意味着通过正确获取客户对象,Ef 将开始跟踪客户的关联并提交必要的 SQL 语句以确保在删除客户之前删除所有相关记录:

Customer customer = context.Customers
                           .Include(c => c.Products)
                           .Single(c => c.CustomerId == 1);

context.Customers.Remove(customer);
context.SaveChanges();    

因此,EF 将向数据库提交以下 SQL 语句,从而完美地按顺序删除所有内容:

exec sp_executesql N'delete [dbo].[SpecializedProduct] where ([Id] = @0)',N'@0 int',@0=1

exec sp_executesql N'delete [dbo].[Product] where (([Id] = @0) and ([Customer_CustomerId] = @1))',N'@0 int,@1 int',@0=1,@1=1

exec sp_executesql N'delete [dbo].[Customer] where ([CustomerId] = @0)',N'@0 int',@0=1

【讨论】:

花了 13 个月,但我得到了答案 :-) 谢谢!我记得我最终执行了一些 sql 来手动定义模型创建时的级联删除。

以上是关于代码优先 TPT 和级联删除的主要内容,如果未能解决你的问题,请参考以下文章

SQL 级联删除与级联更新的方法

sql级联更新和级联删除不起作用

JPA:单向多对一和级联删除

如何理解access设置中的“级联更新”和“级联删除”?

数据库中啥是“级联更新关联字段”和“级联删除关联字段”

SQL数据库怎么进行多表级联更新,求个存储过程