JSON 数组到实体框架核心非常慢?
Posted
技术标签:
【中文标题】JSON 数组到实体框架核心非常慢?【英文标题】:JSON Array to Entity Framework Core VERY Slow? 【发布时间】:2018-03-11 11:35:25 【问题描述】:我正在开发一个实用程序来读取我收到的 JSON 文件并将其转换为 SQL Server。我选择的武器是 .NET Core 控制台应用程序(我正在尝试使用 .NET Core 完成所有新工作,除非有令人信服的理由不这样做)。我的整个东西都在“工作”,但显然某个地方存在问题,因为性能真的很可怕,几乎到了无法使用的地步。
JSON 文件大约 27MB,包含一个由 214 个元素组成的主数组,每个元素包含几个字段以及一个包含 150-350 条记录的数组(该数组有几个字段,可能还有一个小于 5 条记录的小数组或两个)。总记录约为 35,000 条。
在下面的代码中,我更改了一些名称并删除了一些字段以使其更具可读性,但所有实际工作的逻辑和代码都没有改变。
请记住,我已经对 SaveChanges() 的位置和调用次数进行了大量测试,最初认为访问 Db 的次数是问题所在。尽管下面的版本为 214 条记录循环的每次迭代调用一次 SaveChanges(),但我尝试将它移到整个循环结构之外,并且性能没有明显变化。换句话说,在零次到 Db 的情况下,这仍然是缓慢的。你问有多慢,> 24 小时运行对你有什么影响?在这一点上,我愿意尝试任何事情,甚至考虑将整个过程转移到 SQL Server 中,但在 C# 中工作要比在 TSQL 中工作要好得多。
static void Main(string[] args)
string statusMsg = String.Empty;
JArray sets = JArray.Parse(File.ReadAllText(@"C:\Users\Public\Downloads\ImportFile.json"));
try
using (var _db = new WidgetDb())
for (int s = 0; s < sets.Count; s++)
Console.WriteLine($"s.ToString(): sets[s]["name"]");
// First we create the Set
Set eSet = new Set()
SetCode = (string)sets[s]["code"],
SetName = (string)sets[s]["name"],
Type = (string)sets[s]["type"],
Block = (string)sets[s]["block"] ?? ""
;
_db.Entry(eSet).State = Microsoft.EntityFrameworkCore.EntityState.Added;
JArray widgets = sets[s]["widgets"].ToObject<JArray>();
for (int c = 0; c < widgets.Count; c++)
Widget eWidget = new Widget()
WidgetId = (string)widgets[c]["id"],
Layout = (string)widgets[c]["layout"] ?? "",
WidgetName = (string)widgets[c]["name"],
WidgetNames = "",
ReleaseDate = releaseDate,
SetCode = (string)sets[s]["code"]
;
// WidgetColors
if (widgets[c]["colors"] != null)
JArray widgetColors = widgets[c]["colors"].ToObject<JArray>();
for (int cc = 0; cc < widgetColors.Count; cc++)
WidgetColor eWidgetColor = new WidgetColor()
WidgetId = eWidget.WidgetId,
Color = (string)widgets[c]["colors"][cc]
;
_db.Entry(eWidgetColor).State = Microsoft.EntityFrameworkCore.EntityState.Added;
// WidgetTypes
if (widgets[c]["types"] != null)
JArray widgetTypes = widgets[c]["types"].ToObject<JArray>();
for (int ct = 0; ct < widgetTypes.Count; ct++)
WidgetType eWidgetType = new WidgetType()
WidgetId = eWidget.WidgetId,
Type = (string)widgets[c]["types"][ct]
;
_db.Entry(eWidgetType).State = Microsoft.EntityFrameworkCore.EntityState.Added;
// WidgetVariations
if (widgets[c]["variations"] != null)
JArray widgetVariations = widgets[c]["variations"].ToObject<JArray>();
for (int cv = 0; cv < widgetVariations.Count; cv++)
WidgetVariation eWidgetVariation = new WidgetVariation()
WidgetId = eWidget.WidgetId,
Variation = (string)widgets[c]["variations"][cv]
;
_db.Entry(eWidgetVariation).State = Microsoft.EntityFrameworkCore.EntityState.Added;
_db.SaveChanges();
statusMsg = "Import Complete";
catch (Exception ex)
statusMsg = ex.Message + " (" + ex.InnerException + ")";
Console.WriteLine(statusMsg);
Console.ReadKey();
【问题讨论】:
Linq-2-Sql 是另一种选择。问题可能是它在后端对数据库进行单独插入。您是否打开了 EntityFramework 的日志记录功能?只需将其连接到Console.Out
哪一行最慢?您是否尝试使用调试器找出答案?
你说即使不打电话SaveChanges
也很慢?注释掉所有_db.Entry(...
行怎么样?还是很慢?
还有更多的调整选项,例如设置_db.ChangeTracker.AutoDetectChangesEnabled = false;
,或在每次循环迭代时使用上下文实例。
查看 EFC 实现,关键是关闭更改跟踪(@GertArnold 提到的第一个选项)。对于这样的数据量,即使是 24 分钟也太多了。这样的时间表示二次复杂度算法,这正是更改跟踪开启时发生的情况 - 每个Entry
调用都会检查每个当前跟踪的实体ChangeTracker.DetectChanges()
。顺便说一句,使用自然的Add
方法比使用Entry(..).State = Added
更好。
【参考方案1】:
我对这种代码有问题,有很多循环和大量的状态变化。
您在 _db 上下文中所做的任何更改/操作都会生成它的“跟踪”。它每次都会让你的上下文变慢。阅读更多here。
对我来说,解决方法是在某些关键点创建新的 EF 上下文(_db)。每次运行它为我节省了几个小时!
您可以尝试在此循环中的每次迭代中创建一个新的 _db 实例
包含一个由 214 个元素组成的主数组
如果没有改变,请尝试添加一些 stopwatch 以更好地了解什么/哪里花了这么长时间。
【讨论】:
轰隆隆! 24 小时以上到不到 24 分钟!我删除了 Db 上下文的 using 块,并在外部 for 循环内对其进行了实例化,并在调用 Save Changes() 之后将其丢弃。谢谢大佬! 很高兴帮助:)【参考方案2】:如果您要进行数千次更新,那么 EF 并不是真正可行的方法。 SQLBulkCopy 之类的东西可以解决问题。
你可以试试bulkwriter 库。
IEnumerable<string> ReadFile(string path)
using (var stream = File.OpenRead(path))
using (var reader = new StreamReader(stream))
while (reader.Peek() >= 0)
yield return reader.ReadLine();
var items =
from line in ReadFile(@"C:\products.csv")
let values = line.Split(',')
select new Product Sku = values[0], Name = values[1];
然后
using (var bulkWriter = new BulkWriter<Product>(connectionString))
bulkWriter.WriteToDatabase(items);
【讨论】:
以上是关于JSON 数组到实体框架核心非常慢?的主要内容,如果未能解决你的问题,请参考以下文章
实体框架:Count() 在大型 DbSet 和复杂的 WHERE 子句上非常慢
具有 LINQ 的实体框架在 WHERE 子句中使用 CONTAINS 非常慢且具有大整数列表
在 sql server json 列中搜索并使用实体框架核心使用它