Entity Framework는 데이터베이스를 간편하게 사용할 수 있도록 해주는 유용한 도구이지만, 많은 데이터를 한 번에 입력하거나 삭제하는 기능이 제공되지 않는다. Entity Framework의 기능을 이용해 대량의 데이터를 입력하거나 삭제하려면 많은 시간이 소요된다.
Nuget에 공개된 EFCore.BulkExtensions라는 라이브러리를 사용하면 이런 문제를 해결할 수 있다. 하지만 Entity Framework를 통해 대량의 데이터를 처리하는 것보다는 SqlBulkCopy 등을 이용한 SQL문을 작성하여 처리하는 것이 처리 속도가 더 빠르다. EFCore.BulkExtensions는 빠른 반응 속도를 요하지 않을 때 편리하게 사용할 수 있다.
EFCore.BulkExtensions의 GitHub 주소는 다음과 같다. EFCore.BulkExtensions에서 제공되는 기능의 목록과 사용법에 관한 내용이 있다.
https://github.com/borisdj/EFCore.BulkExtensions
EFCore.BulkExtensions 사용하기
using System.ComponentModel.DataAnnotations.Schema;
namespace Sqlite_BulkExtensions_Test {
[Table("Test")]
public class Table_Test {
[Column("ID", TypeName = "NVARCHAR(36)")]
public string Id { get; set; }
[Column("Name", TypeName = "NVARCHAR(20)")]
public string Name { get; set; }
}
}
using Microsoft.EntityFrameworkCore;
namespace Sqlite_BulkExtensions_Test {
public class DbContext_Test : DbContext {
public DbSet<Table_Test> Test { get; set; }
private string _dbPath;
public DbContext_Test(string dbPath) {
SQLitePCL.Batteries.Init();
this._dbPath = dbPath;
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite($"Data Source={this._dbPath}"); }
}
using EFCore.BulkExtensions;
// SQLite DB 파일의 경로
private string DbPath = "Test.db";
// DbContext
private DbContext_Test _db;
private void Form1_Load(object sender, EventArgs e) {
Init();
}
private void Init() {
// DbContext Object 생성
this._db = new DbContext_Test(DbPath);
// 테이블 스키마가 생성되었는 지 확인
this._db.Database.EnsureDeleted();
this._db.Database.EnsureCreated();
}
private void _btBulkInsert_Click(object sender, EventArgs e) {
BulkInsert();
}
private async void BulkInsert() {
Stopwatch sw = new Stopwatch();
// 입력 테스트를 위한 샘플 데이터의 크기 설정
int size = Convert.ToInt32(this._tbSize.Text);
// 입력 테스트를 위한 샘플 데이터 생성
List<Table_Test> list = GenerateSampleData(size);
sw.Start();
// BulkInsert는 sync 방식과 async 방식이 있다. 둘 중 하나를 선택해서 사용한다.
#region ===== sync =====
this._db.BulkInsert(list);
#endregion ===== sync =====
#region ===== async =====
//using (var trans = await this._db.Database.BeginTransactionAsync()) {
// await this._db.BulkInsertAsync(list);
// await trans.CommitAsync();
//}
#endregion ===== async =====
sw.Stop();
this._tbLog.AppendText(String.Format("BulkInsert({0}) : {1}\r\n",
size, sw.ElapsedMilliseconds / 1000));
}
// 주어진 크기만큼 샘플 데이터를 생성한다.
private List<Table_Test> GenerateSampleData(int size) {
List<Table_Test> list = new List<Table_Test>();
for (int i = 0; i < size; i++) {
Table_Test tb = new Table_Test();
tb.Id = Guid.NewGuid().ToString();
tb.Name = i.ToString();
list.Add(tb);
}
return list;
}
private async void _btBatchDelete_Click(object sender, EventArgs e) {
BatchDelete();
}
private void BatchDelete() {
Stopwatch sw = new Stopwatch();
sw.Start();
#region ===== sync =====
this._db.Test.BatchDelete();
#endregion ===== sync =====
#region ===== async =====
//this._db.Test.BatchDeleteAsync();
#endregion ===== async =====
sw.Stop();
this._tbLog.AppendText(String.Format("BatchDelete : {0}\r\n",
sw.ElapsedMilliseconds / 1000));
}
실행할 때, DB 관련 오류가 난다면 Output 폴더에 Test.db 파일이 있는지 확인한다.
Form1.cs 전체 코드
using System.Diagnostics;
using EFCore.BulkExtensions;
namespace Sqlite_BulkExtensions_Test {
public partial class Form1 : Form {
// SQLite DB 파일의 경로
private string DbPath = "Test.db";
// DbContext
private DbContext_Test _db;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
Init();
}
private void Init() {
// DbContext Object 생성
this._db = new DbContext_Test(DbPath);
// 테이블 스키마가 생성되었는 지 확인
this._db.Database.EnsureDeleted();
this._db.Database.EnsureCreated();
}
private void _btBulkInsert_Click(object sender, EventArgs e) {
BulkInsert();
}
private async void BulkInsert() {
Stopwatch sw = new Stopwatch();
// 입력 테스트를 위한 샘플 데이터의 크기 설정
int size = Convert.ToInt32(this._tbSize.Text);
// 입력 테스트를 위한 샘플 데이터 생성
List<Table_Test> list = GenerateSampleData(size);
sw.Start();
// BulkInsert는 sync 방식과 async 방식이 있다. 둘 중 하나를 선택해서 사용한다.
#region ===== sync =====
this._db.BulkInsert(list);
#endregion ===== sync =====
#region ===== async =====
//using (var trans = await this._db.Database.BeginTransactionAsync()) {
// await this._db.BulkInsertAsync(list);
// await trans.CommitAsync();
//}
#endregion ===== async =====
sw.Stop();
this._tbLog.AppendText(String.Format("BulkInsert({0}) : {1}\r\n", size, sw.ElapsedMilliseconds / 1000));
}
// 주어진 크기만큼 샘플 데이터를 생성한다.
private List<Table_Test> GenerateSampleData(int size) {
List<Table_Test> list = new List<Table_Test>();
for (int i = 0; i < size; i++) {
Table_Test tb = new Table_Test();
tb.Id = Guid.NewGuid().ToString();
tb.Name = i.ToString();
list.Add(tb);
}
return list;
}
private async void _btBatchDelete_Click(object sender, EventArgs e) {
BatchDelete();
}
private void BatchDelete() {
Stopwatch sw = new Stopwatch();
sw.Start();
#region ===== sync =====
this._db.Test.BatchDelete();
#endregion ===== sync =====
#region ===== async =====
//this._db.Test.BatchDeleteAsync();
#endregion ===== async =====
sw.Stop();
this._tbLog.AppendText(String.Format("BatchDelete : {0}\r\n", sw.ElapsedMilliseconds / 1000));
}
}
}