C# 实体框架、批量插入和维护关系

C# 实体框架、批量插入和维护关系,c#,entity-framework,nested,bulkinsert,relationships,C#,Entity Framework,Nested,Bulkinsert,Relationships,我有一个似乎是常见的问题,但我无法想出如何达到预期的结果。我有一个嵌套实体,上面定义了导航属性,如下图所示 对于给定的贴图线,“贴图点”集合可能非常大,而对于贴图图层,可能有大量的贴图线 这里的问题是,使用Entity Framework将MapLayer对象插入到数据库中,并保持导航属性定义的关系的最佳方法是什么 标准实体框架实现 dbContext.MapLayers.Add(mapLayer); dbContext.SaveChanges(); 导致较大的内存峰值和非常差的返回时间 我

我有一个似乎是常见的问题,但我无法想出如何达到预期的结果。我有一个嵌套实体,上面定义了导航属性,如下图所示

对于给定的贴图线,“贴图点”集合可能非常大,而对于贴图图层,可能有大量的贴图线

这里的问题是,使用Entity Framework将MapLayer对象插入到数据库中,并保持导航属性定义的关系的最佳方法是什么

标准实体框架实现

dbContext.MapLayers.Add(mapLayer);
dbContext.SaveChanges();
导致较大的内存峰值和非常差的返回时间

我已尝试实施

这似乎是以前有人遇到过的问题,但我似乎找不到任何资源来解释如何完成这项任务

更新

我曾试图实现Richard提供的建议,但我不明白对于我所描述的嵌套实体,我将如何实现这一点。我运行时假设需要插入MapLayer对象,然后是MapLines,然后是MapPoints,以在数据库中遵循PF/FK关系。我目前正在尝试以下代码,但这似乎不正确

dbContext.MapLayers.Add(mapLayer);
dbContext.SaveChanges();

List<MapLine> mapLines = new List<MapLine>();
List<MapPoint> mapPoints = new List<MapPoint>();
foreach (MapLine mapLine in mapLayer.MapLines)
{
    //Update the mapPoints.MapLine properties to reflect the current line object
    var updatedLines = mapLine.MapPoints.Select(x => { x.MapLine = mapLine; return x; }).ToList();

    mapLines.AddRange(updatedLines);
}

using (TransactionScope scope = new TransactionScope())
{
    MyDbContext context = null;
    try
    {
        context = new MyDbContext();
        context.Configuration.AutoDetectChangesEnabled = false;

        int count = 0;
        foreach (var entityToInsert in mapLines)
        {
            ++count;
            context = AddToContext(context, entityToInsert, count, 100, true);
        }

        context.SaveChanges();
    }
    finally
    {
        if (context != null)
            context.Dispose();
    }

    scope.Complete();
}
dbContext.MapLayers.Add(mapLayer);
dbContext.SaveChanges();
列表映射行=新列表();
列表映射点=新列表();
foreach(mapLayer.MapLines中的MapLine MapLine)
{
//更新mapPoints.MapLine属性以反映当前线对象
var updatedLines=mapLine.MapPoints.Select(x=>{x.mapLine=mapLine;返回x;}).ToList();
mapLines.AddRange(updatedLines);
}
使用(TransactionScope范围=新TransactionScope())
{
MyDbContext上下文=null;
尝试
{
context=新的MyDbContext();
context.Configuration.AutoDetectChangesEnabled=false;
整数计数=0;
foreach(映射行中的var entityToInsert)
{
++计数;
context=AddToContext(context,entityToInsert,count,100,true);
}
SaveChanges();
}
最后
{
if(上下文!=null)
context.Dispose();
}
scope.Complete();
}
更新2

在尝试了多种不同的方法来实现这一点之后,我最终放弃了,只是将MapLayer作为一个实体插入,并将MapLines=>MapPoints关系作为原始Json字符串存储在MapLayer实体的字节数组中(因为我不是在查询这些对我有用的结构)

俗话说“它不漂亮,但很管用”


我在BulkInsert包和管理EF之外的关系方面确实取得了一些成功,但在尝试使用EF将数据拉回到系统中时,再次遇到了内存问题。目前,EF似乎无法有效地处理大型数据集和复杂关系

大容量插入并不是使用实体框架高效添加数据的唯一方法-中详细介绍了许多替代方法。您可以使用此处建议的优化(禁用更改跟踪),然后您可以正常添加内容


请注意,当您一次添加多个项目时,您需要相当频繁地重新创建上下文,以阻止内存泄漏和速度降低。

我在大型上下文保存方面有过不好的经验。所有这些关于在迭代中保存100行、1000行、然后处理上下文或清除列表、分离对象、为所有内容分配null等的建议都是胡说八道。我们需要在许多表中每天插入数百万行。在这些情况下,绝对不应该使用实体。当迭代继续进行时,您将面临内存泄漏和插入速度下降的问题

我们的第一个改进是创建存储过程并将它们添加到模型中。它比
Context.SaveChanges()
快100倍,并且没有泄漏,速度不会随时间而降低

但这对我们来说还不够,我们决定使用
SqlBulkCopy
。它超快。比使用存储过程快1000倍

因此,我的建议是: 若您有许多行要插入,但计数低于50000行,请使用存储过程,在模型中导入; 如果有数十万行,请尝试
SqlBulkCopy

下面是一些代码:

EntityConnection ec = (EntityConnection)Context.Connection;
SqlConnection sc = (SqlConnection)ec.StoreConnection;

var copy = new SqlBulkCopy(sc, SqlBulkCopyOptions.CheckConstraints | SqlBulkCopyOptions.Default , null);

copy.DestinationTableName = "TableName";
copy.ColumnMappings.Add("SourceColumn", "DBColumn");
copy.WriteToServer(dataTable);
copy.Close();

如果您将
DbTransaction
与上下文一起使用,您也可以使用该事务进行批量插入,但它需要一些技巧。

您能否解释一下您对数据后缀所做的操作?我很感兴趣,如果不是更好的话,那就是对数据使用简单的二进制格式。“我在大型上下文保存方面有过不好的经验。所有关于在迭代中保存100行、1000行、然后处理上下文或清除列表、分离对象、为所有内容分配空值等的建议都是胡说八道。”-读到这一点,+1已经准备好了,但是关系问题呢?他将如何解决这个问题?@eranotzap,如果你指的是批量插入时的relashinship,我们只是在父表中添加了两列,并在代码中填充它。1代表部分称为PortionID,另一个代表关系称为RelationID。在批量插入之后,我们按部分选择数据,然后选择ID和RelationID。所以我现在有了关系,并将适当的ID分配给子记录,通过RelationID和从db分配ID进行比较。然后我为孩子们做了另一个大插页我也做了。。使用了complextype。虽然现在我在通过存储过程从数据库中以集合形式查询实体时遇到了问题。