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 @@ + +