使用EntityFramework插入大量数据时速度非常缓慢

11
我正在尝试通过Entity Framework 6.1.3向MS Sql Server数据库插入约50,000行数据,但执行时间太长。我按照这个答案的方法禁用了AutoDetectChangesEnabled,并在添加每1000个实体后调用了SaveChanges。但仍需要大约7-8分钟的时间。我已在远程服务器和本地服务器上尝试过,没有太大的区别。我不认为这是正常的。我是否忘记了什么?以下是我的代码:
static void Main(string[] args)
    {

        var personCount = 50000;
        var personList = new List<Person>();
        var random = new Random();

        for (int i = 0; i < personCount; i++)
        {
            personList.Add(new Person
            {
                CreateDate = DateTime.Now,
                DateOfBirth = DateTime.Now,
                FirstName = "Name",
                IsActive = true,
                IsDeleted = false,
                LastName = "Surname",
                PhoneNumber = "01234567890",
                PlaceOfBirth = "Trabzon",
                Value0 = random.NextDouble(),
                Value1 = random.Next(),
                Value10 = random.NextDouble(),
                Value2 = random.Next(),
                Value3 = random.Next(),
                Value4 = random.Next(),
                Value5 = random.Next(),
                Value6 = "Value6",
                Value7 = "Value7",
                Value8 = "Value8",
                Value9 = random.NextDouble()
            });
        }

        MyDbContext context = null;

        try
        {
            context = new MyDbContext();
            context.Configuration.AutoDetectChangesEnabled = false;

            int count = 0;
            foreach (var entityToInsert in personList)
            {
                ++count;
                context = AddToContext(context, entityToInsert, count, 1000, true);
            }

            context.SaveChanges();
        }
        finally
        {
            if (context != null)
                context.Dispose();
        }

    }

    private static MyDbContext AddToContext(MyDbContext context, Person entity, int count, int commitCount, bool recreateContext)
    {
        context.Set<Person>().Add(entity);

        if (count % commitCount == 0)
        {
            context.SaveChanges();
            if (recreateContext)
            {
                context.Dispose();
                context = new MyDbContext();
                context.Configuration.AutoDetectChangesEnabled = false;
            }
        }

        return context;
    } 

人类:

public class Person
{
    public int Id { get; set; }

    [MaxLength(50)]
    public string FirstName { get; set; }

    [MaxLength(50)]
    public string LastName { get; set; }

    public DateTime DateOfBirth { get; set; }

    [MaxLength(50)]
    public string PlaceOfBirth { get; set; }

    [MaxLength(15)]
    public string PhoneNumber { get; set; }

    public bool IsActive { get; set; }

    public DateTime CreateDate { get; set; }

    public int Value1 { get; set; }

    public int Value2 { get; set; }

    public int Value3 { get; set; }

    public int Value4 { get; set; }

    public int Value5 { get; set; }

    [MaxLength(50)]
    public string Value6 { get; set; }

    [MaxLength(50)]
    public string Value7 { get; set; }

    [MaxLength(50)]
    public string Value8 { get; set; }

    public double Value9 { get; set; }

    public double Value10 { get; set; }

    public double Value0 { get; set; }

    public bool IsDeleted { get; set; }
}

来自分析器的查询跟踪:

exec sp_executesql N'INSERT [dbo].[Person]([FirstName], [LastName],       [DateOfBirth], [PlaceOfBirth], [PhoneNumber], [IsActive], [CreateDate],     [Value1], [Value2], [Value3], [Value4], [Value5], [Value6], [Value7], [Value8],     [Value9], [Value10], [Value0], [IsDeleted])
VALUES (@0, @1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16, @17, @18)
SELECT [Id]
FROM [dbo].[Person]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()',N'@0 nvarchar(50),@1     nvarchar(50),@2 datetime2(7),@3 nvarchar(50),@4 nvarchar(15),@5 bit,@6 datetime2(7),@7 int,@8 int,@9 int,@10 int,@11 int,@12 nvarchar(50),@13 nvarchar(50),@14 nvarchar(50),@15 float,@16 float,@17 float,@18 bit',@0=N'Name',@1=N'Surname',@2='2017-01-19 10:59:09.9882591',@3=N'Trabzon',@4=N'01234567890',@5=1,@6='2017-01-19 10:59:09.9882591',@7=731825903,@8=1869842619,@9=1701414555,@10=1468342767,@11=1962019787,@12=N'Value6',@13=N'Value7',@14=N'Value8',@15=0,65330243467041405,@16=0,85324223938083377,@17=0,7146566792925152,@18=0

我希望只使用EF来解决这个问题。我知道有很多替代方案,但假设没有其他选择。

主要问题在于,我使用了与我引用答案相同的方法。它可以在191秒内插入560000个实体。但是我只能在7分钟内插入50000个。


1
你尝试过使用 'context.Configuration.ValidateOnSaveEnabled = false;' 吗? - kgzdev
1
你能展示一下“person”的定义吗?也许有很多索引列?为什么需要AddToContext方法?试着把它移除。 - SirBirne
1
你可以使用Bulk Insert来实现这个功能 https://efbulkinsert.codeplex.com/ - K D
4
ORM通常不适用于批量操作,包括EF。ORM是用来处理实体的,而批量操作则是处理原始数据而非实体。如果您需要快速执行批量操作,请使用SqlBulkCopy。 - Panagiotis Kanavos
有没有比较熟悉 EF 的人能给我解释一下为什么要用 SELECT?谢谢。 - dean
显示剩余17条评论
1个回答

22

通过禁用AutoDetectChanges,您已经解决了ChangeTracker的问题。

我通常建议使用以下其中之一的解决方案:

  1. 使用AddRange而不是Add (推荐)
  2. 将AutoDetectChanges设置为false
  3. 将SaveChanges拆分成多个批次

请参阅:http://entityframework.net/improve-ef-add-performance

由于您已将AutoDectectChanges设置为false,因此拆分多个批次不会真正改善或降低性能。

主要问题在于Entity Framework为每个需要插入的实体执行一次数据库往返。因此,如果您要插入50,000个实体,则会执行50,000个数据库往返,这是疯狂的

为解决此问题,您需要减少数据库往返的数量。

一个免费的方法是使用SqlBulkCopy:https://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopy(v=vs.110).aspx


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

此库允许您执行所需的所有批量操作:

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

您将能够在几秒钟内插入50,000个实体。

示例

// Easy to use
context.BulkSaveChanges();

// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);

// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);

// Customize Primary Key
context.BulkMerge(customers, operation => {
   operation.ColumnPrimaryKeyExpression = 
        customer => customer.Code;
});

您的扩展程序是否支持此功能?或者您知道其他高效地保存带有几何列的多行的方法吗?

是的,我们的库支持几何列。


人们在哪里可以找到sql.data.sqlclient库?有些文章建议安装.NET Compact Framework。这是否意味着它只能针对本地数据库工作,而不能远程访问数据库? - Shinya Koizumi
1
@powerfade917,你不能在SQL Compact中使用SqlBulkCopy。在SQL Compact中,插入记录的最快方法是使用DbDataReader && CreateRecord。你可以在这里找到一个开源示例:https://sqlcebulkcopy.codeplex.com/。 - Jonathan Magnan
2
为什么微软没有收购你,让你的代码成为原生Entity Framework的一部分?或者为什么这些更快的更新不是EF的首要部分呢? - Vlado Pandžić
@JonathanMagnan SqlBulkCopy 不支持几何列。您的扩展程序是否支持此功能?或者您是否知道其他有效地保存具有几何列的多行数据的方法? - Johan
1
在我的情况下,插入速度提高了14倍!谢谢你。 - MohammadHossein R
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接