【问题标题】:Entity Framework Core storing many to many relationship from multiple feeds of dataEntity Framework Core 存储来自多个数据源的多对多关系
【发布时间】:2018-09-27 06:45:26
【问题描述】:

如何正确实现实体框架核心类以支持两个类之间的多对多关系,但这些类和关系的数组/数据来自 2 个或更多数据源。

也就是说,如果我们有一个简单的关系,例如:

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public List<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public List<PostTag> PostTags { get; set; }
}

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

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

我们的服务器将通过 web api 从另外两个或数据源获取一组帖子、标签以及它们的任何关系。因此,如果我们只是尝试直接获取数据并将其序列化回我们的数据库,那么在给定 2 个或更多不同来源的情况下,将会与 Id Key 发生冲突。存储相同类结构的不同源并且不丢失关系信息的正确方法是什么?谢谢

【问题讨论】:

  • 这是一个相当广泛的问题。实际上你在问:我如何处理并发?
  • 我认为Post 应该包含List&lt;Tag&gt; 而不是List&lt;PostTag&gt;
  • @viveknuna 不,EF-core 不支持多对多关系。
  • @GertArnold 我正在考虑做类似的事情: public class RepoPost : Post { [Key] public int Id { get;放; } } 这种方式添加一个额外的 Id 键用作实际的 DB 键,然后修复导入的数据和关系,但是一旦你有一个多对多的关系,这似乎是不可能的。
  • 是的。看看下面的答案。

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


【解决方案1】:

您缺少转换层。让我解释一下。

您存储在数据库中的帖子对象必须具有以下结构:

张贴表

+------------+----------------+-------+----------+
| PostId     | ExternalPostId | Title | Content  |
+------------+----------------+-------+----------+
  • PostId - 这是您自己的表中的主键。自动生成,必填。
  • ExternalPostId - 这是来自 API 的 PostId。
  • 标题 - 这是来自 API 的标题
  • 内容 - 这是来自 API 的内容。

标签表

+--------+---------------+-------------+-------------+
| TagId  | ExternalTagId | OtherField1 | OtherField2 |
+--------+---------------+-------------+-------------+
  • TagId - 这是您自己的 Tag 表的主键。自动生成,必填。
  • ExternalTagId - 这是来自 API 的 TagId
  • OtherField1、OtherField2 - 任何其他字段随 API 中的标记一起到达。

加入表格

+------------+-------+
| PostId     | TagId |
+------------+-------+
  • PostId、TagId - 复合主键
  • PostId - 外键,必需,非自动编号,引用 Post 表。
  • TagId - 外键,必需,非自动编号,引用 Tag 表。

此时您的实体将如下所示:

发布实体

[Table("Post")]
public class DbPost {

  [Key]
  [Column("PostId")]
  public int Id { get; set; }

  [Column("ExternalPostId")]
  public int ExternalId { get; set; }

  [Column("Title")]
  public string Title { get; set; }

  [Column("Content")]
  public string Content { get; set; }

  public IList<PostTag> PostTags { get; set; }
}

标签实体:

[Table("Tag")
public class DbTag
{
    [Key]
    [Column("TagId")]
    public int Id { get; set; }

    [Column("ExternalTagId")]
    public string ExternalId { get; set; }

    public IList<PostTag> PostTags { get; set; }
}

PostTag 实体 该实体保持不变。这里的区别是 PostId 映射到 Post 实体的 Id 属性,这是你自己的 Post 表的主键。以类似的方式,将 TagId 映射到 Tag 表的 Id 属性,这也是您自己的表的主键。

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

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

最后,您的域上下文:

public class Database : DbContext {
  public virtual DbSet<DbPost> Posts { get; set; }
  public virtual DbSet<DbTag> Tags { get; set; }
  public virtual DbSet<DbPostTag> PostTags { get; set; }
  ...
}

当您从 API 插入帖子时,您需要在来自 API 的帖子与数据库中的帖子实体之间创建翻译。您可以通过扩展方法或为您翻译它的方法来做到这一点:

public static class ApiObjectExtensions {

    public static DbPost ToDbPost(this Post post){
      var dbPost = new DbPost(){
        ExternalId = post.PostId,
        Title = post.Title,
        Content = post.Content,
        PostTags = new List<DbPostTags>()
      };
    }

    public static DbTag ToDbTag(this Tag tag){
      return new DbTag(){ 
        ExternalId = tag.Id,
        PostTags = new List<DbPostTags>()
      };

    }
  }

因此,当您在域上下文中插入数据时,您只需转换相应的实体即可。这段代码假设你真的有上面描述的对象图,你的帖子有一堆帖子标签,每个帖子标签都有一堆标签。

foreach(var post in posts){

  var dbPost = post.ToDbPost();

  if(post.PostTags != null && post.PostTags.Any()){

    foreach(var postTag in post.PostTags){

      if(postTag.Tag != null){

         var dbTag = postTag.Tag.ToDbTag();

         var dbPostTag = new DbPostTag(){
           Post = dbPost,
           Tag = dbTag
         };

         dbPost.PostTags.Add(dbPostTag);
      }
    }
  }

  Database.DbPosts.Add(dbPost);
}

Database.SaveChanges();

如果您在单独的调用中接收帖子,帖子标签和标签与您的插入操作会有些不同:

var insertedPosts = new List<DbPost>();
var insertedTags = new List<DbTag>();

foreach(var post in posts){
  var dbPost = post.ToDbPost();
  Database.Posts.Add(dbPost);
  insertedPosts.Add(dbPost);
}

foreach(var tag in tags){
  var dbTag = tag.ToDbTag();
  Database.Tags.Add(dbTag);
  insertedTags.Add(dbTag);
}
Database.SaveChanges(); 

这会将所有 db post 和 db tag 实体写入 db 并分配它们的主键。 下一步是将两者联系在一起。

foreach(var postTag in postTags){
  var dbPost = insertedPosts.FirstOrDefault(p => p.ExternalId = postTag.PostId);
  var dbTag = insertedTags.FirstOrDefault(t => t.ExternalId = postTag.TagId);

  if(dbPost != null && dbTag != null){
    var dbPostTag = new DbPostTag(){
      PostId = dbPost.Id,
      Tag = dbTag.Id
    };
    Database.PostTags.Add(dbPostTag);
  }
}
Database.SaveChanges();

最后一点是当您检索数据并希望它以原始格式显示时。 以下查询将返回所有帖子、它们的链接实体和它们的标签:

var dbPosts = this.Database.Posts
    .Include( p => p.PostTags )
    .ThenInclude(pt => pt.Tag )
    .ToList();

var apiPosts = dbPosts.Select(p => p.ToApiPost()).ToList();

要将这些东西转换回原来的 Post、PostTag 和 Tag 类型,您可以简单地创建扩展方法,如下所示:

public static class DbObjectExtensions {
  public static Post ToApiPost(this DbPost dbPost){
    var post = new Post(){
      PostId = dbPost.ExternalId,
      Title = dbPost.Title,
      Content = dbPost.Content,
      PostTags = new List<PostTag>()
    };

    if(dbPost.PostTags != null) {
       foreach(var dbPostTag in dbPost.PostTags){
         if(dbPostTag.Tag != null){
           var tag = dbPostTag.Tag.ToApiTag();
           var postTag = new PostTag(){
             PostId = post.PostId,
             Post = post,
             TagId = tag.TagId,
             Tag = tag
           };
           post.PostTags.Add(postTag);
           tag.PostTags.Add(postTag);
         }
       }
    }
    return post;
  }

  public static Tag ToApiTag(this DbTag dbTag){
    return new Tag(){
      TagId = dbTag.ExternalId
    };
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-18
    • 2017-02-27
    • 1970-01-01
    • 1970-01-01
    • 2020-12-09
    • 2021-04-09
    • 2022-12-10
    • 2020-07-22
    相关资源
    最近更新 更多