Commit 1a41f12c authored by Marc Gravell's avatar Marc Gravell

Add Type-based API rather than just <T>

parent 73e320c8
...@@ -1169,10 +1169,29 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn ...@@ -1169,10 +1169,29 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn
) )
{ {
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var data = QueryImpl<T>(cnn, command); var data = QueryImpl<T>(cnn, command, typeof(T));
return command.Buffered ? data.ToList() : data; return command.Buffered ? data.ToList() : data;
} }
/// <summary>
/// Executes a 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 IEnumerable<object> Query(
#if CSHARP30
this IDbConnection cnn, Type type, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
if (type == null) throw new ArgumentNullException("type");
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var data = QueryImpl<object>(cnn, command, type);
return command.Buffered ? data.ToList() : data;
}
/// <summary> /// <summary>
/// Executes a query, returning the data typed as per T /// Executes a query, returning the data typed as per T
/// </summary> /// </summary>
...@@ -1182,7 +1201,7 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn ...@@ -1182,7 +1201,7 @@ public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dyn
/// </returns> /// </returns>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition command) public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition command)
{ {
var data = QueryImpl<T>(cnn, command); var data = QueryImpl<T>(cnn, command, typeof(T));
return command.Buffered ? data.ToList() : data; return command.Buffered ? data.ToList() : data;
} }
...@@ -1245,10 +1264,10 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD ...@@ -1245,10 +1264,10 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD
} }
} }
private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command) private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command, Type effectiveType)
{ {
object param = command.Parameters; object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(T), param == null ? null : param.GetType(), null); var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity, param); var info = GetCacheInfo(identity, param);
IDbCommand cmd = null; IDbCommand cmd = null;
...@@ -1269,7 +1288,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini ...@@ -1269,7 +1288,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash) if (tuple.Func == null || tuple.Hash != hash)
{ {
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false)); tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
SetQueryCache(identity, info); SetQueryCache(identity, info);
} }
...@@ -1278,10 +1297,13 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini ...@@ -1278,10 +1297,13 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
while (reader.Read()) while (reader.Read())
{ {
object val = func(reader); object val = func(reader);
if (val == null || val is T) { if (effectiveType == typeof(object))
{
yield return (T)Convert.ChangeType(val, effectiveType);
} else if (val == null || val is T) {
yield return (T)val; yield return (T)val;
} else { } else {
yield return (T)Convert.ChangeType(val, typeof(T)); yield return (T)Convert.ChangeType(val, effectiveType);
} }
} }
// happy path; close the reader cleanly - no // happy path; close the reader cleanly - no
...@@ -3529,6 +3551,33 @@ public IEnumerable<T> Read<T>(bool buffered = true) ...@@ -3529,6 +3551,33 @@ public IEnumerable<T> Read<T>(bool buffered = true)
return buffered ? result.ToList() : result; return buffered ? result.ToList() : result;
} }
/// <summary>
/// Read the next grid of results
/// </summary>
#if CSHARP30
public IEnumerable<object> Read(Type type, bool buffered)
#else
public IEnumerable<object> Read(Type type, bool buffered = true)
#endif
{
if (type == null) throw new ArgumentNullException("type");
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null);
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;
}
consumed = true;
var result = ReadDeferred<object>(gridIndex, deserializer.Func, typedIdentity);
return buffered ? result.ToList() : 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)
{ {
var identity = this.identity.ForGrid(typeof(TReturn), new Type[] { var identity = this.identity.ForGrid(typeof(TReturn), new Type[] {
......
...@@ -18,16 +18,38 @@ public static partial class SqlMapper ...@@ -18,16 +18,38 @@ public static partial class SqlMapper
/// </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, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{ {
return QueryAsync<T>(cnn, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); return QueryAsync<T>(cnn, typeof(T), new CommandDefinition(sql, (object)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>
public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, CommandDefinition command) 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)
{
if (type == null) throw new ArgumentNullException("type");
return QueryAsync<object>(cnn, type, new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, CommandDefinition command)
{
return QueryAsync<T>(cnn, typeof(T), command);
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<IEnumerable<object>> QueryAsync(this IDbConnection cnn, Type type, CommandDefinition command)
{
return QueryAsync<object>(cnn, type, command);
}
private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
{ {
object param = command.Parameters; object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(T), param == null ? null : param.GetType(), null); var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity, param); var info = GetCacheInfo(identity, param);
bool wasClosed = cnn.State == ConnectionState.Closed; bool wasClosed = cnn.State == ConnectionState.Closed;
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader)) using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
...@@ -37,7 +59,7 @@ public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, C ...@@ -37,7 +59,7 @@ public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, C
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false); if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
using (var reader = await cmd.ExecuteReaderAsync(command.CancellationToken).ConfigureAwait(false)) using (var reader = await cmd.ExecuteReaderAsync(command.CancellationToken).ConfigureAwait(false))
{ {
return ExecuteReader<T>(reader, identity, info).ToList(); return ExecuteReader<T>(reader, effectiveType, identity, info).ToList();
} }
} }
finally finally
...@@ -385,13 +407,13 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini ...@@ -385,13 +407,13 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
} }
} }
private static IEnumerable<T> ExecuteReader<T>(IDataReader reader, Identity identity, CacheInfo info) private static IEnumerable<T> ExecuteReader<T>(IDataReader reader, Type effectiveType, Identity identity, CacheInfo info)
{ {
var tuple = info.Deserializer; var tuple = info.Deserializer;
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash) if (tuple.Func == null || tuple.Hash != hash)
{ {
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false)); tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
SetQueryCache(identity, info); SetQueryCache(identity, info);
} }
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
using SqlMapper; using SqlMapper;
using System.Data; using System.Data;
using System.Diagnostics; using System.Diagnostics;
using System;
namespace DapperTests_NET45 namespace DapperTests_NET45
{ {
...@@ -265,5 +266,30 @@ class BasicType ...@@ -265,5 +266,30 @@ class BasicType
{ {
public string Value { get; set; } public string Value { get; set; }
} }
public void TypeBasedViaType()
{
Type type = GetSomeType();
using (var connection = Program.GetOpenConnection(true))
{
dynamic actual = connection.QueryAsync(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }).Result.FirstOrDefault();
((object)actual).GetType().IsEqualTo(type);
int a = actual.A;
string b = actual.B;
a.IsEqualTo(123);
b.IsEqualTo("abc");
}
}
static Type GetSomeType()
{
return typeof(SomeType);
}
public class SomeType
{
public int A { get; set; }
public string B { get; set; }
}
} }
} }
\ No newline at end of file
...@@ -2985,6 +2985,66 @@ public void DBGeography_SO24405645_SO24402424() ...@@ -2985,6 +2985,66 @@ public void DBGeography_SO24405645_SO24402424()
row.Geo.IsNotNull(); row.Geo.IsNotNull();
} }
public void TypeBasedViaDynamic()
{
Type type = GetSomeType();
dynamic template = Activator.CreateInstance(type);
dynamic actual = CheetViaDynamic(template, "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");
}
public void TypeBasedViaType()
{
Type type = GetSomeType();
dynamic actual = connection.Query(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }).FirstOrDefault();
((object)actual).GetType().IsEqualTo(type);
int a = actual.A;
string b = actual.B;
a.IsEqualTo(123);
b.IsEqualTo("abc");
}
public void TypeBasedViaTypeMulti()
{
Type type = GetSomeType();
dynamic first, second;
using(var multi = connection.QueryMultiple("select @A as [A], @B as [B]; select @C as [A], @D as [B]",
new { A = 123, B = "abc", C = 456, D = "def" }))
{
first = multi.Read(type).Single();
second = multi.Read(type).Single();
}
((object)first).GetType().IsEqualTo(type);
int a = first.A;
string b = first.B;
a.IsEqualTo(123);
b.IsEqualTo("abc");
((object)second).GetType().IsEqualTo(type);
a = second.A;
b = second.B;
a.IsEqualTo(456);
b.IsEqualTo("def");
}
T CheetViaDynamic<T>(T template, string query, object args)
{
return connection.Query<T>(query, args).SingleOrDefault();
}
static Type GetSomeType()
{
return typeof(SomeType);
}
public class SomeType
{
public int A { get;set; }
public string B { get;set; }
}
class WithInit : ISupportInitialize class WithInit : ISupportInitialize
{ {
public string Value { get; set; } public string Value { get; set; }
......
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