【发布时间】:2023-03-16 13:32:01
【问题描述】:
我正在开发一个实用程序来读取我收到的 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更好。
标签: c# json entity-framework json.net .net-core