/Dapper/SqlMapper.Async.cs

http://github.com/SamSaffron/dapper-dot-net · C# · 1233 lines · 543 code · 80 blank · 610 comment · 88 complexity · 1a6a5f7a54b7ebf4862c0b2d84fa5f01 MD5 · raw file

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Data;
  5. using System.Data.Common;
  6. using System.Globalization;
  7. using System.Linq;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. namespace Dapper
  11. {
  12. public static partial class SqlMapper
  13. {
  14. /// <summary>
  15. /// Execute a query asynchronously using Task.
  16. /// </summary>
  17. /// <param name="cnn">The connection to query on.</param>
  18. /// <param name="sql">The SQL to execute for the query.</param>
  19. /// <param name="param">The parameters to pass, if any.</param>
  20. /// <param name="transaction">The transaction to use, if any.</param>
  21. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  22. /// <param name="commandType">The type of command to execute.</param>
  23. /// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
  24. public static Task<IEnumerable<dynamic>> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  25. QueryAsync<dynamic>(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
  26. /// <summary>
  27. /// Execute a query asynchronously using Task.
  28. /// </summary>
  29. /// <param name="cnn">The connection to query on.</param>
  30. /// <param name="command">The command used to query on this connection.</param>
  31. /// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
  32. public static Task<IEnumerable<dynamic>> QueryAsync(this IDbConnection cnn, CommandDefinition command) =>
  33. QueryAsync<dynamic>(cnn, typeof(DapperRow), command);
  34. /// <summary>
  35. /// Execute a single-row query asynchronously using Task.
  36. /// </summary>
  37. /// <param name="cnn">The connection to query on.</param>
  38. /// <param name="command">The command used to query on this connection.</param>
  39. /// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
  40. public static Task<dynamic> QueryFirstAsync(this IDbConnection cnn, CommandDefinition command) =>
  41. QueryRowAsync<dynamic>(cnn, Row.First, typeof(DapperRow), command);
  42. /// <summary>
  43. /// Execute a single-row query asynchronously using Task.
  44. /// </summary>
  45. /// <param name="cnn">The connection to query on.</param>
  46. /// <param name="command">The command used to query on this connection.</param>
  47. /// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
  48. public static Task<dynamic> QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) =>
  49. QueryRowAsync<dynamic>(cnn, Row.FirstOrDefault, typeof(DapperRow), command);
  50. /// <summary>
  51. /// Execute a single-row query asynchronously using Task.
  52. /// </summary>
  53. /// <param name="cnn">The connection to query on.</param>
  54. /// <param name="command">The command used to query on this connection.</param>
  55. /// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
  56. public static Task<dynamic> QuerySingleAsync(this IDbConnection cnn, CommandDefinition command) =>
  57. QueryRowAsync<dynamic>(cnn, Row.Single, typeof(DapperRow), command);
  58. /// <summary>
  59. /// Execute a single-row query asynchronously using Task.
  60. /// </summary>
  61. /// <param name="cnn">The connection to query on.</param>
  62. /// <param name="command">The command used to query on this connection.</param>
  63. /// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
  64. public static Task<dynamic> QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) =>
  65. QueryRowAsync<dynamic>(cnn, Row.SingleOrDefault, typeof(DapperRow), command);
  66. /// <summary>
  67. /// Execute a query asynchronously using Task.
  68. /// </summary>
  69. /// <typeparam name="T">The type of results to return.</typeparam>
  70. /// <param name="cnn">The connection to query on.</param>
  71. /// <param name="sql">The SQL to execute for the query.</param>
  72. /// <param name="param">The parameters to pass, if any.</param>
  73. /// <param name="transaction">The transaction to use, if any.</param>
  74. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  75. /// <param name="commandType">The type of command to execute.</param>
  76. /// <returns>
  77. /// A sequence of data of <typeparamref name="T"/>; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
  78. /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
  79. /// </returns>
  80. public static Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  81. QueryAsync<T>(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
  82. /// <summary>
  83. /// Execute a single-row query asynchronously using Task.
  84. /// </summary>
  85. /// <typeparam name="T">The type of result to return.</typeparam>
  86. /// <param name="cnn">The connection to query on.</param>
  87. /// <param name="sql">The SQL to execute for the query.</param>
  88. /// <param name="param">The parameters to pass, if any.</param>
  89. /// <param name="transaction">The transaction to use, if any.</param>
  90. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  91. /// <param name="commandType">The type of command to execute.</param>
  92. public static Task<T> QueryFirstAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  93. QueryRowAsync<T>(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  94. /// <summary>
  95. /// Execute a single-row query asynchronously using Task.
  96. /// </summary>
  97. /// <typeparam name="T">The type of result to return.</typeparam>
  98. /// <param name="cnn">The connection to query on.</param>
  99. /// <param name="sql">The SQL to execute for the query.</param>
  100. /// <param name="param">The parameters to pass, if any.</param>
  101. /// <param name="transaction">The transaction to use, if any.</param>
  102. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  103. /// <param name="commandType">The type of command to execute.</param>
  104. public static Task<T> QueryFirstOrDefaultAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  105. QueryRowAsync<T>(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  106. /// <summary>
  107. /// Execute a single-row query asynchronously using Task.
  108. /// </summary>
  109. /// <typeparam name="T">The type of result to return.</typeparam>
  110. /// <param name="cnn">The connection to query on.</param>
  111. /// <param name="sql">The SQL to execute for the query.</param>
  112. /// <param name="param">The parameters to pass, if any.</param>
  113. /// <param name="transaction">The transaction to use, if any.</param>
  114. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  115. /// <param name="commandType">The type of command to execute.</param>
  116. public static Task<T> QuerySingleAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  117. QueryRowAsync<T>(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  118. /// <summary>
  119. /// Execute a single-row query asynchronously using Task.
  120. /// </summary>
  121. /// <typeparam name="T">The type to return.</typeparam>
  122. /// <param name="cnn">The connection to query on.</param>
  123. /// <param name="sql">The SQL to execute for the query.</param>
  124. /// <param name="param">The parameters to pass, if any.</param>
  125. /// <param name="transaction">The transaction to use, if any.</param>
  126. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  127. /// <param name="commandType">The type of command to execute.</param>
  128. public static Task<T> QuerySingleOrDefaultAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  129. QueryRowAsync<T>(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  130. /// <summary>
  131. /// Execute a single-row query asynchronously using Task.
  132. /// </summary>
  133. /// <param name="cnn">The connection to query on.</param>
  134. /// <param name="sql">The SQL to execute for the query.</param>
  135. /// <param name="param">The parameters to pass, if any.</param>
  136. /// <param name="transaction">The transaction to use, if any.</param>
  137. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  138. /// <param name="commandType">The type of command to execute.</param>
  139. public static Task<dynamic> QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  140. QueryRowAsync<dynamic>(cnn, Row.First, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  141. /// <summary>
  142. /// Execute a single-row query asynchronously using Task.
  143. /// </summary>
  144. /// <param name="cnn">The connection to query on.</param>
  145. /// <param name="sql">The SQL to execute for the query.</param>
  146. /// <param name="param">The parameters to pass, if any.</param>
  147. /// <param name="transaction">The transaction to use, if any.</param>
  148. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  149. /// <param name="commandType">The type of command to execute.</param>
  150. public static Task<dynamic> QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  151. QueryRowAsync<dynamic>(cnn, Row.FirstOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  152. /// <summary>
  153. /// Execute a single-row query asynchronously using Task.
  154. /// </summary>
  155. /// <param name="cnn">The connection to query on.</param>
  156. /// <param name="sql">The SQL to execute for the query.</param>
  157. /// <param name="param">The parameters to pass, if any.</param>
  158. /// <param name="transaction">The transaction to use, if any.</param>
  159. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  160. /// <param name="commandType">The type of command to execute.</param>
  161. public static Task<dynamic> QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  162. QueryRowAsync<dynamic>(cnn, Row.Single, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  163. /// <summary>
  164. /// Execute a single-row query asynchronously using Task.
  165. /// </summary>
  166. /// <param name="cnn">The connection to query on.</param>
  167. /// <param name="sql">The SQL to execute for the query.</param>
  168. /// <param name="param">The parameters to pass, if any.</param>
  169. /// <param name="transaction">The transaction to use, if any.</param>
  170. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  171. /// <param name="commandType">The type of command to execute.</param>
  172. public static Task<dynamic> QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  173. QueryRowAsync<dynamic>(cnn, Row.SingleOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  174. /// <summary>
  175. /// Execute a query asynchronously using Task.
  176. /// </summary>
  177. /// <param name="cnn">The connection to query on.</param>
  178. /// <param name="type">The type to return.</param>
  179. /// <param name="sql">The SQL to execute for the query.</param>
  180. /// <param name="param">The parameters to pass, if any.</param>
  181. /// <param name="transaction">The transaction to use, if any.</param>
  182. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  183. /// <param name="commandType">The type of command to execute.</param>
  184. /// <exception cref="ArgumentNullException"><paramref name="type"/> is <c>null</c>.</exception>
  185. public static Task<IEnumerable<object>> QueryAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
  186. {
  187. if (type == null) throw new ArgumentNullException(nameof(type));
  188. return QueryAsync<object>(cnn, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
  189. }
  190. /// <summary>
  191. /// Execute a single-row query asynchronously using Task.
  192. /// </summary>
  193. /// <param name="cnn">The connection to query on.</param>
  194. /// <param name="type">The type to return.</param>
  195. /// <param name="sql">The SQL to execute for the query.</param>
  196. /// <param name="param">The parameters to pass, if any.</param>
  197. /// <param name="transaction">The transaction to use, if any.</param>
  198. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  199. /// <param name="commandType">The type of command to execute.</param>
  200. /// <exception cref="ArgumentNullException"><paramref name="type"/> is <c>null</c>.</exception>
  201. public static Task<object> QueryFirstAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
  202. {
  203. if (type == null) throw new ArgumentNullException(nameof(type));
  204. return QueryRowAsync<object>(cnn, Row.First, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  205. }
  206. /// <summary>
  207. /// Execute a single-row query asynchronously using Task.
  208. /// </summary>
  209. /// <param name="cnn">The connection to query on.</param>
  210. /// <param name="type">The type to return.</param>
  211. /// <param name="sql">The SQL to execute for the query.</param>
  212. /// <param name="param">The parameters to pass, if any.</param>
  213. /// <param name="transaction">The transaction to use, if any.</param>
  214. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  215. /// <param name="commandType">The type of command to execute.</param>
  216. /// <exception cref="ArgumentNullException"><paramref name="type"/> is <c>null</c>.</exception>
  217. public static Task<object> QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
  218. {
  219. if (type == null) throw new ArgumentNullException(nameof(type));
  220. return QueryRowAsync<object>(cnn, Row.FirstOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  221. }
  222. /// <summary>
  223. /// Execute a single-row query asynchronously using Task.
  224. /// </summary>
  225. /// <param name="cnn">The connection to query on.</param>
  226. /// <param name="type">The type to return.</param>
  227. /// <param name="sql">The SQL to execute for the query.</param>
  228. /// <param name="param">The parameters to pass, if any.</param>
  229. /// <param name="transaction">The transaction to use, if any.</param>
  230. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  231. /// <param name="commandType">The type of command to execute.</param>
  232. /// <exception cref="ArgumentNullException"><paramref name="type"/> is <c>null</c>.</exception>
  233. public static Task<object> QuerySingleAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
  234. {
  235. if (type == null) throw new ArgumentNullException(nameof(type));
  236. return QueryRowAsync<object>(cnn, Row.Single, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  237. }
  238. /// <summary>
  239. /// Execute a single-row query asynchronously using Task.
  240. /// </summary>
  241. /// <param name="cnn">The connection to query on.</param>
  242. /// <param name="type">The type to return.</param>
  243. /// <param name="sql">The SQL to execute for the query.</param>
  244. /// <param name="param">The parameters to pass, if any.</param>
  245. /// <param name="transaction">The transaction to use, if any.</param>
  246. /// <param name="commandTimeout">The command timeout (in seconds).</param>
  247. /// <param name="commandType">The type of command to execute.</param>
  248. /// <exception cref="ArgumentNullException"><paramref name="type"/> is <c>null</c>.</exception>
  249. public static Task<object> QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
  250. {
  251. if (type == null) throw new ArgumentNullException(nameof(type));
  252. return QueryRowAsync<object>(cnn, Row.SingleOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
  253. }
  254. /// <summary>
  255. /// Execute a query asynchronously using Task.
  256. /// </summary>
  257. /// <typeparam name="T">The type to return.</typeparam>
  258. /// <param name="cnn">The connection to query on.</param>
  259. /// <param name="command">The command used to query on this connection.</param>
  260. /// <returns>
  261. /// A sequence of data of <typeparamref name="T"/>; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
  262. /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
  263. /// </returns>
  264. public static Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, CommandDefinition command) =>
  265. QueryAsync<T>(cnn, typeof(T), command);
  266. /// <summary>
  267. /// Execute a query asynchronously using Task.
  268. /// </summary>
  269. /// <param name="cnn">The connection to query on.</param>
  270. /// <param name="type">The type to return.</param>
  271. /// <param name="command">The command used to query on this connection.</param>
  272. public static Task<IEnumerable<object>> QueryAsync(this IDbConnection cnn, Type type, CommandDefinition command) =>
  273. QueryAsync<object>(cnn, type, command);
  274. /// <summary>
  275. /// Execute a single-row query asynchronously using Task.
  276. /// </summary>
  277. /// <param name="cnn">The connection to query on.</param>
  278. /// <param name="type">The type to return.</param>
  279. /// <param name="command">The command used to query on this connection.</param>
  280. public static Task<object> QueryFirstAsync(this IDbConnection cnn, Type type, CommandDefinition command) =>
  281. QueryRowAsync<object>(cnn, Row.First, type, command);
  282. /// <summary>
  283. /// Execute a single-row query asynchronously using Task.
  284. /// </summary>
  285. /// <typeparam name="T">The type to return.</typeparam>
  286. /// <param name="cnn">The connection to query on.</param>
  287. /// <param name="command">The command used to query on this connection.</param>
  288. public static Task<T> QueryFirstAsync<T>(this IDbConnection cnn, CommandDefinition command) =>
  289. QueryRowAsync<T>(cnn, Row.First, typeof(T), command);
  290. /// <summary>
  291. /// Execute a single-row query asynchronously using Task.
  292. /// </summary>
  293. /// <param name="cnn">The connection to query on.</param>
  294. /// <param name="type">The type to return.</param>
  295. /// <param name="command">The command used to query on this connection.</param>
  296. public static Task<object> QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) =>
  297. QueryRowAsync<object>(cnn, Row.FirstOrDefault, type, command);
  298. /// <summary>
  299. /// Execute a single-row query asynchronously using Task.
  300. /// </summary>
  301. /// <typeparam name="T">The type to return.</typeparam>
  302. /// <param name="cnn">The connection to query on.</param>
  303. /// <param name="command">The command used to query on this connection.</param>
  304. public static Task<T> QueryFirstOrDefaultAsync<T>(this IDbConnection cnn, CommandDefinition command) =>
  305. QueryRowAsync<T>(cnn, Row.FirstOrDefault, typeof(T), command);
  306. /// <summary>
  307. /// Execute a single-row query asynchronously using Task.
  308. /// </summary>
  309. /// <param name="cnn">The connection to query on.</param>
  310. /// <param name="type">The type to return.</param>
  311. /// <param name="command">The command used to query on this connection.</param>
  312. public static Task<object> QuerySingleAsync(this IDbConnection cnn, Type type, CommandDefinition command) =>
  313. QueryRowAsync<object>(cnn, Row.Single, type, command);
  314. /// <summary>
  315. /// Execute a single-row query asynchronously using Task.
  316. /// </summary>
  317. /// <typeparam name="T">The type to return.</typeparam>
  318. /// <param name="cnn">The connection to query on.</param>
  319. /// <param name="command">The command used to query on this connection.</param>
  320. public static Task<T> QuerySingleAsync<T>(this IDbConnection cnn, CommandDefinition command) =>
  321. QueryRowAsync<T>(cnn, Row.Single, typeof(T), command);
  322. /// <summary>
  323. /// Execute a single-row query asynchronously using Task.
  324. /// </summary>
  325. /// <param name="cnn">The connection to query on.</param>
  326. /// <param name="type">The type to return.</param>
  327. /// <param name="command">The command used to query on this connection.</param>
  328. public static Task<object> QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) =>
  329. QueryRowAsync<object>(cnn, Row.SingleOrDefault, type, command);
  330. /// <summary>
  331. /// Execute a single-row query asynchronously using Task.
  332. /// </summary>
  333. /// <typeparam name="T">The type to return.</typeparam>
  334. /// <param name="cnn">The connection to query on.</param>
  335. /// <param name="command">The command used to query on this connection.</param>
  336. public static Task<T> QuerySingleOrDefaultAsync<T>(this IDbConnection cnn, CommandDefinition command) =>
  337. QueryRowAsync<T>(cnn, Row.SingleOrDefault, typeof(T), command);
  338. private static Task<DbDataReader> ExecuteReaderWithFlagsFallbackAsync(DbCommand cmd, bool wasClosed, CommandBehavior behavior, CancellationToken cancellationToken)
  339. {
  340. var task = cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken);
  341. if (task.Status == TaskStatus.Faulted && Settings.DisableCommandBehaviorOptimizations(behavior, task.Exception.InnerException))
  342. { // we can retry; this time it will have different flags
  343. return cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken);
  344. }
  345. return task;
  346. }
  347. /// <summary>
  348. /// Attempts to open a connection asynchronously, with a better error message for unsupported usages.
  349. /// </summary>
  350. private static Task TryOpenAsync(this IDbConnection cnn, CancellationToken cancel)
  351. {
  352. if (cnn is DbConnection dbConn)
  353. {
  354. return dbConn.OpenAsync(cancel);
  355. }
  356. else
  357. {
  358. throw new InvalidOperationException("Async operations require use of a DbConnection or an already-open IDbConnection");
  359. }
  360. }
  361. /// <summary>
  362. /// Attempts setup a <see cref="DbCommand"/> on a <see cref="DbConnection"/>, with a better error message for unsupported usages.
  363. /// </summary>
  364. private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, IDbConnection cnn, Action<IDbCommand, object> paramReader)
  365. {
  366. if (command.SetupCommand(cnn, paramReader) is DbCommand dbCommand)
  367. {
  368. return dbCommand;
  369. }
  370. else
  371. {
  372. throw new InvalidOperationException("Async operations require use of a DbConnection or an IDbConnection where .CreateCommand() returns a DbCommand");
  373. }
  374. }
  375. private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
  376. {
  377. object param = command.Parameters;
  378. var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
  379. var info = GetCacheInfo(identity, param, command.AddToCache);
  380. bool wasClosed = cnn.State == ConnectionState.Closed;
  381. var cancel = command.CancellationToken;
  382. using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader))
  383. {
  384. DbDataReader reader = null;
  385. try
  386. {
  387. if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false);
  388. reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false);
  389. var tuple = info.Deserializer;
  390. int hash = GetColumnHash(reader);
  391. if (tuple.Func == null || tuple.Hash != hash)
  392. {
  393. if (reader.FieldCount == 0)
  394. return Enumerable.Empty<T>();
  395. tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
  396. if (command.AddToCache) SetQueryCache(identity, info);
  397. }
  398. var func = tuple.Func;
  399. if (command.Buffered)
  400. {
  401. var buffer = new List<T>();
  402. var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
  403. while (await reader.ReadAsync(cancel).ConfigureAwait(false))
  404. {
  405. object val = func(reader);
  406. buffer.Add(GetValue<T>(reader, effectiveType, val));
  407. }
  408. while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ }
  409. command.OnCompleted();
  410. return buffer;
  411. }
  412. else
  413. {
  414. // can't use ReadAsync / cancellation; but this will have to do
  415. wasClosed = false; // don't close if handing back an open reader; rely on the command-behavior
  416. var deferred = ExecuteReaderSync<T>(reader, func, command.Parameters);
  417. reader = null; // to prevent it being disposed before the caller gets to see it
  418. return deferred;
  419. }
  420. }
  421. finally
  422. {
  423. using (reader) { /* dispose if non-null */ }
  424. if (wasClosed) cnn.Close();
  425. }
  426. }
  427. }
  428. private static async Task<T> QueryRowAsync<T>(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command)
  429. {
  430. object param = command.Parameters;
  431. var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
  432. var info = GetCacheInfo(identity, param, command.AddToCache);
  433. bool wasClosed = cnn.State == ConnectionState.Closed;
  434. var cancel = command.CancellationToken;
  435. using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader))
  436. {
  437. DbDataReader reader = null;
  438. try
  439. {
  440. if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false);
  441. reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, (row & Row.Single) != 0
  442. ? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition
  443. : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow, cancel).ConfigureAwait(false);
  444. T result = default;
  445. if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0)
  446. {
  447. result = ReadRow<T>(info, identity, ref command, effectiveType, reader);
  448. if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row);
  449. while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore rows after the first */ }
  450. }
  451. else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one
  452. {
  453. ThrowZeroRows(row);
  454. }
  455. while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore result sets after the first */ }
  456. return result;
  457. }
  458. finally
  459. {
  460. using (reader) { /* dispose if non-null */ }
  461. if (wasClosed) cnn.Close();
  462. }
  463. }
  464. }
  465. /// <summary>
  466. /// Execute a command asynchronously using Task.
  467. /// </summary>
  468. /// <param name="cnn">The connection to query on.</param>
  469. /// <param name="sql">The SQL to execute for this query.</param>
  470. /// <param name="param">The parameters to use for this query.</param>
  471. /// <param name="transaction">The transaction to use for this query.</param>
  472. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  473. /// <param name="commandType">Is it a stored proc or a batch?</param>
  474. /// <returns>The number of rows affected.</returns>
  475. public static Task<int> ExecuteAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  476. ExecuteAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
  477. /// <summary>
  478. /// Execute a command asynchronously using Task.
  479. /// </summary>
  480. /// <param name="cnn">The connection to execute on.</param>
  481. /// <param name="command">The command to execute on this connection.</param>
  482. /// <returns>The number of rows affected.</returns>
  483. public static Task<int> ExecuteAsync(this IDbConnection cnn, CommandDefinition command)
  484. {
  485. object param = command.Parameters;
  486. IEnumerable multiExec = GetMultiExec(param);
  487. if (multiExec != null)
  488. {
  489. return ExecuteMultiImplAsync(cnn, command, multiExec);
  490. }
  491. else
  492. {
  493. return ExecuteImplAsync(cnn, command, param);
  494. }
  495. }
  496. private struct AsyncExecState
  497. {
  498. public readonly DbCommand Command;
  499. public readonly Task<int> Task;
  500. public AsyncExecState(DbCommand command, Task<int> task)
  501. {
  502. Command = command;
  503. Task = task;
  504. }
  505. }
  506. private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandDefinition command, IEnumerable multiExec)
  507. {
  508. bool isFirst = true;
  509. int total = 0;
  510. bool wasClosed = cnn.State == ConnectionState.Closed;
  511. try
  512. {
  513. if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false);
  514. CacheInfo info = null;
  515. string masterSql = null;
  516. if ((command.Flags & CommandFlags.Pipelined) != 0)
  517. {
  518. const int MAX_PENDING = 100;
  519. var pending = new Queue<AsyncExecState>(MAX_PENDING);
  520. DbCommand cmd = null;
  521. try
  522. {
  523. foreach (var obj in multiExec)
  524. {
  525. if (isFirst)
  526. {
  527. isFirst = false;
  528. cmd = command.TrySetupAsyncCommand(cnn, null);
  529. masterSql = cmd.CommandText;
  530. var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType());
  531. info = GetCacheInfo(identity, obj, command.AddToCache);
  532. }
  533. else if (pending.Count >= MAX_PENDING)
  534. {
  535. var recycled = pending.Dequeue();
  536. total += await recycled.Task.ConfigureAwait(false);
  537. cmd = recycled.Command;
  538. cmd.CommandText = masterSql; // because we do magic replaces on "in" etc
  539. cmd.Parameters.Clear(); // current code is Add-tastic
  540. }
  541. else
  542. {
  543. cmd = command.TrySetupAsyncCommand(cnn, null);
  544. }
  545. info.ParamReader(cmd, obj);
  546. var task = cmd.ExecuteNonQueryAsync(command.CancellationToken);
  547. pending.Enqueue(new AsyncExecState(cmd, task));
  548. cmd = null; // note the using in the finally: this avoids a double-dispose
  549. }
  550. while (pending.Count != 0)
  551. {
  552. var pair = pending.Dequeue();
  553. using (pair.Command) { /* dispose commands */ }
  554. total += await pair.Task.ConfigureAwait(false);
  555. }
  556. }
  557. finally
  558. {
  559. // this only has interesting work to do if there are failures
  560. using (cmd) { /* dispose commands */ }
  561. while (pending.Count != 0)
  562. { // dispose tasks even in failure
  563. using (pending.Dequeue().Command) { /* dispose commands */ }
  564. }
  565. }
  566. }
  567. else
  568. {
  569. using (var cmd = command.TrySetupAsyncCommand(cnn, null))
  570. {
  571. foreach (var obj in multiExec)
  572. {
  573. if (isFirst)
  574. {
  575. masterSql = cmd.CommandText;
  576. isFirst = false;
  577. var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType());
  578. info = GetCacheInfo(identity, obj, command.AddToCache);
  579. }
  580. else
  581. {
  582. cmd.CommandText = masterSql; // because we do magic replaces on "in" etc
  583. cmd.Parameters.Clear(); // current code is Add-tastic
  584. }
  585. info.ParamReader(cmd, obj);
  586. total += await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false);
  587. }
  588. }
  589. }
  590. command.OnCompleted();
  591. }
  592. finally
  593. {
  594. if (wasClosed) cnn.Close();
  595. }
  596. return total;
  597. }
  598. private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object param)
  599. {
  600. var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType());
  601. var info = GetCacheInfo(identity, param, command.AddToCache);
  602. bool wasClosed = cnn.State == ConnectionState.Closed;
  603. using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader))
  604. {
  605. try
  606. {
  607. if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false);
  608. var result = await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false);
  609. command.OnCompleted();
  610. return result;
  611. }
  612. finally
  613. {
  614. if (wasClosed) cnn.Close();
  615. }
  616. }
  617. }
  618. /// <summary>
  619. /// Perform a asynchronous multi-mapping query with 2 input types.
  620. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  621. /// </summary>
  622. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  623. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  624. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  625. /// <param name="cnn">The connection to query on.</param>
  626. /// <param name="sql">The SQL to execute for this query.</param>
  627. /// <param name="map">The function to map row types to the return type.</param>
  628. /// <param name="param">The parameters to use for this query.</param>
  629. /// <param name="transaction">The transaction to use for this query.</param>
  630. /// <param name="buffered">Whether to buffer the results in memory.</param>
  631. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  632. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  633. /// <param name="commandType">Is it a stored proc or a batch?</param>
  634. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  635. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) =>
  636. MultiMapAsync<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn,
  637. new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
  638. /// <summary>
  639. /// Perform a asynchronous multi-mapping query with 2 input types.
  640. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  641. /// </summary>
  642. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  643. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  644. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  645. /// <param name="cnn">The connection to query on.</param>
  646. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  647. /// <param name="command">The command to execute.</param>
  648. /// <param name="map">The function to map row types to the return type.</param>
  649. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  650. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TReturn> map, string splitOn = "Id") =>
  651. MultiMapAsync<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
  652. /// <summary>
  653. /// Perform a asynchronous multi-mapping query with 3 input types.
  654. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  655. /// </summary>
  656. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  657. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  658. /// <typeparam name="TThird">The third type in the recordset.</typeparam>
  659. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  660. /// <param name="cnn">The connection to query on.</param>
  661. /// <param name="sql">The SQL to execute for this query.</param>
  662. /// <param name="map">The function to map row types to the return type.</param>
  663. /// <param name="param">The parameters to use for this query.</param>
  664. /// <param name="transaction">The transaction to use for this query.</param>
  665. /// <param name="buffered">Whether to buffer the results in memory.</param>
  666. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  667. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  668. /// <param name="commandType">Is it a stored proc or a batch?</param>
  669. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  670. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) =>
  671. MultiMapAsync<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn,
  672. new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
  673. /// <summary>
  674. /// Perform a asynchronous multi-mapping query with 3 input types.
  675. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  676. /// </summary>
  677. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  678. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  679. /// <typeparam name="TThird">The third type in the recordset.</typeparam>
  680. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  681. /// <param name="cnn">The connection to query on.</param>
  682. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  683. /// <param name="command">The command to execute.</param>
  684. /// <param name="map">The function to map row types to the return type.</param>
  685. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  686. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TThird, TReturn> map, string splitOn = "Id") =>
  687. MultiMapAsync<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
  688. /// <summary>
  689. /// Perform a asynchronous multi-mapping query with 4 input types.
  690. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  691. /// </summary>
  692. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  693. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  694. /// <typeparam name="TThird">The third type in the recordset.</typeparam>
  695. /// <typeparam name="TFourth">The fourth type in the recordset.</typeparam>
  696. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  697. /// <param name="cnn">The connection to query on.</param>
  698. /// <param name="sql">The SQL to execute for this query.</param>
  699. /// <param name="map">The function to map row types to the return type.</param>
  700. /// <param name="param">The parameters to use for this query.</param>
  701. /// <param name="transaction">The transaction to use for this query.</param>
  702. /// <param name="buffered">Whether to buffer the results in memory.</param>
  703. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  704. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  705. /// <param name="commandType">Is it a stored proc or a batch?</param>
  706. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  707. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) =>
  708. MultiMapAsync<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn,
  709. new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
  710. /// <summary>
  711. /// Perform a asynchronous multi-mapping query with 4 input types.
  712. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  713. /// </summary>
  714. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  715. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  716. /// <typeparam name="TThird">The third type in the recordset.</typeparam>
  717. /// <typeparam name="TFourth">The fourth type in the recordset.</typeparam>
  718. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  719. /// <param name="cnn">The connection to query on.</param>
  720. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  721. /// <param name="command">The command to execute.</param>
  722. /// <param name="map">The function to map row types to the return type.</param>
  723. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  724. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, string splitOn = "Id") =>
  725. MultiMapAsync<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
  726. /// <summary>
  727. /// Perform a asynchronous multi-mapping query with 5 input types.
  728. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  729. /// </summary>
  730. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  731. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  732. /// <typeparam name="TThird">The third type in the recordset.</typeparam>
  733. /// <typeparam name="TFourth">The fourth type in the recordset.</typeparam>
  734. /// <typeparam name="TFifth">The fifth type in the recordset.</typeparam>
  735. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  736. /// <param name="cnn">The connection to query on.</param>
  737. /// <param name="sql">The SQL to execute for this query.</param>
  738. /// <param name="map">The function to map row types to the return type.</param>
  739. /// <param name="param">The parameters to use for this query.</param>
  740. /// <param name="transaction">The transaction to use for this query.</param>
  741. /// <param name="buffered">Whether to buffer the results in memory.</param>
  742. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  743. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  744. /// <param name="commandType">Is it a stored proc or a batch?</param>
  745. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  746. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) =>
  747. MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn,
  748. new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
  749. /// <summary>
  750. /// Perform a asynchronous multi-mapping query with 5 input types.
  751. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  752. /// </summary>
  753. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  754. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  755. /// <typeparam name="TThird">The third type in the recordset.</typeparam>
  756. /// <typeparam name="TFourth">The fourth type in the recordset.</typeparam>
  757. /// <typeparam name="TFifth">The fifth type in the recordset.</typeparam>
  758. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  759. /// <param name="cnn">The connection to query on.</param>
  760. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  761. /// <param name="command">The command to execute.</param>
  762. /// <param name="map">The function to map row types to the return type.</param>
  763. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  764. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, string splitOn = "Id") =>
  765. MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
  766. /// <summary>
  767. /// Perform a asynchronous multi-mapping query with 6 input types.
  768. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  769. /// </summary>
  770. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  771. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  772. /// <typeparam name="TThird">The third type in the recordset.</typeparam>
  773. /// <typeparam name="TFourth">The fourth type in the recordset.</typeparam>
  774. /// <typeparam name="TFifth">The fifth type in the recordset.</typeparam>
  775. /// <typeparam name="TSixth">The sixth type in the recordset.</typeparam>
  776. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  777. /// <param name="cnn">The connection to query on.</param>
  778. /// <param name="sql">The SQL to execute for this query.</param>
  779. /// <param name="map">The function to map row types to the return type.</param>
  780. /// <param name="param">The parameters to use for this query.</param>
  781. /// <param name="transaction">The transaction to use for this query.</param>
  782. /// <param name="buffered">Whether to buffer the results in memory.</param>
  783. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  784. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  785. /// <param name="commandType">Is it a stored proc or a batch?</param>
  786. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  787. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) =>
  788. MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(cnn,
  789. new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
  790. /// <summary>
  791. /// Perform a asynchronous multi-mapping query with 6 input types.
  792. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  793. /// </summary>
  794. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  795. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  796. /// <typeparam name="TThird">The third type in the recordset.</typeparam>
  797. /// <typeparam name="TFourth">The fourth type in the recordset.</typeparam>
  798. /// <typeparam name="TFifth">The fifth type in the recordset.</typeparam>
  799. /// <typeparam name="TSixth">The sixth type in the recordset.</typeparam>
  800. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  801. /// <param name="cnn">The connection to query on.</param>
  802. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  803. /// <param name="command">The command to execute.</param>
  804. /// <param name="map">The function to map row types to the return type.</param>
  805. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  806. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> map, string splitOn = "Id") =>
  807. MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(cnn, command, map, splitOn);
  808. /// <summary>
  809. /// Perform a asynchronous multi-mapping query with 7 input types.
  810. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  811. /// </summary>
  812. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  813. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  814. /// <typeparam name="TThird">The third type in the recordset.</typeparam>
  815. /// <typeparam name="TFourth">The fourth type in the recordset.</typeparam>
  816. /// <typeparam name="TFifth">The fifth type in the recordset.</typeparam>
  817. /// <typeparam name="TSixth">The sixth type in the recordset.</typeparam>
  818. /// <typeparam name="TSeventh">The seventh type in the recordset.</typeparam>
  819. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  820. /// <param name="cnn">The connection to query on.</param>
  821. /// <param name="sql">The SQL to execute for this query.</param>
  822. /// <param name="map">The function to map row types to the return type.</param>
  823. /// <param name="param">The parameters to use for this query.</param>
  824. /// <param name="transaction">The transaction to use for this query.</param>
  825. /// <param name="buffered">Whether to buffer the results in memory.</param>
  826. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  827. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  828. /// <param name="commandType">Is it a stored proc or a batch?</param>
  829. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  830. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) =>
  831. MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn,
  832. new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
  833. /// <summary>
  834. /// Perform an asynchronous multi-mapping query with 7 input types.
  835. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  836. /// </summary>
  837. /// <typeparam name="TFirst">The first type in the recordset.</typeparam>
  838. /// <typeparam name="TSecond">The second type in the recordset.</typeparam>
  839. /// <typeparam name="TThird">The third type in the recordset.</typeparam>
  840. /// <typeparam name="TFourth">The fourth type in the recordset.</typeparam>
  841. /// <typeparam name="TFifth">The fifth type in the recordset.</typeparam>
  842. /// <typeparam name="TSixth">The sixth type in the recordset.</typeparam>
  843. /// <typeparam name="TSeventh">The seventh type in the recordset.</typeparam>
  844. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  845. /// <param name="cnn">The connection to query on.</param>
  846. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  847. /// <param name="command">The command to execute.</param>
  848. /// <param name="map">The function to map row types to the return type.</param>
  849. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  850. public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, string splitOn = "Id") =>
  851. MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, command, map, splitOn);
  852. private static async Task<IEnumerable<TReturn>> MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn)
  853. {
  854. object param = command.Parameters;
  855. var identity = new Identity<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType());
  856. var info = GetCacheInfo(identity, param, command.AddToCache);
  857. bool wasClosed = cnn.State == ConnectionState.Closed;
  858. try
  859. {
  860. if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false);
  861. using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader))
  862. using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false))
  863. {
  864. if (!command.Buffered) wasClosed = false; // handing back open reader; rely on command-behavior
  865. var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true);
  866. return command.Buffered ? results.ToList() : results;
  867. }
  868. }
  869. finally
  870. {
  871. if (wasClosed) cnn.Close();
  872. }
  873. }
  874. /// <summary>
  875. /// Perform a asynchronous multi-mapping query with an arbitrary number of input types.
  876. /// This returns a single type, combined from the raw types via <paramref name="map"/>.
  877. /// </summary>
  878. /// <typeparam name="TReturn">The combined type to return.</typeparam>
  879. /// <param name="cnn">The connection to query on.</param>
  880. /// <param name="sql">The SQL to execute for this query.</param>
  881. /// <param name="types">Array of types in the recordset.</param>
  882. /// <param name="map">The function to map row types to the return type.</param>
  883. /// <param name="param">The parameters to use for this query.</param>
  884. /// <param name="transaction">The transaction to use for this query.</param>
  885. /// <param name="buffered">Whether to buffer the results in memory.</param>
  886. /// <param name="splitOn">The field we should split and read the second object from (default: "Id").</param>
  887. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  888. /// <param name="commandType">Is it a stored proc or a batch?</param>
  889. /// <returns>An enumerable of <typeparamref name="TReturn"/>.</returns>
  890. public static Task<IEnumerable<TReturn>> QueryAsync<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
  891. {
  892. var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken));
  893. return MultiMapAsync(cnn, command, types, map, splitOn);
  894. }
  895. private static async Task<IEnumerable<TReturn>> MultiMapAsync<TReturn>(this IDbConnection cnn, CommandDefinition command, Type[] types, Func<object[], TReturn> map, string splitOn)
  896. {
  897. if (types.Length < 1)
  898. {
  899. throw new ArgumentException("you must provide at least one type to deserialize");
  900. }
  901. object param = command.Parameters;
  902. var identity = new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types);
  903. var info = GetCacheInfo(identity, param, command.AddToCache);
  904. bool wasClosed = cnn.State == ConnectionState.Closed;
  905. try
  906. {
  907. if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false);
  908. using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader))
  909. using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false))
  910. {
  911. var results = MultiMapImpl(null, default(CommandDefinition), types, map, splitOn, reader, identity, true);
  912. return command.Buffered ? results.ToList() : results;
  913. }
  914. }
  915. finally
  916. {
  917. if (wasClosed) cnn.Close();
  918. }
  919. }
  920. private static IEnumerable<T> ExecuteReaderSync<T>(IDataReader reader, Func<IDataReader, object> func, object parameters)
  921. {
  922. using (reader)
  923. {
  924. while (reader.Read())
  925. {
  926. yield return (T)func(reader);
  927. }
  928. while (reader.NextResult()) { /* ignore subsequent result sets */ }
  929. (parameters as IParameterCallbacks)?.OnCompleted();
  930. }
  931. }
  932. /// <summary>
  933. /// Execute a command that returns multiple result sets, and access each in turn.
  934. /// </summary>
  935. /// <param name="cnn">The connection to query on.</param>
  936. /// <param name="sql">The SQL to execute for this query.</param>
  937. /// <param name="param">The parameters to use for this query.</param>
  938. /// <param name="transaction">The transaction to use for this query.</param>
  939. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  940. /// <param name="commandType">Is it a stored proc or a batch?</param>
  941. public static Task<GridReader> QueryMultipleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  942. QueryMultipleAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered));
  943. /// <summary>
  944. /// Execute a command that returns multiple result sets, and access each in turn.
  945. /// </summary>
  946. /// <param name="cnn">The connection to query on.</param>
  947. /// <param name="command">The command to execute for this query.</param>
  948. public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command)
  949. {
  950. object param = command.Parameters;
  951. var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType());
  952. CacheInfo info = GetCacheInfo(identity, param, command.AddToCache);
  953. DbCommand cmd = null;
  954. IDataReader reader = null;
  955. bool wasClosed = cnn.State == ConnectionState.Closed;
  956. try
  957. {
  958. if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false);
  959. cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader);
  960. reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false);
  961. var result = new GridReader(cmd, reader, identity, command.Parameters as DynamicParameters, command.AddToCache, command.CancellationToken);
  962. wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
  963. // with the CloseConnection flag, so the reader will deal with the connection; we
  964. // still need something in the "finally" to ensure that broken SQL still results
  965. // in the connection closing itself
  966. return result;
  967. }
  968. catch
  969. {
  970. if (reader != null)
  971. {
  972. if (!reader.IsClosed)
  973. {
  974. try { cmd.Cancel(); }
  975. catch
  976. { /* don't spoil the existing exception */
  977. }
  978. }
  979. reader.Dispose();
  980. }
  981. cmd?.Dispose();
  982. if (wasClosed) cnn.Close();
  983. throw;
  984. }
  985. }
  986. /// <summary>
  987. /// Execute parameterized SQL and return an <see cref="IDataReader"/>.
  988. /// </summary>
  989. /// <param name="cnn">The connection to execute on.</param>
  990. /// <param name="sql">The SQL to execute.</param>
  991. /// <param name="param">The parameters to use for this command.</param>
  992. /// <param name="transaction">The transaction to use for this command.</param>
  993. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  994. /// <param name="commandType">Is it a stored proc or a batch?</param>
  995. /// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
  996. /// <remarks>
  997. /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a <see cref="DataTable"/>
  998. /// or <see cref="T:DataSet"/>.
  999. /// </remarks>
  1000. /// <example>
  1001. /// <code>
  1002. /// <![CDATA[
  1003. /// DataTable table = new DataTable("MyTable");
  1004. /// using (var reader = ExecuteReader(cnn, sql, param))
  1005. /// {
  1006. /// table.Load(reader);
  1007. /// }
  1008. /// ]]>
  1009. /// </code>
  1010. /// </example>
  1011. public static Task<IDataReader> ExecuteReaderAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  1012. ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default).CastResult<DbDataReader, IDataReader>();
  1013. /// <summary>
  1014. /// Execute parameterized SQL and return a <see cref="DbDataReader"/>.
  1015. /// </summary>
  1016. /// <param name="cnn">The connection to execute on.</param>
  1017. /// <param name="sql">The SQL to execute.</param>
  1018. /// <param name="param">The parameters to use for this command.</param>
  1019. /// <param name="transaction">The transaction to use for this command.</param>
  1020. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  1021. /// <param name="commandType">Is it a stored proc or a batch?</param>
  1022. public static Task<DbDataReader> ExecuteReaderAsync(this DbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  1023. ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default);
  1024. /// <summary>
  1025. /// Execute parameterized SQL and return an <see cref="IDataReader"/>.
  1026. /// </summary>
  1027. /// <param name="cnn">The connection to execute on.</param>
  1028. /// <param name="command">The command to execute.</param>
  1029. /// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
  1030. /// <remarks>
  1031. /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a <see cref="DataTable"/>
  1032. /// or <see cref="T:DataSet"/>.
  1033. /// </remarks>
  1034. public static Task<IDataReader> ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command) =>
  1035. ExecuteWrappedReaderImplAsync(cnn, command, CommandBehavior.Default).CastResult<DbDataReader, IDataReader>();
  1036. /// <summary>
  1037. /// Execute parameterized SQL and return a <see cref="DbDataReader"/>.
  1038. /// </summary>
  1039. /// <param name="cnn">The connection to execute on.</param>
  1040. /// <param name="command">The command to execute.</param>
  1041. public static Task<DbDataReader> ExecuteReaderAsync(this DbConnection cnn, CommandDefinition command) =>
  1042. ExecuteWrappedReaderImplAsync(cnn, command, CommandBehavior.Default);
  1043. /// <summary>
  1044. /// Execute parameterized SQL and return an <see cref="IDataReader"/>.
  1045. /// </summary>
  1046. /// <param name="cnn">The connection to execute on.</param>
  1047. /// <param name="command">The command to execute.</param>
  1048. /// <param name="commandBehavior">The <see cref="CommandBehavior"/> flags for this reader.</param>
  1049. /// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
  1050. /// <remarks>
  1051. /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a <see cref="DataTable"/>
  1052. /// or <see cref="T:DataSet"/>.
  1053. /// </remarks>
  1054. public static Task<IDataReader> ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) =>
  1055. ExecuteWrappedReaderImplAsync(cnn, command, commandBehavior).CastResult<DbDataReader, IDataReader>();
  1056. /// <summary>
  1057. /// Execute parameterized SQL and return a <see cref="DbDataReader"/>.
  1058. /// </summary>
  1059. /// <param name="cnn">The connection to execute on.</param>
  1060. /// <param name="command">The command to execute.</param>
  1061. /// <param name="commandBehavior">The <see cref="CommandBehavior"/> flags for this reader.</param>
  1062. public static Task<DbDataReader> ExecuteReaderAsync(this DbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) =>
  1063. ExecuteWrappedReaderImplAsync(cnn, command, commandBehavior);
  1064. private static async Task<DbDataReader> ExecuteWrappedReaderImplAsync(IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior)
  1065. {
  1066. Action<IDbCommand, object> paramReader = GetParameterReader(cnn, ref command);
  1067. DbCommand cmd = null;
  1068. bool wasClosed = cnn.State == ConnectionState.Closed, disposeCommand = true;
  1069. try
  1070. {
  1071. cmd = command.TrySetupAsyncCommand(cnn, paramReader);
  1072. if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false);
  1073. var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, commandBehavior, command.CancellationToken).ConfigureAwait(false);
  1074. wasClosed = false;
  1075. disposeCommand = false;
  1076. return WrappedReader.Create(cmd, reader);
  1077. }
  1078. finally
  1079. {
  1080. if (wasClosed) cnn.Close();
  1081. if (cmd != null && disposeCommand) cmd.Dispose();
  1082. }
  1083. }
  1084. /// <summary>
  1085. /// Execute parameterized SQL that selects a single value.
  1086. /// </summary>
  1087. /// <param name="cnn">The connection to execute on.</param>
  1088. /// <param name="sql">The SQL to execute.</param>
  1089. /// <param name="param">The parameters to use for this command.</param>
  1090. /// <param name="transaction">The transaction to use for this command.</param>
  1091. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  1092. /// <param name="commandType">Is it a stored proc or a batch?</param>
  1093. /// <returns>The first cell returned, as <see cref="object"/>.</returns>
  1094. public static Task<object> ExecuteScalarAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  1095. ExecuteScalarImplAsync<object>(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered));
  1096. /// <summary>
  1097. /// Execute parameterized SQL that selects a single value.
  1098. /// </summary>
  1099. /// <typeparam name="T">The type to return.</typeparam>
  1100. /// <param name="cnn">The connection to execute on.</param>
  1101. /// <param name="sql">The SQL to execute.</param>
  1102. /// <param name="param">The parameters to use for this command.</param>
  1103. /// <param name="transaction">The transaction to use for this command.</param>
  1104. /// <param name="commandTimeout">Number of seconds before command execution timeout.</param>
  1105. /// <param name="commandType">Is it a stored proc or a batch?</param>
  1106. /// <returns>The first cell returned, as <typeparamref name="T"/>.</returns>
  1107. public static Task<T> ExecuteScalarAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
  1108. ExecuteScalarImplAsync<T>(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered));
  1109. /// <summary>
  1110. /// Execute parameterized SQL that selects a single value.
  1111. /// </summary>
  1112. /// <param name="cnn">The connection to execute on.</param>
  1113. /// <param name="command">The command to execute.</param>
  1114. /// <returns>The first cell selected as <see cref="object"/>.</returns>
  1115. public static Task<object> ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) =>
  1116. ExecuteScalarImplAsync<object>(cnn, command);
  1117. /// <summary>
  1118. /// Execute parameterized SQL that selects a single value.
  1119. /// </summary>
  1120. /// <typeparam name="T">The type to return.</typeparam>
  1121. /// <param name="cnn">The connection to execute on.</param>
  1122. /// <param name="command">The command to execute.</param>
  1123. /// <returns>The first cell selected as <typeparamref name="T"/>.</returns>
  1124. public static Task<T> ExecuteScalarAsync<T>(this IDbConnection cnn, CommandDefinition command) =>
  1125. ExecuteScalarImplAsync<T>(cnn, command);
  1126. private static async Task<T> ExecuteScalarImplAsync<T>(IDbConnection cnn, CommandDefinition command)
  1127. {
  1128. Action<IDbCommand, object> paramReader = null;
  1129. object param = command.Parameters;
  1130. if (param != null)
  1131. {
  1132. var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType());
  1133. paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader;
  1134. }
  1135. DbCommand cmd = null;
  1136. bool wasClosed = cnn.State == ConnectionState.Closed;
  1137. object result;
  1138. try
  1139. {
  1140. cmd = command.TrySetupAsyncCommand(cnn, paramReader);
  1141. if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false);
  1142. result = await cmd.ExecuteScalarAsync(command.CancellationToken).ConfigureAwait(false);
  1143. command.OnCompleted();
  1144. }
  1145. finally
  1146. {
  1147. if (wasClosed) cnn.Close();
  1148. cmd?.Dispose();
  1149. }
  1150. return Parse<T>(result);
  1151. }
  1152. }
  1153. }