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) ...@@ -23,9 +23,9 @@ public static void IsMoreThan(this long a, int b)
Xunit.Assert.True(a > b, $"{a} should be larger than {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) public static void IsFalse(this bool b)
{ {
......
...@@ -280,6 +280,29 @@ public void TestSchemaChangedViaFirstOrDefault() ...@@ -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] [Fact]
public void TestSchemaChangedMultiMap() public void TestSchemaChangedMultiMap()
{ {
......
This diff is collapsed.
...@@ -33,12 +33,36 @@ public Task<IEnumerable<dynamic>> ReadAsync(bool buffered = true) ...@@ -33,12 +33,36 @@ public Task<IEnumerable<dynamic>> ReadAsync(bool buffered = true)
} }
/// <summary> /// <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> /// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks> /// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public Task<dynamic> ReadFirstOrDefaultAsync() 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> /// <summary>
...@@ -51,12 +75,36 @@ public Task<IEnumerable<object>> ReadAsync(Type type, bool buffered = true) ...@@ -51,12 +75,36 @@ public Task<IEnumerable<object>> ReadAsync(Type type, bool buffered = true)
} }
/// <summary> /// <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> /// </summary>
public Task<object> ReadFirstOrDefaultAsync(Type type) public Task<object> ReadFirstOrDefaultAsync(Type type)
{ {
if (type == null) throw new ArgumentNullException(nameof(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> /// <summary>
...@@ -68,11 +116,32 @@ public Task<IEnumerable<T>> ReadAsync<T>(bool buffered = true) ...@@ -68,11 +116,32 @@ public Task<IEnumerable<T>> ReadAsync<T>(bool buffered = true)
} }
/// <summary> /// <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> /// </summary>
public Task<T> ReadFirstOrDefaultAsync<T>() 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() private async Task NextResultAsync()
...@@ -121,23 +190,23 @@ private Task<IEnumerable<T>> ReadAsyncImpl<T>(Type type, bool buffered) ...@@ -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; 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 // 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 (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"); if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
IsConsumed = true; IsConsumed = true;
T result = default(T); 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); var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);
...@@ -150,9 +219,13 @@ private async Task<T> ReadFirstOrDefaultAsyncImplImpl<T>(DbDataReader reader, Ty ...@@ -150,9 +219,13 @@ private async Task<T> ReadFirstOrDefaultAsyncImplImpl<T>(DbDataReader reader, Ty
cache.Deserializer = deserializer; cache.Deserializer = deserializer;
} }
result = (T)deserializer.Func(reader); 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)) { } 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); await NextResultAsync().ConfigureAwait(false);
return result; return result;
} }
......
...@@ -40,12 +40,36 @@ public IEnumerable<dynamic> Read(bool buffered = true) ...@@ -40,12 +40,36 @@ public IEnumerable<dynamic> Read(bool buffered = true)
} }
/// <summary> /// <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> /// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks> /// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public dynamic ReadFirstOrDefault() 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> /// <summary>
...@@ -57,11 +81,32 @@ public IEnumerable<T> Read<T>(bool buffered = true) ...@@ -57,11 +81,32 @@ public IEnumerable<T> Read<T>(bool buffered = true)
} }
/// <summary> /// <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> /// </summary>
public T ReadFirstOrDefault<T>() 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> /// <summary>
...@@ -74,12 +119,36 @@ public IEnumerable<object> Read(Type type, bool buffered = true) ...@@ -74,12 +119,36 @@ public IEnumerable<object> Read(Type type, bool buffered = true)
} }
/// <summary> /// <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> /// </summary>
public object ReadFirstOrDefault(Type type) public object ReadFirstOrDefault(Type type)
{ {
if (type == null) throw new ArgumentNullException(nameof(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) private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
...@@ -101,14 +170,14 @@ 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; 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 (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"); if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
IsConsumed = true; IsConsumed = true;
T result = default(T); T result = default(T);
if(reader.Read()) if(reader.Read() && reader.FieldCount != 0)
{ {
var typedIdentity = identity.ForGrid(type, gridIndex); var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);
...@@ -121,7 +190,11 @@ private T ReadFirstOrDefaultImpl<T>(Type type) ...@@ -121,7 +190,11 @@ private T ReadFirstOrDefaultImpl<T>(Type type)
cache.Deserializer = deserializer; cache.Deserializer = deserializer;
} }
result = (T) deserializer.Func(reader); 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(); NextResult();
return result; 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