我正在使用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; }
}
当我删除客户时,我希望删除与该客户关联的所有产品。我可以在Customer和Product之间指定一个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约束上手动设置一个删除级联,它可以工作,但我希望能够使用模型构建器或代码中的其他方式指定它。这可能吗?
提前致谢!
最好的问候
西蒙
答案 0 :(得分:10)
当谈到数据库时,TPT inheritance在基类(例如Product)和所有派生类(例如SpecializedProduct)之间实现Shared Primary Key Association。现在,当您删除Customer对象而不提取其Products属性时,EF不知道此Customer有一堆产品也需要根据您的要求删除。如果通过根据需要标记客户 - 产品关联来启用级联删除,则数据库将负责从产品表中删除子记录,但如果此子记录是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