【问题标题】:One-to-one mapping in multiple tables多个表中的一对一映射
【发布时间】:2018-08-20 04:02:19
【问题描述】:

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

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

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

乍一看似乎没问题:我可以创建博客文章、cmets 和附加内容(起初我插入内容,很明显)。更新也有效。但是,删除不起作用:当我删除博客文章或评论时,内容并没有被删除(但我想在删除博客文章或评论时删除它,而不是相反)。

据我了解,由于关系方向,我最大的问题是:在我的情况下,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 fluent API 来描述关系方向并组织级联删除。对我来说它看起来很丑,但我没有其他想法。

我的问题:我提出的解决方案是否良好/可靠?还有其他选择吗?

提前致谢!

【问题讨论】:

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


    【解决方案1】:

    你所有的想法都是正确的。您提出的解决方案是传统关系设计的唯一方法。缺点当然是需要多个互斥可为空的 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(主体)之间的一对一关系。

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

    (2) 制作Contentowned 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");
    

    【讨论】:

    • 感谢您的意见!继承不是一个选项,因为Content 将始终被加载。我想有一个选项在我不需要它时不加载它(通过Select 进行投影很痛苦)。我尝试使用 Owned 类型,但是当多个实体尝试拥有相同类型时,EF 会抛出错误。
    • EF 继承(选项 1)将起作用(它支持延迟加载),缺点是我指出的共享主表。选项 (3) 也支持延迟加载。只有选项(2)没有。所以基本上延迟加载目标的可行选项是您的第二种方法和我建议的选项(3)。我希望我的解释很清楚:)
    • 好的,我会尝试第三个选项,如果我有问题会回复你。你的意思是内容不是层次结构的一部分吗?你写了评论,但我相信你的意思是内容。
    • 对不起,Content 当然。我在posting之前已经测试过了,所以当我做db.Set&lt;BlogArticle&gt;().ToList();时,Textnull,当db.Set&lt;BlogArticle&gt;().Include(e =&gt; e.Text).ToList();然后Text是填充数据,但你当然可以自己试试。
    • 完美!让它工作起来很容易!我注意到所有字段都驻留在同一个表中,但在 EF 中表现得像真正的关系。感谢您确认我的想法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-01-12
    • 1970-01-01
    • 2015-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多