代码首先TPT和级联删除

时间:2011-07-19 12:02:17

标签: entity-framework-4.1 ef-code-first

我正在使用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约束上手动设置一个删除级联,它可以工作,但我希望能够使用模型构建器或代码中的其他方式指定它。这可能吗?

提前致谢!

最好的问候

西蒙

1 个答案:

答案 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