diff --git a/Directory.Packages.props b/Directory.Packages.props index 1913e336..c005d433 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,7 +8,7 @@ - + diff --git a/src/Dapper.AOT/CommandFactory.cs b/src/Dapper.AOT/CommandFactory.cs index 435c0e39..c2c7a1e9 100644 --- a/src/Dapper.AOT/CommandFactory.cs +++ b/src/Dapper.AOT/CommandFactory.cs @@ -159,6 +159,26 @@ protected static bool TryRecycle(ref DbCommand? storage, DbCommand command) command.Transaction = null; return Interlocked.CompareExchange(ref storage, command, null) is null; } + + +#if NET6_0_OR_GREATER + /// + /// Provides an opportunity to recycle and reuse batch instances + /// + protected static bool TryRecycle(ref DbBatch? storage, DbBatch batch) + { + // detach and recycle + batch.Connection = null; + batch.Transaction = null; + return Interlocked.CompareExchange(ref storage, batch, null) is null; + } + + /// + /// Provides an opportunity to recycle and reuse batch instances + /// + public virtual bool TryRecycle(DbBatch batch) => false; +#endif + } /// @@ -190,8 +210,7 @@ public virtual DbCommand GetCommand(DbConnection connection, string sql, Command internal void Initialize(in UnifiedCommand cmd, string sql, CommandType commandType, T args) { - cmd.CommandText = sql; - cmd.CommandType = commandType != 0 ? commandType : DapperAotExtensions.GetCommandType(sql); + cmd.SetCommand(sql, commandType != 0 ? commandType : DapperAotExtensions.GetCommandType(sql)); AddParameters(in cmd, args); } @@ -216,9 +235,10 @@ public virtual void AddParameters(in UnifiedCommand command, T args) /// public virtual void UpdateParameters(in UnifiedCommand command, T args) { - if (command.Parameters.Count != 0) // try to avoid rogue "dirty" checks + var ps = command.Parameters; + if (ps.Count != 0) // try to avoid rogue "dirty" checks { - command.Parameters.Clear(); + ps.Clear(); } AddParameters(in command, args); } @@ -234,10 +254,9 @@ public virtual void UpdateParameters(in UnifiedCommand command, T args) // try to avoid any dirty detection in the setters if (cmd.CommandText != sql) cmd.CommandText = sql; if (cmd.CommandType != commandType) cmd.CommandType = commandType; - UpdateParameters(new(cmd), args); + UpdateParameters(new UnifiedCommand(cmd), args); } return cmd; - } /// @@ -257,10 +276,52 @@ public virtual void UpdateParameters(in UnifiedCommand command, T args) #pragma warning restore IDE0079 // following will look unnecessary on up-level public virtual bool UseBatch(string sql) => false; +#if NET6_0_OR_GREATER + /// + /// Create a populated batch from a command + /// + public virtual DbBatch GetBatch(DbConnection connection, string sql, CommandType commandType, T args) + { + Debug.Assert(connection.CanCreateBatch); + var batch = connection.CreateBatch(); + batch.Connection = connection; + AddCommands(new(batch), sql, args); + return batch; + } + + /// + /// Provides an opportunity to recycle and reuse batch instances + /// + protected DbBatch? TryReuse(ref DbBatch? storage, T args) + { + var batch = Interlocked.Exchange(ref storage, null); + if (batch is not null) + { + // try to avoid any dirty detection in the setters + UpdateParameters(new UnifiedBatch(batch), args); + } + return batch; + } +#endif + + /// /// Allows the caller to rewrite a composite command into a multi-command batch. /// - public virtual void AddCommands(in UnifiedBatch batch, string sql, T args) => throw new NotSupportedException(); + public virtual void AddCommands(in UnifiedBatch batch, string sql, T args) + { + // implement as basic mode + batch.SetCommand(sql, CommandType.Text); + AddParameters(in batch.Command, args); + } + + /// + /// Allows the caller to update the parameter values of a multi-command batch. + /// + public virtual void UpdateParameters(in UnifiedBatch batch, T args) + { + UpdateParameters(in batch.Command, args); + } /// /// Allows an implementation to process output parameters etc after a multi-command batch has completed. diff --git a/src/Dapper.AOT/CommandT.cs b/src/Dapper.AOT/CommandT.cs index 5490f3df..6da8a203 100644 --- a/src/Dapper.AOT/CommandT.cs +++ b/src/Dapper.AOT/CommandT.cs @@ -90,12 +90,25 @@ private void GetUnifiedBatch(out UnifiedBatch batch, TArgs args) // it will most { if (commandType == CommandType.Text && commandFactory.UseBatch(sql)) { - batch = new UnifiedBatch(connection, transaction); - if (timeout >= 0) +#if NET6_0_OR_GREATER + if (connection.CanCreateBatch) { - batch.TimeoutSeconds = timeout; + var dbBatch = commandFactory.GetBatch(connection, sql, commandType, args); + dbBatch.Connection = connection; + dbBatch.Timeout = timeout; + dbBatch.Transaction = transaction; + batch = new(dbBatch); + } + else +#endif + { + var cmd = connection.CreateCommand(); + cmd.Connection = connection; + cmd.Transaction = transaction; + cmd.CommandTimeout = timeout; + batch = new(cmd); + commandFactory.AddCommands(in batch, sql, args); } - commandFactory.AddCommands(in batch, sql, args); } else { @@ -118,11 +131,7 @@ internal void PostProcessAndRecycleUnified(ref SyncCommandState state, TArgs arg { Debug.Assert(state.Command is null); // all on unified command now commandFactory.PostProcess(in state.UnifiedBatch.Command, args, rowCount); - - if (state.UnifiedBatch.Command.Command is { } cmd && commandFactory.TryRecycle(cmd)) - { - state.UnifiedBatch.Command.ClearSource(); - } + state.UnifiedBatch.Command.TryRecycle(commandFactory); } internal void PostProcessAndRecycle(AsyncQueryState state, TArgs args, int rowCount) diff --git a/src/Dapper.AOT/UnifiedBatch.cs b/src/Dapper.AOT/UnifiedBatch.cs index 6a9be398..64737530 100644 --- a/src/Dapper.AOT/UnifiedBatch.cs +++ b/src/Dapper.AOT/UnifiedBatch.cs @@ -3,7 +3,6 @@ using System.Data; using System.Data.Common; using System.Diagnostics; -using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -33,6 +32,15 @@ internal UnifiedBatch(DbCommand command) Debug.Assert(Command.CommandCount == 1); } +#if NET6_0_OR_GREATER + internal UnifiedBatch(DbBatch batch) + { + Command = new UnifiedCommand(batch); + commandStart = 0; + commandCount = batch.BatchCommands.Count; + Debug.Assert(Command.CommandCount > 0); // could be multiple for batch re-use scenarios + } +#endif internal UnifiedBatch(DbConnection connection, DbTransaction? transaction) { @@ -91,13 +99,21 @@ private int GetCommandIndex(int localIndex) public string CommandText { get => Command.CommandText; + [Obsolete("When possible, " + nameof(SetCommand) + " should be preferred", false)] set => Command.CommandText = value; } + /// + /// Initialize the and + /// + public void SetCommand(string commandText, CommandType commandType = CommandType.Text) + => Command.SetCommand(commandText, commandType); + /// public CommandType CommandType { get => Command.CommandType; + [Obsolete("When possible, " + nameof(SetCommand) + " should be preferred", false)] set => Command.CommandType = value; } diff --git a/src/Dapper.AOT/UnifiedCommand.cs b/src/Dapper.AOT/UnifiedCommand.cs index b6e0748f..c843a41a 100644 --- a/src/Dapper.AOT/UnifiedCommand.cs +++ b/src/Dapper.AOT/UnifiedCommand.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; @@ -104,6 +104,7 @@ public string CommandText #endif _ => "", }; + [Obsolete("When possible, " + nameof(SetCommand) + " should be preferred", false)] set { switch (_source) @@ -148,6 +149,7 @@ public CommandType CommandType #endif _ => CommandType.Text, }; + [Obsolete("When possible, " + nameof(SetCommand) + " should be preferred", false)] set { switch (_source) @@ -203,25 +205,31 @@ public int TimeoutSeconds /// public DbParameter AddParameter() { - // TODO: optimize to avoid double tests - var p = CreateParameter(); - Parameters.Add(p); + var p = CreateParameter(out var parameters); + parameters.Add(p); return p; } /// - public DbParameter CreateParameter() + public DbParameter CreateParameter() => CreateParameter(out _); + + /// + private DbParameter CreateParameter(out DbParameterCollection parameters) { switch (_source) { case DbCommand cmd: + parameters = cmd.Parameters; return cmd.CreateParameter(); case List list: - return list[_index].CreateParameter(); + var activeCmd = list[_index]; + parameters = activeCmd.Parameters; + return activeCmd.CreateParameter(); #if NET6_0_OR_GREATER case DbBatch batch: -#if NET8_0_OR_GREATER // https://github.com/dotnet/runtime/issues/82326 var bc = batch.BatchCommands[_index]; + parameters = bc.Parameters; +#if NET8_0_OR_GREATER // https://github.com/dotnet/runtime/issues/82326 if (bc.CanCreateParameter) return bc.CreateParameter(); #endif // NET8 return (_spareCommandForParameters ?? UnsafeBatchWithCommandForParameters()).CreateParameter(); @@ -246,12 +254,15 @@ internal UnifiedCommand(DbCommand command) internal UnifiedCommand(DbBatch batch) { - // withCommand is typically true for a ready-to-go command; it is false if, for example, we're - // doing a multi-row exec and want to start completely empty _source = batch; _spareCommandForParameters = null; - - batch.BatchCommands.Add(batch.CreateBatchCommand()); + + var bc = batch.BatchCommands; + if (bc.Count == 0) + { + // initialize the first command + bc.Add(batch.CreateBatchCommand()); + } _index = 0; } @@ -264,8 +275,6 @@ private DbCommand UnsafeBatchWithCommandForParameters() } #endif - internal void ClearSource() => Unsafe.AsRef(in _source) = null!; - internal void PostProcess(IEnumerable source, CommandFactory commandFactory) { var snapshot = _index; @@ -461,19 +470,15 @@ internal DbDataReader ExecuteReader(CommandBehavior flags) internal Task ExecuteNonQueryAsync(CancellationToken cancellationToken) { - switch (_source) + return _source switch { - case DbCommand cmd: - return cmd.ExecuteNonQueryAsync(cancellationToken); - case List list: - return ExecuteListAsync(list, cancellationToken); + DbCommand cmd => cmd.ExecuteNonQueryAsync(cancellationToken), + List list => ExecuteListAsync(list, cancellationToken), #if NET6_0_OR_GREATER - case DbBatch batch: - return batch.ExecuteNonQueryAsync(cancellationToken); + DbBatch batch => batch.ExecuteNonQueryAsync(cancellationToken), #endif - default: - return TaskZero; - } + _ => TaskZero, + }; static async Task ExecuteListAsync(List list, CancellationToken cancellationToken) { @@ -486,5 +491,53 @@ static async Task ExecuteListAsync(List list, CancellationToken } } + /// + /// Initialize the and + /// + public void SetCommand(string commandText, CommandType commandType = CommandType.Text) + { + switch (_source) + { + // note we're trying to avoid triggering any unnecessary side-effects and + // cache-invalidations that could be triggered from setters + case DbCommand cmd: + if (cmd.CommandText != commandText) cmd.CommandText = commandText; + if (cmd.CommandType != commandType) cmd.CommandType = commandType; + break; + case List list: + var activeCmd = list[_index]; + if (activeCmd.CommandText != commandText) activeCmd.CommandText = commandText; + if (activeCmd.CommandType != commandType) activeCmd.CommandType = commandType; + break; +#if NET6_0_OR_GREATER + case DbBatch batch: + var bc = batch.BatchCommands[_index]; + if (bc.CommandText != commandText) bc.CommandText = commandText; + if (bc.CommandType != commandType) bc.CommandType = commandType; + break; +#endif + } + } + + internal void TryRecycle(CommandFactory commandFactory) + { + if (_source switch + { + // note we're trying to avoid triggering any unnecessary side-effects and + // cache-invalidations that could be triggered from setters + DbCommand cmd => commandFactory.TryRecycle(cmd), + // note we don't expect to recycle list usage in this way; we're only expecting + // single-arg scenarios +#if NET6_0_OR_GREATER + DbBatch batch => commandFactory.TryRecycle(batch), +#endif + _ => false, + }) + { + // wipe the source - someone else can see it + Unsafe.AsRef(in _source) = null!; + } + } + private static readonly Task TaskZero = Task.FromResult(0); } \ No newline at end of file diff --git a/test/UsageBenchmark/CommandRewriteBenchmarks.cs b/test/UsageBenchmark/CommandRewriteBenchmarks.cs index 0ad86646..5cfc5975 100644 --- a/test/UsageBenchmark/CommandRewriteBenchmarks.cs +++ b/test/UsageBenchmark/CommandRewriteBenchmarks.cs @@ -47,7 +47,7 @@ public class MyArgsType public string? Name3 { get; set; } } - public static readonly MyArgsType Args = new MyArgsType { Name0 = "abc", Name1 = "def", Name2 ="ghi", Name3 = "jkl" }; + public static readonly MyArgsType Args = new() { Name0 = "abc", Name1 = "def", Name2 = "ghi", Name3 = "jkl" }; // for our test, we're going to do 4 operations and try rewriting it as batch public const string BasicSql = """ @@ -61,53 +61,59 @@ public class MyArgsType public int Dapper() => npgsql.Execute(BasicSql, Args); // note these are hand written + [Benchmark] - public int DapperAOT_Simple() => npgsql.Command(BasicSql, handler: BasicCommand.Instance).Execute(Args); + public int DapperAOT() => npgsql.Command(BasicSql, handler: BasicCommand.NonCached).Execute(Args); [Benchmark] - public int DapperAOT_Rewrite() => npgsql.Command(BasicSql, handler: RewriteCommand.Instance).Execute(Args); + public int DapperAOT_Cached() => npgsql.Command(BasicSql, handler: BasicCommand.Cached).Execute(Args); - static DbCommand CreateCommand(DbConnection connection) - { - var cmd = connection.CreateCommand(); - cmd.Connection = connection; - cmd.CommandText = BasicSql; - cmd.CommandType = CommandType.Text; + [Benchmark] + public int DapperAOT_Batch() => npgsql.Command(BasicSql, handler: RewriteCommand.NonCached).Execute(Args); - var ps = cmd.Parameters; + [Benchmark] + public int DapperAOT_BatchCached() => npgsql.Command(BasicSql, handler: RewriteCommand.Cached).Execute(Args); - var p = cmd.CreateParameter(); - p.ParameterName = "Name0"; - p.DbType = DbType.String; - p.Size = -1; - p.Value = Args.Name0; - ps.Add(p); - - p = cmd.CreateParameter(); - p.ParameterName = "Name1"; - p.DbType = DbType.String; - p.Size = -1; - p.Value = Args.Name1; - ps.Add(p); - - p = cmd.CreateParameter(); - p.ParameterName = "Name2"; - p.DbType = DbType.String; - p.Size = -1; - p.Value = Args.Name1; - ps.Add(p); - - p = cmd.CreateParameter(); - p.ParameterName = "Name3"; - p.DbType = DbType.String; - p.Size = -1; - p.Value = Args.Name1; - ps.Add(p); - - return cmd; - } + static NpgsqlCommand CreateCommand(NpgsqlConnection connection) => new() + { + Connection = connection, + CommandText = BasicSql, + CommandType = CommandType.Text, - static void UpdateCommand(DbCommand cmd, DbConnection connection) + Parameters = + { + new NpgsqlParameter + { + ParameterName = "Name0", + DbType = DbType.String, + Size = -1, + Value = Args.Name0 + }, + new NpgsqlParameter + { + ParameterName = "Name1", + DbType = DbType.String, + Size = -1, + Value = Args.Name1 + }, + new NpgsqlParameter + { + ParameterName = "Name2", + DbType = DbType.String, + Size = -1, + Value = Args.Name2 + }, + new NpgsqlParameter + { + ParameterName = "Name3", + DbType = DbType.String, + Size = -1, + Value = Args.Name3 + }, + }, + }; + + static void UpdateCommand(NpgsqlCommand cmd, NpgsqlConnection connection) { cmd.Connection = connection; var ps = cmd.Parameters; @@ -124,7 +130,7 @@ public int AdoNetCommand() return cmd.ExecuteNonQuery(); } - static DbCommand? _spareCommand; + static NpgsqlCommand? _spareCommand; [Benchmark] public int AdoNetCommandCached() @@ -143,65 +149,81 @@ public int AdoNetCommandCached() Interlocked.Exchange(ref _spareCommand, cmd)?.Dispose(); return result; } - private static DbBatch CreateBatch(DbConnection connection) + private static NpgsqlBatch CreateBatch(NpgsqlConnection connection) => new() { - var batch = connection.CreateBatch(); - batch.Connection = connection; - - var cmd = batch.CreateBatchCommand(); - cmd.CommandText = "insert into RewriteCustomers(Name) values($1)"; - cmd.CommandType = CommandType.Text; - var p = new NpgsqlParameter(); - p.DbType = DbType.String; - p.Size = -1; - p.Value = Args.Name0; - cmd.Parameters.Add(p); - batch.BatchCommands.Add(cmd); - - cmd = batch.CreateBatchCommand(); - cmd.CommandText = "insert into RewriteCustomers(Name) values($1)"; - cmd.CommandType = CommandType.Text; - p = new NpgsqlParameter(); - p.DbType = DbType.String; - p.Size = -1; - p.Value = Args.Name1; - cmd.Parameters.Add(p); - batch.BatchCommands.Add(cmd); - - cmd = batch.CreateBatchCommand(); - cmd.CommandText = "insert into RewriteCustomers(Name) values($1)"; - cmd.CommandType = CommandType.Text; - p = new NpgsqlParameter(); - p.DbType = DbType.String; - p.Size = -1; - p.Value = Args.Name2; - cmd.Parameters.Add(p); - batch.BatchCommands.Add(cmd); - - cmd = batch.CreateBatchCommand(); - cmd.CommandText = "insert into RewriteCustomers(Name) values($1)"; - cmd.CommandType = CommandType.Text; - p = new NpgsqlParameter(); - p.DbType = DbType.String; - p.Size = -1; - p.Value = Args.Name3; - cmd.Parameters.Add(p); - batch.BatchCommands.Add(cmd); - - return batch; - } - - static void UpdateBatch(DbBatch batch, DbConnection connection) + Connection = connection, + BatchCommands = + { + new NpgsqlBatchCommand + { + CommandText = "insert into RewriteCustomers(Name) values($1)", + CommandType = CommandType.Text, + Parameters = + { + new NpgsqlParameter + { + DbType = DbType.String, + Size = -1, + Value = Args.Name0, + } + }, + }, + new NpgsqlBatchCommand + { + CommandText = "insert into RewriteCustomers(Name) values($1)", + CommandType = CommandType.Text, + Parameters = + { + new NpgsqlParameter + { + DbType = DbType.String, + Size = -1, + Value = Args.Name1, + } + }, + }, + new NpgsqlBatchCommand + { + CommandText = "insert into RewriteCustomers(Name) values($1)", + CommandType = CommandType.Text, + Parameters = + { + new NpgsqlParameter + { + DbType = DbType.String, + Size = -1, + Value = Args.Name2, + } + }, + }, + new NpgsqlBatchCommand + { + CommandText = "insert into RewriteCustomers(Name) values($1)", + CommandType = CommandType.Text, + Parameters = + { + new NpgsqlParameter + { + DbType = DbType.String, + Size = -1, + Value = Args.Name3, + } + }, + }, + }, + }; + + static void UpdateBatch(NpgsqlBatch batch, NpgsqlConnection connection) { batch.Connection = connection; - var cmds = batch.BatchCommands; - cmds[0].Parameters[0].Value = Args.Name0; - cmds[1].Parameters[0].Value = Args.Name1; - cmds[2].Parameters[0].Value = Args.Name2; - cmds[3].Parameters[0].Value = Args.Name3; + var commands = batch.BatchCommands; + commands[0].Parameters[0].Value = Args.Name0; + commands[1].Parameters[0].Value = Args.Name1; + commands[2].Parameters[0].Value = Args.Name2; + commands[3].Parameters[0].Value = Args.Name3; } - static DbBatch? _spareBatch; + static NpgsqlBatch? _spareBatch; [Benchmark] public int AdoNetBatch() @@ -237,47 +259,63 @@ public async ValueTask DisposeAsync() private sealed class BasicCommand : CommandFactory { - private BasicCommand() { } - public static BasicCommand Instance { get; } = new BasicCommand(); + private readonly bool cached; + private BasicCommand(bool cached) => this.cached = cached; + public static BasicCommand NonCached { get; } = new BasicCommand(false); + public static BasicCommand Cached { get; } = new BasicCommand(true); public override void AddParameters(in UnifiedCommand command, MyArgsType args) { - var ps = command.Parameters; - - var p = command.CreateParameter(); + var p = command.AddParameter(); p.ParameterName = "Name0"; p.DbType = DbType.String; p.Size = -1; p.Value = AsValue(args.Name0); - ps.Add(p); - p = command.CreateParameter(); + p = command.AddParameter(); p.ParameterName = "Name1"; p.DbType = DbType.String; p.Size = -1; p.Value = AsValue(args.Name1); - ps.Add(p); - p = command.CreateParameter(); + p = command.AddParameter(); p.ParameterName = "Name2"; p.DbType = DbType.String; p.Size = -1; p.Value = AsValue(args.Name2); - ps.Add(p); - p = command.CreateParameter(); + p = command.AddParameter(); p.ParameterName = "Name3"; p.DbType = DbType.String; p.Size = -1; p.Value = AsValue(args.Name3); - ps.Add(p); } + + public override void UpdateParameters(in UnifiedBatch command, MyArgsType args) + { + var ps = command.Parameters; + ps[0].Value = AsValue(args.Name0); + ps[1].Value = AsValue(args.Name1); + ps[2].Value = AsValue(args.Name2); + ps[3].Value = AsValue(args.Name3); + } + + private static DbCommand? _spareCommand; + public override bool TryRecycle(DbCommand command) + => cached && TryRecycle(ref _spareCommand, command); + + + public override DbCommand GetCommand(DbConnection connection, string sql, CommandType commandType, MyArgsType args) + => (cached ? TryReuse(ref _spareCommand, sql, commandType, args) : null) + ?? base.GetCommand(connection, sql, commandType, args); } private sealed class RewriteCommand : CommandFactory { - private RewriteCommand() { } - public static RewriteCommand Instance { get; } = new RewriteCommand(); + private readonly bool cached; + private RewriteCommand(bool cached) => this.cached = cached; + public static RewriteCommand Cached { get; } = new RewriteCommand(true); + public static RewriteCommand NonCached { get; } = new RewriteCommand(true); public override void AddParameters(in UnifiedCommand command, MyArgsType args) => throw new NotSupportedException(); // we don't expect to get here (in reality, we would have both versions) @@ -287,34 +325,47 @@ public override void AddParameters(in UnifiedCommand command, MyArgsType args) public override void AddCommands(in UnifiedBatch batch, string sql, MyArgsType args) { // the first command is initialized automatically - batch.CommandText = "insert into RewriteCustomers(Name) values($1)"; - var p = new NpgsqlParameter(); // batch.AddParameter(); // temp hack because CanCreateParameter => false - batch.Parameters.Add(p); + batch.SetCommand("insert into RewriteCustomers(Name) values($1)"); + var p = batch.AddParameter(); p.DbType = DbType.String; p.Size = -1; p.Value = AsValue(args.Name0); // the fact that the NpgSql command is the same is a coincidence of the test batch.AddCommand("insert into RewriteCustomers(Name) values($1)"); - p = new NpgsqlParameter(); // batch.AddParameter(); - batch.Parameters.Add(p); + p = batch.AddParameter(); p.DbType = DbType.String; p.Size = -1; p.Value = AsValue(args.Name1); batch.AddCommand("insert into RewriteCustomers(Name) values($1)"); - p = new NpgsqlParameter(); // batch.AddParameter(); - batch.Parameters.Add(p); + p = batch.AddParameter(); p.DbType = DbType.String; p.Size = -1; p.Value = AsValue(args.Name2); batch.AddCommand("insert into RewriteCustomers(Name) values($1)"); - p = new NpgsqlParameter(); // batch.AddParameter(); - batch.Parameters.Add(p); + p = batch.AddParameter(); p.DbType = DbType.String; p.Size = -1; p.Value = AsValue(args.Name3); } + + public override void UpdateParameters(in UnifiedBatch command, MyArgsType args) + { + command[0][0].Value = AsValue(args.Name0); + command[1][0].Value = AsValue(args.Name1); + command[2][0].Value = AsValue(args.Name2); + command[3][0].Value = AsValue(args.Name3); + } + + private static DbBatch? _spareBatch; + + public override bool TryRecycle(DbBatch batch) + => cached && TryRecycle(ref _spareBatch, batch); + + public override DbBatch GetBatch(DbConnection connection, string sql, CommandType commandType, MyArgsType args) + => (cached ? TryReuse(ref _spareBatch, args) : null) + ?? base.GetBatch(connection, sql, commandType, args); } } \ No newline at end of file diff --git a/test/UsageBenchmark/Program.cs b/test/UsageBenchmark/Program.cs index 0bf4a29b..d5040045 100644 --- a/test/UsageBenchmark/Program.cs +++ b/test/UsageBenchmark/Program.cs @@ -19,13 +19,18 @@ static async Task Main() for (int i = 0; i < 3; i++) { Console.WriteLine(obj.Dapper()); - Console.WriteLine(obj.DapperAOT_Simple()); - Console.WriteLine(obj.DapperAOT_Rewrite()); + + Console.WriteLine(obj.DapperAOT()); + Console.WriteLine(obj.DapperAOT_Cached()); + Console.WriteLine(obj.DapperAOT_Batch()); + Console.WriteLine(obj.DapperAOT_BatchCached()); + Console.WriteLine(obj.AdoNetCommand()); Console.WriteLine(obj.AdoNetBatch()); Console.WriteLine(obj.AdoNetCommandCached()); Console.WriteLine(obj.AdoNetBatchCached()); } + //await using (var obj = new BatchInsertBenchmarks()) //{ // await RunAllInserts(obj, 10, false);