diff --git a/Directory.Packages.props b/Directory.Packages.props
index 70b50470..e2940d07 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,7 +4,7 @@
-
+
diff --git a/test/UsageBenchmark/BatchInsertBenchmarks.cs b/test/UsageBenchmark/BatchInsertBenchmarks.cs
index 43106b69..a0db9166 100644
--- a/test/UsageBenchmark/BatchInsertBenchmarks.cs
+++ b/test/UsageBenchmark/BatchInsertBenchmarks.cs
@@ -1,28 +1,36 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using Microsoft.Data.SqlClient;
+using Npgsql;
using System;
using System.Data;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
+using Testcontainers.PostgreSql;
using UsageBenchmark;
namespace Dapper;
[ShortRunJob, MemoryDiagnoser, GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory), CategoriesColumn]
-public class BatchInsertBenchmarks : IDisposable
+public class BatchInsertBenchmarks : IAsyncDisposable
{
- private readonly SqlConnection connection = new(Program.ConnectionString);
+ private readonly SqlConnection sqlClient = new(Program.ConnectionString);
+ private readonly NpgsqlConnection npgsql = new();
+
+ private readonly PostgreSqlContainer _postgresContainer = new PostgreSqlBuilder()
+ .WithImage("postgres:15-alpine")
+ .Build();
+
private Customer[] customers = [];
public void DebugState()
{
// waiting on binaries from https://github.com/dotnet/SqlClient/pull/1825
- Console.WriteLine($"{nameof(connection.CanCreateBatch)}: {connection.CanCreateBatch}");
- if (connection.CanCreateBatch)
+ Console.WriteLine($"{nameof(sqlClient.CanCreateBatch)}: {sqlClient.CanCreateBatch}");
+ if (sqlClient.CanCreateBatch)
{
- using var batch = connection.CreateBatch();
+ using var batch = sqlClient.CreateBatch();
var cmd = batch.CreateBatchCommand();
Console.WriteLine($"{nameof(cmd.CanCreateParameter)}: {cmd.CanCreateParameter}");
}
@@ -30,15 +38,25 @@ public void DebugState()
public BatchInsertBenchmarks()
{
- try { connection.Execute("drop table BenchmarkCustomers;"); } catch { }
- connection.Execute("create table BenchmarkCustomers(Id int not null identity(1,1), Name nvarchar(200) not null);");
+ try { sqlClient.Execute("drop table BenchmarkCustomers;"); } catch { }
+ sqlClient.Execute("create table BenchmarkCustomers(Id int not null identity(1,1), Name nvarchar(200) not null);");
+
+ _postgresContainer.StartAsync().GetAwaiter().GetResult(); // yes, I know
+ npgsql.ConnectionString = _postgresContainer.GetConnectionString();
+
+ npgsql.Execute("""
+ CREATE TABLE IF NOT EXISTS BenchmarkCustomers(
+ Id integer GENERATED ALWAYS AS IDENTITY,
+ Name varchar(40) NOT NULL
+ );
+ """);
}
[Params(0, 1, 10, 100, 1000)]
public int Count { get; set; }
[Params(false, true)]
- public bool IsOpen { get; set; }
+ public bool IsOpen { get; set; } = true;
[GlobalSetup]
public void Setup()
@@ -51,42 +69,45 @@ public void Setup()
customers = arr;
if (IsOpen)
{
- connection.Open();
+ sqlClient.Open();
+ npgsql.Open();
}
else
{
- connection.Close();
+ sqlClient.Close();
+ npgsql.Close();
}
- connection.Execute("truncate table BenchmarkCustomers;");
+ sqlClient.Execute("truncate table BenchmarkCustomers;");
+ npgsql.Execute("TRUNCATE BenchmarkCustomers RESTART IDENTITY;");
}
- [Benchmark, BenchmarkCategory("Sync")]
- public int Dapper() => connection.Execute("insert BenchmarkCustomers (Name) values (@name)", customers);
+ [Benchmark, BenchmarkCategory("Sync", "SqlClient")]
+ public int Dapper() => sqlClient.Execute("insert BenchmarkCustomers (Name) values (@name)", customers);
- [Benchmark, BenchmarkCategory("Sync"), DapperAot, CacheCommand]
- public int DapperAot() => connection.Execute("insert BenchmarkCustomers (Name) values (@name)", customers);
+ [Benchmark, BenchmarkCategory("Sync", "SqlClient"), DapperAot, CacheCommand]
+ public int DapperAot() => sqlClient.Execute("insert BenchmarkCustomers (Name) values (@name)", customers);
- [Benchmark, BenchmarkCategory("Sync")]
- public int DapperAotManual() => connection.Command("insert BenchmarkCustomers (Name) values (@name)",
+ [Benchmark, BenchmarkCategory("Sync", "SqlClient")]
+ public int DapperAotManual() => sqlClient.Command("insert BenchmarkCustomers (Name) values (@name)",
handler: CustomHandler.Unprepared).Execute(customers);
- [Benchmark, BenchmarkCategory("Sync")]
- public int DapperAot_PreparedManual() => connection.Command("insert BenchmarkCustomers (Name) values (@name)",
+ [Benchmark, BenchmarkCategory("Sync", "SqlClient")]
+ public int DapperAot_PreparedManual() => sqlClient.Command("insert BenchmarkCustomers (Name) values (@name)",
handler: CustomHandler.Prepared).Execute(customers);
- [Benchmark(Baseline = true), BenchmarkCategory("Sync")]
+ [Benchmark(Baseline = true), BenchmarkCategory("Sync", "SqlClient")]
public int Manual()
{
if (customers.Length == 0) return 0;
bool close = false;
try
{
- if (connection.State != ConnectionState.Open)
+ if (sqlClient.State != ConnectionState.Open)
{
- connection.Open();
+ sqlClient.Open();
close = true;
}
- var cmd = GetManualCommand(connection, out var name);
+ var cmd = GetManualCommand(sqlClient, out var name);
if (customers.Length != 1) cmd.Prepare();
int total = 0;
@@ -100,10 +121,16 @@ public int Manual()
}
finally
{
- if (close) connection.Close();
+ if (close) sqlClient.Close();
}
}
+ [Benchmark, BenchmarkCategory("Sync", "Npgsql"), DapperAot, CacheCommand, BatchSize(0)]
+ public int NpgsqlDapperAotNoBatch() => npgsql.Execute("insert into BenchmarkCustomers (Name) values (@name)", customers);
+
+ [Benchmark, BenchmarkCategory("Sync", "Npgsql"), DapperAot, CacheCommand, BatchSize(-1)]
+ public int NpgsqlDapperAotFullBatch() => npgsql.Execute("insert into BenchmarkCustomers (Name) values (@name)", customers);
+
private static DbCommand? _spare;
private static DbCommand GetManualCommand(DbConnection connection, out DbParameter name)
{
@@ -131,30 +158,30 @@ private static DbCommand GetManualCommand(DbConnection connection, out DbParamet
private static bool RecycleManual(DbCommand cmd)
=> Interlocked.CompareExchange(ref _spare, cmd, null) is null;
- [Benchmark, BenchmarkCategory("Async")]
- public Task DapperAsync() => connection.ExecuteAsync("insert BenchmarkCustomers (Name) values (@name)", customers);
+ [Benchmark, BenchmarkCategory("Async", "SqlClient")]
+ public Task DapperAsync() => sqlClient.ExecuteAsync("insert BenchmarkCustomers (Name) values (@name)", customers);
- [Benchmark, BenchmarkCategory("Async")]
- public Task DapperAotAsync() => connection.Command("insert BenchmarkCustomers (Name) values (@name)",
+ [Benchmark, BenchmarkCategory("Async", "SqlClient")]
+ public Task DapperAotAsync() => sqlClient.Command("insert BenchmarkCustomers (Name) values (@name)",
handler: CustomHandler.Unprepared).ExecuteAsync(customers);
- [Benchmark, BenchmarkCategory("Async")]
- public Task DapperAot_PreparedAsync() => connection.Command("insert BenchmarkCustomers (Name) values (@name)",
+ [Benchmark, BenchmarkCategory("Async", "SqlClient")]
+ public Task DapperAot_PreparedAsync() => sqlClient.Command("insert BenchmarkCustomers (Name) values (@name)",
handler: CustomHandler.Prepared).ExecuteAsync(customers);
- [Benchmark(Baseline = true), BenchmarkCategory("Async")]
+ [Benchmark(Baseline = true), BenchmarkCategory("Async", "SqlClient")]
public async Task ManualAsync()
{
if (customers.Length == 0) return 0;
bool close = false;
try
{
- if (connection.State != ConnectionState.Open)
+ if (sqlClient.State != ConnectionState.Open)
{
- await connection.OpenAsync();
+ await sqlClient.OpenAsync();
close = true;
}
- var cmd = GetManualCommand(connection, out var name);
+ var cmd = GetManualCommand(sqlClient, out var name);
if (customers.Length != 1)
{
await cmd.PrepareAsync();
@@ -177,12 +204,12 @@ public async Task ManualAsync()
{
if (close)
{
- await connection.CloseAsync();
+ await sqlClient.CloseAsync();
}
}
}
- [Benchmark, BenchmarkCategory("Sync")]
+ [Benchmark, BenchmarkCategory("Sync", "SqlClient")]
public int EntityFramework()
{
using var ctx = new MyContext();
@@ -192,7 +219,7 @@ public int EntityFramework()
return result;
}
- [Benchmark, BenchmarkCategory("Async")]
+ [Benchmark, BenchmarkCategory("Async", "SqlClient")]
public async Task EntityFrameworkAsync()
{
using var ctx = new MyContext();
@@ -202,18 +229,18 @@ public async Task EntityFrameworkAsync()
return result;
}
- [Benchmark, BenchmarkCategory("Sync")]
+ [Benchmark, BenchmarkCategory("Sync", "SqlClient")]
public int SqlBulkCopyFastMember()
{
bool close = false;
try
{
- if (connection.State != ConnectionState.Open)
+ if (sqlClient.State != ConnectionState.Open)
{
- connection.Open();
+ sqlClient.Open();
close = true;
}
- using var table = new SqlBulkCopy(connection)
+ using var table = new SqlBulkCopy(sqlClient)
{
DestinationTableName = "BenchmarkCustomers",
ColumnMappings =
@@ -227,22 +254,22 @@ public int SqlBulkCopyFastMember()
}
finally
{
- if (close) connection.Close();
+ if (close) sqlClient.Close();
}
}
- [Benchmark, BenchmarkCategory("Sync")]
+ [Benchmark, BenchmarkCategory("Sync", "SqlClient")]
public int SqlBulkCopyDapper()
{
bool close = false;
try
{
- if (connection.State != ConnectionState.Open)
+ if (sqlClient.State != ConnectionState.Open)
{
- connection.Open();
+ sqlClient.Open();
close = true;
}
- using var table = new SqlBulkCopy(connection)
+ using var table = new SqlBulkCopy(sqlClient)
{
DestinationTableName = "BenchmarkCustomers",
ColumnMappings =
@@ -256,22 +283,22 @@ public int SqlBulkCopyDapper()
}
finally
{
- if (close) connection.Close();
+ if (close) sqlClient.Close();
}
}
- [Benchmark, BenchmarkCategory("Async")]
+ [Benchmark, BenchmarkCategory("Async", "SqlClient")]
public async Task SqlBulkCopyFastMemberAsync()
{
bool close = false;
try
{
- if (connection.State != ConnectionState.Open)
+ if (sqlClient.State != ConnectionState.Open)
{
- await connection.OpenAsync();
+ await sqlClient.OpenAsync();
close = true;
}
- using var table = new SqlBulkCopy(connection)
+ using var table = new SqlBulkCopy(sqlClient)
{
DestinationTableName = "BenchmarkCustomers",
ColumnMappings =
@@ -285,13 +312,15 @@ public async Task SqlBulkCopyFastMemberAsync()
}
finally
{
- if (close) await connection.CloseAsync();
+ if (close) await sqlClient.CloseAsync();
}
}
- public void Dispose()
+ public async ValueTask DisposeAsync()
{
- connection.Dispose();
+ await sqlClient.DisposeAsync();
+ await npgsql.DisposeAsync();
+ await _postgresContainer.DisposeAsync();
GC.SuppressFinalize(this);
}
diff --git a/test/UsageBenchmark/Program.cs b/test/UsageBenchmark/Program.cs
index 5a580520..2ba424f7 100644
--- a/test/UsageBenchmark/Program.cs
+++ b/test/UsageBenchmark/Program.cs
@@ -13,7 +13,7 @@ static class Program
#else
static async Task Main()
{
- using (var obj = new BatchInsertBenchmarks())
+ await using (var obj = new BatchInsertBenchmarks())
{
await RunAllInserts(obj, 10, false);
await RunAllInserts(obj, 10, true);
@@ -54,6 +54,9 @@ static async Task RunAllInserts(BatchInsertBenchmarks obj, int count, bool isOpe
Console.WriteLine(obj.SqlBulkCopyFastMember());
Console.WriteLine(obj.SqlBulkCopyDapper());
Console.WriteLine(await obj.SqlBulkCopyFastMemberAsync());
+
+ Console.WriteLine(obj.NpgsqlDapperAotNoBatch());
+ Console.WriteLine(obj.NpgsqlDapperAotFullBatch());
}
static async Task RunAllQueries(QueryBenchmarks obj, int count, bool isOpen)
diff --git a/test/UsageBenchmark/UsageBenchmark.csproj b/test/UsageBenchmark/UsageBenchmark.csproj
index 241782f8..b3eff8e1 100644
--- a/test/UsageBenchmark/UsageBenchmark.csproj
+++ b/test/UsageBenchmark/UsageBenchmark.csproj
@@ -18,5 +18,7 @@
+
+