#if ASYNC using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; using System.Threading; using System.Threading.Tasks; #if DNXCORE50 using IDbDataParameter = global::System.Data.Common.DbParameter; using IDataParameter = global::System.Data.Common.DbParameter; using IDbTransaction = global::System.Data.Common.DbTransaction; using IDbConnection = global::System.Data.Common.DbConnection; using IDbCommand = global::System.Data.Common.DbCommand; using IDataReader = global::System.Data.Common.DbDataReader; using IDataRecord = global::System.Data.Common.DbDataReader; using IDataParameterCollection = global::System.Data.Common.DbParameterCollection; using DataException = global::System.InvalidOperationException; using ApplicationException = global::System.InvalidOperationException; #endif namespace Dapper { public static partial class SqlMapper { /// /// Execute a query asynchronously using .NET 4.5 Task. /// /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static async Task> QueryAsync(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { return await QueryAsync(cnn, typeof(DapperRow), new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))).ConfigureAwait(false); } /// /// Execute a query asynchronously using .NET 4.5 Task. /// /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static async Task> QueryAsync(this IDbConnection cnn, CommandDefinition command) { return await QueryAsync(cnn, typeof(DapperRow), command).ConfigureAwait(false); } /// /// Execute a query asynchronously using .NET 4.5 Task. /// public static Task> QueryAsync(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { return QueryAsync(cnn, typeof(T), new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); } /// /// Execute a query asynchronously using .NET 4.5 Task. /// public static Task> QueryAsync(this IDbConnection cnn, Type type, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException("type"); return QueryAsync(cnn, type, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); } /// /// Execute a query asynchronously using .NET 4.5 Task. /// public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command) { return QueryAsync(cnn, typeof(T), command); } /// /// Execute a query asynchronously using .NET 4.5 Task. /// public static Task> QueryAsync(this IDbConnection cnn, Type type, CommandDefinition command) { return QueryAsync(cnn, type, command); } private static async Task> QueryAsync(this IDbConnection cnn, Type effectiveType, CommandDefinition command) { object param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param == null ? null : param.GetType(), null); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader)) { DbDataReader reader = null; try { if (wasClosed) await ((DbConnection)cnn).OpenAsync(cancel).ConfigureAwait(false); reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess, cancel).ConfigureAwait(false); var tuple = info.Deserializer; int hash = GetColumnHash(reader); if (tuple.Func == null || tuple.Hash != hash) { tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); if (command.AddToCache) SetQueryCache(identity, info); } var func = tuple.Func; if (command.Buffered) { List buffer = new List(); while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { buffer.Add((T)func(reader)); } while (await reader.NextResultAsync().ConfigureAwait(false)) { } command.OnCompleted(); return buffer; } else { // can't use ReadAsync / cancellation; but this will have to do wasClosed = false; // don't close if handing back an open reader; rely on the command-behavior var deferred = ExecuteReaderSync(reader, func, command.Parameters); reader = null; // to prevent it being disposed before the caller gets to see it return deferred; } } finally { using (reader) { } // dispose if non-null if (wasClosed) cnn.Close(); } } } /// /// Execute a command asynchronously using .NET 4.5 Task. /// public static Task ExecuteAsync(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { return ExecuteAsync(cnn, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); } /// /// Execute a command asynchronously using .NET 4.5 Task. /// public static Task ExecuteAsync(this IDbConnection cnn, CommandDefinition command) { object param = command.Parameters; IEnumerable multiExec = GetMultiExec(param); if (multiExec != null) { return ExecuteMultiImplAsync(cnn, command, multiExec); } else { return ExecuteImplAsync(cnn, command, param); } } private struct AsyncExecState { public readonly DbCommand Command; public readonly Task Task; public AsyncExecState(DbCommand command, Task task) { this.Command = command; this.Task = task; } } private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandDefinition command, IEnumerable multiExec) { bool isFirst = true; int total = 0; bool wasClosed = cnn.State == ConnectionState.Closed; try { if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false); CacheInfo info = null; string masterSql = null; if ((command.Flags & CommandFlags.Pipelined) != 0) { const int MAX_PENDING = 100; var pending = new Queue(MAX_PENDING); DbCommand cmd = null; try { foreach (var obj in multiExec) { if (isFirst) { isFirst = false; cmd = (DbCommand)command.SetupCommand(cnn, null); masterSql = cmd.CommandText; var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); info = GetCacheInfo(identity, obj, command.AddToCache); } else if(pending.Count >= MAX_PENDING) { var recycled = pending.Dequeue(); total += await recycled.Task.ConfigureAwait(false); cmd = recycled.Command; cmd.CommandText = masterSql; // because we do magic replaces on "in" etc cmd.Parameters.Clear(); // current code is Add-tastic } else { cmd = (DbCommand)command.SetupCommand(cnn, null); } info.ParamReader(cmd, obj); var task = cmd.ExecuteNonQueryAsync(command.CancellationToken); pending.Enqueue(new AsyncExecState(cmd, task)); cmd = null; // note the using in the finally: this avoids a double-dispose } while (pending.Count != 0) { var pair = pending.Dequeue(); using (pair.Command) { } // dispose commands total += await pair.Task.ConfigureAwait(false); } } finally { // this only has interesting work to do if there are failures using (cmd) { } // dispose commands while (pending.Count != 0) { // dispose tasks even in failure using (pending.Dequeue().Command) { } // dispose commands } } } else { using (var cmd = (DbCommand)command.SetupCommand(cnn, null)) { foreach (var obj in multiExec) { if (isFirst) { masterSql = cmd.CommandText; isFirst = false; var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); info = GetCacheInfo(identity, obj, command.AddToCache); } else { cmd.CommandText = masterSql; // because we do magic replaces on "in" etc cmd.Parameters.Clear(); // current code is Add-tastic } info.ParamReader(cmd, obj); total += await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); } } } command.OnCompleted(); } finally { if (wasClosed) cnn.Close(); } return total; } private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object param) { var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param == null ? null : param.GetType(), null); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader)) { try { if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false); var result = await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); command.OnCompleted(); return result; } finally { if (wasClosed) cnn.Close(); } } } /// /// Maps a query to objects /// /// The first type in the recordset /// The second type in the recordset /// The return type /// /// /// /// /// /// /// The field we should split and read the second object from (default: id) /// Number of seconds before command execution timeout /// Is it a stored proc or a batch? /// public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { return MultiMapAsync(cnn, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); } /// /// Maps a query to objects /// /// The first type in the recordset /// The second type in the recordset /// The return type /// /// The field we should split and read the second object from (default: id) /// The command to execute /// /// public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") { return MultiMapAsync(cnn, command, map, splitOn); } /// /// Maps a query to objects /// /// /// /// /// /// /// /// /// /// /// /// The Field we should split and read the second object from (default: id) /// Number of seconds before command execution timeout /// /// public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { return MultiMapAsync(cnn, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); } /// /// Maps a query to objects /// /// /// /// /// /// /// The field we should split and read the second object from (default: id) /// The command to execute /// /// public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") { return MultiMapAsync(cnn, command, map, splitOn); } /// /// Perform a multi mapping query with 4 input parameters /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { return MultiMapAsync(cnn, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); } /// /// Perform a multi mapping query with 4 input parameters /// /// /// /// /// /// /// /// The field we should split and read the second object from (default: id) /// The command to execute /// /// public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") { return MultiMapAsync(cnn, command, map, splitOn); } /// /// Perform a multi mapping query with 5 input parameters /// public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { return MultiMapAsync(cnn, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); } /// /// Perform a multi mapping query with 5 input parameters /// public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") { return MultiMapAsync(cnn, command, map, splitOn); } /// /// Perform a multi mapping query with 6 input parameters /// public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { return MultiMapAsync(cnn, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); } /// /// Perform a multi mapping query with 6 input parameters /// public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") { return MultiMapAsync(cnn, command, map, splitOn); } /// /// Perform a multi mapping query with 7 input parameters /// public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { return MultiMapAsync(cnn, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); } /// /// Perform a multi mapping query with 7 input parameters /// public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") { return MultiMapAsync(cnn, command, map, splitOn); } private static async Task> MultiMapAsync(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn) { object param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; try { if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false); using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader)) using (var reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false)) { if (!command.Buffered) wasClosed = false; // handing back open reader; rely on command-behavior var results = MultiMapImpl(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true); return command.Buffered ? results.ToList() : results; } } finally { if (wasClosed) cnn.Close(); } } /// /// Perform a multi mapping query with arbitrary input parameters /// /// The return type /// /// /// array of types in the recordset /// /// /// /// /// The Field we should split and read the second object from (default: id) /// Number of seconds before command execution timeout /// Is it a stored proc or a batch? /// public static Task> QueryAsync(this IDbConnection cnn, string sql, Type[] types, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)); return MultiMapAsync(cnn, command, types, map, splitOn); } private static async Task> MultiMapAsync(this IDbConnection cnn, CommandDefinition command, Type[] types, Func map, string splitOn) { if (types.Length < 1) { throw new ArgumentException("you must provide at least one type to deserialize"); } object param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, types[0], param == null ? null : param.GetType(), types); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; try { if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false); using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader)) using (var reader = await cmd.ExecuteReaderAsync(command.CancellationToken).ConfigureAwait(false)) { var results = MultiMapImpl(null, default(CommandDefinition), types, map, splitOn, reader, identity, true); return command.Buffered ? results.ToList() : results; } } finally { if (wasClosed) cnn.Close(); } } private static IEnumerable ExecuteReaderSync(IDataReader reader, Func func, object parameters) { using (reader) { while (reader.Read()) { yield return (T)func(reader); } while (reader.NextResult()) { } if (parameters is SqlMapper.IParameterCallbacks) ((SqlMapper.IParameterCallbacks)parameters).OnCompleted(); } } /// /// Execute a command that returns multiple result sets, and access each in turn /// public static Task QueryMultipleAsync( #if CSHARP30 this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType #else this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null #endif ) { var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return QueryMultipleAsync(cnn, command); } partial class GridReader { CancellationToken cancel; internal GridReader(IDbCommand command, IDataReader reader, Identity identity, DynamicParameters dynamicParams, bool addToCache, CancellationToken cancel) : this(command, reader, identity, dynamicParams, addToCache) { this.cancel = cancel; } /// /// Read the next grid of results, returned as a dynamic object /// /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task> ReadAsync(bool buffered = true) { return ReadAsyncImpl(typeof(DapperRow), buffered); } /// /// Read the next grid of results /// public Task> ReadAsync(Type type, bool buffered = true) { if (type == null) throw new ArgumentNullException("type"); return ReadAsyncImpl(type, buffered); } /// /// Read the next grid of results /// public Task> ReadAsync(bool buffered = true) { return ReadAsyncImpl(typeof(T), buffered); } private async Task NextResultAsync() { if (await ((DbDataReader)reader).NextResultAsync(cancel).ConfigureAwait(false)) { readCount++; gridIndex++; consumed = false; } else { // happy path; close the reader cleanly - no // need for "Cancel" etc reader.Dispose(); reader = null; if (callbacks != null) callbacks.OnCompleted(); Dispose(); } } private Task> ReadAsyncImpl(Type type, bool buffered) { if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); if (consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); var typedIdentity = identity.ForGrid(type, gridIndex); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); if (deserializer.Func == null || deserializer.Hash != hash) { deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; } consumed = true; if (buffered && this.reader is DbDataReader) { return ReadBufferedAsync(gridIndex, deserializer.Func, typedIdentity); } else { var result = ReadDeferred(gridIndex, deserializer.Func, typedIdentity); if (buffered) result = result.ToList(); // for the "not a DbDataReader" scenario return Task.FromResult(result); } } private async Task> ReadBufferedAsync(int index, Func deserializer, Identity typedIdentity) { //try //{ var reader = (DbDataReader)this.reader; List buffer = new List(); while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false)) { buffer.Add((T)deserializer(reader)); } if (index == gridIndex) // need to do this outside of the finally pre-C#6 { await NextResultAsync().ConfigureAwait(false); } return buffer; //} //finally // finally so that First etc progresses things even when multiple rows //{ // if (index == gridIndex) // { // await NextResultAsync().ConfigureAwait(false); // } //} } } /// /// Execute a command that returns multiple result sets, and access each in turn /// public static async Task QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command) { object param = command.Parameters; Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); DbCommand cmd = null; IDataReader reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false); cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader); reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false); var result = new GridReader(cmd, reader, identity, command.Parameters as DynamicParameters, command.AddToCache, command.CancellationToken); wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader // with the CloseConnection flag, so the reader will deal with the connection; we // still need something in the "finally" to ensure that broken SQL still results // in the connection closing itself return result; } catch { if (reader != null) { if (!reader.IsClosed) try { cmd.Cancel(); } catch { /* don't spoil the existing exception */ } reader.Dispose(); } if (cmd != null) cmd.Dispose(); if (wasClosed) cnn.Close(); throw; } } /// /// Execute parameterized SQL and return an /// /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a /// or . /// /// /// /// /// /// public static Task ExecuteReaderAsync( #if CSHARP30 this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType #else this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null #endif ) { var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteReaderImplAsync(cnn, command); } /// /// Execute parameterized SQL and return an /// /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a /// or . /// public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command) { return ExecuteReaderImplAsync(cnn, command); } private static async Task ExecuteReaderImplAsync(IDbConnection cnn, CommandDefinition command) { Action paramReader = GetParameterReader(cnn, ref command); DbCommand cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { cmd = (DbCommand)command.SetupCommand(cnn, paramReader); if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false); var reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false); wasClosed = false; return reader; } finally { if (wasClosed) cnn.Close(); if (cmd != null) cmd.Dispose(); } } /// /// Execute parameterized SQL that selects a single value /// /// The first cell selected public static Task ExecuteScalarAsync( #if CSHARP30 this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType #else this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null #endif ) { var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteScalarImplAsync(cnn, command); } /// /// Execute parameterized SQL that selects a single value /// /// The first cell selected public static Task ExecuteScalarAsync( #if CSHARP30 this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType #else this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null #endif ) { var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteScalarImplAsync(cnn, command); } /// /// Execute parameterized SQL that selects a single value /// /// The first cell selected public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) { return ExecuteScalarImplAsync(cnn, command); } /// /// Execute parameterized SQL that selects a single value /// /// The first cell selected public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) { return ExecuteScalarImplAsync(cnn, command); } private async static Task ExecuteScalarImplAsync(IDbConnection cnn, CommandDefinition command) { Action paramReader = null; object param = command.Parameters; if (param != null) { var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader; } DbCommand cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed; object result; try { cmd = (DbCommand)command.SetupCommand(cnn, paramReader); if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false); result = await cmd.ExecuteScalarAsync(command.CancellationToken).ConfigureAwait(false); command.OnCompleted(); } finally { if (wasClosed) cnn.Close(); if (cmd != null) cmd.Dispose(); } return Parse(result); } } } #endif