【问题标题】:Using a junction table in a many to many relationship在多对多关系中使用联结表
【发布时间】:2019-04-29 16:18:08
【问题描述】:

我正在学习 EF Core。假设我与这样的联结表有多对多关系:

public class PostTag
{
    public int PostId { get; set; }
    public Post Post { get; set; }

    public int TagId { get; set; }
    public Tag Tag { get; set; }
}

假设我想执行数据库插入。

var post = new Post { Id=Guid.NewGuid(), Description="Test Post" };
var tag = new Tag { Id=Guid.NewGuid(), Description="Test Text"  };
var PostTag = new PostTag { Post=post; Tag=tag; PostId=Post.Id, TagId=Tag.Id};
context.Add(PostTag);

这一插入似乎是:

1) Insert a post.
2) Insert a Tag
3) Insert a PostTag

Q1) 为什么 PostTag 包含 PostId 和 TagId?此信息包含在 Post 和 Tag(也是 PostTag 中的两个字段)中。看来这些字段是重复的。

Q2) 添加 PostTag 是否也会添加 Post 和 Tag?

【问题讨论】:

    标签: asp.net-core entity-framework-core


    【解决方案1】:

    为什么 PostTag 包含 PostId 和 TagId?此信息包含在 Post 和 Tag(也是 PostTag 中的两个字段)中。看来这些字段是重复的。

    如您所知,PostTag.PostId 是引用 Post.Id 的外键,PostTag.TagId 是引用 Tag.Id 的外键。 PostTag.PostId 称为 Foreign Key PropertyPostTag.PostNavigation Property

    实际上,如果您没有明确的导航属性的外键属性,EF Core 也将按预期工作。在这种情况下,将自动为您引入PostIdTagId 两个影子外键属性。它被称为Shadow Properties

    当发现关系但在依赖实体类中找不到外键属性时,可以按约定创建阴影属性。在这种情况下,将引入影子外键属性。影子外键属性将被命名为<navigation property name><principal key property name>(依赖实体上的导航,指向主体实体,用于命名)。如果主键属性名称包含导航属性的名称,则名称将只是 <principal key property name>。如果依赖实体上没有导航属性,则使用主体类型名称代替它。

    1. 虽然这些外键可以分别由PostTag.Post.IdPostTag.Tag.Id 推断出来,但它们不是重复的

      假设我们有一个现有的数据库,其中外键命名为fk_post_idfk_tag_id,并且您想将外键属性PostId 重命名为MyPostId,我们不能省略PostIdTagId 属性:

      public class PostTag
      {
          [Column("fk_post_id")]
          public Guid MyPostId { get; set; }
          [ForeignKey("MyPostId")]
          public Post Post { get; set; }
      
      
          [Column("fk_tag_id")]
          public Guid TagId { get; set; }
          public Tag Tag { get; set; }
      }
      
    2. 建议在导航属性的依赖实体类中定义外键属性

    添加 PostTag 是否也会添加 Post 和 Tag?

    您的代码会自动为您插入帖子和标签,因为它知道您需要创建一个全新的帖子和一个全新的标签。但情况并非总是如此。这取决于跟踪状态和实体本身。

    如果执行以下代码:

        var tagTracked = this._dbContext.Tag.FirstOrDefault();
        var post = new Post { Id=Guid.NewGuid(), Description="Test Post3" };
        var PostTag = new PostTag { 
            Post=post, 
            Tag=tagTracked, 
            MyPostId= post.Id, 
            TagId= tagTracked.Id,
        };
        this._dbContext.Add(PostTag);
        this._dbContext.SaveChanges();
    

    tagTracked 不会被插入。如果要插入新的标签记录,需要将tag设为untracked:

        var tagTracked = this._dbContext.Tag.AsNoTracking().FirstOrDefault();
        tagTracked.Id = Guid.NewGuid();
        var post = new Post { Id=Guid.NewGuid(), Description="Test Post3" };
        var PostTag = new PostTag { 
            Post=post, 
            Tag=tagTracked, 
            MyPostId= post.Id, 
            TagId= tagTracked.Id,
        };
        this._dbContext.Add(PostTag);
        this._dbContext.SaveChanges();
    

    再次,它将在数据库中插入一条新的标签记录。更多信息请参考docs here

    【讨论】:

    • 谢谢。如果你有一个多对多的关系,那么我相信你必须有一个连接实体(在这种情况下是战后)。这似乎是 ef core 的一个显着缺点。那正确吗?我看过 github 文章 - 我只是想确保它在多年后仍然是真实的。
    • @w0051977 你是对的。根据the official document 的说法,在没有联结实体的情况下,EF Core 暂时不会自动处理多对多关系。
    猜你喜欢
    • 2016-08-21
    • 2013-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多