【问题标题】:EF AddRange method does not work with big dataEF AddRange 方法不适用于大数据
【发布时间】:2019-05-08 12:19:48
【问题描述】:

早上好。

今天我遇到了 EF 6 和 AddRange 方法的问题。有一个 WPF 应用程序可以处理约 100000 条记录。我写了导入功能,从.csv文件中导入数据,出现了问题。

看起来像这样:

 private void FileImport()
    {
        //Open dialog to choose file
        OpenFileDialog ofd = new OpenFileDialog();

        string fileName = string.Empty;

        if (ofd.ShowDialog() == true)
        {
            fileName = ofd.FileName;
        }

        if (!string.IsNullOrEmpty(fileName))
        {
            //getting all lines
            var lines = File.ReadAllLines(fileName).ToList();

            //File requirements says that there cannot be empty values in first element
            if (lines.Any(line => line.Split(';')[0].Equals("null")))
            {
                MessageBox.Show("BLA BLA BLA");
            }
            else
            {

                List<List<string>> splitLines = new List<List<string>>();

                //split lines into smaller list. For every sublist in list we will do things separatly in separate threads to get it faster.
                for (int i = 0; i < lines.Count; i += 1000)
                {
                    splitLines.Add(lines.GetRange(i, Math.Min(1000, lines.Count - i)));
                }

                var taskList = new List<Task>();

                List<ut_katabcdx_file_filters> filterList = new List<ut_katabcdx_file_filters>();

                foreach (var list in splitLines)
                {
                    //define a Task
                    var t = new Task(() =>
                    {
                        foreach (var line in list)
                        {
                            var filters = line.Split(';');

                            //split line into elements array. It must have 6 elemets
                            if (filters.Count() == 6)
                            {

                                //Temporary declaration for parsing. If element that pretends to be decimals are empty we set its value to -100000.
                                decimal temp;
                                int tempi;
                                decimal? proga = filters[1].Equals("") ? -100000 : (decimal.TryParse(filters[1], out temp) ? (decimal?)temp : null);
                                decimal? progb = filters[2].Equals("") ? -100000 : (decimal.TryParse(filters[2], out temp) ? (decimal?)temp : null);
                                int? plan_sprz_rok = filters[3].Equals("") ? -100000 : (int.TryParse(filters[3], out tempi) ? (int?)tempi : null);

                                ut_katabcdx_file_filters filter = new ut_katabcdx_file_filters()
                                {
                                    indeks = filters[0],
                                    //produkty_iz = ProduktyIzChecked ? (filters[1].Equals("null") ? null : filters[1]) : string.Empty,
                                    proga = ProgaChecked ? proga : -100000,
                                    progb = ProgbChecked ? progb : -100000,
                                    plan_sprz_rok = PlanSprzRokChecked ? plan_sprz_rok : -100000,
                                    kat_tech = KatTechChecked ? (filters[4].Equals("null") ? null : filters[4]) : string.Empty,
                                    kat_handl = KatHandlChecked ? (filters[5].Equals("null") ? null : filters[5]) : string.Empty,
                                };

                                filterList.Add(filter);
                            }
                        }
                    });

                    taskList.Add(t);
                    t.Start();
                }

                //wait for all tasks to end
                Task.WaitAll(taskList.ToArray());

                using (var ctx = new ABCDXContext())
                {
                    ctx.ut_katabcdx_file_filters.AddRange(filterList);
                    ctx.SaveChanges();
                    string param_xml = GetParamXml();
                    Products = new ObservableCollection<ut_katabcdx_wytwor>(ctx.WytworFileUpdate(param_xml));
                }
            }

        }
    }

当我调试代码时,它会在ctx.ut_katabcdx_file_filters.AddRange(filterList); 处停止,并且不会更进一步。我检查了filterList.Count,大约有 60000 行。我也检查了数据库表,但它是空的。

是因为数据量大还是我做的不对? 如果有任何建议,我将非常感激。

【问题讨论】:

  • 当你说它“不会走得更远”时,你需要等到它完成后才能看到数据库中的任何内容。插入 60k 条记录并不是一项快速的任务,尤其是对于 EF。您可能想查看其他用于批量插入数据的工具,
  • @DavidG 能坚持20分钟吗?
  • 可能需要几个小时,这取决于很多因素。
  • 简短回答 - 不要为此使用 EF。更长的答案 - 阅读stackoverflow.com/questions/13722014/…(这应该从> 20分钟到stackoverflow.com/questions/5940225/… 也值得一读。
  • 当像这样插入大量行时,使用 SQL 批量复制的性能要高得多。有几个 Nuget 包为 Entity Framework 6 添加了批量插入支持。

标签: c# wpf entity-framework


【解决方案1】:

我同意 DavidG 关于尝试使用 AddRange 插入 60k 记录的评论。它会很慢。您要么只需要等待它完成,要么找到替代方案。

我发现当您定期致电SaveChanges 时,您的Context 会更开心。似乎在更改跟踪器中堆积大量项目会对性能造成巨大影响。

所以你可以这样做:

using (var ctx = new ABCDXContext())
{
    var count = 0;
    foreach (var filter in filterList)
    {
        ctx.ut_katabcdx_file_filters.Add(filter);
        count++;
        if (count > 100)
        {
            ctx.SaveChanges();
            count = 0;
        }
    }
    if (count > 0)
        ctx.SaveChanges();
    string param_xml = GetParamXml();
    Products = new ObservableCollection<ut_katabcdx_wytwor>(ctx.WytworFileUpdate(param_xml));
}

...只是为了将存档分解成块。您可以将count 调整为一个平衡您方案性能的值。

This answer 提供您可以尝试的其他建议。那里的评论表明效果很好。

【讨论】:

  • 这个代码的警告词,它只会插入100条记录的批次。例如,如果您有 199 个,那么最后的 99 个将丢失。
  • 可能调用 Add 方法不是一个好主意。它可以在上下文中引起额外的变化检测。至于我,最好将列表分成小块(如 1000 条记录)并调用 AddRange(chunkList);使用 SaveChanges()
  • 手动批处理通常可以替换为 MoreLinq 的Batch,以避免@DavidG(正确地)强调的那种问题。
  • @DavidG 真的,忘记了额外的保存调用,我现在已经添加了。
  • @DavidG 为什么?如果更改跟踪器为 0,则不会发生任何事情。如果无事可做,尝试的开销应该为零。我继续添加它......我想这两种方式都没有害处。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多