Commit 23e6653f authored by Marc Gravell's avatar Marc Gravell

Add all the missing First/Single/SingleOrDefault methods to match the...

Add all the missing First/Single/SingleOrDefault methods to match the FirstOrDefault added previously
parent b6cbb98a
......@@ -23,9 +23,9 @@ public static void IsMoreThan(this long a, int b)
Xunit.Assert.True(a > b, $"{a} should be larger than {b}");
}
public static void Fail()
public static void Fail(string message = null)
{
Xunit.Assert.True(false, "Expectation failed");
Xunit.Assert.True(false, message ?? "Expectation failed");
}
public static void IsFalse(this bool b)
{
......
......@@ -280,6 +280,29 @@ public void TestSchemaChangedViaFirstOrDefault()
}
}
[Fact]
public void Test_Single_First_Default()
{
var sql = "select 0 where 1 = 0;"; // no rows
try { connection.QueryFirst<int>(sql); Assert.Fail("QueryFirst, 0"); } catch (InvalidOperationException ex) { ex.Message.Equals("???"); }
try { connection.QuerySingle<int>(sql); Assert.Fail("QuerySingle, 0"); } catch (InvalidOperationException ex) { ex.Message.Equals("???"); }
connection.QueryFirstOrDefault<int>(sql).IsEqualTo(0);
connection.QuerySingleOrDefault<int>(sql).IsEqualTo(0);
sql = "select 1;"; // one row
connection.QueryFirst<int>(sql).IsEqualTo(1);
connection.QuerySingle<int>(sql).IsEqualTo(1);
connection.QueryFirstOrDefault<int>(sql).IsEqualTo(1);
connection.QuerySingleOrDefault<int>(sql).IsEqualTo(1);
sql = "select 2 union select 3 order by 1;"; // two rows
connection.QueryFirst<int>(sql).IsEqualTo(2);
try { connection.QuerySingle<int>(sql); Assert.Fail("QuerySingle, 2"); } catch (InvalidOperationException ex) { ex.Message.Equals(" ???"); }
connection.QueryFirstOrDefault<int>(sql).IsEqualTo(2);
try { connection.QuerySingleOrDefault<int>(sql); Assert.Fail("QuerySingleOrDefault, 2"); } catch (InvalidOperationException ex) { ex.Message.Equals("???"); }
}
[Fact]
public void TestSchemaChangedMultiMap()
{
......
......@@ -38,13 +38,37 @@ public static Task<IEnumerable<dynamic>> QueryAsync(this IDbConnection cnn, Comm
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> QueryFirstAsync(this IDbConnection cnn, CommandDefinition command)
{
return QueryRowAsync<dynamic>(cnn, Row.First, 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);
return QueryRowAsync<dynamic>(cnn, Row.FirstOrDefault, 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> QuerySingleAsync(this IDbConnection cnn, CommandDefinition command)
{
return QueryRowAsync<dynamic>(cnn, Row.Single, 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> QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command)
{
return QueryRowAsync<dynamic>(cnn, Row.SingleOrDefault, typeof(DapperRow), command);
}
/// <summary>
......@@ -55,12 +79,33 @@ public static Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, string
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> QueryFirstAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryRowAsync<T>(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, 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)));
return QueryRowAsync<T>(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<T> QuerySingleAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryRowAsync<T>(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<T> QuerySingleOrDefaultAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryRowAsync<T>(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
......@@ -72,13 +117,37 @@ public static Task<IEnumerable<object>> QueryAsync(this IDbConnection cnn, Type
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> QueryFirstAsync(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 QueryRowAsync<object>(cnn, Row.First, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, 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)));
return QueryRowAsync<object>(cnn, Row.FirstOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QuerySingleAsync(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 QueryRowAsync<object>(cnn, Row.Single, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QuerySingleOrDefaultAsync(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 QueryRowAsync<object>(cnn, Row.SingleOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
......@@ -99,9 +168,30 @@ public static Task<IEnumerable<object>> QueryAsync(this IDbConnection cnn, Type
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QueryFirstAsync(this IDbConnection cnn, Type type, CommandDefinition command)
{
return QueryRowAsync<object>(cnn, Row.First, 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);
return QueryRowAsync<object>(cnn, Row.FirstOrDefault, type, command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QuerySingleAsync(this IDbConnection cnn, Type type, CommandDefinition command)
{
return QueryRowAsync<object>(cnn, Row.Single, type, command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command)
{
return QueryRowAsync<object>(cnn, Row.SingleOrDefault, type, command);
}
private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
......@@ -166,7 +256,7 @@ 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)
private static async Task<T> QueryRowAsync<T>(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null);
......@@ -179,10 +269,12 @@ private static async Task<T> QueryFirstOrDefaultAsync<T>(this IDbConnection cnn,
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(cancel).ConfigureAwait(false);
reader = await cmd.ExecuteReaderAsync(GetBehavior(wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow), cancel).ConfigureAwait(false);
reader = await cmd.ExecuteReaderAsync(GetBehavior(wasClosed, (row & Row.Single) != 0
? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition
: CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow), cancel).ConfigureAwait(false);
T result = default(T);
if(await reader.ReadAsync(cancel).ConfigureAwait(false))
if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0)
{
var tuple = info.Deserializer;
int hash = GetColumnHash(reader);
......@@ -204,8 +296,13 @@ private static async Task<T> QueryFirstOrDefaultAsync<T>(this IDbConnection cnn,
var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
}
if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row);
while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { }
}
else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one
{
ThrowZeroRows(row);
}
while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { }
return result;
}
......
......@@ -33,12 +33,36 @@ public Task<IEnumerable<dynamic>> ReadAsync(bool buffered = true)
}
/// <summary>
/// Read the first row of the next grid of results, returned as a dynamic object
/// Read an individual 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> ReadFirstAsync()
{
return ReadRowAsyncImpl<dynamic>(typeof(DapperRow), Row.First);
}
/// <summary>
/// Read an individual 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));
return ReadRowAsyncImpl<dynamic>(typeof(DapperRow), Row.FirstOrDefault);
}
/// <summary>
/// Read an individual 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> ReadSingleAsync()
{
return ReadRowAsyncImpl<dynamic>(typeof(DapperRow), Row.Single);
}
/// <summary>
/// Read an individual 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> ReadSingleOrDefaultAsync()
{
return ReadRowAsyncImpl<dynamic>(typeof(DapperRow), Row.SingleOrDefault);
}
/// <summary>
......@@ -51,12 +75,36 @@ public Task<IEnumerable<object>> ReadAsync(Type type, bool buffered = true)
}
/// <summary>
/// Read the first row of the next grid of results
/// Read an individual row of the next grid of results
/// </summary>
public Task<object> ReadFirstAsync(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRowAsyncImpl<object>(type, Row.First);
}
/// <summary>
/// Read an individual 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);
return ReadRowAsyncImpl<object>(type, Row.FirstOrDefault);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<object> ReadSingleAsync(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRowAsyncImpl<object>(type, Row.Single);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<object> ReadSingleOrDefaultAsync(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRowAsyncImpl<object>(type, Row.SingleOrDefault);
}
/// <summary>
......@@ -68,11 +116,32 @@ public Task<IEnumerable<T>> ReadAsync<T>(bool buffered = true)
}
/// <summary>
/// Read the first row of the next grid of results
/// Read an individual row of the next grid of results
/// </summary>
public Task<T> ReadFirstAsync<T>()
{
return ReadRowAsyncImpl<T>(typeof(T), Row.First);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<T> ReadFirstOrDefaultAsync<T>()
{
return ReadFirstOrDefaultAsyncImpl<T>(typeof(T));
return ReadRowAsyncImpl<T>(typeof(T), Row.FirstOrDefault);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<T> ReadSingleAsync<T>()
{
return ReadRowAsyncImpl<T>(typeof(T), Row.Single);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<T> ReadSingleOrDefaultAsync<T>()
{
return ReadRowAsyncImpl<T>(typeof(T), Row.SingleOrDefault);
}
private async Task NextResultAsync()
......@@ -121,23 +190,23 @@ private Task<IEnumerable<T>> ReadAsyncImpl<T>(Type type, bool buffered)
}
}
private Task<T> ReadFirstOrDefaultAsyncImpl<T>(Type type)
private Task<T> ReadRowAsyncImpl<T>(Type type, Row row)
{
var dbReader = reader as DbDataReader;
if (dbReader != null) return ReadFirstOrDefaultAsyncImplImpl<T>(dbReader, type);
if (dbReader != null) return ReadRowAsyncImplViaDbReader<T>(dbReader, type, row);
// no async API available; use non-async and fake it
return Task.FromResult<T>(ReadFirstOrDefaultImpl<T>(type));
return Task.FromResult<T>(ReadRow<T>(type, row));
}
private async Task<T> ReadFirstOrDefaultAsyncImplImpl<T>(DbDataReader reader, Type type)
private async Task<T> ReadRowAsyncImplViaDbReader<T>(DbDataReader reader, Type type, Row row)
{
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))
if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0)
{
var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);
......@@ -150,9 +219,13 @@ private async Task<T> ReadFirstOrDefaultAsyncImplImpl<T>(DbDataReader reader, Ty
cache.Deserializer = deserializer;
}
result = (T)deserializer.Func(reader);
if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row);
while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { }
}
else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one
{
ThrowZeroRows(row);
}
await NextResultAsync().ConfigureAwait(false);
return result;
}
......
......@@ -40,12 +40,36 @@ public IEnumerable<dynamic> Read(bool buffered = true)
}
/// <summary>
/// Read the first row of the next grid of results, returned as a dynamic object
/// Read an individual 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 ReadFirst()
{
return ReadRow<dynamic>(typeof(DapperRow), Row.First);
}
/// <summary>
/// Read an individual 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));
return ReadRow<dynamic>(typeof(DapperRow), Row.FirstOrDefault);
}
/// <summary>
/// Read an individual 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 ReadSingle()
{
return ReadRow<dynamic>(typeof(DapperRow), Row.Single);
}
/// <summary>
/// Read an individual 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 ReadSingleOrDefault()
{
return ReadRow<dynamic>(typeof(DapperRow), Row.SingleOrDefault);
}
/// <summary>
......@@ -57,11 +81,32 @@ public IEnumerable<T> Read<T>(bool buffered = true)
}
/// <summary>
/// Read the first row of the next grid of results
/// Read an individual row of the next grid of results
/// </summary>
public T ReadFirst<T>()
{
return ReadRow<T>(typeof(T), Row.First);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public T ReadFirstOrDefault<T>()
{
return ReadFirstOrDefaultImpl<T>(typeof(T));
return ReadRow<T>(typeof(T), Row.FirstOrDefault);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public T ReadSingle<T>()
{
return ReadRow<T>(typeof(T), Row.Single);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public T ReadSingleOrDefault<T>()
{
return ReadRow<T>(typeof(T), Row.SingleOrDefault);
}
/// <summary>
......@@ -74,12 +119,36 @@ public IEnumerable<object> Read(Type type, bool buffered = true)
}
/// <summary>
/// Read the first row of the next grid of results
/// Read an individual row of the next grid of results
/// </summary>
public object ReadFirst(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRow<object>(type, Row.First);
}
/// <summary>
/// Read an individual 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);
return ReadRow<object>(type, Row.FirstOrDefault);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public object ReadSingle(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRow<object>(type, Row.Single);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public object ReadSingleOrDefault(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRow<object>(type, Row.SingleOrDefault);
}
private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
......@@ -101,14 +170,14 @@ private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
return buffered ? result.ToList() : result;
}
private T ReadFirstOrDefaultImpl<T>(Type type)
private T ReadRow<T>(Type type, Row row)
{
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())
if(reader.Read() && reader.FieldCount != 0)
{
var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);
......@@ -121,7 +190,11 @@ private T ReadFirstOrDefaultImpl<T>(Type type)
cache.Deserializer = deserializer;
}
result = (T) deserializer.Func(reader);
while (reader.Read()) { }
if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row); while (reader.Read()) { }
}
else if((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one
{
ThrowZeroRows(row);
}
NextResult();
return result;
......
......@@ -587,6 +587,14 @@ 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 QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryFirst<DapperRow>(cnn, sql, param as object, transaction, commandTimeout, commandType);
}
/// <summary>
/// Return a dynamic object with properties matching the columns
/// </summary>
......@@ -595,6 +603,22 @@ public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, ob
{
return QueryFirstOrDefault<DapperRow>(cnn, sql, param as object, transaction, 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 QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QuerySingle<DapperRow>(cnn, sql, param as object, transaction, 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 QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QuerySingleOrDefault<DapperRow>(cnn, sql, param as object, transaction, commandTimeout, commandType);
}
/// <summary>
/// Executes a query, returning the data typed as per T
......@@ -611,6 +635,19 @@ public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, ob
return command.Buffered ? data.ToList() : data;
}
/// <summary>
/// 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 QueryFirst<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 QueryRowImpl<T>(cnn, Row.First, ref command, typeof(T));
}
/// <summary>
/// Executes a single-row query, returning the data typed as per T
/// </summary>
......@@ -622,7 +659,33 @@ public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, ob
)
{
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None);
return QueryFirstOrDefaultImpl<T>(cnn, ref command, typeof(T));
return QueryRowImpl<T>(cnn, Row.FirstOrDefault, ref command, typeof(T));
}
/// <summary>
/// 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 QuerySingle<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 QueryRowImpl<T>(cnn, Row.Single, ref command, typeof(T));
}
/// <summary>
/// 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 QuerySingleOrDefault<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 QueryRowImpl<T>(cnn, Row.SingleOrDefault, ref command, typeof(T));
}
/// <summary>
......@@ -646,13 +709,55 @@ public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, ob
/// <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(
public static object QueryFirst(
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);
return QueryRowImpl<object>(cnn, Row.First, ref command, type);
}
/// <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 QueryRowImpl<object>(cnn, Row.FirstOrDefault, ref command, type);
}
/// <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 QuerySingle(
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 QueryRowImpl<object>(cnn, Row.Single, ref command, type);
}
/// <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 QuerySingleOrDefault(
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 QueryRowImpl<object>(cnn, Row.SingleOrDefault, ref command, type);
}
/// <summary>
/// Executes a query, returning the data typed as per T
......@@ -667,6 +772,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 QueryFirst<T>(this IDbConnection cnn, CommandDefinition command)
{
return QueryRowImpl<T>(cnn, Row.First, ref command, typeof(T));
}
/// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
......@@ -676,7 +792,29 @@ public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition
/// </returns>
public static T QueryFirstOrDefault<T>(this IDbConnection cnn, CommandDefinition command)
{
return QueryFirstOrDefaultImpl<T>(cnn, ref command, typeof(T));
return QueryRowImpl<T>(cnn, Row.FirstOrDefault, ref command, typeof(T));
}
/// <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 QuerySingle<T>(this IDbConnection cnn, CommandDefinition command)
{
return QueryRowImpl<T>(cnn, Row.Single, ref command, typeof(T));
}
/// <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 QuerySingleOrDefault<T>(this IDbConnection cnn, CommandDefinition command)
{
return QueryRowImpl<T>(cnn, Row.SingleOrDefault, ref command, typeof(T));
}
......@@ -797,7 +935,34 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
}
}
private static T QueryFirstOrDefaultImpl<T>(this IDbConnection cnn, ref CommandDefinition command, Type effectiveType)
[Flags]
internal enum Row
{
First = 0,
FirstOrDefault = 1, // &FirstOrDefault != 0: allow zero rows
Single = 2, // & Single != 0: demand at least one row
SingleOrDefault = 3
}
static readonly int[] ErrTwoRows = new int[2], ErrZeroRows = new int[0];
static void ThrowMultipleRows(Row row)
{
switch (row)
{ // get the standard exception from the runtime
case Row.Single: ErrTwoRows.Single(); break;
case Row.SingleOrDefault: ErrTwoRows.SingleOrDefault(); break;
default: throw new InvalidOperationException();
}
}
static void ThrowZeroRows(Row row)
{
switch (row)
{ // get the standard exception from the runtime
case Row.First: ErrZeroRows.First(); break;
case Row.Single: ErrZeroRows.Single(); break;
default: throw new InvalidOperationException();
}
}
private static T QueryRowImpl<T>(IDbConnection cnn, Row row, ref CommandDefinition command, Type effectiveType)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null);
......@@ -812,7 +977,9 @@ private static T QueryFirstOrDefaultImpl<T>(this IDbConnection cnn, ref CommandD
cmd = command.SetupCommand(cnn, info.ParamReader);
if (wasClosed) cnn.Open();
reader = cmd.ExecuteReader(GetBehavior(wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow));
reader = cmd.ExecuteReader(GetBehavior(wasClosed, (row & Row.Single) != 0
? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition
: CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow));
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
T result = default(T);
......@@ -835,13 +1002,18 @@ private static T QueryFirstOrDefaultImpl<T>(this IDbConnection cnn, ref CommandD
{
result = (T)val;
}
else {
else
{
var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
}
if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row);
while (reader.Read()) { }
}
else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one
{
ThrowZeroRows(row);
}
while (reader.NextResult()) { }
// happy path; close the reader cleanly - no
// need for "Cancel" etc
......
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