【问题标题】:how to insert records to unrelated tables using EF如何使用 EF 向不相关的表中插入记录
【发布时间】:2021-03-22 04:28:02
【问题描述】:

我有一个Comment 表,它可以链接到许多具有 cmets 的不同实体,但由于某些原因,我没有链接这些表。而Comment 包含TableReferenceIdEntryReferenceIdTableReferenceId 只是一个 int,我们可以在应用程序层中检查评论所指的实体/表,EntryReferenceId 是一个 int,它指的是评论所属的实体/表中的特定条目。

通过表和条目引用查询此类 cmets 可以,但是在插入批量数据时,我画的是空白。例如,如果我有Vehicle 实体并且Vehicle 可以有很多cmets,那么在插入数据时,由于我还没有VehicleId,我将如何链接它们?这是可行的还是只为链接到 cmets 的每个表走多对多路由更好?

【问题讨论】:

    标签: c# .net entity-framework entity-framework-6


    【解决方案1】:

    如果您可以避免这种情况,那么您应该尝试,或者您应该尽量避免支持批量插入。如果您必须这样做,那么以下任一模式都可能适合您。

    1. 分两个阶段执行批量插入,在正常导入之前,维护记录的映射或字典以及它们链接到的 cmets,然后在第一次调用 SaveChanges() 之后,ID 将可用于插入。

    2. 您可以将映射的 cmets 存储在实体上的未绑定集合中,SaveChanges() 之后,如果此集合中有任何条目,则应使用新记录的 Id 插入它们。

    让我们看看第一个选项:

    var mappedComments = new Dictionary<Vehicle,Comment[]>();
    // bulk processing, however you choose to do it
    // importantly for each item, capture the record reference and the comments
    foreach(var item in source)
    {
        Vehicle newItem;
        ... construct/parse the new Entity object
        List<Comment> newComments = new List<Comment>();
        ... parse the comments records
        // store the map
        mappedComments.Add(newItem, newComments.ToArray());
    
        // Add the entity to the context?
        db.AddToVehicles(newItem);
    }
    
    db.SaveChanges();
    
    foreach(var mapEntry in mappedComments)
    {
        var newVehicle = mapEntry.Key;
        // replace this with your actual logic of course...
        int vehicleTableReferenceId = db.TableReferences.Single(x => x.TableName == nameof(Vehicle));
        foreach(var comment in mappEntry.Value)
        {
            comment.TableReferenceId = vehicleTableReferenceId;
            comment.EntityReferenceId = newVehicle.Id; // the Id that is now populated
            db.AddToComments(comment);
        }
    }
    
    db.SaveChanges();
    

    如果您有很多表现出这种链接行为的实体类型,那么您可以通过将映射的 cmets 嵌入到实体本身中来将此功能构建到实体本身中。

    1. 定义一个描述对象的接口,该对象对这些注释

      具有弱引用
      public interface ICommentsToInsert
      {
          // Only necessary if your convention is NOT to use a common name for the PK
          int Id { get; }
          ICollection<Comment> CommentsToInsert { get;set;}
      }
      
    2. 实现此接口并向实体添加一个未映射的集合属性,以存储要针对每条记录插入的评论条目

      partial class Vehicle : ICommentsToInsert
      {
          [NotMapped]
          int ICommentsToInsert.Id { get => Vehicle_Id; }
          [NotMapped]
          public ICollection<Comment> CommentsToInsert { get;set; } = new HashSet<Comment>();
      }
      
    3. 在您的批量逻辑中,将 Comment 记录添加到 Vehicle.CommentsToInsert 集合中,我将把它留给您...

    4. 覆盖 SaveChanges() 以检测具有 cmets 的实体并在保存操作后重新处理它们。

      在此示例中,我在保存之前为 所有 个已修改条目存储 EntityState,这对于此特定示例来说太过分了,但您只会在保存期间丢失此状态信息,并记录下来对于后处理逻辑的一系列其他应用程序变得有用。

      public override int SaveChanges()
      {
          var beforeStates = BeforeSaveChanges();
          int result = base.SaveChanges();
          if (AfterSaveChanges(beforeStates);
              result += base.SaveChanges();
          return results;
      }
      
      private Dictionary<DbEntityEntry, EntityState> BeforeSaveChanges()
      {
          var beforeSaveChanges = new Dictionary<DbEntityEntry, EntityState>();
          foreach( var entry in this.ChangeTracker.Entries())
          {
              //skip unchanged entries!
              if (entry.State == EntityState.Unchanged)
                  continue;
              // Today, only cache the ICommentsToInsert records...
              if (entry.Entity is ICommentsToInsert)
                  beforeSaveChanges.Add(entry, entry.State);
          }
          return beforeSaveChanges;
      }
      
      private bool AfterSaveChanges(Dictionary<DbEntityEntry, EntityState> statesBeforeSaveChanges)
      {
          bool moreChanges = false;
          foreach (var entry in statesBeforeChanges)
          {
              if (entry.Key.Entity is ICommentsToInsert hasComments)
              {
                  if(hasComments.CommentsToInsert.Any())
                  {
                      moreChanges = true;
                      // Get the Id to the TableReference, based on the name of the Entity type
                      // you would normally cache this type of lookup, rather than hitting the DB every time
                      int tableReferenceId = db.TableReferences.Single(x =
           > x.TableName == entry.Key.Entity.GetType().Name);
                      foreach (var comment in hasComments.CommentsToInsert)
                      {
                          comment.TableReferenceId = tableReferenceId;
                          comment.EntityReferenceId = hasComments.Id;
                          db.AddToComments(comment);
                      }
                  }
              }
          }
          return moreChanges;
      }
      

    您可以通过实现DbTransaction 范围来进一步改进这一点,以便在失败时回滚全部内容,此代码本身是从我在生产代码中使用的常用例程中转述的,因此虽然它可能无法按原样工作,这个概念在我的许多项目中都很有用。

    【讨论】:

      猜你喜欢
      • 2017-04-19
      • 1970-01-01
      • 2021-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多