Commit 8f31bdc2 authored by Marc Gravell's avatar Marc Gravell

Added ExecuteScalar (issue 22), and optional plan caching

parent b07897d1
...@@ -1010,6 +1010,58 @@ public static int Execute(this IDbConnection cnn, CommandDefinition command) ...@@ -1010,6 +1010,58 @@ public static int Execute(this IDbConnection cnn, CommandDefinition command)
{ {
return ExecuteImpl(cnn, ref command); return ExecuteImpl(cnn, ref command);
} }
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static object ExecuteScalar(
#if CSHARP30
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);
return ExecuteScalarImpl<object>(cnn, ref command);
}
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static T ExecuteScalar<T>(
#if CSHARP30
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);
return ExecuteScalarImpl<T>(cnn, ref command);
}
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static object ExecuteScalar(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteScalarImpl<object>(cnn, ref command);
}
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static T ExecuteScalar<T>(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteScalarImpl<T>(cnn, ref command);
}
private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition command) private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition command)
{ {
object param = command.Parameters; object param = command.Parameters;
...@@ -1068,12 +1120,6 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com ...@@ -1068,12 +1120,6 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com
return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader); return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader);
} }
private static CacheInfo GetCacheInfo(Identity identity, object obj, object addToCache)
{
throw new NotImplementedException();
}
/// <summary> /// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/> /// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary> /// </summary>
...@@ -1304,7 +1350,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini ...@@ -1304,7 +1350,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
if (tuple.Func == null || tuple.Hash != hash) if (tuple.Func == null || tuple.Hash != hash)
{ {
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
SetQueryCache(identity, info); if(command.AddToCache) SetQueryCache(identity, info);
} }
var func = tuple.Func; var func = tuple.Func;
...@@ -1318,7 +1364,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini ...@@ -1318,7 +1364,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
} else if (val == null || val is T) { } else if (val == null || val is T) {
yield return (T)val; yield return (T)val;
} else { } else {
yield return (T)Convert.ChangeType(val, effectiveType); yield return (T)Convert.ChangeType(val, effectiveType, CultureInfo.InvariantCulture);
} }
} }
// happy path; close the reader cleanly - no // happy path; close the reader cleanly - no
...@@ -1541,7 +1587,7 @@ partial class DontMap { } ...@@ -1541,7 +1587,7 @@ partial class DontMap { }
var deserializers = GenerateDeserializers(new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }, splitOn, reader); var deserializers = GenerateDeserializers(new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }, splitOn, reader);
deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]);
otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray(); otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray();
SetQueryCache(identity, cinfo); if(command.AddToCache) SetQueryCache(identity, cinfo);
} }
Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(deserializer.Func, otherDeserializers, map); Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(deserializer.Func, otherDeserializers, map);
...@@ -1764,7 +1810,6 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter ...@@ -1764,7 +1810,6 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter
return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing); return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing);
} }
return GetStructDeserializer(type, underlyingType ?? type, startBound); return GetStructDeserializer(type, underlyingType ?? type, startBound);
} }
static Func<IDataReader, object> GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound) static Func<IDataReader, object> GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound)
{ {
...@@ -2854,6 +2899,33 @@ private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition comma ...@@ -2854,6 +2899,33 @@ private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition comma
} }
} }
private static T ExecuteScalarImpl<T>(IDbConnection cnn, ref CommandDefinition command)
{
Action<IDbCommand, object> paramReader = null;
object param = command.Parameters;
if (param != null)
{
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader;
}
IDbCommand cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
object result;
try
{
cmd = command.SetupCommand(cnn, paramReader);
if (wasClosed) cnn.Open();
result =cmd.ExecuteScalar();
}
finally
{
if (wasClosed) cnn.Close();
if (cmd != null) cmd.Dispose();
}
return Parse<T>(result);
}
private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command) private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command)
{ {
Action<IDbCommand, object> paramReader = GetParameterReader(cnn, ref command); Action<IDbCommand, object> paramReader = GetParameterReader(cnn, ref command);
...@@ -2938,6 +3010,23 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin ...@@ -2938,6 +3010,23 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin
}; };
} }
private static T Parse<T>(object value)
{
if (value == null || value is DBNull) return default(T);
if (value is T) return (T)value;
var type = typeof(T);
if (type.IsEnum)
{
return (T)Enum.ToObject(type, value);
}
ITypeHandler handler;
if (typeHandlers.TryGetValue(type, out handler))
{
return (T)handler.Parse(type, value);
}
return (T)Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
}
static readonly MethodInfo static readonly MethodInfo
enumParse = typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string), typeof(bool) }), enumParse = typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string), typeof(bool) }),
getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public) getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public)
......
...@@ -59,7 +59,7 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, ...@@ -59,7 +59,7 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn,
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, effectiveType, identity, info).ToList(); return ExecuteReader<T>(reader, effectiveType, identity, info, command.AddToCache).ToList();
} }
} }
finally finally
...@@ -407,14 +407,14 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini ...@@ -407,14 +407,14 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
} }
} }
private static IEnumerable<T> ExecuteReader<T>(IDataReader reader, Type effectiveType, Identity identity, CacheInfo info) private static IEnumerable<T> ExecuteReader<T>(IDataReader reader, Type effectiveType, Identity identity, CacheInfo info, bool addToCache)
{ {
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(effectiveType, reader, 0, -1, false)); tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
SetQueryCache(identity, info); if(addToCache) SetQueryCache(identity, info);
} }
var func = tuple.Func; var func = tuple.Func;
...@@ -546,5 +546,82 @@ private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn, ...@@ -546,5 +546,82 @@ private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn,
if (cmd != null) cmd.Dispose(); if (cmd != null) cmd.Dispose();
} }
} }
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static Task<object> ExecuteScalarAsync(
#if CSHARP30
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);
return ExecuteScalarImplAsync<object>(cnn, command);
}
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static Task<T> ExecuteScalarAsync<T>(
#if CSHARP30
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);
return ExecuteScalarImplAsync<T>(cnn, command);
}
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static Task<object> ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteScalarImplAsync<object>(cnn, command);
}
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static Task<T> ExecuteScalarAsync<T>(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteScalarImplAsync<T>(cnn, command);
}
private async static Task<T> ExecuteScalarImplAsync<T>(IDbConnection cnn, CommandDefinition command)
{
Action<IDbCommand, object> paramReader = null;
object param = command.Parameters;
if (param != null)
{
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader;
}
DbCommand cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
object result;
try
{
cmd = (DbCommand)command.SetupCommand(cnn, paramReader);
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
result = await cmd.ExecuteScalarAsync(command.CancellationToken).ConfigureAwait(false);
}
finally
{
if (wasClosed) cnn.Close();
if (cmd != null) cmd.Dispose();
}
return Parse<T>(result);
}
} }
} }
\ No newline at end of file
...@@ -292,5 +292,26 @@ public class SomeType ...@@ -292,5 +292,26 @@ public class SomeType
public int A { get; set; } public int A { get; set; }
public string B { get; set; } public string B { get; set; }
} }
public void Issue22_ExecuteScalar()
{
using (var connection = Program.GetOpenConnection())
{
int i = connection.ExecuteScalarAsync<int>("select 123").Result;
i.IsEqualTo(123);
i = connection.ExecuteScalarAsync<int>("select cast(123 as bigint)").Result;
i.IsEqualTo(123);
long j = connection.ExecuteScalarAsync<long>("select 123").Result;
j.IsEqualTo(123L);
j = connection.ExecuteScalarAsync<long>("select cast(123 as bigint)").Result;
j.IsEqualTo(123L);
int? k = connection.ExecuteScalar<int?>("select @i", new { i = default(int?) });
k.IsNull();
}
}
} }
} }
\ No newline at end of file
...@@ -3161,6 +3161,29 @@ public void Issue130_IConvertible() ...@@ -3161,6 +3161,29 @@ public void Issue130_IConvertible()
b.IsEqualTo("4"); b.IsEqualTo("4");
} }
public void Issue22_ExecuteScalar()
{
int i = connection.ExecuteScalar<int>("select 123");
i.IsEqualTo(123);
i = connection.ExecuteScalar<int>("select cast(123 as bigint)");
i.IsEqualTo(123);
long j = connection.ExecuteScalar<long>("select 123");
j.IsEqualTo(123L);
j = connection.ExecuteScalar<long>("select cast(123 as bigint)");
j.IsEqualTo(123L);
int? k = connection.ExecuteScalar<int?>("select @i", new { i = default(int?) });
k.IsNull();
Dapper.EntityFramework.Handlers.Register();
var geo = DbGeography.LineFromText("LINESTRING(-122.360 47.656, -122.343 47.656 )", 4326);
var geo2 = connection.ExecuteScalar<DbGeography>("select @geo", new { geo });
geo2.IsNotNull();
}
#if POSTGRESQL #if POSTGRESQL
class Cat class Cat
......
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