Skip to content

Commit 4e54217

Browse files
author
fabien.menager
committed
Add Linq2DB to benchmarks
1 parent 400da75 commit 4e54217

7 files changed

Lines changed: 177 additions & 153 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Legend :
9393
* `RawInsert`: no library, using the native provider API (SqlBulkCopy for SQL Server, BeginBinaryImport for PostgreSQL, raw inserts for SQLite)
9494
* `Z_EntityFramework_Extensions_EFCore`: https://entityframework-extensions.net/bulk-extensions
9595
* `EFCore_BulkExtensions`: https://github.com/borisdj/EFCore.BulkExtensions
96-
* `EFCore_SaveChanges`: EF Core SaveChanges classic method
96+
* `Linq2Db`: https://github.com/linq2db/linq2db
9797

9898
SQL Server results with 500 000 rows :
9999

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
using System.Data;
2+
3+
using Microsoft.Data.SqlClient;
4+
using Microsoft.Data.Sqlite;
5+
using Microsoft.EntityFrameworkCore;
6+
7+
using Npgsql;
8+
9+
namespace PhenX.EntityFrameworkCore.BulkInsert.Benchmark;
10+
11+
public abstract partial class LibComparator
12+
{
13+
private void RawInsertPostgreSql()
14+
{
15+
using var connection = (NpgsqlConnection)DbContext.Database.GetDbConnection();
16+
if (connection.State != ConnectionState.Open)
17+
{
18+
connection.Open();
19+
}
20+
21+
const string copyCommand = $"""
22+
COPY "{nameof(TestEntity)}" (
23+
"Name",
24+
"Price",
25+
"Identifier",
26+
"CreatedAt",
27+
"UpdatedAt",
28+
"StringEnumValue",
29+
"NumericEnumValue"
30+
) FROM STDIN (FORMAT BINARY)
31+
""";
32+
33+
using var writer = connection.BeginBinaryImport(copyCommand);
34+
foreach (var entity in data)
35+
{
36+
writer.StartRow();
37+
writer.Write(entity.Name);
38+
writer.Write(entity.Price);
39+
writer.Write(entity.Identifier);
40+
writer.Write(entity.CreatedAt);
41+
writer.Write(entity.UpdatedAt);
42+
writer.Write(entity.StringEnumValue.ToString());
43+
writer.Write((int)entity.NumericEnumValue);
44+
}
45+
46+
writer.Complete();
47+
}
48+
49+
private void RawInsertSqlite()
50+
{
51+
var connection = (SqliteConnection)DbContext.Database.GetDbConnection();
52+
if (connection.State != ConnectionState.Open)
53+
{
54+
connection.Open();
55+
}
56+
57+
using var transaction = connection.BeginTransaction();
58+
using var command = connection.CreateCommand();
59+
command.CommandText = $"""
60+
INSERT INTO "{nameof(TestEntity)}" (
61+
"Name",
62+
"Price",
63+
"Identifier",
64+
"CreatedAt",
65+
"UpdatedAt",
66+
"StringEnumValue",
67+
"NumericEnumValue"
68+
) VALUES (@Name, @Price, @Identifier, @CreatedAt, @UpdatedAt, @StringEnumValue, @NumericEnumValue)
69+
""";
70+
71+
command.Parameters.Add(new SqliteParameter("@Name", DbType.String));
72+
command.Parameters.Add(new SqliteParameter("@Price", DbType.Decimal));
73+
command.Parameters.Add(new SqliteParameter("@Identifier", DbType.Guid));
74+
command.Parameters.Add(new SqliteParameter("@CreatedAt", DbType.DateTime));
75+
command.Parameters.Add(new SqliteParameter("@UpdatedAt", DbType.DateTime2));
76+
command.Parameters.Add(new SqliteParameter("@StringEnumValue", DbType.String));
77+
command.Parameters.Add(new SqliteParameter("@NumericEnumValue", DbType.Int32));
78+
79+
foreach (var entity in data)
80+
{
81+
command.Parameters["@Name"].Value = entity.Name;
82+
command.Parameters["@Price"].Value = entity.Price;
83+
command.Parameters["@Identifier"].Value = entity.Identifier;
84+
command.Parameters["@CreatedAt"].Value = entity.CreatedAt;
85+
command.Parameters["@UpdatedAt"].Value = entity.UpdatedAt;
86+
command.Parameters["@StringEnumValue"].Value = entity.StringEnumValue.ToString();
87+
command.Parameters["@NumericEnumValue"].Value = (int)entity.NumericEnumValue;
88+
89+
command.ExecuteNonQuery();
90+
}
91+
92+
transaction.Commit();
93+
}
94+
95+
private void RawInsertSqlServer()
96+
{
97+
var connection = (SqlConnection)DbContext.Database.GetDbConnection();
98+
if (connection.State != ConnectionState.Open)
99+
{
100+
connection.Open();
101+
}
102+
103+
using var bulkCopy = new SqlBulkCopy(connection);
104+
105+
bulkCopy.DestinationTableName = nameof(TestEntity);
106+
bulkCopy.BatchSize = 50_000;
107+
bulkCopy.BulkCopyTimeout = 60;
108+
109+
bulkCopy.ColumnMappings.Add("Name", "Name");
110+
bulkCopy.ColumnMappings.Add("Price", "Price");
111+
bulkCopy.ColumnMappings.Add("Identifier", "Identifier");
112+
bulkCopy.ColumnMappings.Add("CreatedAt", "CreatedAt");
113+
bulkCopy.ColumnMappings.Add("UpdatedAt", "UpdatedAt");
114+
bulkCopy.ColumnMappings.Add("StringEnumValue", "StringEnumValue");
115+
bulkCopy.ColumnMappings.Add("NumericEnumValue", "NumericEnumValue");
116+
117+
var dataTable = new DataTable();
118+
dataTable.Columns.Add("Name", typeof(string));
119+
dataTable.Columns.Add("Price", typeof(decimal));
120+
dataTable.Columns.Add("Identifier", typeof(Guid));
121+
dataTable.Columns.Add("CreatedAt", typeof(DateTime));
122+
dataTable.Columns.Add("UpdatedAt", typeof(DateTimeOffset));
123+
dataTable.Columns.Add("StringEnumValue", typeof(string));
124+
dataTable.Columns.Add("NumericEnumValue", typeof(int));
125+
126+
foreach (var entity in data)
127+
{
128+
var row = dataTable.NewRow();
129+
row["Name"] = entity.Name;
130+
row["Price"] = entity.Price;
131+
row["Identifier"] = entity.Identifier;
132+
row["CreatedAt"] = entity.CreatedAt;
133+
row["UpdatedAt"] = entity.UpdatedAt;
134+
row["StringEnumValue"] = entity.StringEnumValue.ToString();
135+
row["NumericEnumValue"] = (int)entity.NumericEnumValue;
136+
dataTable.Rows.Add(row);
137+
138+
if (dataTable.Rows.Count >= 50_000)
139+
{
140+
bulkCopy.WriteToServer(dataTable);
141+
dataTable.Clear();
142+
}
143+
}
144+
145+
if (dataTable.Rows.Count > 0)
146+
{
147+
bulkCopy.WriteToServer(dataTable);
148+
}
149+
}
150+
}
Lines changed: 15 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
1-
using System.Data;
2-
31
using BenchmarkDotNet.Attributes;
42

53
using DotNet.Testcontainers.Containers;
64

75
using EFCore.BulkExtensions;
86

9-
using Microsoft.Data.SqlClient;
10-
using Microsoft.Data.Sqlite;
11-
using Microsoft.EntityFrameworkCore;
12-
13-
using Npgsql;
7+
using LinqToDB.EntityFrameworkCore;
148

159
using PhenX.EntityFrameworkCore.BulkInsert.Extensions;
1610

1711
namespace PhenX.EntityFrameworkCore.BulkInsert.Benchmark;
1812

19-
public abstract class LibComparator
13+
public abstract partial class LibComparator
2014
{
2115
[Params(500_000/*, 1_000_000/*, 10_000_000*/)]
2216
public int N;
@@ -44,6 +38,7 @@ protected LibComparator()
4438
{
4539
DbContainer = GetDbContainer();
4640
DbContainer?.StartAsync().GetAwaiter().GetResult();
41+
LinqToDBForEFTools.Initialize();
4742
}
4843

4944
protected abstract void ConfigureDbContext();
@@ -89,6 +84,12 @@ public void RawInsert()
8984
}
9085
}
9186

87+
[Benchmark]
88+
public async Task Linq2Db()
89+
{
90+
await DbContext.BulkCopyAsync(data);
91+
}
92+
9293
[Benchmark]
9394
public async Task Z_EntityFramework_Extensions_EFCore()
9495
{
@@ -121,147 +122,10 @@ await DbContext.BulkInsertAsync(data, options =>
121122
// });
122123
// }
123124

124-
[Benchmark]
125-
public async Task EFCore_SaveChanges()
126-
{
127-
DbContext.AddRange(data);
128-
await DbContext.SaveChangesAsync();
129-
}
130-
131-
private void RawInsertPostgreSql()
132-
{
133-
using var connection = (NpgsqlConnection)DbContext.Database.GetDbConnection();
134-
if (connection.State != ConnectionState.Open)
135-
{
136-
connection.Open();
137-
}
138-
139-
const string copyCommand = $"""
140-
COPY "{nameof(TestEntity)}" (
141-
"Name",
142-
"Price",
143-
"Identifier",
144-
"CreatedAt",
145-
"UpdatedAt",
146-
"StringEnumValue",
147-
"NumericEnumValue"
148-
) FROM STDIN (FORMAT BINARY)
149-
""";
150-
151-
using var writer = connection.BeginBinaryImport(copyCommand);
152-
foreach (var entity in data)
153-
{
154-
writer.StartRow();
155-
writer.Write(entity.Name);
156-
writer.Write(entity.Price);
157-
writer.Write(entity.Identifier);
158-
writer.Write(entity.CreatedAt);
159-
writer.Write(entity.UpdatedAt);
160-
writer.Write(entity.StringEnumValue.ToString());
161-
writer.Write((int)entity.NumericEnumValue);
162-
}
163-
164-
writer.Complete();
165-
}
166-
167-
private void RawInsertSqlite()
168-
{
169-
var connection = (SqliteConnection)DbContext.Database.GetDbConnection();
170-
if (connection.State != ConnectionState.Open)
171-
{
172-
connection.Open();
173-
}
174-
using var transaction = connection.BeginTransaction();
175-
using var command = connection.CreateCommand();
176-
command.CommandText = $"""
177-
INSERT INTO "{nameof(TestEntity)}" (
178-
"Name",
179-
"Price",
180-
"Identifier",
181-
"CreatedAt",
182-
"UpdatedAt",
183-
"StringEnumValue",
184-
"NumericEnumValue"
185-
) VALUES (@Name, @Price, @Identifier, @CreatedAt, @UpdatedAt, @StringEnumValue, @NumericEnumValue)
186-
""";
187-
188-
command.Parameters.Add(new SqliteParameter("@Name", DbType.String));
189-
command.Parameters.Add(new SqliteParameter("@Price", DbType.Decimal));
190-
command.Parameters.Add(new SqliteParameter("@Identifier", DbType.Guid));
191-
command.Parameters.Add(new SqliteParameter("@CreatedAt", DbType.DateTime));
192-
command.Parameters.Add(new SqliteParameter("@UpdatedAt", DbType.DateTime2));
193-
command.Parameters.Add(new SqliteParameter("@StringEnumValue", DbType.String));
194-
command.Parameters.Add(new SqliteParameter("@NumericEnumValue", DbType.Int32));
195-
196-
foreach (var entity in data)
197-
{
198-
command.Parameters["@Name"].Value = entity.Name;
199-
command.Parameters["@Price"].Value = entity.Price;
200-
command.Parameters["@Identifier"].Value = entity.Identifier;
201-
command.Parameters["@CreatedAt"].Value = entity.CreatedAt;
202-
command.Parameters["@UpdatedAt"].Value = entity.UpdatedAt;
203-
command.Parameters["@StringEnumValue"].Value = entity.StringEnumValue.ToString();
204-
command.Parameters["@NumericEnumValue"].Value = (int)entity.NumericEnumValue;
205-
206-
command.ExecuteNonQuery();
207-
}
208-
209-
transaction.Commit();
210-
}
211-
212-
private void RawInsertSqlServer()
213-
{
214-
var connection = (SqlConnection)DbContext.Database.GetDbConnection();
215-
if (connection.State != ConnectionState.Open)
216-
{
217-
connection.Open();
218-
}
219-
220-
using var bulkCopy = new SqlBulkCopy(connection);
221-
222-
bulkCopy.DestinationTableName = nameof(TestEntity);
223-
bulkCopy.BatchSize = 50_000;
224-
bulkCopy.BulkCopyTimeout = 60;
225-
226-
bulkCopy.ColumnMappings.Add("Name", "Name");
227-
bulkCopy.ColumnMappings.Add("Price", "Price");
228-
bulkCopy.ColumnMappings.Add("Identifier", "Identifier");
229-
bulkCopy.ColumnMappings.Add("CreatedAt", "CreatedAt");
230-
bulkCopy.ColumnMappings.Add("UpdatedAt", "UpdatedAt");
231-
bulkCopy.ColumnMappings.Add("StringEnumValue", "StringEnumValue");
232-
bulkCopy.ColumnMappings.Add("NumericEnumValue", "NumericEnumValue");
233-
234-
var dataTable = new DataTable();
235-
dataTable.Columns.Add("Name", typeof(string));
236-
dataTable.Columns.Add("Price", typeof(decimal));
237-
dataTable.Columns.Add("Identifier", typeof(Guid));
238-
dataTable.Columns.Add("CreatedAt", typeof(DateTime));
239-
dataTable.Columns.Add("UpdatedAt", typeof(DateTimeOffset));
240-
dataTable.Columns.Add("StringEnumValue", typeof(string));
241-
dataTable.Columns.Add("NumericEnumValue", typeof(int));
242-
243-
foreach (var entity in data)
244-
{
245-
var row = dataTable.NewRow();
246-
row["Name"] = entity.Name;
247-
row["Price"] = entity.Price;
248-
row["Identifier"] = entity.Identifier;
249-
row["CreatedAt"] = entity.CreatedAt;
250-
row["UpdatedAt"] = entity.UpdatedAt;
251-
row["StringEnumValue"] = entity.StringEnumValue.ToString();
252-
row["NumericEnumValue"] = (int)entity.NumericEnumValue;
253-
dataTable.Rows.Add(row);
254-
255-
if (dataTable.Rows.Count >= 50_000)
256-
{
257-
bulkCopy.WriteToServer(dataTable);
258-
dataTable.Clear();
259-
}
260-
}
261-
262-
if (dataTable.Rows.Count > 0)
263-
{
264-
bulkCopy.WriteToServer(dataTable);
265-
}
266-
}
125+
// [Benchmark]
126+
// public async Task EFCore_SaveChanges()
127+
// {
128+
// DbContext.AddRange(data);
129+
// await DbContext.SaveChangesAsync();
130+
// }
267131
}

tests/PhenX.EntityFrameworkCore.BulkInsert.Benchmark/LibComparatorPostgreSql.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using DotNet.Testcontainers.Containers;
55

6+
using LinqToDB.EntityFrameworkCore;
7+
68
using Microsoft.EntityFrameworkCore;
79

810
using PhenX.EntityFrameworkCore.BulkInsert.PostgreSql;
@@ -11,7 +13,6 @@
1113

1214
namespace PhenX.EntityFrameworkCore.BulkInsert.Benchmark;
1315

14-
[MinColumn, MaxColumn, BaselineColumn]
1516
[MemoryDiagnoser]
1617
[SimpleJob(RunStrategy.Throughput, launchCount: 1, warmupCount: 0, iterationCount: 5)]
1718
public class LibComparatorPostgreSql : LibComparator
@@ -23,6 +24,7 @@ protected override void ConfigureDbContext()
2324
DbContext = new TestDbContext(p => p
2425
.UseNpgsql(connectionString)
2526
.UseBulkInsertPostgreSql()
27+
.UseLinqToDB()
2628
);
2729
}
2830

0 commit comments

Comments
 (0)