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)
{
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)
{
object param = command.Parameters;
......@@ -1068,12 +1120,6 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com
return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader);
}
private static CacheInfo GetCacheInfo(Identity identity, object obj, object addToCache)
{
throw new NotImplementedException();
}
/// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
......@@ -1304,7 +1350,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
if (tuple.Func == null || tuple.Hash != hash)
{
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;
......@@ -1318,7 +1364,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
} else if (val == null || val is T) {
yield return (T)val;
} else {
yield return (T)Convert.ChangeType(val, effectiveType);
yield return (T)Convert.ChangeType(val, effectiveType, CultureInfo.InvariantCulture);
}
}
// happy path; close the reader cleanly - no
......@@ -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);
deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]);
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);
......@@ -1764,7 +1810,6 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter
return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing);
}
return GetStructDeserializer(type, underlyingType ?? type, 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
}
}
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)
{
Action<IDbCommand, object> paramReader = GetParameterReader(cnn, ref command);
......@@ -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
enumParse = typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string), typeof(bool) }),
getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public)
......
......@@ -59,7 +59,7 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn,
if (wasClosed) await ((DbConnection)cnn).OpenAsync().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
......@@ -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;
int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash)
{
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
SetQueryCache(identity, info);
if(addToCache) SetQueryCache(identity, info);
}
var func = tuple.Func;
......@@ -546,5 +546,82 @@ private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn,
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
public int A { 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()
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
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