C# EF 批量操作


背景

源代碼下載地址在最后
知識要求:ef code first
ef 批量操作是最近遇到的一個新問題,ef這個orm為我們解決了大量的curd操作,但是,對於批量操作,其性能一直沒有很好的方案,不管是 foreach 方式,還是 addorupdate(這個擴展內部實現原理還是一個一個add),當數據量很大的時候,其性能簡直是不能容忍,差不多1萬多的數據,需要等半個小時左右!
於是開始着手尋找一個可以使用ef進行批量操作的的類庫,開始用的是 zzz projects 的類庫,但是其免費版,只有更新和刪除,沒有插入,使用非免費版,如果時間過期,會導致程序出問題(血的教訓),於是有找啊找,皇天不負有心人,這里需要吐槽下百度,在百度上,基本上搜索到的答案,均是 zzz projects 的類庫 或者 ef extend ,后者也沒有 批量插入。
不得已,我們翻出去看看,不會翻牆的程序員不是好的搬磚工,在google一搜,搜到了很漂亮,很好用的一個類庫:EntityFramework.Utilities,地址:https://github.com/MikaelEliasson/EntityFramework.Utilities, 關鍵這貨還是免費開源的……
我們的故事就從這里開始!


創建項目

使用vs工具,創建一個控制台程序,並引入nuget 包:

 
圖片.png

實體類如下:

using System.ComponentModel.DataAnnotations; public class TestEntity { [Key] public int Id { get; set; } public int RandomVlue { get; set; } } 

上下文如下:

public class BatchDemoContext : DbContext { public BatchDemoContext() : base("Default") { } public IDbSet<TestEntity> TestEntities { get; set; } } 

有了以上代碼,我們就可以使用code first 命令創建數據庫,前提是,在配置文件里,添加了數據庫連接字符串


創建數據源

有了以上內容,接下來,我們需要生產多條數據,比如,生產100000條數據,具體的代碼如下


        /// <summary> /// 產生需要生產的數據 /// </summary> /// <returns></returns> private static IEnumerable<TestEntity> GetInsertDatas() { // 線程安全的list ConcurrentBag<TestEntity> datas=new ConcurrentBag<TestEntity>(); Parallel.For(0, 100000, (index, state) => { Random rand = new Random(); var newData = new TestEntity { RandomVlue = rand.Next(1, 100) }; datas.Add(newData); }); return datas; } 

為了生產數據快,使用了Parallel,不懂的可以自行 google,如果不能翻牆你就bing一下,或者是看《 C# 高級編程》 關於異步 的這章,這里,只需要知道,生產100000條數據,用parallel會很快的產生數據即可!


批量插入

我們獲取了數據源,那么,如何對這些數據進行插入呢,看如下的測試代碼:


        /// <summary> /// 批量插入 /// </summary> private static void BatchInster() { var datas = GetInsertDatas(); var testEntities = datas as IList<TestEntity> ?? datas.ToList(); Stopwatch watch =new Stopwatch(); Console.WriteLine("開始插入計時,總共數據:{0}條",testEntities.Count()); watch.Start(); using (var context=new BatchDemoContext()) { EFBatchOperation.For(context,context.TestEntities) .InsertAll(testEntities); } watch.Stop(); Console.WriteLine("結束插入計時,工用時:{0}ms",watch.ElapsedMilliseconds); using (var context = new BatchDemoContext()) { var count = context.TestEntities.Count(); Console.WriteLine("數據庫總共數據:{0}條",count); var minId = context.TestEntities.Min(c => c.Id); // 隨機取十條數據進行驗證 for (int i = 1; i <= 10; i++) { Random rand = new Random(); var id = rand.Next(minId, minId+ 100000); var testdata = context.TestEntities.FirstOrDefault(c => c.Id == id); Console.WriteLine("插入的數據 id:{0} randomvalue:{1}",testdata.Id,testdata.RandomVlue); } } Console.WriteLine("-----------------華麗的分割線 插入-------------------------"); } 

用 Stopwatch 監測 插入所執行的時間
通過使用代碼

using (var context=new BatchDemoContext()) { EFBatchOperation.For(context,context.TestEntities) .InsertAll(testEntities); } 

即可實現批量插入,然后,針對這10調數據,隨機隨十條進行驗證,我們在生產數據的時候,默認的隨機值不會超過100,如果這10條隨機值,都是小於100的,可以認為插入成功。
使用
var minId = context.TestEntities.Min(c => c.Id);
是為了保證,id條件最小,不然第二次運行程序,會出錯,因為id是自增的,刪除數據,id也不會從1開始!
插入的運行結果如下:

 
圖片.png

通過上圖可以看出
需要插入的數據有 100000條,插入僅用了 3070ms,這比原生態的ef要快了不知道多少倍……
通過插入數據的id和randomvalue,可以看出,我們的數據,也的確是正確的插入到了數據庫!


批量更新

通過上面的步驟,我們數據庫里已經有100000條數據里,現在,我們將數據庫里的數據,randomvalue 全部設置為 1000,於是我們需要,獲取全部數據,然后並行運算,改randomvalue的值為100000,在然后批量更新修改后的數據
代碼如下


        /// <summary> /// 批量更新 /// </summary> private static void BatchUpdate() { IEnumerable<TestEntity> toUpdates=new List<TestEntity>(); // 獲取所有數據 using (var context = new BatchDemoContext()) { toUpdates = context.TestEntities.ToList(); } // 所有的值 都為 1000 Parallel.ForEach(toUpdates, (entity, state) => { entity.RandomVlue = 1000; }); Stopwatch watch = new Stopwatch(); Console.WriteLine("開始更新計時,總共數據:{0}條", toUpdates.Count()); watch.Start(); using (var context = new BatchDemoContext()) { EFBatchOperation.For(context, context.TestEntities).UpdateAll(toUpdates, x => x.ColumnsToUpdate(c => c.RandomVlue)); } watch.Stop(); Console.WriteLine("結束更新計時,工用時:{0}ms", watch.ElapsedMilliseconds); using (var context = new BatchDemoContext()) { var count = context.TestEntities.Count(); Console.WriteLine("數據庫總共數據:{0}條", count); var minId = context.TestEntities.Min(c => c.Id); // 隨機取十條數據進行驗證 for (int i = 1; i <= 10; i++) { Random Rand = new Random(); var id = Rand.Next(minId, minId+100000); var testdata = context.TestEntities.FirstOrDefault(c => c.Id == id); Console.WriteLine("更新的數據 id:{0} randomvalue:{1}", testdata.Id, testdata.RandomVlue); } } Console.WriteLine("-----------------華麗的分割線 更新-------------------------"); } 

通過

using (var context = new BatchDemoContext()) { EFBatchOperation.For(context, context.TestEntities).UpdateAll(toUpdates, x => x.ColumnsToUpdate(c => c.RandomVlue)); } 

這條語句,告訴ef,需要更新哪個集合的randomvalue!
然后隨機取10條數據,發現,randomvalue的值全部都是 1000,說明我們批量更新成功!
運行結果如下:

 
圖片.png

查詢更新

什么是查詢更新呢?就是當我們滿足什么條件的時候,對屬性進行什么操作,類似於 update table set col=value where id=1 這樣的 sql 語句,和批量更新有什么區別的?我這邊的批量更新,是指從數據庫中加載的多個實體到內存,在內存中改變了屬性值,在將這一批數據,更新到數據庫,而查詢更新,無需查詢到內存!
這里,我們將id大於等於 minid+10000的數據和 id 小於等於 minid+50000的數據進行改值,修改ran 的值為 500,
代碼如下


        /// <summary> /// 將id >= 1w 小於 5w 的隨機值等於 500 /// </summary> private static void BatchUpdateQuery() { Stopwatch watch = new Stopwatch(); Console.WriteLine("開始查詢更新計時"); watch.Start(); using (var context = new BatchDemoContext()) { var minId = context.TestEntities.Min(c => c.Id); EFBatchOperation.For(context, context.TestEntities) .Where(c=>c.Id>= minId+10000 && c.Id<= minId+50000) .Update(c=>c.RandomVlue,rv=>500); } watch.Stop(); Console.WriteLine("結束查詢更新計時,工用時:{0}ms", watch.ElapsedMilliseconds); using (var context = new BatchDemoContext()) { var count = context.TestEntities.Count(); Console.WriteLine("數據庫總共數據:{0}條", count); var minId = context.TestEntities.Min(c => c.Id); // 隨機取十條數據進行驗證 for (int i = 1; i <= 10; i++) { Random rand = new Random(); var id = rand.Next(minId+10000, minId+ 50000); var testdata = context.TestEntities.FirstOrDefault(c => c.Id == id); Console.WriteLine("查詢更新的數據 id:{0} randomvalue:{1}", testdata.Id, testdata.RandomVlue); } } Console.WriteLine("-----------------華麗的分割線 查詢更新-------------------------"); } 

通過代碼


                var minId = context.TestEntities.Min(c => c.Id); EFBatchOperation.For(context, context.TestEntities) .Where(c=>c.Id>= minId+10000 && c.Id<= minId+50000) .Update(c=>c.RandomVlue,rv=>500); 

進行查詢更新
然后取十條數據進行驗證,具體的運行結果如下:

 
圖片.png

通過結果,我們可以看出,查詢更新也執行成功了


批量刪除

類似的sql語句是 : delete from table where id=1
在ef里,刪除只能是先獲取,在remove,我們如何使用efUtilities進行批量刪除呢?
看代碼


        /// <summary> /// 刪除所有數據 /// </summary> private static void BatchDelete() { Stopwatch watch = new Stopwatch(); Console.WriteLine("開始刪除計時"); watch.Start(); using (var context = new BatchDemoContext()) { EFBatchOperation.For(context, context.TestEntities) .Where(c=>c.Id>=1).Delete(); } watch.Stop(); Console.WriteLine("結束刪除計時,工用時:{0}ms", watch.ElapsedMilliseconds); using (var context = new BatchDemoContext()) { var count = context.TestEntities.Count(); Console.WriteLine("數據庫總共數據:{0}條", count); } Console.WriteLine("-----------------華麗的分割線 刪除-------------------------"); } 

使用代碼


            using (var context = new BatchDemoContext()) { EFBatchOperation.For(context, context.TestEntities) .Where(c=>c.Id>=1).Delete(); } 

進行批量刪除,當我們刪除之后,數據庫數據應該為空,即條目為0,為了驗證是否刪除,我們只需獲取條目即可,運行結果如下

 
圖片.png

實踐證明,批量刪除是成功的


總結

efUtilities地址:https://github.com/MikaelEliasson/EntityFramework.Utilities ,也可以從這里看到文檔

相比 zzz projects ,其提供的功能還算是很全的,批量插入,批量更新,查詢更新和批量刪除,但是, ef utilities 是 免費開源的,免費開源的,免費開源的,重要的事情說五遍,開源的代碼,我們可以學習甚至是改造,打造符合自己的代碼!

相比 ef extend , ef utilities 提供的功能全面,基本上是 extend有的,utilities 有,extend 沒有的,utilities 也有,(只針對批量操作,查詢方面,還是extend 強大)

什么情況下,會用到批量操作?
我遇到的有:導入數據、錄入多條數據、批量計算然后保存每一條數據等等………………

qq:961823316
源代碼:https://git.oschina.net/zhaord/EfBatchDemo.git
轉載注明:http://www.jianshu.com/p/dff3c684a0e4


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM