我有一个似乎常见的问题,但我无法弄清楚如何达到预期的结果。我有一个嵌套的实体,其上定义了导航属性,如下图所示。
对于给定的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无法有效处理大型数据集和复杂关系。
答案 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中详细介绍了许多替代方法。您可以使用那里建议的优化(禁用更改跟踪),然后您可以正常添加内容。
请注意,当您一次添加多个项目时,您需要相当频繁地重新创建上下文,以阻止内存泄漏并减慢您的速度。