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 ...@@ -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="transaction">The transaction to run under, null (the defualt) if none</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param> /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <returns>Entity of T</returns> /// <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 type = typeof(T);
var cacheType = typeof(List<T>); var cacheType = typeof(List<T>);
...@@ -95,9 +95,12 @@ public static partial class SqlMapperExtensions ...@@ -95,9 +95,12 @@ public static partial class SqlMapperExtensions
if (!type.IsInterface()) 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 result = await connection.QueryAsync(sql);
var list = new List<T>(); var list = new List<T>();
foreach (IDictionary<string, object> res in result) foreach (IDictionary<string, object> res in result)
...@@ -124,7 +127,7 @@ public static partial class SqlMapperExtensions ...@@ -124,7 +127,7 @@ public static partial class SqlMapperExtensions
/// <param name="commandTimeout">Number of seconds before command execution timeout</param> /// <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> /// <param name="sqlAdapter">The specific ISqlAdapter to use, auto-detected based on connection if null</param>
/// <returns>Identity of inserted entity</returns> /// <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 int? commandTimeout = null, ISqlAdapter sqlAdapter = null) where T : class
{ {
var type = typeof(T); var type = typeof(T);
...@@ -164,13 +167,13 @@ public static partial class SqlMapperExtensions ...@@ -164,13 +167,13 @@ public static partial class SqlMapperExtensions
if (!isList) //single entity 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); sbParameterList.ToString(), keyProperties, entityToInsert);
} }
//insert list of entities //insert list of entities
var cmd = $"INSERT INTO {name} ({sbColumnList}) values ({sbParameterList})"; var cmd = $"INSERT INTO {name} ({sbColumnList}) values ({sbParameterList})";
return await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout); return connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout);
} }
/// <summary> /// <summary>
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0", "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0",
"summary": "A high performance Micro-ORM", "summary": "A high performance Micro-ORM",
"description": "A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..", "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)", "title": "Dapper dot net (strong named)",
"tags": [ "orm", "sql", "micro-orm" ], "tags": [ "orm", "sql", "micro-orm" ],
"copyright": "2015 Stack Exchange, Inc.", "copyright": "2015 Stack Exchange, Inc.",
......
...@@ -130,10 +130,13 @@ public void Run(int iterations) ...@@ -130,10 +130,13 @@ public void Run(int iterations)
var mapperConnection = TestSuite.GetOpenConnection(); 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: 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.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(); 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: 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.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 // dapper.contrib
var mapperConnection3 = TestSuite.GetOpenConnection(); var mapperConnection3 = TestSuite.GetOpenConnection();
......
...@@ -156,6 +156,19 @@ public async Task TestMultiAsync() ...@@ -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] [Fact]
public async Task TestMultiClosedConnAsync() public async Task TestMultiClosedConnAsync()
{ {
...@@ -166,6 +179,19 @@ 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 #if EXTERNALS
[Fact] [Fact]
public async Task ExecuteReaderOpenAsync() public async Task ExecuteReaderOpenAsync()
...@@ -337,6 +363,19 @@ public async Task TypeBasedViaTypeAsync() ...@@ -337,6 +363,19 @@ public async Task TypeBasedViaTypeAsync()
b.IsEqualTo("abc"); 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] [Fact]
public async Task Issue22_ExecuteScalarAsync() public async Task Issue22_ExecuteScalarAsync()
{ {
...@@ -562,7 +601,26 @@ public async Task TestSubsequentQueriesSuccessAsync() ...@@ -562,7 +601,26 @@ public async Task TestSubsequentQueriesSuccessAsync()
class AsyncFoo0 { public int Id { get; set; } } class AsyncFoo0 { public int Id { get; set; } }
class AsyncFoo1 { public int Id { get; set; } } class AsyncFoo1 { public int Id { get; set; } }
class AsyncFoo2 { 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] [Fact]
public async Task TestMultiMapArbitraryMapsAsync() public async Task TestMultiMapArbitraryMapsAsync()
......
...@@ -258,6 +258,26 @@ public void TestSchemaChanged() ...@@ -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] [Fact]
public void TestSchemaChangedMultiMap() public void TestSchemaChangedMultiMap()
{ {
...@@ -845,6 +865,8 @@ public void TestFastExpandoSupportsIDictionary() ...@@ -845,6 +865,8 @@ public void TestFastExpandoSupportsIDictionary()
public void TestDapperSetsPrivates() public void TestDapperSetsPrivates()
{ {
connection.Query<PrivateDan>("select 'one' ShadowInDB").First().Shadow.IsEqualTo(1); connection.Query<PrivateDan>("select 'one' ShadowInDB").First().Shadow.IsEqualTo(1);
connection.QueryFirstOrDefault<PrivateDan>("select 'one' ShadowInDB").Shadow.IsEqualTo(1);
} }
class PrivateDan class PrivateDan
...@@ -1631,6 +1653,25 @@ public void TestChangingDefaultStringTypeMappingToAnsiString() ...@@ -1631,6 +1653,25 @@ public void TestChangingDefaultStringTypeMappingToAnsiString()
Dapper.SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String 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 #if COREFX
class TransactedConnection : IDbConnection class TransactedConnection : IDbConnection
{ {
......
...@@ -24,35 +24,61 @@ public static partial class SqlMapper ...@@ -24,35 +24,61 @@ public static partial class SqlMapper
/// Execute a query asynchronously using .NET 4.5 Task. /// Execute a query asynchronously using .NET 4.5 Task.
/// </summary> /// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks> /// <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> /// <summary>
/// Execute a query asynchronously using .NET 4.5 Task. /// Execute a query asynchronously using .NET 4.5 Task.
/// </summary> /// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks> /// <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> /// <summary>
/// Execute a query asynchronously using .NET 4.5 Task. /// Execute a query asynchronously using .NET 4.5 Task.
/// </summary> /// </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> /// <summary>
/// Execute a query asynchronously using .NET 4.5 Task. /// Execute a query asynchronously using .NET 4.5 Task.
/// </summary> /// </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)); 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> /// <summary>
...@@ -70,6 +96,13 @@ public static Task<IEnumerable<object>> QueryAsync(this IDbConnection cnn, Type ...@@ -70,6 +96,13 @@ public static Task<IEnumerable<object>> QueryAsync(this IDbConnection cnn, Type
{ {
return QueryAsync<object>(cnn, type, command); 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) 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, ...@@ -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> /// <summary>
/// Execute a command asynchronously using .NET 4.5 Task. /// Execute a command asynchronously using .NET 4.5 Task.
/// </summary> /// </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> /// <summary>
...@@ -299,10 +382,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini ...@@ -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="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, 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, 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> /// <summary>
...@@ -338,10 +421,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini ...@@ -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="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType"></param> /// <param name="commandType"></param>
/// <returns></returns> /// <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, 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> /// <summary>
...@@ -379,10 +462,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini ...@@ -379,10 +462,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
/// <param name="commandTimeout"></param> /// <param name="commandTimeout"></param>
/// <param name="commandType"></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, 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, 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> /// <summary>
...@@ -406,10 +489,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini ...@@ -406,10 +489,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
/// <summary> /// <summary>
/// Perform a multi mapping query with 5 input parameters /// Perform a multi mapping query with 5 input parameters
/// </summary> /// </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, 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> /// <summary>
...@@ -423,10 +506,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini ...@@ -423,10 +506,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
/// <summary> /// <summary>
/// Perform a multi mapping query with 6 input parameters /// Perform a multi mapping query with 6 input parameters
/// </summary> /// </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, 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> /// <summary>
...@@ -440,10 +523,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini ...@@ -440,10 +523,10 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
/// <summary> /// <summary>
/// Perform a multi mapping query with 7 input parameters /// Perform a multi mapping query with 7 input parameters
/// </summary> /// </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, 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> /// <summary>
...@@ -491,9 +574,9 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini ...@@ -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="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<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); return MultiMapAsync<TReturn>(cnn, command, types, map, splitOn);
} }
...@@ -538,14 +621,10 @@ private static IEnumerable<T> ExecuteReaderSync<T>(IDataReader reader, Func<IDat ...@@ -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 /// Execute a command that returns multiple result sets, and access each in turn
/// </summary> /// </summary>
public static Task<GridReader> QueryMultipleAsync( public static Task<GridReader> QueryMultipleAsync(
#if CSHARP30 this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType )
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{ {
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return QueryMultipleAsync(cnn, command); return QueryMultipleAsync(cnn, command);
} }
...@@ -612,14 +691,10 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn, ...@@ -612,14 +691,10 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn,
/// </code> /// </code>
/// </example> /// </example>
public static Task<IDataReader> ExecuteReaderAsync( public static Task<IDataReader> ExecuteReaderAsync(
#if CSHARP30 this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType )
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{ {
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteReaderImplAsync(cnn, command); return ExecuteReaderImplAsync(cnn, command);
} }
...@@ -663,14 +738,10 @@ private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn, ...@@ -663,14 +738,10 @@ private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn,
/// </summary> /// </summary>
/// <returns>The first cell selected</returns> /// <returns>The first cell selected</returns>
public static Task<object> ExecuteScalarAsync( public static Task<object> ExecuteScalarAsync(
#if CSHARP30 this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType )
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{ {
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteScalarImplAsync<object>(cnn, command); return ExecuteScalarImplAsync<object>(cnn, command);
} }
...@@ -679,14 +750,10 @@ private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn, ...@@ -679,14 +750,10 @@ private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn,
/// </summary> /// </summary>
/// <returns>The first cell selected</returns> /// <returns>The first cell selected</returns>
public static Task<T> ExecuteScalarAsync<T>( public static Task<T> ExecuteScalarAsync<T>(
#if CSHARP30 this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType )
#else
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{ {
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteScalarImplAsync<T>(cnn, command); return ExecuteScalarImplAsync<T>(cnn, command);
} }
......
...@@ -32,6 +32,15 @@ public Task<IEnumerable<dynamic>> ReadAsync(bool buffered = true) ...@@ -32,6 +32,15 @@ public Task<IEnumerable<dynamic>> ReadAsync(bool buffered = true)
return ReadAsyncImpl<dynamic>(typeof(DapperRow), buffered); 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> /// <summary>
/// Read the next grid of results /// Read the next grid of results
/// </summary> /// </summary>
...@@ -40,6 +49,16 @@ public Task<IEnumerable<object>> ReadAsync(Type type, bool buffered = true) ...@@ -40,6 +49,16 @@ public Task<IEnumerable<object>> ReadAsync(Type type, bool buffered = true)
if (type == null) throw new ArgumentNullException(nameof(type)); if (type == null) throw new ArgumentNullException(nameof(type));
return ReadAsyncImpl<object>(type, buffered); 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> /// <summary>
/// Read the next grid of results /// Read the next grid of results
/// </summary> /// </summary>
...@@ -48,6 +67,14 @@ public Task<IEnumerable<T>> ReadAsync<T>(bool buffered = true) ...@@ -48,6 +67,14 @@ public Task<IEnumerable<T>> ReadAsync<T>(bool buffered = true)
return ReadAsyncImpl<T>(typeof(T), buffered); 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() private async Task NextResultAsync()
{ {
if (await ((DbDataReader)reader).NextResultAsync(cancel).ConfigureAwait(false)) if (await ((DbDataReader)reader).NextResultAsync(cancel).ConfigureAwait(false))
...@@ -94,6 +121,42 @@ private Task<IEnumerable<T>> ReadAsyncImpl<T>(Type type, bool buffered) ...@@ -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) private async Task<IEnumerable<T>> ReadBufferedAsync<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity)
{ {
try try
......
...@@ -39,6 +39,15 @@ public IEnumerable<dynamic> Read(bool buffered = true) ...@@ -39,6 +39,15 @@ public IEnumerable<dynamic> Read(bool buffered = true)
return ReadImpl<dynamic>(typeof(DapperRow), buffered); 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> /// <summary>
/// Read the next grid of results /// Read the next grid of results
/// </summary> /// </summary>
...@@ -47,6 +56,14 @@ public IEnumerable<T> Read<T>(bool buffered = true) ...@@ -47,6 +56,14 @@ public IEnumerable<T> Read<T>(bool buffered = true)
return ReadImpl<T>(typeof(T), buffered); 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> /// <summary>
/// Read the next grid of results /// Read the next grid of results
/// </summary> /// </summary>
...@@ -56,6 +73,15 @@ public IEnumerable<object> Read(Type type, bool buffered = true) ...@@ -56,6 +73,15 @@ public IEnumerable<object> Read(Type type, bool buffered = true)
return ReadImpl<object>(type, buffered); 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) 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"); 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) ...@@ -75,6 +101,32 @@ private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
return buffered ? result.ToList() : result; 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) 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) ...@@ -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 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); return ExecuteImpl(cnn, ref command);
} }
/// <summary> /// <summary>
...@@ -418,7 +418,7 @@ public static int Execute(this IDbConnection cnn, CommandDefinition command) ...@@ -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 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); return ExecuteScalarImpl<object>(cnn, ref command);
} }
...@@ -430,7 +430,7 @@ public static int Execute(this IDbConnection cnn, CommandDefinition 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 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); return ExecuteScalarImpl<T>(cnn, ref command);
} }
...@@ -543,7 +543,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com ...@@ -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 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; IDbCommand dbcmd;
var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out dbcmd); var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out dbcmd);
return new WrappedReader(dbcmd, reader); return new WrappedReader(dbcmd, reader);
...@@ -579,7 +579,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio ...@@ -579,7 +579,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio
} }
/// <summary> /// <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> /// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks> /// <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) 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 ...@@ -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); 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> /// <summary>
/// Executes a query, returning the data typed as per T /// Executes a query, returning the data typed as per T
/// </summary> /// </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 /// <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). /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns> /// </returns>
...@@ -598,13 +606,27 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, obj ...@@ -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 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)); var data = QueryImpl<T>(cnn, command, typeof(T));
return command.Buffered ? data.ToList() : data; return command.Buffered ? data.ToList() : data;
} }
/// <summary> /// <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> /// </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 /// <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). /// 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 ...@@ -614,11 +636,25 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, obj
) )
{ {
if (type == null) throw new ArgumentNullException(nameof(type)); 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); var data = QueryImpl<object>(cnn, command, type);
return command.Buffered ? data.ToList() : data; return command.Buffered ? data.ToList() : data;
} }
/// <summary> /// <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 /// Executes a query, returning the data typed as per T
/// </summary> /// </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> /// <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 ...@@ -631,6 +667,17 @@ public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition
return command.Buffered ? data.ToList() : data; 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> /// <summary>
...@@ -640,7 +687,7 @@ public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition ...@@ -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 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); return QueryMultipleImpl(cnn, ref command);
} }
/// <summary> /// <summary>
...@@ -749,6 +796,73 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini ...@@ -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> /// <summary>
/// Maps a query to objects /// Maps a query to objects
/// </summary> /// </summary>
...@@ -916,9 +1030,9 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini ...@@ -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="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 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); var results = MultiMapImpl<TReturn>(cnn, command, types, map, splitOn, null, null, true);
return buffered ? results.ToList() : results; return buffered ? results.ToList() : results;
} }
...@@ -926,7 +1040,7 @@ public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string ...@@ -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>( 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) 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); var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, command, map, splitOn, null, null, true);
return buffered ? results.ToList() : results; return buffered ? results.ToList() : results;
} }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0", "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0",
"summary": "A high performance Micro-ORM", "summary": "A high performance Micro-ORM",
"description": "A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..", "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", "title": "Dapper dot net",
"tags": [ "orm", "sql", "micro-orm" ], "tags": [ "orm", "sql", "micro-orm" ],
"copyright": "2015 Stack Exchange, Inc.", "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