【问题标题】:how to insert records to unrelated tables using EF如何使用 EF 向不相关的表中插入记录
【发布时间】:2021-03-22 04:28:02
【问题描述】:
我有一个Comment 表,它可以链接到许多具有 cmets 的不同实体,但由于某些原因,我没有链接这些表。而Comment 包含TableReferenceId 和EntryReferenceId。 TableReferenceId 只是一个 int,我们可以在应用程序层中检查评论所指的实体/表,EntryReferenceId 是一个 int,它指的是评论所属的实体/表中的特定条目。
通过表和条目引用查询此类 cmets 可以,但是在插入批量数据时,我画的是空白。例如,如果我有Vehicle 实体并且Vehicle 可以有很多cmets,那么在插入数据时,由于我还没有VehicleId,我将如何链接它们?这是可行的还是只为链接到 cmets 的每个表走多对多路由更好?
【问题讨论】:
标签:
c#
.net
entity-framework
entity-framework-6
【解决方案1】:
如果您可以避免这种情况,那么您应该尝试,或者您应该尽量避免支持批量插入。如果您必须这样做,那么以下任一模式都可能适合您。
-
分两个阶段执行批量插入,在正常导入之前,维护记录的映射或字典以及它们链接到的 cmets,然后在第一次调用 SaveChanges() 之后,ID 将可用于插入。
-
您可以将映射的 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 嵌入到实体本身中来将此功能构建到实体本身中。
-
定义一个描述对象的接口,该对象对这些注释
具有弱引用
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;}
}
-
实现此接口并向实体添加一个未映射的集合属性,以存储要针对每条记录插入的评论条目。
partial class Vehicle : ICommentsToInsert
{
[NotMapped]
int ICommentsToInsert.Id { get => Vehicle_Id; }
[NotMapped]
public ICollection<Comment> CommentsToInsert { get;set; } = new HashSet<Comment>();
}
-
在您的批量逻辑中,将 Comment 记录添加到 Vehicle.CommentsToInsert 集合中,我将把它留给您...
-
覆盖 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 范围来进一步改进这一点,以便在失败时回滚全部内容,此代码本身是从我在生产代码中使用的常用例程中转述的,因此虽然它可能无法按原样工作,这个概念在我的许多项目中都很有用。