代码首先TPT和级联删除

Sim*_*sen 10 ef-code-first entity-framework-4.1

我正在使用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; }
}
Run Code Online (Sandbox Code Playgroud)

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

modelBuilder.Entity<Customer>().HasMany(e => e.Products).WithRequired(p => p.Customer).WillCascadeOnDelete(true);
Run Code Online (Sandbox Code Playgroud)

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

DELETE语句与REFERENCE约束"SpecializedProduct _TypeConstraint_From_Product_To_SpecializedProduct"冲突.冲突发生在数据库"Test",表"dbo.SpecializedProduct",列"Id"中.该语句已终止.

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

提前致谢!

最好的祝福

西蒙

Mor*_*avi 10

说到数据库,TPT继承是通过基类(例如Product)和所有派生类(例如SpecializedProduct)之间的共享主键关联实现的.现在,当您删除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();    
Run Code Online (Sandbox Code Playgroud)

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

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


也就是说,没有办法在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();    
Run Code Online (Sandbox Code Playgroud)

因此,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
Run Code Online (Sandbox Code Playgroud)

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