如何使用 EF 将记录插入到不相关的表中

how to insert records to unrelated tables using EF

我有一个 Comment table 可以 linked 到许多有评论的不同实体,但由于某些原因,我没有 linked 那些 tables。相反 Comment 包含 TableReferenceIdEntryReferenceIdTableReferenceId 只是一个整数,我们可以在应用程序层中检查评论所指的 entity/table,而 EntryReferenceId 是一个整数,它指的是所述 [=23] 中的特定条目=]评论所属。

通过table和entry reference来查询这样的评论就可以了,但是在插入批量数据时,我画了一个空白。例如,如果我有 Vehicle 实体并且 Vehicle 可以有很多评论,当插入数据时,我将如何 link 它们,因为我还没有 VehicleId ?这是可行的还是为link评论的每个table选择多对多路线更好?

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

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

  2. 您可以将映射的评论存储在实体的未绑定集合中,在 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();

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

  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() 以检测有评论的实体并在保存操作后重新处理它们。

    In this example I am storing the EntityState for all modified entries before the save, this is overkill for this particular example, but you only lose this state information during the save, keeping a record of it becomes useful for a whole range of other applications for post-processing logic.

    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 范围来进一步改进它,以在出现问题时回滚全部内容,此代码本身是从我在生产代码中使用的常用例程中转述的,因此虽然它可能不会按原样工作,这个概念在很多项目中都对我很有帮助。