【问题标题】:Entity Framework 6 Code First - Comments on a Tree StructureEntity Framework 6 Code First - 对树形结构的评论
【发布时间】:2014-12-29 22:17:26
【问题描述】:

我正在尝试在我的数据模型中实现异构关联(Entity Framework 6,代码优先方法)。

我有一个现有的类结构,我们称它们为TreeBranchLeaf。一个Tree 可能有很多Branch 对象,而一个Branch 可能有很多Leaf 对象。三个级别之间的关系有一个cascade-delete 行为(删除一个分支,你也删除了叶子等)。

现在我试图让用户在每个级别上添加一个类似评论的对象。 I had a few problems related to data-modelling,因为我希望 3 种实体类型中的每一种都能够拥有许多 cmets,并且每个评论都属于一个且仅属于一个条目。我还希望所有 cmets 都在同一张桌子上。我尝试了两种不同的方法:


替代。 1

实现继承,以便Comment(抽象)可以是TreeCommentBranchCommentLeafComment,遵循Table per Hierarchy (TPH) 方法(如图所示,对于例如,here) 为 cmets 提供一个抽象类 (Comment),然后将其派生为 TreeCommentBranchComment 等。这是通过对模型进行编码来实现的:

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.)

...可以用这张图表示:

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

引入 FOREIGN KEY 约束“FK_Comment_Branch_BranchID” 表“注释”可能会导致循环或多个级联路径。指定开 DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。

我知道这是因为 SQL Server“不知道”在任何时候都应该使用 Comment 表中的 FK 之一。


替代。 2

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

这种方法有两个问题:

  1. 删除具体对象(例如分支)will not delete the base entryin the abstract table,留下“垃圾”(抽象实体及其 cmets)。

  2. 抽象类和具体类之间的 FK 关系缺少 cascade delete。所以我不能真正删除基础对象。如果我尝试添加一个,我会收到另一个抱怨,即引入此类规则会导致 多个级联路径的循环


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

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

编辑:

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

【问题讨论】:

  • 当 cmets 和节点之间没有级联删除时,您将在“Alt 1”中遇到什么问题。这对我来说似乎很合理。
  • 删除节点不会删除 cmets,也不会将它们的 FK 设置为节点为 NULL(尝试添加 SET NULL 约束也会给我 可能导致循环或多个级联路径 错误)。如果你不能做任何这些事情,你就不能删除一个节点(因为它会违反 FK 约束)。
  • 如果我说你只需要两张桌子怎么办??一个用于树/分支/叶子,另一个仅用于评论??我们也可以这样做。如果你说我已经为你提供了代码..
  • 您是否正在考虑树/分支/叶上的 Table Per Hierarchy(而不是 Table Per Type)?即,那些节点的单个表(例如Node)包括一个 Discriminator 属性,其值可以是“树/分支/叶子”?这有点混乱,因为不同的节点类具有一些不同的属性,更不用说它会导致自引用表可能与其他两种方法具有相同的问题。我想我已经尝试过这种方法,但我会再试一次并发布结果。
  • Alt 1 上的消息不是反过来吗?我读到“删除评论不应删除节点”,这是合理的。您不能通过将 Comment DependentNavigationPropertyConfigaration 的 WillCascadeOnDelete 属性设置为 false 来在 DbModelBuilder 中使用“Comment”完成删除周期。

标签: c# sql-server asp.net-mvc entity-framework data-modeling


【解决方案1】:

可能的答案是通过在 OnModelCreating 方法中声明一些规则来扩充 Alt 1。这还假设 Tree、Branch 和 Leaf 类有一个针对它们的 Comments 集合。

在您的 DbContext 中,您可以执行以下操作...

public class YourDbContext : DbContext
{
     ... your DbSet properties

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {

        modelBuilder.Entity<Tree>()
                .HasMany<TreeComment>(o => o.Comments)
                .WithRequired(com => com.Tree)
                .HasForeignKey(com => ds.TreeID)
                .WillCascadeOnDelete(false);
     }
}

【讨论】:

  • 感谢您的意见。但是,我已经尝试过使用 Fluent API。由于 EF 已经创建了数据库,但在 Nodes to Comment 关系上没有 cascade-delete,因此此类代码变得多余。我想我找到了一个同时使用触发器和存储过程的解决方案。我会测试它,如果它是正确的,我会把它发布给有兴趣的人。
【解决方案2】:

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

我选择了第二种选择——使用TPT 方法将Tree/Branch/Leaf 三重奏(我们称这些“节点”简化)为CommentableEntity(基类)——如上所示。我最终为三个节点类中的每一个创建了一个表 + 一个与 Comment 表保持关系的基类。

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

1) 存储过程必须是mapped in EF,如下所示:

this.MapToStoredProcedures(s => s.Delete(d => d.HasName("TriggerName").Parameter(b => b.ID, "parameter_name")));

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-02
    • 1970-01-01
    • 1970-01-01
    • 2013-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多