Entity Framework、批量插入和维护关系

Entity Framework, Bulk Inserts, and Maintaining Relationships

我遇到了一个似乎很常见的问题,但我不知道如何达到预期的结果。我有一个嵌套实体,其上定义了导航属性,如下图所示。

对于给定的 MapLine,地图点集合可能非常大,对于 MapLayer 可能有相当多的 MapLine。

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

标准 Entity Framework 实现

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

导致大量内存峰值和相当差的 return 次。

我已经尝试实施 EntityFramework.BulkInsert package but it does not honor the relationships of the objects.

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

更新

我已尝试实施 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();
}

更新 2

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

俗话说"It aint pretty, but it works"。

我确实在 BulkInsert 包和管理 EF 外部的关系方面取得了一些成功,但在尝试使用 EF 将数据拉回系统时再次 运行 陷入内存问题。目前看来,EF 还不能有效地处理大型数据集和复杂的关系。

批量插入并不是使用 Entity Framework 有效添加数据的唯一方法 - this answer 中详细介绍了一些替代方法。您可以使用那里建议的优化(禁用更改跟踪)然后您可以像往常一样添加东西。

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

我对大量上下文保存的体验很糟糕。所有关于在迭代中保存 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 与上下文一起使用,您也可以设法使用该事务进行批量插入,但它需要一些技巧。