【问题标题】:WebAPI EF update 30,000 rows of data is very slowWebAPI EF更新30000行数据很慢
【发布时间】:2016-08-12 20:15:22
【问题描述】:

我正在尝试让我的 asp.net WebAPI Web 服务读取 .csv 并使用实体框架更新数据库。 .csv 文件大约有 20,000-30,000 行。

到目前为止,我正在使用TextfieldParser 读取.csv.csv 文件的每一行我创建一个新对象,然后将对象添加到 EF 上下文中。

将所有行添加到上下文后,我会调用db.SaveChanges();

观察控制台我注意到它为每一行调用一个更新语句......这需要很长时间。有没有更好更有效的方法来实现这一点?

if (filetype == "xxx")
{
    using (TextFieldParser csvReader = new TextFieldParser(downloadFolder + fileName))
    {
        csvReader.SetDelimiters(new string[] { "," });
        csvReader.HasFieldsEnclosedInQuotes = true;

        int rowCount = 1;

        while (!csvReader.EndOfData)
        {
            string[] fieldData = csvReader.ReadFields();

            //skip header row
            if (rowCount != 1)
            {                           
                var t = new GMI_adatpos
                        {
                            PACCT = fieldData[3]
                        };

                db.GMI_adatpos.Add(t);
            }

            rowCount++;
        }
    }
}

db.SaveChanges();

【问题讨论】:

  • 更新语句?这应该生成插入语句。什么是“很长时间”?你不会在眨眼之间得到它。您的实际期望是什么?

标签: sql-server entity-framework asp.net-web-api


【解决方案1】:

这个问题很常见,

在您的情况下,我们可以将其分为两类:

  • Add 与 AddRange 性能
  • 写入和数据库往返

Add vs AddRange 性能

每次添加新记录时,Add 方法都会尝试检测更改,而 AddRange 只执行一次。每次检测更改可能需要几分钟时间。

这个问题很容易解决,只需创建一个列表,将实体添加到此列表中,然后在列表末尾使用 AddRange。

List<GMI_adatpo> list = new List<GMI_adatpo>();

if (filetype == "xxx")
{
    using (TextFieldParser csvReader = new TextFieldParser(downloadFolder + fileName))
    {
        csvReader.SetDelimiters(new string[] { "," });
        csvReader.HasFieldsEnclosedInQuotes = true;

        int rowCount = 1;

        while (!csvReader.EndOfData)
        {
            string[] fieldData = csvReader.ReadFields();

            //skip header row
            if (rowCount != 1)
            {                           
                var t = new GMI_adatpos
                        {
                            PACCT = fieldData[3]
                        };

                list.Add(t);
            }

            rowCount++;
        }
    }
}

db.GMI_adatpos.AddRange(list)
db.SaveChanges();

写入和数据库往返

每次保存记录时,都会执行一次数据库往返。因此,如果您平均插入 30,000 条记录,您将执行 30,000 次数据库往返,这太疯狂了!

免责声明:我是项目的所有者Entity Framework Extensions

这个库允许执行:

  • 批量保存更改
  • 批量插入
  • 批量更新
  • 批量删除
  • 批量合并

您可以调用 BulkSaveChanges 而不是 SaveChanges,也可以创建一个列表来插入并直接使用 BulkInsert 以获得更高的性能。

BulkSaveChanges 解决方案(比 SaveChanges 快得多)

db.GMI_adatpos.AddRange(list)
db.SaveChanges();

BulkInsert 解决方案(比 BulkSaveChanges 最快,但不保存相关实体)

db.BulkInsert(list);

【讨论】:

  • 感谢 Jonathan... 该列表减少了大约 30-40% 的处理时间,您的项目 EntityFrameworkExtension 减少了大约 90-95% 的时间。谢谢!
【解决方案2】:

由于添加到DbContext中的item非常多,ram空间逐渐被填满,操作很慢。因此,最好在几条记录(例如 100 条)之后,调用 SaveChanges 方法并更新 DbContext。

if (filetype == "xxx")
{
    using (TextFieldParser csvReader = new TextFieldParser(downloadFolder + fileName))
    {
        csvReader.SetDelimiters(new string[] { "," });
        csvReader.HasFieldsEnclosedInQuotes = true;

        int rowCount = 1;

        while (!csvReader.EndOfData)
        {
            if(rowCount%100 == 0)
            {
                db.Dispose();
                db.SaveChanges();
                db = new AppDbContext();//Your DbContext
            }

            string[] fieldData = csvReader.ReadFields();

            //skip header row
            if (rowCount != 1)
            {                           
                var t = new GMI_adatpos
                        {
                            PACCT = fieldData[3]
                        };

                db.GMI_adatpos.Add(t);
            }

            rowCount++;
        }
    }
}  

【讨论】:

  • 感谢 Mohammad,这减少了大约 30-40% 的时间
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-16
  • 1970-01-01
  • 2013-07-14
  • 2020-06-23
  • 1970-01-01
  • 2015-03-30
  • 2015-11-01
相关资源
最近更新 更多