One-to-one 在多个表中映射

One-to-one mapping in multiple tables

我正在尝试解决一个难题,但到目前为止还没有成功。

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

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 是从属端。为了解决这个难题,我需要改变 principal/dependent 关系。同样,根据我的理解,为了改变关系方向,我需要在 Content 实体中有一个外键并使用流利的 API 来描述谁是 parent (主体)和谁是 child(依赖)在 one-to-one 关系中。由于许多table(可能还有其他内容为属性的实体)都指向Contenttable,这似乎并不容易。我的理解正确吗?

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

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描述关系方向,组织级联删除。对我来说它看起来很难看,但我没有其他想法。

我的问题:我提出的解决方案是good/reliable吗?我可以查看其他选项吗?

提前致谢!

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

我看到的其他选项如下:

(1) 持有Content的实体使用EF inheritance。例如

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(主体)之间的 one-to-one 关系。

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

(2) 制作 Content owned type.

这更接近本意,但不幸的是,EF Core 目前总是将拥有的实体数据与所有者数据一起加载(即使它们被配置为由不同的数据库 tables 提供),这是违背你最初的目标,所以我也不建议。

(3) 使用 table splitting 功能。

如果主要目标是简单地支持受控 (lazy/eager/explicit) 加载并且 Content 始终是 必需的 ,那么这可能是最佳解决方案到目前为止。

它需要更多的配置,但最后它会给你原始的 table 设计(每个实体单个 table)和所需的加载行为:

型号:

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
{
}

注意这里的Contentclass不是EF继承层次结构的一部分,而是简单的基础class具有共同的属性(abstract修饰符不是非常必要) .实际派生的 classes 可能定义也可能不定义它们自己的属性。

配置:

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");