为什么EF5会在SaveChanges()次产生这些峰值?

时间:2012-09-02 21:13:44

标签: c# performance sql-server-2008-r2 entity-framework-5

我始终如一地得到如下图所示的结果。我想知道我能做些什么来提高可预测性。我没有使用SqlBulkCopy,因为我需要利用EFv5的验证功能。

如果有人可以验证/反驳我的发现,那就太好了。我的目标是摆脱两种尖峰。我在下面提供了源代码,以便您快速完成。您只需要一个VS中的类库项目,引用EFv5和NUnit,两者都可以通过NuGet获得。只需将此代码粘贴到Class1中,修改连接字符串并运行它即可。您可以使用下面的sql脚本重新创建表。

我正在使用.Net 4.5,EF 5,NUnit 2.6.1,在发布模式下运行代码,没有附加调试器。 db是SqlServer 2008 R2。我在64位模式下使用NUnit.exe运行测试,该模式显示了Net 4.0'作为框架版本。

EFv5 simple entity insert. 1000 batches of 100 entities

X轴是批号(总共1000批),Y轴是毫秒。您可以看到第一批需要大约30秒,这是预期的,因为dbContext是“冷”#39;每批保存100个实体。

请注意,此问题正在寻找this answer中缺少的一些信息,即EF保存中的抖动

以下是我使用的代码:

表格:

CREATE TABLE [dbo].[Entity1](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [IntField] [int] NOT NULL,
    [StrField] [nvarchar](50) NOT NULL,
    [DateField] [datetimeoffset](7) NOT NULL,
CONSTRAINT [PK_Entity1] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF,
    ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

课程:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Diagnostics;
using NUnit.Framework;

namespace ClassLibrary1
{
    public class Entity1
    {
        public int Id { get; protected set; }
        public int IntField { get; set; }
        public string StrField { get; set; }
        public DateTimeOffset DateField { get; set; }
    }

    public class MyContext : DbContext
    {
        public MyContext(string connStr) : base(connStr) { }
        public virtual DbSet<Entity1> Entities { get { return base.Set<Entity1>(); } }
    }

    [TestFixture]
    public class Class1
    {
        [Test]
        public void EfPerf()
        {
            var entities = buildEntities(100000);
            int batchSize = 100;
            var batchTimes = new List<Stopwatch>();

            for (int i = 0; i < entities.Length; i += batchSize)
            {
                var sw = Stopwatch.StartNew();
                using (var ctx = buildCtx())
                {
                    for (int j = i; j < i + batchSize; j++)
                        ctx.Entities.Add(entities[j]);
                    ctx.SaveChanges();
                }
                sw.Stop();
                batchTimes.Add(sw);
            }

            batchTimes.ForEach(sw => Console.Out.WriteLine("Elapsed ms: " + 
                sw.ElapsedMilliseconds));
        }

        private MyContext buildCtx()
        {
            var cs = "Data Source=your db server;" +
                    "Initial Catalog=your db;" +
                    "Persist Security Info=True;" +
                    "User ID=your user;" + 
                    "Password=your pwd";
            var ctx = new MyContext(cs);
            //ctx.Configuration.ProxyCreationEnabled = false;
            return ctx;
        }

        private Entity1[] buildEntities(int count)
        {
            var entities = new Entity1[count];
            for (int i = 0; i < count; i++)
                entities[i] = new Entity1 { IntField = i, StrField = "str" + i, 
                    DateField = DateTimeOffset.UtcNow };
            return entities;
        }
    }
}

1 个答案:

答案 0 :(得分:1)

我感觉您遇到了数据库死锁的问题。 EF将所有saveChanges()调用放在事务中。默认情况下,SQL Server中的事务作为Read Committed运行,这是一种高度限制性的。也许您可以尝试更改隔离级别:

 using (var scope = new TransactionScope(TransactionScopeOption.Required, new 
 2: TransactionOptions { IsolationLevel= IsolationLevel.Snapshot }))
 3: {
 4: // do something with EF here
 5: scope.Complete();
 6: }