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 列中搜索并使用实体框架核心使用它

JSON数组到核心数据实体

将核心数据行从一个表(实体)映射到 [CLLocation] 数组?

实体框架的大批量更新比我自己批量更新慢得多