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

ese*_*ura 7 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简单实体插入. 1000批100个实体

X轴是批号(总共1000批),Y轴是毫秒.您可以看到第一批需要大约30秒,这是因为dbContext是"冷"的.每批保存100个实体.

请注意,这个问题正在寻找这个答案中缺少的一些信息,它是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]
Run Code Online (Sandbox Code Playgroud)

课程:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

小智 1

我有一种感觉,您的数据库遇到了死锁问题。EF 将所有 saveChanges() 调用置于事务中。默认情况下,SQL Server 中的事务以已提交读取方式运行,这是高度限制性的。也许您可以尝试更改隔离级别,如下所示:

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