AutoDetectChangesEnabled及AddRange解决EF插入的性能问题
转自:http://www.cnblogs.com/nianming/archive/2013/06/07/3123103.html#2699851
记录下。
园友莱布尼茨写了一篇《Entity Framework数据插入性能追踪》的文章,我感觉不错,至少他提出了问题,写了出来,引起了大家的讨论,这就是一个氛围。读完文章+评论,于是我自己也写了个简单的程序试了试。
先晒一下代码:
两个简单的类:
1: /// <summary> 2: /// 消费者 3: /// </summary> 4: public class Consumer 5: { 6: public int CId { get; set; } 7: public string CName { get; set; } 8: public List<Order> Orders { get; set; } 9: } 10: 11: /// <summary> 12: /// 订单 13: /// </summary> 14: public class Order 15: { 16: public int OrderNo { get; set; } 17: public DateTime OrderDate { get; set; } 18: public decimal TotalMoney { get; set; } 19: public int CId { get; set; } 20: 21: public Consumer Consumer { get; set; } 22: } 映射配置: 1: public class ConsumerConfiguration : EntityTypeConfiguration<Consumer> 2: { 3: public ConsumerConfiguration() 4: { 5: HasKey(t => t.CId).Property(t => t.CId).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 6: Property(t => t.CName).IsRequired().HasMaxLength(50); 7: } 8: } 9: 10: public class OrderConfiguration : EntityTypeConfiguration<Order> 11: { 12: public OrderConfiguration() 13: { 14: HasKey(t => t.OrderNo); 15: HasRequired(t => t.Consumer).WithMany(t => t.Orders).HasForeignKey(t => t.CId); 16: } 17: }
Context: 1: public class TestContext : DbContext 2: { 3: public DbSet<Consumer> Consumers { get; set; } 4: public DbSet<Order> Orders { get; set; } 5: 6: protected override void OnModelCreating(DbModelBuilder modelBuilder) 7: { 8: modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); 9: 10: modelBuilder.Configurations.Add(new ConsumerConfiguration()); 11: modelBuilder.Configurations.Add(new OrderConfiguration()); 12: 13: base.OnModelCreating(modelBuilder); 14: } 15: } 测试代码: 1: static void Main(string[] args) 2: { 3: using (var ctx = new TestContext()) 4: { 5: ctx.Consumers.Add(new Consumer() 6: { 7: CName = "张三" 8: }); 9: ctx.SaveChanges(); 10: 11: Stopwatch sw = new Stopwatch(); 12: sw.Start(); 13: Console.WriteLine("订单开始:\n"); 14: 15: for (int outer = 0; outer < 20000; outer++) 16: { 17: ctx.Orders.Add(new Order() 18: { 19: OrderDate = DateTime.Now, 20: TotalMoney = 100, 21: CId = 1, 22: }); 23: } 24: ctx.SaveChanges(); 25: sw.Stop(); 26: Console.WriteLine(sw.Elapsed.Minutes + "分" + sw.Elapsed.Seconds + "秒" + sw.Elapsed.Milliseconds + "毫秒"); 27: } 28: }
上面的代码是最平常的代码了,没有什么可解释的,将内容放到重点上。
运行以上代码的环境是VS2012+SQL SERVER 2008 R2,机器配置:4G,N年以前的CPU。
运行上面的代码非常的慢,正如莱布尼茨说的,在数据Add到上下文这个阶段比较耗时。出现这个问题的原因是:每次调用ctx.Orders.Add(order)之前,EF都会调用DetectChanges,在StackOverFlow上有解释,地址是:http://stackoverflow.com/questions/9439430/improving-performance-of-initializing-dbset-in-seed,另外在Programming Entity Framework DbContext这本书的60也有DetectChange的介绍。
解决上面速度慢的问题的办法就是设置
1: ctx.Configuration.AutoDetectChangesEnabled = false;
下面来看看禁用以后的执行速度:
另外一个解决办法就是使用DbSet<T>.AddRange方法,这个方法是在6.0 beta1中加入的。
1: List<Order> orderList = new List<Order>(); 2: for (int outer = 0; outer < 20000; outer++) 3: { 4: orderList.Add(new Order() 5: { 6: OrderDate = DateTime.Now, 7: TotalMoney = 100, 8: CId = 1 9: }); 10: } 11: ctx.Orders.AddRange(orderList); 12: ctx.SaveChanges();
AddRange方法在System.Data.Entity 泛型DbSet类中,下图是我通过Reflector截的图
从上面两幅图中可以看到,Add和AddRange都是添加到_internalSet中,但是如果AutoDetectChangesEnabled设置为true的话,添加任何实体之前都会调用DetectChanges,注意看Remarks中的解释。