实体框架6代码优先 - 关于树结构的评论

use*_*392 6 c# sql-server asp.net-mvc entity-framework data-modeling

我正在尝试在我的数据模型(实体框架6,代码优先方法)中实现异构关联.

我有一个现有的类结构,让我们称之为Tree,BranchLeaf.A Tree可以有许多Branch对象,并且Branch可以包含许多Leaf对象.三个级别之间的关系具有cascade-delete行为(删除分支,您还删除叶子等).

现在,我试图让用户在每个级别上添加类似注释的对象.我有一些与数据建模有关的问题,因为我希望3种实体类型中的每一种都能够有很多注释,每条注释都属于一个且只有一个条目.我也希望所有评论都在同一张表中.我尝试了两种不同的方法:


Alt键.1

实现继承使得Comment(摘要)可以是一个TreeComment,BranchComment或者LeafComment,下面的每层次结构表(TPH)的方法(如看到的那样,例如,在这里),其具有一个抽象类(的Comment征求意见),然后将其导出到TreeComment,BranchComment等这是通过编码这样的模型来实现的:

public abstract class Comment
{
    // ID
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid ID { get; set; }
}

public class TreeComment: Comment
{
    // Foreign Keys
    public Guid TreeID { get; set; }

    // Navigation Properties
    public virtual Tree Tree { get; set; }
}

(... BranchComment and LeafComment ...)

(... add virtual ICollection<TreeComment> to Tree, virtual ICollection<BranchComment> to Branch, etc.)
Run Code Online (Sandbox Code Playgroud)

......可以用这个图表表达:

每个评论层次结构的表

这种方法的问题在于Comment表和其他3之间的关系没有ON DELETE CASCADE或没有ON DELETE SET NULL设置.如果我尝试将其更改为多个表格,我会得到:

在表'Comment'上引入FOREIGN KEY约束'FK_Comment_Branch_BranchID'可能会导致循环或多个级联路径.指定ON DELETE NO ACTION或ON UPDATE NO ACTION,或修改其他FOREIGN KEY约束.

我知道这是因为SQL Server"不知道"只应该在任何时候使用Comment表中的一个FK.


Alt键.2

使用每种类型(TPT)方法将Tree/ Branch/ Leaftrio 概括为a CommentableEntity并将连接Comment到该抽象的表.这可以通过在模型类中实现继承(就像我之前做的那样)并添加注释来实现[Table("Tree")],[Table("Branch")]并且[Table("Leaf")]可以通过每个子类来确保为每个子类获取一个表(而不是像TPH中那样的单个表).模型,然后看起来像这样:

树元素上的每种类型的表

这种方法有两个问题:

  1. 删除具体对象(例如分支)不会删除 抽象表中的基本条目,而是留下"垃圾"(抽象实体及其注释).

  2. 抽象类和具体类之间的FK关系缺乏cascade delete.所以我无法真正删除基础对象.如果我尝试添加一个,我会得到另一个抱怨,如何引入这样的规则将导致多个级联路径的循环.


我也尝试CREATE TRIGGER ... INSTEAD OF DELETE...在两种方法上使用数据库触发器(),但它们似乎是一个很大的禁忌,因为EF无法跟踪它们所做的更改.

这令人沮丧,我确信这(对树结构的评论)是Web开发中非常典型的场景; 但我似乎找不到允许它的方法.我正在寻找有关如何有效地建模这些关系(EF 6 Code First)的所有建议,而不会过多地重视业务逻辑层.

编辑:

我相信这是用户@Deepak Sharma在评论中提到的:节点类中的TPH继承.如果是这样,由于同样的原因,这也不起作用:多个级联路径的循环.

每个树元素的层次结构表

use*_*392 2

好的,这就是我目前解决问题的方法:

我选择了第二种选择 -使用TPTTree方法将//BranchLeaf重奏(让我们将这些“节点”称为简化起见)概括为CommentableEntity(基类)- 如上所示。我最终为三个节点类中的每一个提供一个表 + 一个保存与表的关系的基类。Comment

然后,在我的 中InitializeDatabase(MuDbContext context),我使用该方法为数据库中的三个表中的每一个添加了一个存储过程触发器context.Database.ExecuteSqlCommand()

1)存储过程必须像这样映射到 EF 中:

this.MapToStoredProcedures(s => s.Delete(d => d.HasName("TriggerName").Parameter(b => b.ID, "parameter_name")));
Run Code Online (Sandbox Code Playgroud)

...对于三个模型中的每一个,基本上都是默认删除的替代。就我而言,我编写它时首先删除其表中的实际节点 ( Tree// Branch) Leaf,然后删除相应的基础对象 ( CommentableEntity)。

2)触发器在节点被删除后触发,并确保相应的基础对象也被删除。

如果您想知道为什么我有这样的冗余(一个触发器和一个存储过程几乎做同样的事情),这是因为每当删除一个节点(例如,一棵树)时,EF 都会调用它的存储过程。以便将其删除。然后,通过数据库删除嵌套节点(树的分支)cascade-delete,这不会删除基础对象,也不通过存储过程删除。因此,触发器。另一方面,如果我只有触发器(没有存储过程),EF 在删除后会崩溃,因为它无法跟踪其更改。

当然,我可以只更改每个存储过程。对于每个表,以便它们也删除所有嵌套对象并删除设置cascade-delete。但当前的解决方案似乎有效并且对我来说足够好。

如果我发现这实际上不起作用,我会对此进行测试并删除此答案。如果您发现这种方法有任何缺点(并知道如何避免它们),请发表评论。