文章目錄
使用 Entity Framework Insert 大量資料
這是參加 黃忠成老師的 Entity Framework 全線開發 課程時他提出讓學員思考的問題:如何使用 Entity Framework Insert 大量資料,我當下立馬想起印象中以前同事也被這個問題困擾過,而我自己本身則沒有遇到相同困擾,剛好透過這個機會來瞭解其中的差異也順便嘗試看看不同解法
立馬動手模擬看看,親身體驗實際情況吧
基本環境及情境說明
- 使用 Entity Framework 6.1.3 新增資料至 SQL Server 2017
- SQL Server 與 EntityFramework Client 都在本機電腦上 ,減少 network io
- 測試新增
10
,100
,1000
,10000
,100000
筆資料至資料庫所需時間 - 測試的 EF 使用方式有:
- 逐筆 Add 並逐筆 SaveChanges
- 逐筆 Add 及一次 SaveChanges
- 批次 Add 及 SaveChanges
- 批次 Add 及 SaveChanges 並 dispose context
測試內容
逐筆 Add 並逐筆發動 SaveChanges
程式碼
TestNestedEF.Models.TestEntities db = new TestEntities(); var count = 100000; Stopwatch sw = new Stopwatch(); sw.Reset(); sw.Start(); for (int i = 0; i < count; i++) { Parent parent = new Parent() { Name = i.ToString() }; parent.Child = new List<Child>() { new Child() { Name = $"{i}_Son" } }; db.Parent.Add(parent); db.SaveChanges(); } sw.Stop(); sw.ElapsedMilliseconds.Dump();
測試筆數
10:
11 ms
100:
187 ms
1000:
4282 ms
10000:
401177 ms
100000:
44827184 ms
逐筆 Add 及一次 SaveChanges
程式碼
TestNestedEF.Models.TestEntities db = new TestEntities(); var count = 10000; Stopwatch sw = new Stopwatch(); sw.Reset(); sw.Start(); for (int i = 0; i < count; i++) { Parent parent = new Parent() { Name = i.ToString() }; parent.Child = new List<Child>() { new Child() { Name = $"{i}_Son" } }; db.Parent.Add(parent); } db.SaveChanges(); sw.Stop(); sw.ElapsedMilliseconds.Dump();
測試筆數
10:
7 ms
100:
79 ms
1000:
2309 ms
10000:
195678 ms
100000:
19916508 ms
批次 Add 及 SaveChanges
程式碼
TestNestedEF.Models.TestEntities db = new TestEntities(); var count = 100000; Stopwatch sw = new Stopwatch(); sw.Reset(); sw.Start(); var batchcount = 100; for (int i = 0; i < (count / batchcount); i++) { for (int j = 0; j < batchcount; j++) { Parent parent = new Parent() { Name = $"{i * batchcount + j}" }; parent.Child = new List<Child>() { new Child() { Name = $"{i * batchcount + j}_Son" } }; db.Parent.Add(parent); } db.SaveChanges(); } sw.Stop(); sw.ElapsedMilliseconds.Dump();
測試筆數
10:
9 ms
100:
77 ms
1000:
1955 ms
10000:
179073 ms
100000:
16710546 ms
批次 Add 及 SaveChanges 並 dispose context
程式碼
var count = 100000; Stopwatch sw = new Stopwatch(); sw.Reset(); sw.Start(); var batchcount = 100; for (int i = 0; i < (count / batchcount); i++) { using (TestNestedEF.Models.TestEntities db = new TestEntities()) { for (int j = 0; j < batchcount; j++) { Parent parent = new Parent() { Name = $"{i * batchcount + j}" }; parent.Child = new List<Child>() { new Child() { Name = $"{i * batchcount + j}_Son" } }; db.Parent.Add(parent); } db.SaveChanges(); } } sw.Stop(); sw.ElapsedMilliseconds.Dump();
測試筆數
10:
12 ms
100:
91 ms
1000:
717 ms
10000:
9401 ms
100000:
81462 ms
測試結果
處理方式\筆數 | 10 | 100 | 1,000 | 10,000 | 10,000 |
---|---|---|---|---|---|
逐筆 Add 並逐筆 SaveChanges | 11 | 187 | 4,282 | 401,177 | 44,827,184 |
逐筆 Add 及一次 SaveChanges | 7 | 79 | 2,309 | 195,678 | 19,916,508 |
批次 Add 及 SaveChanges | 9 | 77 | 1,955 | 179,073 | 16,710,546 |
批次 Add 及 SaveChanges 並 dispose context | 12 | 91 | 717 | 9,401 | 81,462 |
單位
ms
心得
實驗時間超長的,有些情境的執行時間遠超出我的想像 (使用 逐筆 Add 並逐筆發動 SaveChanges
處理 10 萬筆資料時竟花超過 12 個小時),為了避免造成實驗數據誤差,還不能同時執行其他情境,真的很花時間。
原本打算各個情境都測個三、五次取平均值,讓數字相對準確點,無奈某些情境實在太耗時,不得不放棄,實際執行時間的數字部份就勉強當做趨勢比較參考吧
實際測試前,原本就料想到一定是 逐筆 Add 並逐筆 SaveChanges
速度最慢,原以為 逐筆 Add 及一次 SaveChanges
會有巨大的效能優化,想不到還是極度緩慢,批次 Add 及 SaveChanges
已獲得倍數級改善,而 批次 Add 及 SaveChanges 並 dispose context
竟然出現令人咋舌的速度優化,還好有來參加忠成老師的課程,學到 EntityFramework 這等調校手法就值回票價了
假設沒有這次學習經驗,我想我應該會直接透過 dapper 或是 ado.net 來處理,在效能與便利性上的取捨我個人意見跟忠成老師很接近:沒有放諸四海皆適合的技術及架構,依不同情境選擇最合適的技術才是正確做法,不該一昧堅持特定做法而畫地自限。
參考資訊
文章作者 Yowko Tsai
上次更新 2021-11-03
授權合約
本部落格 (Yowko's Notes) 所有的文章內容(包含圖片),任何轉載行為,必須通知並獲本部落格作者 (Yowko Tsai) 的同意始得轉載,且轉載皆須註明出處與作者。
Yowko's Notes 由 Yowko Tsai 製作,以創用CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款 釋出。