为什么Entity Framework在一个SaveChanges()中添加多个项目这么慢?

时间:2017-07-20 05:38:15

标签: c# entity-framework entity-framework-6

这是a previous question的后续内容,其中我试图弄清楚代码运行缓慢的主要原因。我想我已经把它缩小到下面的一个最小例子。我有一个基本的数据库结构如下:

public class Foo
{
    public int Id { get; set; }
    public string Bar { get; set; }
}

public class FooContext : DbContext
{
    public DbSet<Foo> Foos { get; set; }
}

现在,如果我有一个Foo个对象列表,并希望将它们添加到数据库中,建议的方法是使用AddRange()。但是我注意到它花了很长时间,并且受到集合中项目数量的影响很小,即使是200这样的少量项目。所以我手动编写了它,并且中提琴,它运行得更快!

class Program
{
    static void Main(string[] args)
    {
        var foos = Enumerable.Range(0, 200).Select(index => new Foo { Bar = index.ToString() });

        // Make sure the timing doesn't include the first connection
        using (var context = new FooContext())
        {
            context.Database.Connection.Open();
        }

        var s1 = Stopwatch.StartNew();
        using (var context = new FooContext())
        {
            context.Foos.AddRange(foos);
            context.SaveChanges();
        }
        s1.Stop();

        var s2 = Stopwatch.StartNew();
        using (var context = new FooContext())
        {
            // Ignore the lack of sanitization, this is for demonstration purposes
            var query = string.Join(";\n", foos.Select(f => "INSERT INTO Foos ([Bar]) VALUES (" + f.Bar + ")"));
            context.Database.ExecuteSqlCommand(query);
        }
        s2.Stop();

        Console.WriteLine("Normal way: {0}", s1.Elapsed);
        Console.WriteLine("Hard way  : {0}", s2.Elapsed);
        Console.ReadKey();
    }
}

我最初的想法是Entity Framework可能为每个条目使用单独的事务,但是记录SQL表明情况并非如此。那为什么执行时间会有这么大差异呢?

3 个答案:

答案 0 :(得分:5)

在对您的问题进行一些研究时,我发现了这篇具有启发性的文章:http://www.codinghelmet.com/?path=howto/bulk-insert

这是一个引用:

  

插入的每个对象都需要两个SQL语句 - 一个用于插入记录,另外一个用于获取新记录的标识

插入多个记录时会出现问题。每个记录一次插入一个事实会加剧一个问题(但是这已经超出了你的问题的上下文,因为你已经在逐个插入测试)。因此,如果你要插入200条记录,那就是逐个执行400条sql语句。

所以根据我的理解,EF根本不是为批量插入而构建的。即使它像插入200条记录一样简单。对我来说,这似乎是一个很大的失望。

我开始思考,“那么EF无论如何都是好事。它甚至不能插入几条记录”。那么我会在两个方面给出EF道具:

  1. 选择查询:编写查询并快速将数据导入应用程序非常容易。
  2. 简化复杂记录的插入。如果您曾经拥有一张包含大量外键的表,而您尝试在一次交易中插入所有链接的记录,那么您就知道我在做什么谈论。值得庆幸的是,EF按顺序插入每条记录,并在一次交易中为您链接所有相关记录。但如上所述,这需要付出代价。
  3. 所以简单地说,似乎,如果你有一个需要插入一堆记录的操作,最好使用 SqlBulkCopy 。这可以在几秒钟内插入数千条记录。

    我知道这可能不是你想听到的答案,因为相信我,因为我使用了很多EF,所以它也让我感到不安,但是我没有看到任何解决方法

答案 1 :(得分:0)

这只是猜测,但您是否尝试在完成第一个查询后通过Entity Framework运行第二个等效查询,然后计时以查看它是否更接近原始SQL时间?

This answer和其他人表示实体框架执行第一个查询的速度很慢,因为它有构建模型的开销。我不知道这是不是你看到的问题,但似乎有可能。无论哪种方式,知道第二次贯穿是否明显快于第一次贯穿是有用的,以便我们证明或排除这种可能性。

答案 2 :(得分:0)

既然你不能忍受它而不能没有它,你考虑过调用SaveChangesAsync()吗?

我进行了广泛搜索,找到了一种禁用主键同步的方法,但找不到任何适用于EF 6和更低版本的方法。

EF核心从DBContext.SaveChanges()传递一个true,我认为最终会触发此同步。另一个重载允许调用者传递false作为控制参数。