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()
{
......
This diff is collapsed.
......@@ -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;
......
This diff is collapsed.
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