EntityFramework Core 5.0 VS SQLBulkCopy

LongtengGensSupreme發表於2020-12-08

EntityFramework Core 5.0 VS SQLBulkCopy

EF Core 5.0伴隨著.NET 5.0釋出已有一段時日,本節我們來預估當大批量新增資料時,大概是多少區間我們應該考慮SQLBulkCopy而不是EF Core

SQLBulkCopy早出現於.NET Framework 2.0,將資料批量寫入利用此類毫無疑問最佳,雖其來源任意,但此類僅適用於SQL Server,每個關聯式資料庫都有其批量處理驅動,這裡我們僅僅只討論SQL Server

效能差異預估批量資料大小

 

首先給出我們需要用到的測試模型

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFCOREDB
{
    public class Test
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public DateTime CreateDateTime { get; set; }
    }
}

接下來我們則需要模擬資料,為偽造實際生產資料,這裡我們介紹一個包Bogus,此包專用來偽造資料,一直在更新從未間斷,版本也達到32,如下:

此包中有針對使用者類的模擬,具體使用這裡就不詳細展開,我們構造一個方法來偽造指定數量的使用者資料,如下:

using Bogus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFCOREDB
{
    /// <summary>
    /// 產生模擬資料
    /// </summary>
    public class GenerateMockData
    {
        /// <summary>
        /// 產生指定資料量的資料
        /// </summary>
        /// <param name="count"></param>
        /// <returns></returns>
        public static IEnumerable<Test> GetTests(int count)
        {
            var profileGenerator = new Faker<Test>()
                 .RuleFor(p => p.Title, v => v.Person.FirstName)
                 .RuleFor(p => p.Content, v => v.Person.LastName)
                 .RuleFor(p => p.CreateDateTime, v => v.Person.DateOfBirth);
            return profileGenerator.Generate(count);
        }        
    }
}

有了批量偽造資料,接下來我們再利用上下文去新增資料,然後分別列印偽造資料和新增成功所耗費時間,如下:

        /// <summary>
        /// EntityFramework Core 5.0 上下文去新增資料,然後分別列印偽造資料和新增成功所耗費時間
        /// </summary>
        public static async void TestGenerateInsertAndInsertWithEFCoreData()
        {
            Console.WriteLine("產生模擬資料");
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            var data = GenerateMockData.GetTests(10000);
            //產生10條模擬資料
            //產生模擬資料, 花費時間:0.2591446 秒
            //產生模擬資料, 花費時間:0.2591446 秒,插入資料的資料數量: 10,插入資料花費時間: 0.2411458

            //產生10000條模擬資料
            //產生模擬資料,花費時間:0.9874856 秒
            //產生模擬資料,花費時間:0.9874856 秒,插入資料的資料數量: 10000,插入資料花費時間: 1.4542355

            var TotalSeconds = stopwatch.Elapsed.TotalSeconds;
            Console.WriteLine($"產生模擬資料,花費時間:{TotalSeconds} 秒");
            //上下文去新增資料,然後分別列印偽造資料和新增成功所耗費時間
            var datetime1 = DateTime.Now;
            using var EFCoreVSSqlBulkCopyContext = new EFCoreVSSqlBulkCopyContext() { CreateDateTime = datetime1 };
            EFCoreVSSqlBulkCopyContext.Database.EnsureCreated();
            stopwatch.Restart();
            EFCoreVSSqlBulkCopyContext.Tests.AddRange(data);
            await EFCoreVSSqlBulkCopyContext.AddRangeAsync(data);
            var result = await EFCoreVSSqlBulkCopyContext.SaveChangesAsync();

            Console.WriteLine($"產生模擬資料,花費時間:{TotalSeconds} 秒,插入資料的資料數量:{result},插入資料花費時間:{stopwatch.Elapsed.TotalSeconds}");
        }

新增100條資料太小,這裡我們直接從批量10000條資料開始測試,此時我們將看到所儲存資料和實際資料完全一樣

 

通過SQL Server Profiler工具監控得到如下一堆語句如下

通過執行多次,當然也和筆記本配置有關(i7,6核,記憶體8G),但還是可以預估批量新增1000條大概耗時為毫秒級,如下:

 

 

 

當然你也可所以使用十萬條或者百萬條資料,由於本電腦的配置,因此沒有測試使用十萬條或者百萬條資料,有興趣的可以再往上增長資料自己測試一下,

接下來我們來看看利用SqlBulkCopy看看效能如何

        /// <summary>
        /// SQLBulkCopy 上下文去新增資料,然後分別列印偽造資料和新增成功所耗費時間
        /// </summary>
        public static async void TestGenerateAndInsertWithSqlBulkCopyData()
        {
            Console.WriteLine("產生模擬資料");
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            var data = GenerateMockData.GetTests(10000);

            //產生10000條模擬資料
            //產生模擬資料,花費時間:1.0126009 秒
            //產生模擬資料, 花費時間:1.0126009 秒,插入資料的資料數量: 10000,插入資料花費時間: 0.3210853

            var TotalSeconds = stopwatch.Elapsed.TotalSeconds;
            Console.WriteLine($"產生模擬資料,花費時間:{TotalSeconds} 秒");
            //上下文去新增資料,然後分別列印偽造資料和新增成功所耗費時間
            var datetime1 = DateTime.Now;
            using (var EFCoreVSSqlBulkCopyContext = new EFCoreVSSqlBulkCopyContext() { CreateDateTime = datetime1 })
            {
                EFCoreVSSqlBulkCopyContext.Database.EnsureCreated();
            }
            stopwatch.Restart();

            var dt = new DataTable();
            dt.Columns.Add("Id");
            dt.Columns.Add("Title");
            dt.Columns.Add("Content");
            dt.Columns.Add("CreateDateTime");
            foreach (var item in data)
            {
                dt.Rows.Add(item.Id, item.Title, item.Content, item.CreateDateTime);
            }
            //注意DestinationTableName 必須是全路徑即 資料庫名稱.架構名稱.表名稱
            using var sqlbulkcopy = new SqlBulkCopy("server=127.0.0.1;database=EFCoreVSSqlBulkCopyContext;user=sa;password=sa123;") { DestinationTableName = "EFCoreVSSqlBulkCopyContext.dbo.[20201208]" };
            await sqlbulkcopy.WriteToServerAsync(dt);

            Console.WriteLine($"產生模擬資料,花費時間:{TotalSeconds} 秒,插入資料的資料數量:{10000},插入資料花費時間:{stopwatch.Elapsed.TotalSeconds}");
        }

因如上利用EF Core新增時間在毫秒級,那麼我們則直接從新增1萬條開始測試,如下我們可看到此時與EF Core新增1萬條資料差異,耗時遠遠小於1.6秒

還可以繼續增長資料,本文不在展示測試結果,測試10萬條資料很顯然EF Core耗時結果將為SqlBulkCopy的指數倍(大致14倍,若資料為100萬,想想二者其效能差異)

若繼續通過SQL Server Profiler監控工具檢視SQL語句,很顯然SQL語句會很簡短,據我所知,SqlBulkCopy是直接將資料通過流形式傳輸到資料庫伺服器,然後一次性插入到目標表中,所以效能是槓槓的。 

利用SqlBulkCopy和EF Core 5.0,當然理論上不論是EF Core更新到其他任何版本,其效能與SqlBulkCopy不可同日而語,本文我們只是稍加探討下資料量達到多少時不得不考慮其他手段來處理,而不是利用EF Core新增資料 

EF Core和SqlBulkCopy效能差異比較,毫無疑問SqlBulkCopy為最佳選手,當新增資料量達到1萬+時,若需考慮效能,可採用SqlBulkCopy或其他手段處理資料而不再是EF Core,二者效能差異將呈指數增長

相關文章