Commit 4f3a04cf authored by Marc Gravell's avatar Marc Gravell

Merge pull request #412 from StackExchange/QueryFirstOrDefault

Add {Query|Read}FirstOrDefault methods with optimized paths
parents 337af552 5708d498
......@@ -78,7 +78,7 @@ public static partial class SqlMapperExtensions
/// <param name="transaction">The transaction to run under, null (the defualt) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>Entity of T</returns>
public static async Task<IEnumerable<T>> GetAllAsync<T>(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
public static Task<IEnumerable<T>> GetAllAsync<T>(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var type = typeof(T);
var cacheType = typeof(List<T>);
......@@ -95,9 +95,12 @@ public static partial class SqlMapperExtensions
if (!type.IsInterface())
{
return await connection.QueryAsync<T>(sql, null, transaction, commandTimeout);
return connection.QueryAsync<T>(sql, null, transaction, commandTimeout);
}
return GetAllAsyncImpl<T>(connection, transaction, commandTimeout, sql, type);
}
private static async Task<IEnumerable<T>> GetAllAsyncImpl<T>(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string sql, Type type) where T : class
{
var result = await connection.QueryAsync(sql);
var list = new List<T>();
foreach (IDictionary<string, object> res in result)
......@@ -124,7 +127,7 @@ public static partial class SqlMapperExtensions
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="sqlAdapter">The specific ISqlAdapter to use, auto-detected based on connection if null</param>
/// <returns>Identity of inserted entity</returns>
public static async Task<int> InsertAsync<T>(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null,
public static Task<int> InsertAsync<T>(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null,
int? commandTimeout = null, ISqlAdapter sqlAdapter = null) where T : class
{
var type = typeof(T);
......@@ -164,13 +167,13 @@ public static partial class SqlMapperExtensions
if (!isList) //single entity
{
return await sqlAdapter.InsertAsync(connection, transaction, commandTimeout, name, sbColumnList.ToString(),
return sqlAdapter.InsertAsync(connection, transaction, commandTimeout, name, sbColumnList.ToString(),
sbParameterList.ToString(), keyProperties, entityToInsert);
}
//insert list of entities
var cmd = $"INSERT INTO {name} ({sbColumnList}) values ({sbParameterList})";
return await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout);
return connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout);
}
/// <summary>
......
......@@ -5,7 +5,7 @@
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0",
"summary": "A high performance Micro-ORM",
"description": "A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..",
"version": "1.50-beta3",
"version": "1.50-beta4",
"title": "Dapper dot net (strong named)",
"tags": [ "orm", "sql", "micro-orm" ],
"copyright": "2015 Stack Exchange, Inc.",
......
......@@ -130,10 +130,13 @@ public void Run(int iterations)
var mapperConnection = TestSuite.GetOpenConnection();
tests.Add(id => mapperConnection.Query<Post>("select * from Posts where Id = @Id", new { Id = id }, buffered: true).First(), "Mapper Query (buffered)");
tests.Add(id => mapperConnection.Query<Post>("select * from Posts where Id = @Id", new { Id = id }, buffered: false).First(), "Mapper Query (non-buffered)");
tests.Add(id => mapperConnection.QueryFirstOrDefault<Post>("select * from Posts where Id = @Id", new { Id = id }), "Mapper QueryFirstOrDefault");
var mapperConnection2 = TestSuite.GetOpenConnection();
tests.Add(id => mapperConnection2.Query("select * from Posts where Id = @Id", new { Id = id }, buffered: true).First(), "Dynamic Mapper Query (buffered)");
tests.Add(id => mapperConnection2.Query("select * from Posts where Id = @Id", new { Id = id }, buffered: false).First(), "Dynamic Mapper Query (non-buffered)");
tests.Add(id => mapperConnection2.QueryFirstOrDefault("select * from Posts where Id = @Id", new { Id = id }), "Dynamic Mapper QueryQueryFirstOrDefault");
// dapper.contrib
var mapperConnection3 = TestSuite.GetOpenConnection();
......
......@@ -156,6 +156,19 @@ public async Task TestMultiAsync()
}
}
[Fact]
public async Task TestMultiAsyncViaFirstOrDefault()
{
using (SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select 1; select 2; select 3; select 4; select 5"))
{
multi.ReadFirstOrDefaultAsync<int>().Result.IsEqualTo(1);
multi.ReadAsync<int>().Result.Single().IsEqualTo(2);
multi.ReadFirstOrDefaultAsync<int>().Result.IsEqualTo(3);
multi.ReadAsync<int>().Result.Single().IsEqualTo(4);
multi.ReadFirstOrDefaultAsync<int>().Result.IsEqualTo(5);
}
}
[Fact]
public async Task TestMultiClosedConnAsync()
{
......@@ -166,6 +179,19 @@ public async Task TestMultiClosedConnAsync()
}
}
[Fact]
public async Task TestMultiClosedConnAsyncViaFirstOrDefault()
{
using (SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select 1; select 2; select 3; select 4; select 5;"))
{
multi.ReadFirstOrDefaultAsync<int>().Result.IsEqualTo(1);
multi.ReadAsync<int>().Result.Single().IsEqualTo(2);
multi.ReadFirstOrDefaultAsync<int>().Result.IsEqualTo(3);
multi.ReadAsync<int>().Result.Single().IsEqualTo(4);
multi.ReadFirstOrDefaultAsync<int>().Result.IsEqualTo(5);
}
}
#if EXTERNALS
[Fact]
public async Task ExecuteReaderOpenAsync()
......@@ -337,6 +363,19 @@ public async Task TypeBasedViaTypeAsync()
b.IsEqualTo("abc");
}
[Fact]
public async Task TypeBasedViaTypeAsyncFirstOrDefault()
{
Type type = GetSomeType();
dynamic actual = (await marsConnection.QueryFirstOrDefaultAsync(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }));
((object)actual).GetType().IsEqualTo(type);
int a = actual.A;
string b = actual.B;
a.IsEqualTo(123);
b.IsEqualTo("abc");
}
[Fact]
public async Task Issue22_ExecuteScalarAsync()
{
......@@ -562,7 +601,26 @@ public async Task TestSubsequentQueriesSuccessAsync()
class AsyncFoo0 { public int Id { get; set; } }
class AsyncFoo1 { public int Id { get; set; } }
class AsyncFoo2 { public int Id { get; set; } }
[Fact]
public async Task TestSchemaChangedViaFirstOrDefaultAsync()
{
await connection.ExecuteAsync("create table #dog(Age int, Name nvarchar(max)) insert #dog values(1, 'Alf')");
try
{
var d = await connection.QueryFirstOrDefaultAsync<Dog>("select * from #dog");
d.Name.IsEqualTo("Alf");
d.Age.IsEqualTo(1);
connection.Execute("alter table #dog drop column Name");
d = await connection.QueryFirstOrDefaultAsync<Dog>("select * from #dog");
d.Name.IsNull();
d.Age.IsEqualTo(1);
}
finally
{
await connection.ExecuteAsync("drop table #dog");
}
}
[Fact]
public async Task TestMultiMapArbitraryMapsAsync()
......
......@@ -258,6 +258,26 @@ public void TestSchemaChanged()
}
}
[Fact]
public void TestSchemaChangedViaFirstOrDefault()
{
connection.Execute("create table #dog(Age int, Name nvarchar(max)) insert #dog values(1, 'Alf')");
try
{
var d = connection.QueryFirstOrDefault<Dog>("select * from #dog");
d.Name.IsEqualTo("Alf");
d.Age.IsEqualTo(1);
connection.Execute("alter table #dog drop column Name");
d = connection.QueryFirstOrDefault<Dog>("select * from #dog");
d.Name.IsNull();
d.Age.IsEqualTo(1);
}
finally
{
connection.Execute("drop table #dog");
}
}
[Fact]
public void TestSchemaChangedMultiMap()
{
......@@ -845,6 +865,8 @@ public void TestFastExpandoSupportsIDictionary()
public void TestDapperSetsPrivates()
{
connection.Query<PrivateDan>("select 'one' ShadowInDB").First().Shadow.IsEqualTo(1);
connection.QueryFirstOrDefault<PrivateDan>("select 'one' ShadowInDB").Shadow.IsEqualTo(1);
}
class PrivateDan
......@@ -1631,6 +1653,25 @@ public void TestChangingDefaultStringTypeMappingToAnsiString()
Dapper.SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String
}
[Fact]
public void TestChangingDefaultStringTypeMappingToAnsiStringFirstOrDefault()
{
var sql = "SELECT SQL_VARIANT_PROPERTY(CONVERT(sql_variant, @testParam),'BaseType') AS BaseType";
var param = new { testParam = "TestString" };
var result01 = connection.QueryFirstOrDefault<string>(sql, param);
result01.IsEqualTo("nvarchar");
Dapper.SqlMapper.PurgeQueryCache();
Dapper.SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString); // Change Default String Handling to AnsiString
var result02 = connection.QueryFirstOrDefault<string>(sql, param);
result02.IsEqualTo("varchar");
Dapper.SqlMapper.PurgeQueryCache();
Dapper.SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String
}
#if COREFX
class TransactedConnection : IDbConnection
{
......
......@@ -24,35 +24,61 @@ public static partial class SqlMapper
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static async Task<IEnumerable<dynamic>> QueryAsync(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
public static Task<IEnumerable<dynamic>> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return await QueryAsync<DapperRow>(cnn, typeof(DapperRow), new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))).ConfigureAwait(false);
return QueryAsync<dynamic>(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static async Task<IEnumerable<dynamic>> QueryAsync(this IDbConnection cnn, CommandDefinition command)
public static Task<IEnumerable<dynamic>> QueryAsync(this IDbConnection cnn, CommandDefinition command)
{
return await QueryAsync<DapperRow>(cnn, typeof(DapperRow), command).ConfigureAwait(false);
return QueryAsync<dynamic>(cnn, typeof(DapperRow), command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static Task<dynamic> QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command)
{
return QueryFirstOrDefaultAsync<dynamic>(cnn, typeof(DapperRow), command);
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
public static 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, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryAsync<T>(cnn, typeof(T), new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
return QueryAsync<T>(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<T> QueryFirstOrDefaultAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryFirstOrDefaultAsync<T>(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<IEnumerable<object>> QueryAsync(this IDbConnection cnn, Type type, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
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)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return QueryAsync<object>(cnn, type, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
return QueryAsync<object>(cnn, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return QueryFirstOrDefaultAsync<object>(cnn, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
......@@ -70,6 +96,13 @@ public static Task<IEnumerable<object>> QueryAsync(this IDbConnection cnn, Type
{
return QueryAsync<object>(cnn, type, command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command)
{
return QueryFirstOrDefaultAsync<object>(cnn, type, command);
}
private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
{
......@@ -133,13 +166,63 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn,
}
}
}
private static async Task<T> QueryFirstOrDefaultAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null);
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
var cancel = command.CancellationToken;
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
{
DbDataReader reader = null;
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(cancel).ConfigureAwait(false);
reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess, cancel).ConfigureAwait(false);
T result = default(T);
if(await reader.ReadAsync(cancel).ConfigureAwait(false))
{
var tuple = info.Deserializer;
int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash)
{
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
if (command.AddToCache) SetQueryCache(identity, info);
}
var func = tuple.Func;
object val = func(reader);
if (val == null || val is T)
{
result = (T)val;
}
else
{
var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
}
while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { }
}
while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { }
return result;
}
finally
{
using (reader) { } // dispose if non-null
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)
public static Task<int> ExecuteAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return ExecuteAsync(cnn, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
return ExecuteAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
}
/// <summary>
......@@ -299,10 +382,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType">Is it a stored proc or a batch?</param>
/// <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, object 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,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <summary>
......@@ -338,10 +421,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType"></param>
/// <returns></returns>
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)
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)
{
return MultiMapAsync<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <summary>
......@@ -379,10 +462,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <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, 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)
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <summary>
......@@ -406,10 +489,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
/// <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)
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <summary>
......@@ -423,10 +506,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
/// <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, object 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,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <summary>
......@@ -440,10 +523,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
/// <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, 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, object 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,
new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <summary>
......@@ -491,9 +574,9 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType">Is it a stored proc or a batch?</param>
/// <returns></returns>
public static Task<IEnumerable<TReturn>> QueryAsync<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], 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<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken));
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken));
return MultiMapAsync<TReturn>(cnn, command, types, map, splitOn);
}
......@@ -538,14 +621,10 @@ private static IEnumerable<T> ExecuteReaderSync<T>(IDataReader reader, Func<IDat
/// 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
)
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return QueryMultipleAsync(cnn, command);
}
......@@ -612,14 +691,10 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn,
/// </code>
/// </example>
public static Task<IDataReader> ExecuteReaderAsync(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteReaderImplAsync(cnn, command);
}
......@@ -663,14 +738,10 @@ private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn,
/// </summary>
/// <returns>The first cell selected</returns>
public static Task<object> ExecuteScalarAsync(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteScalarImplAsync<object>(cnn, command);
}
......@@ -679,14 +750,10 @@ private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn,
/// </summary>
/// <returns>The first cell selected</returns>
public static Task<T> ExecuteScalarAsync<T>(
#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
)
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteScalarImplAsync<T>(cnn, command);
}
......
......@@ -32,6 +32,15 @@ public Task<IEnumerable<dynamic>> ReadAsync(bool buffered = true)
return ReadAsyncImpl<dynamic>(typeof(DapperRow), buffered);
}
/// <summary>
/// Read the first row of the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public Task<dynamic> ReadFirstOrDefaultAsync()
{
return ReadFirstOrDefaultAsyncImpl<dynamic>(typeof(DapperRow));
}
/// <summary>
/// Read the next grid of results
/// </summary>
......@@ -40,6 +49,16 @@ public Task<IEnumerable<object>> ReadAsync(Type type, bool buffered = true)
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadAsyncImpl<object>(type, buffered);
}
/// <summary>
/// Read the first row of the next grid of results
/// </summary>
public Task<object> ReadFirstOrDefaultAsync(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadFirstOrDefaultAsyncImpl<object>(type);
}
/// <summary>
/// Read the next grid of results
/// </summary>
......@@ -48,6 +67,14 @@ public Task<IEnumerable<T>> ReadAsync<T>(bool buffered = true)
return ReadAsyncImpl<T>(typeof(T), buffered);
}
/// <summary>
/// Read the first row of the next grid of results
/// </summary>
public Task<T> ReadFirstOrDefaultAsync<T>()
{
return ReadFirstOrDefaultAsyncImpl<T>(typeof(T));
}
private async Task NextResultAsync()
{
if (await ((DbDataReader)reader).NextResultAsync(cancel).ConfigureAwait(false))
......@@ -94,6 +121,42 @@ private Task<IEnumerable<T>> ReadAsyncImpl<T>(Type type, bool buffered)
}
}
private Task<T> ReadFirstOrDefaultAsyncImpl<T>(Type type)
{
var dbReader = reader as DbDataReader;
if (dbReader != null) return ReadFirstOrDefaultAsyncImplImpl<T>(dbReader, type);
// no async API available; use non-async and fake it
return Task.FromResult<T>(ReadFirstOrDefaultImpl<T>(type));
}
private async Task<T> ReadFirstOrDefaultAsyncImplImpl<T>(DbDataReader reader, Type type)
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
IsConsumed = true;
T result = default(T);
if (await reader.ReadAsync(cancel).ConfigureAwait(false))
{
var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);
var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false));
cache.Deserializer = deserializer;
}
result = (T)deserializer.Func(reader);
while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { }
}
await NextResultAsync().ConfigureAwait(false);
return result;
}
private async Task<IEnumerable<T>> ReadBufferedAsync<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity)
{
try
......
......@@ -39,6 +39,15 @@ public IEnumerable<dynamic> Read(bool buffered = true)
return ReadImpl<dynamic>(typeof(DapperRow), buffered);
}
/// <summary>
/// Read the first row of the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public dynamic ReadFirstOrDefault()
{
return ReadFirstOrDefaultImpl<dynamic>(typeof(DapperRow));
}
/// <summary>
/// Read the next grid of results
/// </summary>
......@@ -47,6 +56,14 @@ public IEnumerable<T> Read<T>(bool buffered = true)
return ReadImpl<T>(typeof(T), buffered);
}
/// <summary>
/// Read the first row of the next grid of results
/// </summary>
public T ReadFirstOrDefault<T>()
{
return ReadFirstOrDefaultImpl<T>(typeof(T));
}
/// <summary>
/// Read the next grid of results
/// </summary>
......@@ -56,6 +73,15 @@ public IEnumerable<object> Read(Type type, bool buffered = true)
return ReadImpl<object>(type, buffered);
}
/// <summary>
/// Read the first row of the next grid of results
/// </summary>
public object ReadFirstOrDefault(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadFirstOrDefaultImpl<object>(type);
}
private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
......@@ -75,6 +101,32 @@ private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
return buffered ? result.ToList() : result;
}
private T ReadFirstOrDefaultImpl<T>(Type type)
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
IsConsumed = true;
T result = default(T);
if(reader.Read())
{
var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);
var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false));
cache.Deserializer = deserializer;
}
result = (T) deserializer.Func(reader);
while (reader.Read()) { }
}
NextResult();
return result;
}
private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Delegate func, string splitOn)
{
......
......@@ -397,7 +397,7 @@ public static List<T> AsList<T>(this IEnumerable<T> source)
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteImpl(cnn, ref command);
}
/// <summary>
......@@ -418,7 +418,7 @@ public static int Execute(this IDbConnection cnn, CommandDefinition command)
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteScalarImpl<object>(cnn, ref command);
}
......@@ -430,7 +430,7 @@ public static int Execute(this IDbConnection cnn, CommandDefinition command)
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteScalarImpl<T>(cnn, ref command);
}
......@@ -543,7 +543,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
IDbCommand dbcmd;
var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out dbcmd);
return new WrappedReader(dbcmd, reader);
......@@ -579,7 +579,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio
}
/// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// Return a sequence of dynamic objects with properties matching the columns
/// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
......@@ -587,10 +587,18 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, obj
return Query<DapperRow>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType);
}
/// <summary>
/// Return a dynamic object with properties matching the columns
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryFirstOrDefault<DapperRow>(cnn, sql, param as object, transaction, commandTimeout, commandType);
}
/// <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>
......@@ -598,13 +606,27 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, obj
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var data = QueryImpl<T>(cnn, command, typeof(T));
return command.Buffered ? data.ToList() : data;
}
/// <summary>
/// Executes a query, returning the data typed as per the Type suggested
/// Executes a single-row query, returning the data typed as per T
/// </summary>
/// <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 T QueryFirstOrDefault<T>(
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None);
return QueryFirstOrDefaultImpl<T>(cnn, ref command, typeof(T));
}
/// <summary>
/// Executes a single-row query, returning the data typed as per the Type suggested
/// </summary>
/// <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).
......@@ -614,11 +636,25 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, obj
)
{
if (type == null) throw new ArgumentNullException(nameof(type));
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var data = QueryImpl<object>(cnn, command, type);
return command.Buffered ? data.ToList() : data;
}
/// <summary>
/// Executes a single-row query, returning the data typed as per the Type suggested
/// </summary>
/// <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 object QueryFirstOrDefault(
this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
if (type == null) throw new ArgumentNullException(nameof(type));
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None);
return QueryFirstOrDefaultImpl<object>(cnn, ref command, type);
}
/// <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>
......@@ -631,6 +667,17 @@ public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition
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 T QueryFirstOrDefault<T>(this IDbConnection cnn, CommandDefinition command)
{
return QueryFirstOrDefaultImpl<T>(cnn, ref command, typeof(T));
}
/// <summary>
......@@ -640,7 +687,7 @@ public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return QueryMultipleImpl(cnn, ref command);
}
/// <summary>
......@@ -749,6 +796,73 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
}
}
private static T QueryFirstOrDefaultImpl<T>(this IDbConnection cnn, ref CommandDefinition command, Type effectiveType)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null);
var info = GetCacheInfo(identity, param, command.AddToCache);
IDbCommand cmd = null;
IDataReader reader = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
cmd = command.SetupCommand(cnn, info.ParamReader);
if (wasClosed) cnn.Open();
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess);
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
T result = default(T);
if (reader.Read() && reader.FieldCount != 0)
{
// 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
var tuple = info.Deserializer;
int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash)
{
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
if (command.AddToCache) SetQueryCache(identity, info);
}
var func = tuple.Func;
object val = func(reader);
if (val == null || val is T)
{
result = (T)val;
}
else {
var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
}
while (reader.Read()) { }
}
while (reader.NextResult()) { }
// happy path; close the reader cleanly - no
// need for "Cancel" etc
reader.Dispose();
reader = null;
command.OnCompleted();
return result;
}
finally
{
if (reader != null)
{
if (!reader.IsClosed) try { cmd.Cancel(); }
catch { /* don't spoil the existing exception */ }
reader.Dispose();
}
if (wasClosed) cnn.Close();
cmd?.Dispose();
}
}
/// <summary>
/// Maps a query to objects
/// </summary>
......@@ -916,9 +1030,9 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType">Is it a stored proc or a batch?</param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var results = MultiMapImpl<TReturn>(cnn, command, types, map, splitOn, null, null, true);
return buffered ? results.ToList() : results;
}
......@@ -926,7 +1040,7 @@ public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string
static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(
this IDbConnection cnn, string sql, Delegate map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, command, map, splitOn, null, null, true);
return buffered ? results.ToList() : results;
}
......
......@@ -5,7 +5,7 @@
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0",
"summary": "A high performance Micro-ORM",
"description": "A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..",
"version": "1.50-beta3",
"version": "1.50-beta4",
"title": "Dapper dot net",
"tags": [ "orm", "sql", "micro-orm" ],
"copyright": "2015 Stack Exchange, Inc.",
......
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