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

7

我有一个看似常见的问题,但我无法想出如何实现所需的结果。我有一个嵌套的实体,其中定义了导航属性,如下图所示。

enter image description here

对于给定的MapLine,地图点集合可能非常大,而且对于一个MapLayer,可能有大量的MapLines。

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

标准的Entity Framework实现。

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

这会导致内存急剧增长和返回时间较差。

我尝试过实现EntityFramework.BulkInsert包, 但它不符合对象之间的关系。

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

更新

我尝试了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实体的字节数组中(因为我不针对这些结构进行查询,所以对我来说这很有效)。

俗话说得好,“它并不美观,但它有效”。

我确实成功地使用BulkInsert包管理了EF之外的关系,但是当我尝试使用EF将数据重新引入系统时,又遇到了内存问题。目前看来,EF无法高效地处理大型数据集和复杂关系。


你能解释一下之后你会如何处理这些数据吗?如果只是使用简单的二进制格式来处理数据是否更好呢?我很感兴趣。 - Andreas Dirnberger
2个回答

16

我遇到了大数据量保存的坏体验。所有那些建议按100行、1000行的迭代保存,然后处理上下文(context)或清空列表并分离对象,把一切都赋值为空等等 - 这都是胡说八道。我们有要求在许多表中每天插入数百万行。当迭代进行时,绝对不应该在这些条件下使用实体(entity)。你会与内存泄漏和插入速度降低作斗争。

我们的第一个改进是创建存储过程并将它们添加到模型中。它比 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行迭代保存、然后处理上下文或清除列表和分离对象、将所有内容赋值为空等等的建议 - 都是胡说八道。- 已经读到这里并点赞了。 - eran otzap
但是关于关系问题呢? 他会如何解决? - eran otzap
1
@eranotzap,如果您在批量插入时指的是关系,我们只需向父表添加2个附加列并在代码中填充它。一个用于部分,比如PortionID,另一个用于关系,比如RelationID。批量插入后,我们按部分选择数据,并选择ID和RelationID。因此,我现在有了关系,并通过比较RelationID并从数据库分配ID来为子记录分配适当的ID。然后我为子项执行另一个批量插入。 - Giorgi Nakeuri
我也这样做了。 使用了一个复杂类型。 但是现在,当通过存储过程从数据库查询集合时,我遇到了将实体翻译回来的问题。 - eran otzap

6
批量插入并不是使用Entity Framework高效添加数据的唯一方法。this answer详细介绍了一些替代方案。您可以使用其中建议的优化措施(禁用更改跟踪),然后像正常添加一样添加内容。
请注意,由于您一次添加许多项,您需要经常重新创建上下文以防止内存泄漏和减速。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接