昨天在园子里发了一篇如题的文章 EF大数据批量添加性能问题 ,就引来一大堆的吐槽,我认为知识就应该这样分享出来,不然总以为自己很了不起;再说说昨天那篇文章,很多自认为很牛逼的人都评论说把SaveChanges()放在for循环外面,我不知道他们有没有亲自去尝试过,反正我尝试了,然而并没什么卵用。
下面是我按照他们说的进行更改后的代码:
1 public ActionResult Add(ItemDetails entity) 2 { 3 var sw = new Stopwatch(); 4 var count = 0; 5 //var counts = 0; 6 sw.Start(); 7 using (var db = new ShoppingDBConn()) 8 { 9 for (var i = 0; i < 10000; i++) 10 { 11 var data = new ItemDetails 12 { 13 AddedBy = entity.AddedBy, 14 Description = entity.Description, 15 Image_Name = entity.Image_Name, 16 Item_Name = entity.Item_Name, 17 Item_Price = entity.Item_Price 18 }; 19 db.ItemDetails.Add(data); 20 } 21 count = db.SaveChanges(); 22 } 23 sw.Stop(); 24 var date = sw.Elapsed; 25 return Json(string.Format("总耗时:{0},添加数量:{1}", date, count)); 26 }
运行耗时:
再看看AddRange方式:
1 public ActionResult Add(ItemDetails entity) 2 { 3 var sw = new Stopwatch(); 4 var count = 0; 5 //var counts = 0; 6 sw.Start(); 7 using (var db = new ShoppingDBConn()) 8 { 9 var list = new List<ItemDetails>(); 10 for (var i = 0; i < 10000; i++) 11 { 12 list.Add(new ItemDetails 13 { 14 AddedBy = entity.AddedBy, 15 Description = entity.Description, 16 Image_Name = entity.Image_Name, 17 Item_Name = entity.Item_Name, 18 Item_Price = entity.Item_Price 19 }); 20 } 21 db.ItemDetails.AddRange(list); 22 count = db.SaveChanges(); 23 } 24 sw.Stop(); 25 var date = sw.Elapsed; 26 return Json(string.Format("总耗时:{0},添加数量:{1}", date, count)); 27 }
耗时情况:
不过还好有几位给出了很好的建议,用SqlBulkCopy,下面是优化后的代码,比上面任何一种都要快好几倍:
1 public void BulkInsertAll<T>(IEnumerable<T> entities) 2 { 3 entities = entities.ToArray(); 4 var cons=new ShoppingDBConn(); 5 string cs = cons.Database.Connection.ConnectionString; 6 var conn = new SqlConnection(cs); 7 conn.Open(); 8 9 Type t = typeof(T); 10 11 var bulkCopy = new SqlBulkCopy(conn) 12 { 13 DestinationTableName = t.Name 14 }; 15 16 var properties = t.GetProperties().Where(EventTypeFilter).ToArray(); 17 var table = new DataTable(); 18 19 foreach (var property in properties) 20 { 21 Type propertyType = property.PropertyType; 22 if (propertyType.IsGenericType && 23 propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) 24 { 25 propertyType = Nullable.GetUnderlyingType(propertyType); 26 } 27 28 table.Columns.Add(new DataColumn(property.Name, propertyType)); 29 } 30 31 foreach (var entity in entities) 32 { 33 table.Rows.Add(properties.Select( 34 property => GetPropertyValue( 35 property.GetValue(entity, null))).ToArray()); 36 } 37 38 bulkCopy.WriteToServer(table); 39 conn.Close(); 40 } 41 42 private bool EventTypeFilter(System.Reflection.PropertyInfo p) 43 { 44 var attribute = Attribute.GetCustomAttribute(p, 45 typeof(AssociationAttribute)) as AssociationAttribute; 46 47 if (attribute == null) return true; 48 if (attribute.IsForeignKey == false) return true; 49 50 return false; 51 } 52 53 private object GetPropertyValue(object o) 54 { 55 if (o == null) 56 return DBNull.Value; 57 return o; 58 }
调用该方法:
1 public ActionResult Add(ItemDetails entity) 2 { 3 var sw = new Stopwatch(); 4 var count = 0; 5 //var counts = 0; 6 sw.Start(); 7 using (var db = new ShoppingDBConn()) 8 { 9 var list = new List<ItemDetails>(); 10 for (var i = 0; i < 10000; i++) 11 { 12 list.Add(new ItemDetails 13 { 14 AddedBy = entity.AddedBy, 15 Description = entity.Description, 16 Image_Name = entity.Image_Name, 17 Item_Name = entity.Item_Name, 18 Item_Price = entity.Item_Price 19 }); 20 count++; 21 } 22 BulkInsertAll(list); 23 } 24 sw.Stop(); 25 var date = sw.Elapsed; 26 return Json(string.Format("总耗时:{0},添加数量:{1}", date, count)); 27 }
总耗时情况: