实体框架,批量插入和维护关系

时间:2015-01-12 20:47:36

标签: c# entity-framework nested bulkinsert relationships

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

enter image description here

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

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

标准实体框架实施

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

导致大量内存峰值和非常差的返回时间。

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

这似乎是某人之前遇到的问题,但我似乎无法找到解释如何完成此任务的任何资源。

更新

我试图实施理查德提供的建议,但我不明白我将如何为嵌套实体(例如我所描述的实体)进行此操作。我假设我需要插入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 =&gt; MapPoints关系作为MapLayer实体上一个字节数组中的原始Json字符串(因为我没有查询这些对我有用的结构)。

俗话说&#34;它不是很漂亮,但它有效&#34;。

我确实使用BulkInsert包并管理EF之外的关系,但在尝试使用EF将数据拉回系统时又遇到了内存问题。目前看来,EF无法有效处理大型数据集和复杂关系。

2 个答案:

答案 0 :(得分:16)

我在巨大的上下文保存方面经验不佳。关于在迭代中保存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,您也可以设法使用该交易批量插入,但它需要一些黑客攻击。

答案 1 :(得分:6)

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

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