多个表中的一对一映射

时间:2018-03-12 07:26:50

标签: entity-framework entity-framework-core relationship ef-core-2.0

我正试图解决一个难题,但到目前为止还没有运气。

我有一篇文章(或博客文章)和评论实体,他们都有内容。为了支持内容的延迟加载(当我需要显示文章或评论列表时不需要加载内容)我决定将内容移动到单独的表并组织一对一映射。这是我想的一个例子:

public class Content {
    [Key]
    public int ID { get; set; }
    public string RawContent { get; set; }
    // a bunch of scalar properties, like content type and so on
}

public class BlogArticle {
    [Key]
    public int ID { get; set; }
    public int ContentID { get; set; }
    [ForeignKey(nameof(ContentID)]
    public virtual Content Text { get; set; }
    // other properties related to BlogArticle
}

public class Comment {
    [Key]
    public int ID { get; set; }
    public int ContentID { get; set; }
    [ForeignKey(nameof(ContentID)]
    public virtual Content Text { get; set; }
    // other properties related to comment
}
<...>

从第一眼看起来似乎没问题:我可以创建博客文章,评论和附加内容(起初我插入内容,显然)。更新也有效。但是,删除不起作用:当我删除博客文章或评论时,内容不会被删除(但我想在删除博客文章或评论时将其删除,而不是相反)。

根据我的理解,由于关系方向,我理解我最大的问题:在我的情况下,Content实体是主要结束,BlogArticleComment是依赖目的。为了解决难题,我需要改变主要/依赖关系。同样,根据我的理解,为了改变关系方向,我需要在Content实体中使用外键并使用流畅的API来描述谁是父(主)和谁是孩子(依赖)在一对一中一个关系。由于许多表(可能有其他具有content属性的实体)指向Content表,因此它似乎并不容易。我的理解是否正确?

我能想象的一个可能的解决方案是在Content表中创建多个外键并指向每个相关表:

public class Content {
    [Key]
    public int ID { get; set; }
    public string RawContent { get; set; }
    // foreign keys
    public int BlogArticleID { get; set; }
    public int CommentID { get; set; }
    public int WebWidgetID { get; set; }
    // other foreign keys if necessary
}

可能,外键必须可以为空(因为一次只使用一个外键)。然后使用Entity Framework流畅的API来描述关系方向并组织级联删除。对我来说它看起来很难看,但我没有其他想法。

我的问题:我建议的解决方案是好还是可靠?我还可以看看其他选项吗?

提前致谢!

1 个答案:

答案 0 :(得分:2)

你的所有想法都是正确的。您提出的解决方案是传统关系设计的唯一方法。当然缺点是需要多个互斥的可空FK。

我看到的其他选项如下:

(1)对持有Content的实体使用EF inheritance。 e.g。

public abstract class EntityWithContent
{
    [Key]
    public int ID { get; set; }
    public virtual Content Text { get; set; }
}

public class BlogArticle : EntityWithContent
{
    // other specific properties
}

public class Comment : EntityWithContent
{
    // other specific properties
}

并使用共享PK关联或FK关联在Content(依赖)和EntityWithContent(主体)之间配置一对一关系。

但由于EF Core目前仅支持TPH策略(即所有派生实体与所有字段的并集共享同一个表),我不会推荐它。

(2)制作Content owned type

这更接近于意图,但不幸的是,EF Core目前总是加载拥有的实体数据以及所有者数据(即使它们被配置为由不同的数据库表提供),这违背了您的原始目标,所以我也不会暗示这一点。

(3)使用table splitting功能。

如果主要目标很简单,支持受控(延迟/急切/显式)加载,而Content总是需要,那么这可能是迄今为止最好的解决方案。

这需要更多的配置,但最后它将为您提供原始的表设计(每个实体的单个表)以及所需的加载行为:

型号:

public abstract class Content
{
    [Key]
    public int ID { get; set; }
    public string RawContent { get; set; }
    // a bunch of scalar properties, like content type and so on
}

public class BlogArticle
{
    [Key]
    public int ID { get; set; }
    public virtual BlogArticleContent Text { get; set; }
    // other properties related to BlogArticle
}

public class BlogArticleContent : Content
{
}

public class Comment
{
    [Key]
    public int ID { get; set; }
    public virtual CommentContent Text { get; set; }
    // other properties related to comment
}

public class CommentContent : Content
{
}

请注意,此处Content类不是EF继承层次结构的一部分,但是具有公共属性(abstract修饰符)的简单基类不是必需的。实际的派生类可能会也可能不会定义自己的属性。

配置:

modelBuilder.Entity<BlogArticle>().ToTable("BlogArticles");
modelBuilder.Entity<BlogArticle>()
    .HasOne(e => e.Text)
    .WithOne()
    .HasForeignKey<BlogArticleContent>(e => e.ID);
modelBuilder.Entity<BlogArticleContent>().ToTable("BlogArticles");

modelBuilder.Entity<Comment>().ToTable("Comments");
modelBuilder.Entity<Comment>()
    .HasOne(e => e.Text)
    .WithOne()
    .HasForeignKey<CommentContent>(e => e.ID);
modelBuilder.Entity<CommentContent>().ToTable("Comments");