/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

Large files are truncated click here to view the full 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>…