代码优先 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 和级联删除的主要内容,如果未能解决你的问题,请参考以下文章