Commit dbd5455f authored by Marc Gravell's avatar Marc Gravell

Async cleanup; implement missing async methods based on multiple pull...

Async cleanup; implement missing async methods based on multiple pull requests, but introducing CommandDefinition to preserve binary compatibility while allowing new options
parent 885a8d46
...@@ -8,3 +8,4 @@ NuGet.exe ...@@ -8,3 +8,4 @@ NuGet.exe
*.user *.user
*.nupkg *.nupkg
.docstats .docstats
*.ide/
\ No newline at end of file
...@@ -22,6 +22,133 @@ ...@@ -22,6 +22,133 @@
namespace Dapper namespace Dapper
{ {
/// <summary>
/// Represents the key aspects of a sql operation
/// </summary>
public struct CommandDefinition
{
private readonly string commandText;
private readonly object parameters;
private readonly IDbTransaction transaction;
private readonly int? commandTimeout;
private readonly CommandType? commandType;
private readonly bool buffered;
/// <summary>
/// The command (sql or a stored-procedure name) to execute
/// </summary>
public string CommandText { get { return commandText; } }
/// <summary>
/// The parameters associated with the command
/// </summary>
public object Parameters { get { return parameters; } }
/// <summary>
/// The active transaction for the command
/// </summary>
public IDbTransaction Transaction { get { return transaction; } }
/// <summary>
/// The effective timeout for the command
/// </summary>
public int? CommandTimeout { get { return commandTimeout; } }
/// <summary>
/// The type of command that the command-text represents
/// </summary>
public CommandType? CommandType { get { return commandType; } }
/// <summary>
/// Should data be buffered before returning?
/// </summary>
public bool Buffered { get { return buffered; } }
/// <summary>
/// Initialize the command definition
/// </summary>
#if CSHARP30
public CommandDefinition(string commandText, object parameters, IDbTransaction transaction, int? commandTimeout,
CommandType? commandType, bool buffered)
#else
public CommandDefinition(string commandText, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null,
CommandType? commandType = null, bool buffered = true
#if ASYNC
, CancellationToken cancellationToken = default(CancellationToken)
#endif
)
#endif
{
this.commandText = commandText;
this.parameters = parameters;
this.transaction = transaction;
this.commandTimeout = commandTimeout;
this.commandType = commandType;
this.buffered = buffered;
#if ASYNC
this.cancellationToken = cancellationToken;
#endif
}
#if ASYNC
private readonly CancellationToken cancellationToken;
/// <summary>
/// For asynchronous operations, the cancellation-token
/// </summary>
public CancellationToken CancellationToken { get { return cancellationToken; } }
#endif
internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object> paramReader)
{
var cmd = cnn.CreateCommand();
var bindByName = GetBindByName(cmd.GetType());
if (bindByName != null) bindByName(cmd, true);
if (transaction != null)
cmd.Transaction = transaction;
cmd.CommandText = commandText;
if (commandTimeout.HasValue)
cmd.CommandTimeout = commandTimeout.Value;
if (commandType.HasValue)
cmd.CommandType = commandType.Value;
if (paramReader != null)
{
paramReader(cmd, parameters);
}
return cmd;
}
static SqlMapper.Link<Type, Action<IDbCommand, bool>> bindByNameCache;
static Action<IDbCommand, bool> GetBindByName(Type commandType)
{
if (commandType == null) return null; // GIGO
Action<IDbCommand, bool> action;
if (SqlMapper.Link<Type, Action<IDbCommand, bool>>.TryGet(bindByNameCache, commandType, out action))
{
return action;
}
var prop = commandType.GetProperty("BindByName", BindingFlags.Public | BindingFlags.Instance);
action = null;
ParameterInfo[] indexers;
MethodInfo setter;
if (prop != null && prop.CanWrite && prop.PropertyType == typeof(bool)
&& ((indexers = prop.GetIndexParameters()) == null || indexers.Length == 0)
&& (setter = prop.GetSetMethod()) != null
)
{
var method = new DynamicMethod(commandType.Name + "_BindByName", null, new Type[] { typeof(IDbCommand), typeof(bool) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldarg_1);
il.EmitCall(OpCodes.Callvirt, setter, null);
il.Emit(OpCodes.Ret);
action = (Action<IDbCommand, bool>)method.CreateDelegate(typeof(Action<IDbCommand, bool>));
}
// cache it
SqlMapper.Link<Type, Action<IDbCommand, bool>>.TryAdd(ref bindByNameCache, commandType, ref action);
return action;
}
}
/// <summary> /// <summary>
/// Dapper, a light weight object mapper for ADO.NET /// Dapper, a light weight object mapper for ADO.NET
/// </summary> /// </summary>
...@@ -113,43 +240,12 @@ public interface IMemberMap ...@@ -113,43 +240,12 @@ public interface IMemberMap
ParameterInfo Parameter { get; } ParameterInfo Parameter { get; }
} }
static Link<Type, Action<IDbCommand, bool>> bindByNameCache;
static Action<IDbCommand, bool> GetBindByName(Type commandType)
{
if (commandType == null) return null; // GIGO
Action<IDbCommand, bool> action;
if (Link<Type, Action<IDbCommand, bool>>.TryGet(bindByNameCache, commandType, out action))
{
return action;
}
var prop = commandType.GetProperty("BindByName", BindingFlags.Public | BindingFlags.Instance);
action = null;
ParameterInfo[] indexers;
MethodInfo setter;
if (prop != null && prop.CanWrite && prop.PropertyType == typeof(bool)
&& ((indexers = prop.GetIndexParameters()) == null || indexers.Length == 0)
&& (setter = prop.GetSetMethod()) != null
)
{
var method = new DynamicMethod(commandType.Name + "_BindByName", null, new Type[] { typeof(IDbCommand), typeof(bool) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldarg_1);
il.EmitCall(OpCodes.Callvirt, setter, null);
il.Emit(OpCodes.Ret);
action = (Action<IDbCommand, bool>)method.CreateDelegate(typeof(Action<IDbCommand, bool>));
}
// cache it
Link<Type, Action<IDbCommand, bool>>.TryAdd(ref bindByNameCache, commandType, ref action);
return action;
}
/// <summary> /// <summary>
/// This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example), /// This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example),
/// and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE** /// and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE**
/// equality. The type is fully thread-safe. /// equality. The type is fully thread-safe.
/// </summary> /// </summary>
partial class Link<TKey, TValue> where TKey : class internal partial class Link<TKey, TValue> where TKey : class
{ {
public static bool TryGet(Link<TKey, TValue> link, TKey key, out TValue value) public static bool TryGet(Link<TKey, TValue> link, TKey key, out TValue value)
{ {
...@@ -689,14 +785,28 @@ public static GridReader QueryMultiple(this IDbConnection cnn, string sql, objec ...@@ -689,14 +785,28 @@ public static GridReader QueryMultiple(this IDbConnection cnn, string sql, objec
#endif #endif
) )
{ {
IEnumerable multiExec = (object)param as IEnumerable; var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, true);
return ExecuteImpl(cnn, ref command);
}
/// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteImpl(cnn, ref command);
}
private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition command)
{
object param = command.Parameters;
IEnumerable multiExec = param as IEnumerable;
Identity identity; Identity identity;
CacheInfo info = null; CacheInfo info = null;
if (multiExec != null && !(multiExec is string)) if (multiExec != null && !(multiExec is string))
{ {
bool isFirst = true; bool isFirst = true;
int total = 0; int total = 0;
using (var cmd = SetupCommand(cnn, transaction, sql, null, null, commandTimeout, commandType)) using (var cmd = command.SetupCommand(cnn, null))
{ {
string masterSql = null; string masterSql = null;
...@@ -706,7 +816,7 @@ public static GridReader QueryMultiple(this IDbConnection cnn, string sql, objec ...@@ -706,7 +816,7 @@ public static GridReader QueryMultiple(this IDbConnection cnn, string sql, objec
{ {
masterSql = cmd.CommandText; masterSql = cmd.CommandText;
isFirst = false; isFirst = false;
identity = new Identity(sql, cmd.CommandType, cnn, null, obj.GetType(), null); identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
info = GetCacheInfo(identity); info = GetCacheInfo(identity);
} }
else else
...@@ -722,12 +832,12 @@ public static GridReader QueryMultiple(this IDbConnection cnn, string sql, objec ...@@ -722,12 +832,12 @@ public static GridReader QueryMultiple(this IDbConnection cnn, string sql, objec
} }
// nice and simple // nice and simple
if ((object)param != null) if (param != null)
{ {
identity = new Identity(sql, commandType, cnn, null, (object)param == null ? null : ((object)param).GetType(), null); identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
info = GetCacheInfo(identity); info = GetCacheInfo(identity);
} }
return ExecuteCommand(cnn, transaction, sql, (object)param == null ? null : info.ParamReader, (object)param, commandTimeout, commandType); return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader);
} }
#if !CSHARP30 #if !CSHARP30
/// <summary> /// <summary>
...@@ -794,10 +904,26 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn ...@@ -794,10 +904,26 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn
#endif #endif
) )
{ {
var data = QueryInternal<T>(cnn, sql, param as object, transaction, commandTimeout, commandType); var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered);
return buffered ? data.ToList() : data; var data = QueryImpl<T>(cnn, command);
return command.Buffered ? data.ToList() : data;
} }
/// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
/// <remarks>the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object</remarks>
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition command)
{
var data = QueryImpl<T>(cnn, command);
return command.Buffered ? data.ToList() : data;
}
/// <summary> /// <summary>
/// Execute a command that returns multiple result sets, and access each in turn /// Execute a command that returns multiple result sets, and access each in turn
/// </summary> /// </summary>
...@@ -809,7 +935,20 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn ...@@ -809,7 +935,20 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn
#endif #endif
) )
{ {
Identity identity = new Identity(sql, commandType, cnn, typeof(GridReader), (object)param == null ? null : ((object)param).GetType(), null); var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, true);
return QueryMultipleImpl(cnn, ref command);
}
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static GridReader QueryMultiple(this IDbConnection cnn, CommandDefinition command)
{
return QueryMultipleImpl(cnn, ref command);
}
private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandDefinition command)
{
object param = command.Parameters;
Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null);
CacheInfo info = GetCacheInfo(identity); CacheInfo info = GetCacheInfo(identity);
IDbCommand cmd = null; IDbCommand cmd = null;
...@@ -818,7 +957,7 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn ...@@ -818,7 +957,7 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn
try try
{ {
if (wasClosed) cnn.Open(); if (wasClosed) cnn.Open();
cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType); cmd = command.SetupCommand(cnn, info.ParamReader);
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default); reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
var result = new GridReader(cmd, reader, identity); var result = new GridReader(cmd, reader, identity);
...@@ -842,12 +981,10 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn ...@@ -842,12 +981,10 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn
} }
} }
/// <summary> private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command)
/// Return a typed list of objects, reader is closed after the call
/// </summary>
private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType)
{ {
var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null); object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity); var info = GetCacheInfo(identity);
IDbCommand cmd = null; IDbCommand cmd = null;
...@@ -856,7 +993,7 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq ...@@ -856,7 +993,7 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
bool wasClosed = cnn.State == ConnectionState.Closed; bool wasClosed = cnn.State == ConnectionState.Closed;
try try
{ {
cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType); cmd = command.SetupCommand(cnn, info.ParamReader);
if (wasClosed) cnn.Open(); if (wasClosed) cnn.Open();
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default); reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
...@@ -1063,16 +1200,18 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq ...@@ -1063,16 +1200,18 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
#endif #endif
partial class DontMap { } partial class DontMap { }
static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>( static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(
this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType) this IDbConnection cnn, string sql, Delegate map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType)
{ {
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, sql, map, param, transaction, splitOn, commandTimeout, commandType, null, null); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered);
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, command, map, splitOn, null, null);
return buffered ? results.ToList() : results; return buffered ? results.ToList() : results;
} }
static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, string splitOn, int? commandTimeout, CommandType? commandType, IDataReader reader, Identity identity) static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity)
{ {
identity = identity ?? new Identity(sql, commandType, cnn, typeof(TFirst), (object)param == null ? null : ((object)param).GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }); object param = command.Parameters;
identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
CacheInfo cinfo = GetCacheInfo(identity); CacheInfo cinfo = GetCacheInfo(identity);
IDbCommand ownedCommand = null; IDbCommand ownedCommand = null;
...@@ -1083,7 +1222,7 @@ partial class DontMap { } ...@@ -1083,7 +1222,7 @@ partial class DontMap { }
{ {
if (reader == null) if (reader == null)
{ {
ownedCommand = SetupCommand(cnn, transaction, sql, cinfo.ParamReader, (object)param, commandTimeout, commandType); ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader);
if (wasClosed) cnn.Open(); if (wasClosed) cnn.Open();
ownedReader = ownedCommand.ExecuteReader(); ownedReader = ownedCommand.ExecuteReader();
reader = ownedReader; reader = ownedReader;
...@@ -1502,7 +1641,7 @@ IEnumerator IEnumerable.GetEnumerator() ...@@ -1502,7 +1641,7 @@ IEnumerator IEnumerable.GetEnumerator()
return GetEnumerator(); return GetEnumerator();
} }
#region Implementation of ICollection<KeyValuePair<string,object>> #region Implementation of ICollection<KeyValuePair<string,object>>
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{ {
...@@ -1541,9 +1680,9 @@ IEnumerator IEnumerable.GetEnumerator() ...@@ -1541,9 +1680,9 @@ IEnumerator IEnumerable.GetEnumerator()
get { return false; } get { return false; }
} }
#endregion #endregion
#region Implementation of IDictionary<string,object> #region Implementation of IDictionary<string,object>
bool IDictionary<string, object>.ContainsKey(string key) bool IDictionary<string, object>.ContainsKey(string key)
{ {
...@@ -1612,7 +1751,7 @@ private object SetValue(string key, object value, bool isAdd) ...@@ -1612,7 +1751,7 @@ private object SetValue(string key, object value, bool isAdd)
get { return this.Select(kv => kv.Value).ToArray(); } get { return this.Select(kv => kv.Value).ToArray(); }
} }
#endregion #endregion
} }
#endif #endif
private const string MultiMapSplitExceptionMessage = "When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id"; private const string MultiMapSplitExceptionMessage = "When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id";
...@@ -2078,33 +2217,13 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn ...@@ -2078,33 +2217,13 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn
return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>)); return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>));
} }
private static IDbCommand SetupCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType) private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action<IDbCommand, object> paramReader)
{
var cmd = cnn.CreateCommand();
var bindByName = GetBindByName(cmd.GetType());
if (bindByName != null) bindByName(cmd, true);
if (transaction != null)
cmd.Transaction = transaction;
cmd.CommandText = sql;
if (commandTimeout.HasValue)
cmd.CommandTimeout = commandTimeout.Value;
if (commandType.HasValue)
cmd.CommandType = commandType.Value;
if (paramReader != null)
{
paramReader(cmd, obj);
}
return cmd;
}
private static int ExecuteCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType)
{ {
IDbCommand cmd = null; IDbCommand cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed; bool wasClosed = cnn.State == ConnectionState.Closed;
try try
{ {
cmd = SetupCommand(cnn, transaction, sql, paramReader, obj, commandTimeout, commandType); cmd = command.SetupCommand(cnn, paramReader);
if (wasClosed) cnn.Open(); if (wasClosed) cnn.Open();
return cmd.ExecuteNonQuery(); return cmd.ExecuteNonQuery();
} }
...@@ -2760,7 +2879,7 @@ public IEnumerable<T> Read<T>(bool buffered = true) ...@@ -2760,7 +2879,7 @@ public IEnumerable<T> Read<T>(bool buffered = true)
return buffered ? result.ToList() : result; return buffered ? result.ToList() : result;
} }
private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(object func, string splitOn) private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Delegate func, string splitOn)
{ {
var identity = this.identity.ForGrid(typeof(TReturn), new Type[] { var identity = this.identity.ForGrid(typeof(TReturn), new Type[] {
typeof(TFirst), typeof(TFirst),
...@@ -2773,7 +2892,7 @@ public IEnumerable<T> Read<T>(bool buffered = true) ...@@ -2773,7 +2892,7 @@ public IEnumerable<T> Read<T>(bool buffered = true)
}, gridIndex); }, gridIndex);
try try
{ {
foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, null, func, null, null, splitOn, null, null, reader, identity)) foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default(CommandDefinition), func, splitOn, reader, identity))
{ {
yield return r; yield return r;
} }
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>TRACE;DEBUG;ASYNC</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath> <OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE;ASYNC</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Dapper.xml</DocumentationFile> <DocumentationFile>bin\Release\Dapper.xml</DocumentationFile>
......
...@@ -3,24 +3,77 @@ ...@@ -3,24 +3,77 @@
using System.Data; using System.Data;
using System.Data.Common; using System.Data.Common;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Dapper namespace Dapper
{ {
public static partial class SqlMapper public static partial class SqlMapper
{ {
/// <summary> /// <summary>
/// Execute a query asynchronously using .NET 4.5 Task. /// Execute a query asynchronously using .NET 4.5 Task.
/// </summary> /// </summary>
public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) public static Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryAsync<T>(cnn, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, true, default(CancellationToken)));
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, CommandDefinition command)
{ {
var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null); object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity); var info = GetCacheInfo(identity);
var cmd = (DbCommand)SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType); bool wasClosed = cnn.State == ConnectionState.Closed;
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
{
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync();
using (var reader = await cmd.ExecuteReaderAsync(command.CancellationToken))
{
return ExecuteReader<T>(reader, identity, info).ToList();
}
}
finally
{
if (wasClosed) cnn.Close();
}
}
}
/// <summary>
/// Execute a command asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<int> ExecuteAsync(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return ExecuteAsync(cnn, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, true, default(CancellationToken)));
}
using (var reader = await cmd.ExecuteReaderAsync()) /// <summary>
/// Execute a command asynchronously using .NET 4.5 Task.
/// </summary>
public static async Task<int> ExecuteAsync(this IDbConnection cnn, CommandDefinition command)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity);
bool wasClosed = cnn.State == ConnectionState.Closed;
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
{ {
return ExecuteReader<T>(reader, identity, info).ToList(); try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync();
return await cmd.ExecuteNonQueryAsync(command.CancellationToken);
}
finally
{
if (wasClosed) cnn.Close();
}
} }
} }
...@@ -36,13 +89,30 @@ public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, s ...@@ -36,13 +89,30 @@ public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, s
/// <param name="param"></param> /// <param name="param"></param>
/// <param name="transaction"></param> /// <param name="transaction"></param>
/// <param name="buffered"></param> /// <param name="buffered"></param>
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param> /// <param name="splitOn">The field we should split and read the second object from (default: id)</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param> /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType">Is it a stored proc or a batch?</param> /// <param name="commandType">Is it a stored proc or a batch?</param>
/// <returns></returns> /// <returns></returns>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{ {
return MultiMapAsync<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); return MultiMapAsync<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered, default(CancellationToken)), map, splitOn);
}
/// <summary>
/// Maps a query to objects
/// </summary>
/// <typeparam name="TFirst">The first type in the recordset</typeparam>
/// <typeparam name="TSecond">The second type in the recordset</typeparam>
/// <typeparam name="TReturn">The return type</typeparam>
/// <param name="cnn"></param>
/// <param name="splitOn">The field we should split and read the second object from (default: id)</param>
/// <param name="command">The command to execute</param>
/// <param name="map"></param>
/// <returns></returns>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TReturn> map, string splitOn = "Id")
{
return MultiMapAsync<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
} }
/// <summary> /// <summary>
...@@ -62,42 +132,36 @@ public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, s ...@@ -62,42 +132,36 @@ public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, s
/// <param name="commandTimeout">Number of seconds before command execution timeout</param> /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType"></param> /// <param name="commandType"></param>
/// <returns></returns> /// <returns></returns>
public static async Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{ {
return await MultiMapAsync<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); return MultiMapAsync<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered, default(CancellationToken)), map, splitOn);
} }
/// <summary> /// <summary>
/// Perform a multi mapping query with 4 input parameters /// Maps a query to objects
/// </summary> /// </summary>
/// <typeparam name="TFirst"></typeparam> /// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam> /// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam> /// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TReturn"></typeparam> /// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param> /// <param name="cnn"></param>
/// <param name="sql"></param> /// <param name="splitOn">The field we should split and read the second object from (default: id)</param>
/// <param name="command">The command to execute</param>
/// <param name="map"></param> /// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns> /// <returns></returns>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TThird, TReturn> map, string splitOn = "Id")
{ {
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); return MultiMapAsync<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
} }
/// <summary> /// <summary>
/// Perform a multi mapping query with 5 input parameters /// Perform a multi mapping query with 4 input parameters
/// </summary> /// </summary>
/// <typeparam name="TFirst"></typeparam> /// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam> /// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam> /// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam> /// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TFifth"></typeparam>
/// <typeparam name="TReturn"></typeparam> /// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param> /// <param name="cnn"></param>
/// <param name="sql"></param> /// <param name="sql"></param>
...@@ -109,71 +173,99 @@ public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, s ...@@ -109,71 +173,99 @@ public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, s
/// <param name="commandTimeout"></param> /// <param name="commandTimeout"></param>
/// <param name="commandType"></param> /// <param name="commandType"></param>
/// <returns></returns> /// <returns></returns>
public static async Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{ {
return await MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); return MultiMapAsync<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered, default(CancellationToken)), map, splitOn);
} }
/// <summary> /// <summary>
/// Perform a multi mapping query with 6 input parameters /// Perform a multi mapping query with 4 input parameters
/// </summary> /// </summary>
/// <typeparam name="TFirst"></typeparam> /// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam> /// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam> /// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam> /// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TFifth"></typeparam>
/// <typeparam name="TSixth"></typeparam>
/// <typeparam name="TReturn"></typeparam> /// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param> /// <param name="cnn"></param>
/// <param name="sql"></param> /// <param name="splitOn">The field we should split and read the second object from (default: id)</param>
/// <param name="command">The command to execute</param>
/// <param name="map"></param> /// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns> /// <returns></returns>
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")
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
}
/// <summary>
/// Perform a multi mapping query with 5 input parameters
/// </summary>
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, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered, default(CancellationToken)), map, splitOn);
}
/// <summary>
/// Perform a multi mapping query with 5 input parameters
/// </summary>
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")
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
}
/// <summary>
/// Perform a multi mapping query with 6 input parameters
/// </summary>
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, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) 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, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{ {
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(cnn,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered, default(CancellationToken)), map, splitOn);
}
/// <summary>
/// Perform a multi mapping query with 6 input parameters
/// </summary>
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")
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(cnn, command, map, splitOn);
} }
/// <summary> /// <summary>
/// Perform a multi mapping query with 7 input parameters /// Perform a multi mapping query with 7 input parameters
/// </summary> /// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TFifth"></typeparam>
/// <typeparam name="TSixth"></typeparam>
/// <typeparam name="TSeventh"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
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, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) 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, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{ {
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered, default(CancellationToken)), map, splitOn);
} }
static async Task<IEnumerable<TReturn>> MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType) /// <summary>
/// Perform a multi mapping query with 7 input parameters
/// </summary>
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")
{ {
var identity = new Identity(sql, commandType, cnn, typeof(TFirst), (object)param == null ? null : ((object)param).GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }); return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, command, map, splitOn);
}
private static async Task<IEnumerable<TReturn>> MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
var info = GetCacheInfo(identity); var info = GetCacheInfo(identity);
var cmd = (DbCommand)SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType); bool wasClosed = cnn.State == ConnectionState.Closed;
using (var reader = await cmd.ExecuteReaderAsync()) try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync();
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
using (var reader = await cmd.ExecuteReaderAsync(command.CancellationToken))
{
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default(CommandDefinition), map, splitOn, reader, identity);
return command.Buffered ? results.ToList() : results;
}
} finally
{ {
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, null, map, null, null, splitOn, null, null, reader, identity); if (wasClosed) cnn.Close();
return buffered ? results.ToList() : results;
} }
} }
...@@ -194,5 +286,61 @@ private static IEnumerable<T> ExecuteReader<T>(IDataReader reader, Identity iden ...@@ -194,5 +286,61 @@ private static IEnumerable<T> ExecuteReader<T>(IDataReader reader, Identity iden
yield return (T)func(reader); yield return (T)func(reader);
} }
} }
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static Task<GridReader> QueryMultipleAsync(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, true);
return QueryMultipleAsync(cnn, command);
}
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command)
{
object param = command.Parameters;
Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null);
CacheInfo info = GetCacheInfo(identity);
DbCommand cmd = null;
IDataReader reader = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync();
cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader);
reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default, command.CancellationToken);
var result = new GridReader(cmd, reader, identity);
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
// with the CloseConnection flag, so the reader will deal with the connection; we
// still need something in the "finally" to ensure that broken SQL still results
// in the connection closing itself
return result;
}
catch
{
if (reader != null)
{
if (!reader.IsClosed)
try
{ cmd.Cancel(); }
catch
{ /* don't spoil the existing exception */ }
reader.Dispose();
}
if (cmd != null) cmd.Dispose();
if (wasClosed) cnn.Close();
throw;
}
}
} }
} }
\ No newline at end of file
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<AssemblyName>DapperTests NET45</AssemblyName> <AssemblyName>DapperTests NET45</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
......
using System; using System;
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Linq;
using System.Reflection; using System.Reflection;
namespace DapperTests_NET45 namespace DapperTests_NET45
...@@ -21,14 +22,29 @@ public static SqlConnection GetOpenConnection() ...@@ -21,14 +22,29 @@ public static SqlConnection GetOpenConnection()
connection.Open(); connection.Open();
return connection; return connection;
} }
public static SqlConnection GetClosedConnection()
{
return new SqlConnection(connectionString);
}
private static void RunTests() private static void RunTests()
{ {
var tester = new Tests(); var tester = new Tests();
foreach (var method in typeof(Tests).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) foreach (var method in typeof(Tests).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{ {
Console.Write("Running " + method.Name); Console.Write("Running " + method.Name);
method.Invoke(tester, null); try
Console.WriteLine(" - OK!"); {
method.Invoke(tester, null);
Console.WriteLine(" - OK!");
} catch(TargetInvocationException ex)
{
var inner = ex.InnerException;
if(inner is AggregateException && ((AggregateException)inner).InnerExceptions.Count == 1)
{
inner = ((AggregateException)inner).InnerExceptions.Single();
}
Console.WriteLine(" - ERR: " + inner.Message);
}
} }
} }
} }
......
...@@ -16,6 +16,16 @@ public void TestBasicStringUsageAsync() ...@@ -16,6 +16,16 @@ public void TestBasicStringUsageAsync()
} }
} }
public void TestBasicStringUsageClosedAsync()
{
using (var connection = Program.GetClosedConnection())
{
var query = connection.QueryAsync<string>("select 'abc' as [Value] union all select @txt", new { txt = "def" });
var arr = query.Result.ToArray();
arr.IsSequenceEqualTo(new[] { "abc", "def" });
}
}
public void TestClassWithStringUsageAsync() public void TestClassWithStringUsageAsync()
{ {
using (var connection = Program.GetOpenConnection()) using (var connection = Program.GetOpenConnection())
...@@ -26,6 +36,25 @@ public void TestClassWithStringUsageAsync() ...@@ -26,6 +36,25 @@ public void TestClassWithStringUsageAsync()
} }
} }
public void TestExecuteAsync()
{
using (var connection = Program.GetOpenConnection())
{
var query = connection.ExecuteAsync("declare @foo table(id int not null); insert @foo values(@id);", new { id = 1 });
var val = query.Result;
val.Equals(1);
}
}
public void TestExecuteClosedConnAsync()
{
using (var connection = Program.GetClosedConnection())
{
var query = connection.ExecuteAsync("declare @foo table(id int not null); insert @foo values(@id);", new { id = 1 });
var val = query.Result;
val.Equals(1);
}
}
public void TestMultiMapWithSplitAsync() public void TestMultiMapWithSplitAsync()
{ {
var sql = @"select 1 as id, 'abc' as name, 2 as id, 'def' as name"; var sql = @"select 1 as id, 'abc' as name, 2 as id, 'def' as name";
...@@ -46,6 +75,49 @@ public void TestMultiMapWithSplitAsync() ...@@ -46,6 +75,49 @@ public void TestMultiMapWithSplitAsync()
} }
} }
public void TestMultiMapWithSplitClosedConnAsync()
{
var sql = @"select 1 as id, 'abc' as name, 2 as id, 'def' as name";
using (var connection = Program.GetClosedConnection())
{
var productQuery = connection.QueryAsync<Product, Category, Product>(sql, (prod, cat) =>
{
prod.Category = cat;
return prod;
});
var product = productQuery.Result.First();
// assertions
product.Id.IsEqualTo(1);
product.Name.IsEqualTo("abc");
product.Category.Id.IsEqualTo(2);
product.Category.Name.IsEqualTo("def");
}
}
public void TestMultiAsync()
{
using(var conn = Program.GetOpenConnection())
{
using(Dapper.SqlMapper.GridReader multi = conn.QueryMultipleAsync("select 1; select 2").Result)
{
multi.Read<int>().Single().IsEqualTo(1);
multi.Read<int>().Single().IsEqualTo(2);
}
}
}
public void TestMultiClosedConnAsync()
{
using (var conn = Program.GetClosedConnection())
{
using (Dapper.SqlMapper.GridReader multi = conn.QueryMultipleAsync("select 1; select 2").Result)
{
multi.Read<int>().Single().IsEqualTo(1);
multi.Read<int>().Single().IsEqualTo(2);
}
}
}
class Product class Product
{ {
public int Id { get; set; } public int Id { get; set; }
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
<TargetFrameworkProfile> <TargetFrameworkProfile>
</TargetFrameworkProfile> </TargetFrameworkProfile>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
using System.Data.SqlClient; using System.Data.SqlClient;
using System.Reflection; using System.Reflection;
using System.Linq; using System.Linq;
using System.Collections.Generic;
namespace SqlMapper namespace SqlMapper
{ {
[ServiceStack.DataAnnotations.Alias("Posts")] [ServiceStack.DataAnnotations.Alias("Posts")]
...@@ -116,6 +118,7 @@ private static void RunTests() ...@@ -116,6 +118,7 @@ private static void RunTests()
MethodInfo[] methods = typeof(Tests).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); MethodInfo[] methods = typeof(Tests).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
var activeTests = methods.Where(m => Attribute.IsDefined(m, typeof(ActiveTestAttribute))).ToArray(); var activeTests = methods.Where(m => Attribute.IsDefined(m, typeof(ActiveTestAttribute))).ToArray();
if (activeTests.Length != 0) methods = activeTests; if (activeTests.Length != 0) methods = activeTests;
List<string> failNames = new List<string>();
foreach (var method in methods) foreach (var method in methods)
{ {
Console.Write("Running " + method.Name); Console.Write("Running " + method.Name);
...@@ -127,6 +130,7 @@ private static void RunTests() ...@@ -127,6 +130,7 @@ private static void RunTests()
{ {
fail++; fail++;
Console.WriteLine(" - " + tie.InnerException.Message); Console.WriteLine(" - " + tie.InnerException.Message);
failNames.Add(method.Name);
}catch (Exception ex) }catch (Exception ex)
{ {
...@@ -142,6 +146,10 @@ private static void RunTests() ...@@ -142,6 +146,10 @@ private static void RunTests()
else else
{ {
Console.WriteLine("#### FAILED: {0}", fail); Console.WriteLine("#### FAILED: {0}", fail);
foreach(var failName in failNames)
{
Console.WriteLine(failName);
}
} }
} }
} }
......
...@@ -659,6 +659,7 @@ public void TestExpandWithNullableFields() ...@@ -659,6 +659,7 @@ public void TestExpandWithNullableFields()
((int?)row.B) ((int?)row.B)
.IsEqualTo(2); .IsEqualTo(2);
} }
public void TestEnumeration() public void TestEnumeration()
{ {
var en = connection.Query<int>("select 1 as one union all select 2 as one", buffered: false); var en = connection.Query<int>("select 1 as one union all select 2 as one", buffered: false);
...@@ -678,7 +679,7 @@ public void TestEnumeration() ...@@ -678,7 +679,7 @@ public void TestEnumeration()
while (i.MoveNext()) while (i.MoveNext())
{ } { }
// should not exception, since enumertated // should not exception, since enumerated
en = connection.Query<int>("select 1 as one", buffered: false); en = connection.Query<int>("select 1 as one", buffered: false);
gotException.IsTrue(); gotException.IsTrue();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment