实体框架核心存储来自多个数据源的多对多关系

时间:2018-09-27 06:45:27

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

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

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

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密钥会发生冲突。存储相同类结构的差异源并且不丢失关系信息的正确方法是什么?谢谢

1 个答案:

答案 0 :(得分:1)

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

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

发布表

+------------+----------------+-------+----------+
| 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>()
      };

    }
  }

因此,当您在域上下文中插入数据时,只需转换相应的实体即可。该代码假定您确实具有上述对象图,并且您的帖子具有一堆post标签,并且每个post标签都有一堆标签。

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

这会将所有数据库发布和数据库标签实体写入数据库并分配其主键。 下一步是将两者链接在一起。

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