Commit 2de05697 authored by James Holwell's avatar James Holwell

Merge remote-tracking branch 'upstream/master' into issue-138

parents d149e0c2 ce23d47d
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013 # Visual Studio 2013
VisualStudioVersion = 12.0.30501.0 VisualStudioVersion = 12.0.30723.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DapperTests NET40", "Tests\DapperTests NET40.csproj", "{A2A80512-11F4-4028-A995-505463632C84}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DapperTests NET40", "Tests\DapperTests NET40.csproj", "{A2A80512-11F4-4028-A995-505463632C84}"
EndProject EndProject
...@@ -28,6 +28,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper NET45", "Dapper NET4 ...@@ -28,6 +28,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper NET45", "Dapper NET4
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E757192C-9411-458D-8815-8DAB34E12D03}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E757192C-9411-458D-8815-8DAB34E12D03}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
Dapper.Contrib.nuspec = Dapper.Contrib.nuspec
Dapper.EntityFramework.nuspec = Dapper.EntityFramework.nuspec Dapper.EntityFramework.nuspec = Dapper.EntityFramework.nuspec
Dapper.nuspec = Dapper.nuspec Dapper.nuspec = Dapper.nuspec
License.txt = License.txt License.txt = License.txt
...@@ -39,6 +40,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.EntityFramework NET4 ...@@ -39,6 +40,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.EntityFramework NET4
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.EntityFramework NET40", "Dapper.EntityFramework NET40\Dapper.EntityFramework NET40.csproj", "{2544DFBA-7F64-4003-9C36-D8337F770A36}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.EntityFramework NET40", "Dapper.EntityFramework NET40\Dapper.EntityFramework NET40.csproj", "{2544DFBA-7F64-4003-9C36-D8337F770A36}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.Contrib NET45", "Dapper.Contrib NET45\Dapper.Contrib NET45.csproj", "{302EC82F-A81B-48C5-B653-B5C75D2BD103}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
...@@ -169,6 +172,16 @@ Global ...@@ -169,6 +172,16 @@ Global
{2544DFBA-7F64-4003-9C36-D8337F770A36}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {2544DFBA-7F64-4003-9C36-D8337F770A36}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{2544DFBA-7F64-4003-9C36-D8337F770A36}.Release|Mixed Platforms.Build.0 = Release|Any CPU {2544DFBA-7F64-4003-9C36-D8337F770A36}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{2544DFBA-7F64-4003-9C36-D8337F770A36}.Release|x86.ActiveCfg = Release|Any CPU {2544DFBA-7F64-4003-9C36-D8337F770A36}.Release|x86.ActiveCfg = Release|Any CPU
{302EC82F-A81B-48C5-B653-B5C75D2BD103}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{302EC82F-A81B-48C5-B653-B5C75D2BD103}.Debug|Any CPU.Build.0 = Debug|Any CPU
{302EC82F-A81B-48C5-B653-B5C75D2BD103}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{302EC82F-A81B-48C5-B653-B5C75D2BD103}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{302EC82F-A81B-48C5-B653-B5C75D2BD103}.Debug|x86.ActiveCfg = Debug|Any CPU
{302EC82F-A81B-48C5-B653-B5C75D2BD103}.Release|Any CPU.ActiveCfg = Release|Any CPU
{302EC82F-A81B-48C5-B653-B5C75D2BD103}.Release|Any CPU.Build.0 = Release|Any CPU
{302EC82F-A81B-48C5-B653-B5C75D2BD103}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{302EC82F-A81B-48C5-B653-B5C75D2BD103}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{302EC82F-A81B-48C5-B653-B5C75D2BD103}.Release|x86.ActiveCfg = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
......
...@@ -32,5 +32,5 @@ ...@@ -32,5 +32,5 @@
// //
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("1.27.0.0")] [assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.27.0.0")] [assembly: AssemblyFileVersion("1.28.0.0")]
...@@ -43,6 +43,10 @@ public enum CommandFlags ...@@ -43,6 +43,10 @@ public enum CommandFlags
/// Can async queries be pipelined? /// Can async queries be pipelined?
/// </summary> /// </summary>
Pipelined = 2, Pipelined = 2,
/// <summary>
/// Should the plan cache be bypassed?
/// </summary>
NoCache = 4
} }
/// <summary> /// <summary>
/// Represents the key aspects of a sql operation /// Represents the key aspects of a sql operation
...@@ -84,6 +88,11 @@ public struct CommandDefinition ...@@ -84,6 +88,11 @@ public struct CommandDefinition
/// </summary> /// </summary>
public bool Buffered { get { return (flags & CommandFlags.Buffered) != 0; } } public bool Buffered { get { return (flags & CommandFlags.Buffered) != 0; } }
/// <summary>
/// Should the plan for this query be cached?
/// </summary>
internal bool AddToCache { get { return (flags & CommandFlags.NoCache) == 0; } }
/// <summary> /// <summary>
/// Additional state flags against this command /// Additional state flags against this command
/// </summary> /// </summary>
...@@ -132,8 +141,8 @@ public struct CommandDefinition ...@@ -132,8 +141,8 @@ public struct CommandDefinition
internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object> paramReader) internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object> paramReader)
{ {
var cmd = cnn.CreateCommand(); var cmd = cnn.CreateCommand();
var bindByName = GetBindByName(cmd.GetType()); var init = GetInit(cmd.GetType());
if (bindByName != null) bindByName(cmd, true); if (init != null) init(cmd);
if (transaction != null) if (transaction != null)
cmd.Transaction = transaction; cmd.Transaction = transaction;
cmd.CommandText = commandText; cmd.CommandText = commandText;
...@@ -148,37 +157,58 @@ internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object> p ...@@ -148,37 +157,58 @@ internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object> p
return cmd; return cmd;
} }
static SqlMapper.Link<Type, Action<IDbCommand, bool>> bindByNameCache; static SqlMapper.Link<Type, Action<IDbCommand>> commandInitCache;
static Action<IDbCommand, bool> GetBindByName(Type commandType) static Action<IDbCommand> GetInit(Type commandType)
{ {
if (commandType == null) return null; // GIGO if (commandType == null) return null; // GIGO
Action<IDbCommand, bool> action; Action<IDbCommand> action;
if (SqlMapper.Link<Type, Action<IDbCommand, bool>>.TryGet(bindByNameCache, commandType, out action)) if (SqlMapper.Link<Type, Action<IDbCommand>>.TryGet(commandInitCache, commandType, out action))
{ {
return action; return action;
} }
var prop = commandType.GetProperty("BindByName", BindingFlags.Public | BindingFlags.Instance); var bindByName = GetBasicPropertySetter(commandType, "BindByName", typeof(bool));
var initialLongFetchSize = GetBasicPropertySetter(commandType, "InitialLONGFetchSize", typeof(int));
action = null; action = null;
ParameterInfo[] indexers; if (bindByName != null || initialLongFetchSize != null)
MethodInfo setter;
if (prop != null && prop.CanWrite && prop.PropertyType == typeof(bool)
&& ((indexers = prop.GetIndexParameters()) == null || indexers.Length == 0)
&& (setter = prop.GetSetMethod()) != null
)
{ {
var method = new DynamicMethod(commandType.Name + "_BindByName", null, new Type[] { typeof(IDbCommand), typeof(bool) }); var method = new DynamicMethod(commandType.Name + "_init", null, new Type[] { typeof(IDbCommand) });
var il = method.GetILGenerator(); var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType); if (bindByName != null)
il.Emit(OpCodes.Ldarg_1); {
il.EmitCall(OpCodes.Callvirt, setter, null); // .BindByName = true
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldc_I4_1);
il.EmitCall(OpCodes.Callvirt, bindByName, null);
}
if (initialLongFetchSize != null)
{
// .InitialLONGFetchSize = -1
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldc_I4_M1);
il.EmitCall(OpCodes.Callvirt, initialLongFetchSize, null);
}
il.Emit(OpCodes.Ret); il.Emit(OpCodes.Ret);
action = (Action<IDbCommand, bool>)method.CreateDelegate(typeof(Action<IDbCommand, bool>)); action = (Action<IDbCommand>)method.CreateDelegate(typeof(Action<IDbCommand>));
} }
// cache it // cache it
SqlMapper.Link<Type, Action<IDbCommand, bool>>.TryAdd(ref bindByNameCache, commandType, ref action); SqlMapper.Link<Type, Action<IDbCommand>>.TryAdd(ref commandInitCache, commandType, ref action);
return action; return action;
} }
static MethodInfo GetBasicPropertySetter(Type declaringType, string name, Type expectedType)
{
var prop = declaringType.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
ParameterInfo[] indexers;
if (prop != null && prop.CanWrite && prop.PropertyType == expectedType
&& ((indexers = prop.GetIndexParameters()) == null || indexers.Length == 0))
{
return prop.GetSetMethod();
}
return null;
}
} }
/// <summary> /// <summary>
...@@ -578,7 +608,7 @@ public static int GetCachedSQLCount() ...@@ -578,7 +608,7 @@ public static int GetCachedSQLCount()
#endif #endif
static readonly Dictionary<Type, DbType> typeMap; static Dictionary<Type, DbType> typeMap;
static SqlMapper() static SqlMapper()
{ {
...@@ -621,34 +651,96 @@ static SqlMapper() ...@@ -621,34 +651,96 @@ static SqlMapper()
typeMap[typeof(TimeSpan?)] = DbType.Time; typeMap[typeof(TimeSpan?)] = DbType.Time;
typeMap[typeof(object)] = DbType.Object; typeMap[typeof(object)] = DbType.Object;
AddTypeHandler(typeof(DataTable), new DataTableHandler()); AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), false);
}
/// <summary>
/// Clear the registered type handlers
/// </summary>
public static void ResetTypeHandlers()
{
typeHandlers = new Dictionary<Type, ITypeHandler>();
AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), true);
} }
/// <summary> /// <summary>
/// Configire the specified type to be mapped to a given db-type /// Configire the specified type to be mapped to a given db-type
/// </summary> /// </summary>
public static void AddTypeMap(Type type, DbType dbType) public static void AddTypeMap(Type type, DbType dbType)
{ {
typeMap[type] = dbType; // use clone, mutate, replace to avoid threading issues
var snapshot = typeMap;
DbType oldValue;
if (snapshot.TryGetValue(type, out oldValue) && oldValue == dbType) return; // nothing to do
var newCopy = new Dictionary<Type, DbType>(snapshot);
newCopy[type] = dbType;
typeMap = newCopy;
} }
/// <summary> /// <summary>
/// Configire the specified type to be processed by a custom handler /// Configire the specified type to be processed by a custom handler
/// </summary> /// </summary>
public static void AddTypeHandler(Type type, ITypeHandler handler) public static void AddTypeHandler(Type type, ITypeHandler handler)
{
AddTypeHandlerImpl(type, handler, true);
}
/// <summary>
/// Configire the specified type to be processed by a custom handler
/// </summary>
public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clone)
{ {
if (type == null) throw new ArgumentNullException("type"); if (type == null) throw new ArgumentNullException("type");
Type secondary = null;
if(type.IsValueType)
{
var underlying = Nullable.GetUnderlyingType(type);
if(underlying == null)
{
secondary = typeof(Nullable<>).MakeGenericType(type); // the Nullable<T>
// type is already the T
}
else
{
secondary = type; // the Nullable<T>
type = underlying; // the T
}
}
var snapshot = typeHandlers;
ITypeHandler oldValue;
if (snapshot.TryGetValue(type, out oldValue) && handler == oldValue) return; // nothing to do
var newCopy = clone ? new Dictionary<Type, ITypeHandler>(snapshot) : snapshot;
#pragma warning disable 618 #pragma warning disable 618
typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod("SetHandler", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { handler }); typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod("SetHandler", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { handler });
if(secondary != null)
{
typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod("SetHandler", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { handler });
}
#pragma warning restore 618 #pragma warning restore 618
if (handler == null) typeHandlers.Remove(type); if (handler == null)
else typeHandlers[type] = handler; {
newCopy.Remove(type);
if (secondary != null) newCopy.Remove(secondary);
}
else
{
newCopy[type] = handler;
if(secondary != null) newCopy[secondary] = handler;
}
typeHandlers = newCopy;
} }
/// <summary> /// <summary>
/// Configire the specified type to be processed by a custom handler /// Configire the specified type to be processed by a custom handler
/// </summary> /// </summary>
public static void AddTypeHandler<T>(TypeHandler<T> handler) public static void AddTypeHandler<T>(TypeHandler<T> handler)
{ {
AddTypeHandler(typeof(T), handler); AddTypeHandlerImpl(typeof(T), handler, true);
} }
/// <summary> /// <summary>
...@@ -687,7 +779,7 @@ internal static void SetHandler(ITypeHandler handler) ...@@ -687,7 +779,7 @@ internal static void SetHandler(ITypeHandler handler)
private static ITypeHandler handler; private static ITypeHandler handler;
} }
private static readonly Dictionary<Type, ITypeHandler> typeHandlers = new Dictionary<Type, ITypeHandler>(); private static Dictionary<Type, ITypeHandler> typeHandlers = new Dictionary<Type, ITypeHandler>();
internal const string LinqBinary = "System.Data.Linq.Binary"; internal const string LinqBinary = "System.Data.Linq.Binary";
internal static DbType LookupDbType(Type type, string name, out ITypeHandler handler) internal static DbType LookupDbType(Type type, string name, out ITypeHandler handler)
...@@ -1001,6 +1093,58 @@ public static int Execute(this IDbConnection cnn, CommandDefinition command) ...@@ -1001,6 +1093,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;
...@@ -1032,7 +1176,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com ...@@ -1032,7 +1176,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com
masterSql = cmd.CommandText; masterSql = cmd.CommandText;
isFirst = false; isFirst = false;
identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
info = GetCacheInfo(identity, obj); info = GetCacheInfo(identity, obj, command.AddToCache);
} }
else else
{ {
...@@ -1054,7 +1198,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com ...@@ -1054,7 +1198,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com
if (param != null) if (param != null)
{ {
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
info = GetCacheInfo(identity, param); info = GetCacheInfo(identity, param, command.AddToCache);
} }
return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader); return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader);
} }
...@@ -1232,7 +1376,7 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD ...@@ -1232,7 +1376,7 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD
{ {
object param = command.Parameters; object param = command.Parameters;
Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null); Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null);
CacheInfo info = GetCacheInfo(identity, param); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache);
IDbCommand cmd = null; IDbCommand cmd = null;
IDataReader reader = null; IDataReader reader = null;
...@@ -1268,7 +1412,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini ...@@ -1268,7 +1412,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
{ {
object param = command.Parameters; object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, 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, command.AddToCache);
IDbCommand cmd = null; IDbCommand cmd = null;
IDataReader reader = null; IDataReader reader = null;
...@@ -1288,22 +1432,21 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini ...@@ -1288,22 +1432,21 @@ 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)
{ {
if (reader.FieldCount == 0) //https://code.google.com/p/dapper-dot-net/issues/detail?id=57
yield break;
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;
var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
while (reader.Read()) while (reader.Read())
{ {
object val = func(reader); object val = func(reader);
if (effectiveType == typeof(object)) if (val == null || val is T) {
{
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, effectiveType); yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
} }
} }
// happy path; close the reader cleanly - no // happy path; close the reader cleanly - no
...@@ -1502,7 +1645,7 @@ partial class DontMap { } ...@@ -1502,7 +1645,7 @@ partial class DontMap { }
{ {
object param = command.Parameters; object param = command.Parameters;
identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }); identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
CacheInfo cinfo = GetCacheInfo(identity, param); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache);
IDbCommand ownedCommand = null; IDbCommand ownedCommand = null;
IDataReader ownedReader = null; IDataReader ownedReader = null;
...@@ -1526,7 +1669,7 @@ partial class DontMap { } ...@@ -1526,7 +1669,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);
...@@ -1688,7 +1831,7 @@ private static int GetNextSplit(int startIdx, string splitOn, IDataReader reader ...@@ -1688,7 +1831,7 @@ private static int GetNextSplit(int startIdx, string splitOn, IDataReader reader
throw new ArgumentException(MultiMapSplitExceptionMessage); throw new ArgumentException(MultiMapSplitExceptionMessage);
} }
private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters) private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters, bool addToCache)
{ {
CacheInfo info; CacheInfo info;
if (!TryGetQueryCache(identity, out info)) if (!TryGetQueryCache(identity, out info))
...@@ -1717,7 +1860,7 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter ...@@ -1717,7 +1860,7 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter
info.ParamReader = CreateParamInfoGenerator(identity, false, true, literals); info.ParamReader = CreateParamInfoGenerator(identity, false, true, literals);
} }
} }
SetQueryCache(identity, info); if(addToCache) SetQueryCache(identity, info);
} }
return info; return info;
} }
...@@ -1749,7 +1892,6 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter ...@@ -1749,7 +1892,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)
{ {
...@@ -2636,7 +2778,7 @@ internal static IList<LiteralToken> GetLiteralTokens(string sql) ...@@ -2636,7 +2778,7 @@ internal static IList<LiteralToken> GetLiteralTokens(string sql)
} }
if (checkForNull) if (checkForNull)
{ {
if (dbType == DbType.String && !haveInt32Arg1) if ((dbType == DbType.String || dbType == DbType.AnsiString) && !haveInt32Arg1)
{ {
il.DeclareLocal(typeof(int)); il.DeclareLocal(typeof(int));
haveInt32Arg1 = true; haveInt32Arg1 = true;
...@@ -2644,12 +2786,12 @@ internal static IList<LiteralToken> GetLiteralTokens(string sql) ...@@ -2644,12 +2786,12 @@ internal static IList<LiteralToken> GetLiteralTokens(string sql)
// relative stack: [boxed value] // relative stack: [boxed value]
il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value] il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value]
Label notNull = il.DefineLabel(); Label notNull = il.DefineLabel();
Label? allDone = dbType == DbType.String ? il.DefineLabel() : (Label?)null; Label? allDone = (dbType == DbType.String || dbType == DbType.AnsiString) ? il.DefineLabel() : (Label?)null;
il.Emit(OpCodes.Brtrue_S, notNull); il.Emit(OpCodes.Brtrue_S, notNull);
// relative stack [boxed value = null] // relative stack [boxed value = null]
il.Emit(OpCodes.Pop); // relative stack empty il.Emit(OpCodes.Pop); // relative stack empty
il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); // relative stack [DBNull] il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); // relative stack [DBNull]
if (dbType == DbType.String) if (dbType == DbType.String || dbType == DbType.AnsiString)
{ {
EmitInt32(il, 0); EmitInt32(il, 0);
il.Emit(OpCodes.Stloc_1); il.Emit(OpCodes.Stloc_1);
...@@ -2839,6 +2981,33 @@ private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition comma ...@@ -2839,6 +2981,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);
...@@ -2875,7 +3044,7 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin ...@@ -2875,7 +3044,7 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin
if (param != null) if (param != null)
{ {
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
info = GetCacheInfo(identity, param); info = GetCacheInfo(identity, param, command.AddToCache);
} }
var paramReader = info == null ? null : info.ParamReader; var paramReader = info == null ? null : info.ParamReader;
return paramReader; return paramReader;
...@@ -2904,6 +3073,10 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin ...@@ -2904,6 +3073,10 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin
return r => return r =>
{ {
var val = r.GetValue(index); var val = r.GetValue(index);
if(val is float || val is double || val is decimal)
{
val = Convert.ChangeType(val, Enum.GetUnderlyingType(effectiveType), CultureInfo.InvariantCulture);
}
return val is DBNull ? null : Enum.ToObject(effectiveType, val); return val is DBNull ? null : Enum.ToObject(effectiveType, val);
}; };
} }
...@@ -2923,6 +3096,28 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin ...@@ -2923,6 +3096,28 @@ 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);
type = Nullable.GetUnderlyingType(type) ?? type;
if (type.IsEnum)
{
if (value is float || value is double || value is decimal)
{
value = Convert.ChangeType(value, Enum.GetUnderlyingType(type), CultureInfo.InvariantCulture);
}
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)
...@@ -3096,7 +3291,7 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -3096,7 +3291,7 @@ public static void SetTypeMap(Type type, ITypeMap map)
il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index] il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index]
il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index] il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index]
il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object] il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object]
Type colType = reader.GetFieldType(index);
Type memberType = item.MemberType; Type memberType = item.MemberType;
if (memberType == typeof(char) || memberType == typeof(char?)) if (memberType == typeof(char) || memberType == typeof(char?))
...@@ -3117,33 +3312,30 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -3117,33 +3312,30 @@ public static void SetTypeMap(Type type, ITypeMap map)
if (unboxType.IsEnum) if (unboxType.IsEnum)
{ {
if (enumDeclareLocal == -1) Type numericType = Enum.GetUnderlyingType(unboxType);
if(colType == typeof(string))
{ {
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex; if (enumDeclareLocal == -1)
{
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
}
il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [target][target][string]
StoreLocal(il, enumDeclareLocal); // stack is now [target][target]
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string]
il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
}
else
{
FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType);
} }
Label isNotString = il.DefineLabel();
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null]
StoreLocal(il, enumDeclareLocal); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object]
il.Emit(OpCodes.Pop); // stack is now [target][target]
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string]
il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
il.MarkLabel(isNotString);
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
if (nullUnderlyingType != null) if (nullUnderlyingType != null)
{ {
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][enum-value] il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
} }
} }
else if (memberType.FullName == LinqBinary) else if (memberType.FullName == LinqBinary)
...@@ -3153,10 +3345,9 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -3153,10 +3345,9 @@ public static void SetTypeMap(Type type, ITypeMap map)
} }
else else
{ {
Type dataType = reader.GetFieldType(index); TypeCode dataTypeCode = Type.GetTypeCode(colType), unboxTypeCode = Type.GetTypeCode(unboxType);
TypeCode dataTypeCode = Type.GetTypeCode(dataType), unboxTypeCode = Type.GetTypeCode(unboxType);
bool hasTypeHandler; bool hasTypeHandler;
if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || dataType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType)) if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType))
{ {
if (hasTypeHandler) if (hasTypeHandler)
{ {
...@@ -3172,71 +3363,7 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -3172,71 +3363,7 @@ public static void SetTypeMap(Type type, ITypeMap map)
else else
{ {
// not a direct match; need to tweak the unbox // not a direct match; need to tweak the unbox
MethodInfo op; FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null);
if ((op = GetOperator(dataType, nullUnderlyingType ?? unboxType)) != null)
{ // this is handy for things like decimal <===> double
il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]
il.Emit(OpCodes.Call, op); // stack is now [target][target][typed-value]
}
else
{
bool handled = true;
OpCode opCode = default(OpCode);
if (dataTypeCode == TypeCode.Decimal || unboxTypeCode == TypeCode.Decimal)
{ // no IL level conversions to/from decimal; I guess we could use the static operators, but
// this feels an edge-case
handled = false;
}
else
{
switch (unboxTypeCode)
{
case TypeCode.Byte:
opCode = OpCodes.Conv_Ovf_I1_Un; break;
case TypeCode.SByte:
opCode = OpCodes.Conv_Ovf_I1; break;
case TypeCode.UInt16:
opCode = OpCodes.Conv_Ovf_I2_Un; break;
case TypeCode.Int16:
opCode = OpCodes.Conv_Ovf_I2; break;
case TypeCode.UInt32:
opCode = OpCodes.Conv_Ovf_I4_Un; break;
case TypeCode.Boolean: // boolean is basically an int, at least at this level
case TypeCode.Int32:
opCode = OpCodes.Conv_Ovf_I4; break;
case TypeCode.UInt64:
opCode = OpCodes.Conv_Ovf_I8_Un; break;
case TypeCode.Int64:
opCode = OpCodes.Conv_Ovf_I8; break;
case TypeCode.Single:
opCode = OpCodes.Conv_R4; break;
case TypeCode.Double:
opCode = OpCodes.Conv_R8; break;
default:
handled = false;
break;
}
}
if (handled)
{ // unbox as the data-type, then use IL-level convert
il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]
il.Emit(opCode); // stack is now [target][target][typed-value]
if (unboxTypeCode == TypeCode.Boolean)
{ // compare to zero; I checked "csc" - this is the trick it uses; nice
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
}
}
else
{ // use flexible conversion
il.Emit(OpCodes.Ldtoken, nullUnderlyingType ?? unboxType); // stack is now [target][target][value][member-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // stack is now [target][target][value][member-type]
il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value]
il.Emit(OpCodes.Unbox_Any, nullUnderlyingType ?? unboxType); // stack is now [target][target][typed-value]
}
}
if (nullUnderlyingType != null) if (nullUnderlyingType != null)
{ {
il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value] il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
...@@ -3336,6 +3463,89 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -3336,6 +3463,89 @@ public static void SetTypeMap(Type type, ITypeMap map)
return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>)); return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>));
} }
private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type from, Type to, Type via)
{
MethodInfo op;
if(from == (via ?? to))
{
il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value]
}
else if ((op = GetOperator(from,to)) != null)
{
// this is handy for things like decimal <===> double
il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][data-typed-value]
il.Emit(OpCodes.Call, op); // stack is now [target][target][typed-value]
}
else
{
bool handled = false;
OpCode opCode = default(OpCode);
switch (Type.GetTypeCode(from))
{
case TypeCode.Boolean:
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.UInt16:
case TypeCode.Int32:
case TypeCode.UInt32:
case TypeCode.Int64:
case TypeCode.UInt64:
case TypeCode.Single:
case TypeCode.Double:
handled = true;
switch (Type.GetTypeCode(via ?? to))
{
case TypeCode.Byte:
opCode = OpCodes.Conv_Ovf_I1_Un; break;
case TypeCode.SByte:
opCode = OpCodes.Conv_Ovf_I1; break;
case TypeCode.UInt16:
opCode = OpCodes.Conv_Ovf_I2_Un; break;
case TypeCode.Int16:
opCode = OpCodes.Conv_Ovf_I2; break;
case TypeCode.UInt32:
opCode = OpCodes.Conv_Ovf_I4_Un; break;
case TypeCode.Boolean: // boolean is basically an int, at least at this level
case TypeCode.Int32:
opCode = OpCodes.Conv_Ovf_I4; break;
case TypeCode.UInt64:
opCode = OpCodes.Conv_Ovf_I8_Un; break;
case TypeCode.Int64:
opCode = OpCodes.Conv_Ovf_I8; break;
case TypeCode.Single:
opCode = OpCodes.Conv_R4; break;
case TypeCode.Double:
opCode = OpCodes.Conv_R8; break;
default:
handled = false;
break;
}
break;
}
if (handled)
{
il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][col-typed-value]
il.Emit(opCode); // stack is now [target][target][typed-value]
if (to == typeof(bool))
{ // compare to zero; I checked "csc" - this is the trick it uses; nice
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
}
}
else
{
il.Emit(OpCodes.Ldtoken, via ?? to); // stack is now [target][target][value][member-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // stack is now [target][target][value][member-type]
il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value]
il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value]
}
}
}
static MethodInfo GetOperator(Type from, Type to) static MethodInfo GetOperator(Type from, Type to)
{ {
if (to == null) return null; if (to == null) return null;
...@@ -3548,7 +3758,7 @@ public IEnumerable<T> Read<T>(bool buffered = true) ...@@ -3548,7 +3758,7 @@ public IEnumerable<T> Read<T>(bool buffered = true)
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 (consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); 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(typeof(T), gridIndex); var typedIdentity = identity.ForGrid(typeof(T), gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null); CacheInfo cache = GetCacheInfo(typedIdentity, null, true);
var deserializer = cache.Deserializer; var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
...@@ -3575,7 +3785,7 @@ public IEnumerable<object> Read(Type type, bool buffered = true) ...@@ -3575,7 +3785,7 @@ public IEnumerable<object> Read(Type type, bool buffered = true)
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 (consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); 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); var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null); CacheInfo cache = GetCacheInfo(typedIdentity, null, true);
var deserializer = cache.Deserializer; var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
...@@ -3728,6 +3938,16 @@ private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> dese ...@@ -3728,6 +3938,16 @@ private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> dese
} }
private int gridIndex, readCount; private int gridIndex, readCount;
private bool consumed; private bool consumed;
/// <summary>
/// Has the underlying reader been consumed?
/// </summary>
public bool IsConsumed
{
get
{
return consumed;
}
}
private void NextResult() private void NextResult()
{ {
if (reader.NextResult()) if (reader.NextResult())
...@@ -4458,13 +4678,17 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, ...@@ -4458,13 +4678,17 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor,
public SqlMapper.IMemberMap GetMember(string columnName) public SqlMapper.IMemberMap GetMember(string columnName)
{ {
var property = _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) var property = _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)); ?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase))
?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.Ordinal))
?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.OrdinalIgnoreCase));
if (property != null) if (property != null)
return new SimpleMemberMap(columnName, property); return new SimpleMemberMap(columnName, property);
var field = _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) var field = _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)); ?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.Ordinal))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.OrdinalIgnoreCase));
if (field != null) if (field != null)
return new SimpleMemberMap(columnName, field); return new SimpleMemberMap(columnName, field);
......
...@@ -50,7 +50,7 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, ...@@ -50,7 +50,7 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn,
{ {
object param = command.Parameters; object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, 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, command.AddToCache);
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))
{ {
...@@ -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
...@@ -130,7 +130,7 @@ private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandD ...@@ -130,7 +130,7 @@ private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandD
cmd = (DbCommand)command.SetupCommand(cnn, null); cmd = (DbCommand)command.SetupCommand(cnn, null);
masterSql = cmd.CommandText; masterSql = cmd.CommandText;
var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
info = GetCacheInfo(identity, obj); info = GetCacheInfo(identity, obj, command.AddToCache);
} else if(pending.Count >= MAX_PENDING) } else if(pending.Count >= MAX_PENDING)
{ {
var recycled = pending.Dequeue(); var recycled = pending.Dequeue();
...@@ -177,7 +177,7 @@ private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandD ...@@ -177,7 +177,7 @@ private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandD
masterSql = cmd.CommandText; masterSql = cmd.CommandText;
isFirst = false; isFirst = false;
var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
info = GetCacheInfo(identity, obj); info = GetCacheInfo(identity, obj, command.AddToCache);
} }
else else
{ {
...@@ -199,7 +199,7 @@ private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandD ...@@ -199,7 +199,7 @@ private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandD
private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object param) private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object param)
{ {
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param == null ? null : param.GetType(), null); var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity, param); var info = GetCacheInfo(identity, param, command.AddToCache);
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))
{ {
...@@ -390,7 +390,7 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini ...@@ -390,7 +390,7 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
{ {
object param = command.Parameters; object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }); var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
var info = GetCacheInfo(identity, param); var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed; bool wasClosed = cnn.State == ConnectionState.Closed;
try try
{ {
...@@ -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;
...@@ -446,7 +446,7 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn, ...@@ -446,7 +446,7 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn,
{ {
object param = command.Parameters; object param = command.Parameters;
Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null); Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null);
CacheInfo info = GetCacheInfo(identity, param); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache);
DbCommand cmd = null; DbCommand cmd = null;
IDataReader reader = null; IDataReader reader = null;
...@@ -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
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{302EC82F-A81B-48C5-B653-B5C75D2BD103}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Dapper.Contrib</RootNamespace>
<AssemblyName>Dapper.Contrib</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Dapper.Contrib.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Dapper.Contrib.XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Dapper.Contrib\Properties\AssemblyInfo.cs">
<Link>AssemblyInfo.cs</Link>
</Compile>
<Compile Include="..\Dapper.Contrib\SqlMapperExtensions.cs">
<Link>SqlMapperExtensions.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dapper NET45\Dapper NET45.csproj">
<Project>{0fff5bc7-0a4b-4d87-835e-4fad70937507}</Project>
<Name>Dapper NET45</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
...@@ -31,5 +31,5 @@ ...@@ -31,5 +31,5 @@
// //
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("1.27.0.0")] [assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.27.0.0")] [assembly: AssemblyFileVersion("1.28.0.0")]
<?xml version="1.0"?> <?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Dapper.Contrib</id> <id>Dapper.Contrib</id>
<version>1.1</version> <version>1.1</version>
<authors>Sam Saffron,Johan Danforth</authors> <authors>Sam Saffron,Johan Danforth</authors>
<owners>Sam Saffron,Johan Danforth</owners> <owners>Sam Saffron,Johan Danforth</owners>
<licenseUrl>http://www.apache.org/licenses/LICENSE-2.0</licenseUrl> <licenseUrl>http://www.apache.org/licenses/LICENSE-2.0</licenseUrl>
<projectUrl>http://code.google.com/p/dapper-dot-net/</projectUrl> <projectUrl>http://code.google.com/p/dapper-dot-net/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>A collection of create,insert,update,delete helpers for dapper.net</description> <description>A collection of create,insert,update,delete helpers for dapper.net</description>
<tags>orm sql micro-orm</tags> <tags>orm sql micro-orm</tags>
<dependencies> <dependencies>
<dependency id="Dapper" /> <dependency id="Dapper" version="1.27" />
</dependencies> </dependencies>
<frameworkAssemblies> <frameworkAssemblies>
<frameworkAssembly assemblyName="System.Core" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" /> <frameworkAssembly assemblyName="System.Core" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" />
<frameworkAssembly assemblyName="System" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" /> <frameworkAssembly assemblyName="System" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" />
<frameworkAssembly assemblyName="System.Data" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" /> <frameworkAssembly assemblyName="System.Data" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" />
<frameworkAssembly assemblyName="Microsoft.CSharp" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" /> <frameworkAssembly assemblyName="Microsoft.CSharp" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" />
</frameworkAssemblies> </frameworkAssemblies>
</metadata> </metadata>
<files> <files>
<file src="SqlMapperExtensions.cs" target="content\Dapper\SqlMapperExtensions.cs" /> <file src="Dapper.Contrib NET45\bin\Release\Dapper.Contrib.*" target="lib\net45" />
</files> <file src="Dapper.Contrib\bin\Release\Dapper.Contrib.*" target="lib\net40" />
</files>
</package> </package>
\ No newline at end of file
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Dapper.Contrib.XML</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
...@@ -37,6 +38,7 @@ ...@@ -37,6 +38,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Dapper.Contrib.XML</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
...@@ -51,9 +53,6 @@ ...@@ -51,9 +53,6 @@
<Compile Include="SqlMapperExtensions.cs" /> <Compile Include="SqlMapperExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="Dapper.Contrib.nuspec" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Dapper NET40\Dapper NET40.csproj"> <ProjectReference Include="..\Dapper NET40\Dapper NET40.csproj">
<Project>{daf737e1-05b5-4189-a5aa-dac6233b64d7}</Project> <Project>{daf737e1-05b5-4189-a5aa-dac6233b64d7}</Project>
......
...@@ -31,5 +31,5 @@ ...@@ -31,5 +31,5 @@
// //
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("1.27.0.0")] [assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.27.0.0")] [assembly: AssemblyFileVersion("1.28.0.0")]
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Dapper; using Dapper;
#pragma warning disable 1573, 1591 // xml comments
namespace Dapper.Contrib.Extensions namespace Dapper.Contrib.Extensions
{ {
...@@ -240,7 +242,8 @@ private static string GetTableName(Type type) ...@@ -240,7 +242,8 @@ private static string GetTableName(Type type)
sb.AppendFormat("update {0} set ", name); sb.AppendFormat("update {0} set ", name);
var allProperties = TypePropertiesCache(type); var allProperties = TypePropertiesCache(type);
var nonIdProps = allProperties.Where(a => !keyProperties.Contains(a)); var computedProperties = ComputedPropertiesCache(type);
var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties));
for (var i = 0; i < nonIdProps.Count(); i++) for (var i = 0; i < nonIdProps.Count(); i++)
{ {
......
...@@ -31,5 +31,5 @@ ...@@ -31,5 +31,5 @@
// //
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("1.27.0.0")] [assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.27.0.0")] [assembly: AssemblyFileVersion("1.28.0.0")]
...@@ -14,6 +14,7 @@ class Clause ...@@ -14,6 +14,7 @@ class Clause
{ {
public string Sql { get; set; } public string Sql { get; set; }
public object Parameters { get; set; } public object Parameters { get; set; }
public bool IsInclusive { get; set; }
} }
class Clauses : List<Clause> class Clauses : List<Clause>
...@@ -35,7 +36,18 @@ public string ResolveClauses(DynamicParameters p) ...@@ -35,7 +36,18 @@ public string ResolveClauses(DynamicParameters p)
{ {
p.AddDynamicParams(item.Parameters); p.AddDynamicParams(item.Parameters);
} }
return prefix + string.Join(joiner, this.Select(c => c.Sql)) + postfix; return this.Any(a => a.IsInclusive)
? prefix +
string.Join(joiner,
this.Where(a => !a.IsInclusive)
.Select(c => c.Sql)
.Union(new[]
{
" ( " +
string.Join(" OR ", this.Where(a => a.IsInclusive).Select(c => c.Sql).ToArray()) +
" ) "
})) + postfix
: prefix + string.Join(joiner, this.Select(c => c.Sql)) + postfix;
} }
} }
...@@ -94,7 +106,7 @@ public Template AddTemplate(string sql, dynamic parameters = null) ...@@ -94,7 +106,7 @@ public Template AddTemplate(string sql, dynamic parameters = null)
return new Template(this, sql, parameters); return new Template(this, sql, parameters);
} }
void AddClause(string name, string sql, object parameters, string joiner, string prefix = "", string postfix = "") void AddClause(string name, string sql, object parameters, string joiner, string prefix = "", string postfix = "", bool IsInclusive = false)
{ {
Clauses clauses; Clauses clauses;
if (!data.TryGetValue(name, out clauses)) if (!data.TryGetValue(name, out clauses))
...@@ -106,6 +118,12 @@ void AddClause(string name, string sql, object parameters, string joiner, string ...@@ -106,6 +118,12 @@ void AddClause(string name, string sql, object parameters, string joiner, string
seq++; seq++;
} }
public SqlBuilder Intersect(string sql, dynamic parameters = null)
{
AddClause("intersect", sql, parameters, joiner: "\nINTERSECT\n ", prefix: "\n ", postfix: "\n");
return this;
}
public SqlBuilder InnerJoin(string sql, dynamic parameters = null) public SqlBuilder InnerJoin(string sql, dynamic parameters = null)
{ {
AddClause("innerjoin", sql, parameters, joiner: "\nINNER JOIN ", prefix: "\nINNER JOIN ", postfix: "\n"); AddClause("innerjoin", sql, parameters, joiner: "\nINNER JOIN ", prefix: "\nINNER JOIN ", postfix: "\n");
...@@ -130,6 +148,12 @@ public SqlBuilder Where(string sql, dynamic parameters = null) ...@@ -130,6 +148,12 @@ public SqlBuilder Where(string sql, dynamic parameters = null)
return this; return this;
} }
public SqlBuilder OrWhere(string sql, dynamic parameters = null)
{
AddClause("where", sql, parameters, " AND ", prefix: "WHERE ", postfix: "\n", IsInclusive: true);
return this;
}
public SqlBuilder OrderBy(string sql, dynamic parameters = null) public SqlBuilder OrderBy(string sql, dynamic parameters = null)
{ {
AddClause("orderby", sql, parameters, " , ", prefix: "ORDER BY ", postfix: "\n"); AddClause("orderby", sql, parameters, " , ", prefix: "ORDER BY ", postfix: "\n");
......
...@@ -31,5 +31,5 @@ ...@@ -31,5 +31,5 @@
// //
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("1.27.0.0")] [assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.27.0.0")] [assembly: AssemblyFileVersion("1.28.0.0")]
...@@ -17,12 +17,14 @@ public void TestBasicStringUsage() ...@@ -17,12 +17,14 @@ public void TestBasicStringUsage()
} }
public void TestClassWithStringUsage() public void TestClassWithStringUsage()
{ {
var arr = connection.Query<BasicType>("select 'abc' as [Value] union all select @txt", new { txt = "def" }).ToArray(); var arr = connection.Query<BasicType>("select 'abc' as [Value], '123' as [Another_Value] union all select @txt, @txt2", new { txt = "def", txt2 = "456" }).ToArray();
arr.Select(x => x.Value).IsSequenceEqualTo(new[] { "abc", "def" }); arr.Select(x => x.Value).IsSequenceEqualTo(new[] { "abc", "def" });
arr.Select(x => x.AnotherValue).IsSequenceEqualTo(new[] { "123", "456" });
} }
class BasicType class BasicType
{ {
public string Value { get; set; } public string Value { get; set; }
public string AnotherValue { get; set; }
} }
public void TestDynamicSimulatedQuery() { public void TestDynamicSimulatedQuery() {
......
...@@ -31,5 +31,5 @@ ...@@ -31,5 +31,5 @@
// //
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("1.27.0.0")] [assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.27.0.0")] [assembly: AssemblyFileVersion("1.28.0.0")]
...@@ -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
...@@ -352,6 +352,6 @@ Who is using this? ...@@ -352,6 +352,6 @@ Who is using this?
--------------------- ---------------------
Dapper is in production use at: Dapper is in production use at:
[Stack Overflow](http://stackoverflow.com/), [xpfest.com](http://www.xapfest.com/), [helpdesk](http://www.jitbit.com/helpdesk-software/) [Stack Overflow](http://stackoverflow.com/), [helpdesk](http://www.jitbit.com/helpdesk-software/)
(if you would like to be listed here let me know) (if you would like to be listed here let me know)
...@@ -128,7 +128,7 @@ public void Run(int iterations) ...@@ -128,7 +128,7 @@ public void Run(int iterations)
// dapper.contrib // dapper.contrib
var mapperConnection3 = Program.GetOpenConnection(); var mapperConnection3 = Program.GetOpenConnection();
tests.Add(id => mapperConnection2.Get<Post>(id), "Dapper.Cotrib"); tests.Add(id => mapperConnection3.Get<Post>(id), "Dapper.Cotrib");
var massiveModel = new DynamicModel(Program.ConnectionString); var massiveModel = new DynamicModel(Program.ConnectionString);
var massiveConnection = Program.GetOpenConnection(); var massiveConnection = Program.GetOpenConnection();
......
...@@ -31,5 +31,5 @@ ...@@ -31,5 +31,5 @@
// //
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("1.27.0.0")] [assembly: AssemblyVersion("1.28.0.0")]
[assembly: AssemblyFileVersion("1.27.0.0")] [assembly: AssemblyFileVersion("1.28.0.0")]
...@@ -2575,6 +2575,24 @@ public void TestNullFromInt_NoRows() ...@@ -2575,6 +2575,24 @@ public void TestNullFromInt_NoRows()
} }
public void TestChangingDefaultStringTypeMappingToAnsiString()
{
var sql = "SELECT SQL_VARIANT_PROPERTY(CONVERT(sql_variant, @testParam),'BaseType') AS BaseType";
var param = new {testParam = "TestString"};
var result01 = connection.Query<string>(sql, param).FirstOrDefault();
result01.IsEqualTo("nvarchar");
Dapper.SqlMapper.PurgeQueryCache();
Dapper.SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString); // Change Default String Handling to AnsiString
var result02 = connection.Query<string>(sql, param).FirstOrDefault();
result02.IsEqualTo("varchar");
Dapper.SqlMapper.PurgeQueryCache();
Dapper.SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String
}
class TransactedConnection : IDbConnection class TransactedConnection : IDbConnection
{ {
IDbConnection _conn; IDbConnection _conn;
...@@ -3146,6 +3164,306 @@ public void SO24740733_TestCustomValueSingleColumn() ...@@ -3146,6 +3164,306 @@ public void SO24740733_TestCustomValueSingleColumn()
foo.Value.IsEqualTo(200); foo.Value.IsEqualTo(200);
} }
public void Issue130_IConvertible()
{
dynamic row = connection.Query("select 1 as [a], '2' as [b]").Single();
int a = row.a;
string b = row.b;
a.IsEqualTo(1);
b.IsEqualTo("2");
row = connection.Query<dynamic>("select 3 as [a], '4' as [b]").Single();
a = row.a;
b = row.b;
a.IsEqualTo(3);
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();
}
public void Issue142_FailsNamedStatus()
{
var row1 = connection.Query<Issue142_Status>("select @Status as [Status]", new { Status = StatusType.Started }).Single();
row1.Status.IsEqualTo(StatusType.Started);
var row2 = connection.Query<Issue142_StatusType>("select @Status as [Status]", new { Status = Status.Started }).Single();
row2.Status.IsEqualTo(Status.Started);
}
public class Issue142_Status
{
public StatusType Status { get; set; }
}
public class Issue142_StatusType
{
public Status Status { get; set; }
}
public enum StatusType : byte
{
NotStarted = 1, Started = 2, Finished = 3
}
public enum Status : byte
{
NotStarted = 1, Started = 2, Finished = 3
}
public void Issue136_ValueTypeHandlers()
{
Dapper.SqlMapper.ResetTypeHandlers();
Dapper.SqlMapper.AddTypeHandler(typeof(LocalDate), LocalDateHandler.Default);
var param = new LocalDateResult
{
NotNullable = new LocalDate { Year = 2014, Month = 7, Day = 25 },
NullableNotNull = new LocalDate { Year = 2014, Month = 7, Day = 26 },
NullableIsNull = null,
};
var result = connection.Query<LocalDateResult>("SELECT @NotNullable AS NotNullable, @NullableNotNull AS NullableNotNull, @NullableIsNull AS NullableIsNull", param).Single();
Dapper.SqlMapper.ResetTypeHandlers();
Dapper.SqlMapper.AddTypeHandler(typeof(LocalDate?), LocalDateHandler.Default);
result = connection.Query<LocalDateResult>("SELECT @NotNullable AS NotNullable, @NullableNotNull AS NullableNotNull, @NullableIsNull AS NullableIsNull", param).Single();
}
public class LocalDateHandler : Dapper.SqlMapper.TypeHandler<LocalDate>
{
private LocalDateHandler() { }
// Make the field type ITypeHandler to ensure it cannot be used with SqlMapper.AddTypeHandler<T>(TypeHandler<T>)
// by mistake.
public static readonly Dapper.SqlMapper.ITypeHandler Default = new LocalDateHandler();
public override LocalDate Parse(object value)
{
var date = (DateTime)value;
return new LocalDate { Year = date.Year, Month = date.Month, Day = date.Day };
}
public override void SetValue(IDbDataParameter parameter, LocalDate value)
{
parameter.DbType = DbType.DateTime;
parameter.Value = new DateTime(value.Year, value.Month, value.Day);
}
}
public struct LocalDate
{
public int Year { get; set; }
public int Month { get; set; }
public int Day { get; set; }
}
public class LocalDateResult
{
public LocalDate NotNullable { get; set; }
public LocalDate? NullableNotNull { get; set; }
public LocalDate? NullableIsNull { get; set; }
}
public class LotsOfNumerics {
public enum E_Byte : byte { A = 0, B = 1 }
public enum E_SByte : sbyte { A = 0, B = 1 }
public enum E_Short : short { A = 0, B = 1 }
public enum E_UShort : ushort { A = 0, B = 1 }
public enum E_Int : int { A = 0, B = 1 }
public enum E_UInt : uint { A = 0, B = 1 }
public enum E_Long : long { A = 0, B = 1 }
public enum E_ULong : ulong { A = 0, B = 1 }
public E_Byte P_Byte { get; set; }
public E_SByte P_SByte { get; set; }
public E_Short P_Short { get; set; }
public E_UShort P_UShort { get; set; }
public E_Int P_Int { get; set; }
public E_UInt P_UInt { get; set; }
public E_Long P_Long { get; set; }
public E_ULong P_ULong { get; set; }
public bool N_Bool { get; set; }
public byte N_Byte { get; set; }
public sbyte N_SByte { get; set; }
public short N_Short { get; set; }
public ushort N_UShort { get; set; }
public int N_Int { get; set; }
public uint N_UInt { get; set; }
public long N_Long { get; set; }
public ulong N_ULong { get; set; }
public float N_Float { get; set; }
public double N_Double { get; set; }
public decimal N_Decimal { get; set; }
public E_Byte? N_P_Byte { get; set; }
public E_SByte? N_P_SByte { get; set; }
public E_Short? N_P_Short { get; set; }
public E_UShort? N_P_UShort { get; set; }
public E_Int? N_P_Int { get; set; }
public E_UInt? N_P_UInt { get; set; }
public E_Long? N_P_Long { get; set; }
public E_ULong? N_P_ULong { get; set; }
public bool? N_N_Bool { get; set; }
public byte? N_N_Byte { get; set; }
public sbyte? N_N_SByte { get; set; }
public short? N_N_Short { get; set; }
public ushort? N_N_UShort { get; set; }
public int? N_N_Int { get; set; }
public uint? N_N_UInt { get; set; }
public long? N_N_Long { get; set; }
public ulong? N_N_ULong { get; set; }
public float? N_N_Float { get; set; }
public double? N_N_Double { get; set; }
public decimal? N_N_Decimal { get; set; }
}
public void TestBigIntForEverythingWorks_SqlLite()
{
TestBigIntForEverythingWorks_SqlLite_ByDataType<long>("bigint");
TestBigIntForEverythingWorks_SqlLite_ByDataType<int>("int");
TestBigIntForEverythingWorks_SqlLite_ByDataType<byte>("tinyint");
TestBigIntForEverythingWorks_SqlLite_ByDataType<short>("smallint");
TestBigIntForEverythingWorks_SqlLite_ByDataType<bool>("bit");
TestBigIntForEverythingWorks_SqlLite_ByDataType<float>("float(24)");
TestBigIntForEverythingWorks_SqlLite_ByDataType<double>("float(53)");
}
private void TestBigIntForEverythingWorks_SqlLite_ByDataType<T>(string dbType)
{
using(var reader = connection.ExecuteReader("select cast(1 as " + dbType + ")"))
{
reader.Read().IsTrue();
reader.GetFieldType(0).Equals(typeof(T));
reader.Read().IsFalse();
reader.NextResult().IsFalse();
}
string sql = "select " + string.Join(",", typeof(LotsOfNumerics).GetProperties().Select(
x => "cast (1 as " + dbType + ") as [" + x.Name + "]"));
var row = connection.Query<LotsOfNumerics>(sql).Single();
row.N_Bool.IsTrue();
row.N_SByte.IsEqualTo((sbyte)1);
row.N_Byte.IsEqualTo((byte)1);
row.N_Int.IsEqualTo((int)1);
row.N_UInt.IsEqualTo((uint)1);
row.N_Short.IsEqualTo((short)1);
row.N_UShort.IsEqualTo((ushort)1);
row.N_Long.IsEqualTo((long)1);
row.N_ULong.IsEqualTo((ulong)1);
row.N_Float.IsEqualTo((float)1);
row.N_Double.IsEqualTo((double)1);
row.N_Decimal.IsEqualTo((decimal)1);
row.P_Byte.IsEqualTo(LotsOfNumerics.E_Byte.B);
row.P_SByte.IsEqualTo(LotsOfNumerics.E_SByte.B);
row.P_Short.IsEqualTo(LotsOfNumerics.E_Short.B);
row.P_UShort.IsEqualTo(LotsOfNumerics.E_UShort.B);
row.P_Int.IsEqualTo(LotsOfNumerics.E_Int.B);
row.P_UInt.IsEqualTo(LotsOfNumerics.E_UInt.B);
row.P_Long.IsEqualTo(LotsOfNumerics.E_Long.B);
row.P_ULong.IsEqualTo(LotsOfNumerics.E_ULong.B);
row.N_N_Bool.Value.IsTrue();
row.N_N_SByte.Value.IsEqualTo((sbyte)1);
row.N_N_Byte.Value.IsEqualTo((byte)1);
row.N_N_Int.Value.IsEqualTo((int)1);
row.N_N_UInt.Value.IsEqualTo((uint)1);
row.N_N_Short.Value.IsEqualTo((short)1);
row.N_N_UShort.Value.IsEqualTo((ushort)1);
row.N_N_Long.Value.IsEqualTo((long)1);
row.N_N_ULong.Value.IsEqualTo((ulong)1);
row.N_N_Float.Value.IsEqualTo((float)1);
row.N_N_Double.Value.IsEqualTo((double)1);
row.N_N_Decimal.IsEqualTo((decimal)1);
row.N_P_Byte.Value.IsEqualTo(LotsOfNumerics.E_Byte.B);
row.N_P_SByte.Value.IsEqualTo(LotsOfNumerics.E_SByte.B);
row.N_P_Short.Value.IsEqualTo(LotsOfNumerics.E_Short.B);
row.N_P_UShort.Value.IsEqualTo(LotsOfNumerics.E_UShort.B);
row.N_P_Int.Value.IsEqualTo(LotsOfNumerics.E_Int.B);
row.N_P_UInt.Value.IsEqualTo(LotsOfNumerics.E_UInt.B);
row.N_P_Long.Value.IsEqualTo(LotsOfNumerics.E_Long.B);
row.N_P_ULong.Value.IsEqualTo(LotsOfNumerics.E_ULong.B);
TestBigIntForEverythingWorks<bool>(true, dbType);
TestBigIntForEverythingWorks<sbyte>((sbyte)1, dbType);
TestBigIntForEverythingWorks<byte>((byte)1, dbType);
TestBigIntForEverythingWorks<int>((int)1, dbType);
TestBigIntForEverythingWorks<uint>((uint)1, dbType);
TestBigIntForEverythingWorks<short>((short)1, dbType);
TestBigIntForEverythingWorks<ushort>((ushort)1, dbType);
TestBigIntForEverythingWorks<long>((long)1, dbType);
TestBigIntForEverythingWorks<ulong>((ulong)1, dbType);
TestBigIntForEverythingWorks<float>((float)1, dbType);
TestBigIntForEverythingWorks<double>((double)1, dbType);
TestBigIntForEverythingWorks<decimal>((decimal)1, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_Byte.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_SByte.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_Int.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_UInt.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_Short.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_UShort.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_Long.B, dbType);
TestBigIntForEverythingWorks(LotsOfNumerics.E_ULong.B, dbType);
TestBigIntForEverythingWorks<bool?>(true, dbType);
TestBigIntForEverythingWorks<sbyte?>((sbyte)1, dbType);
TestBigIntForEverythingWorks<byte?>((byte)1, dbType);
TestBigIntForEverythingWorks<int?>((int)1, dbType);
TestBigIntForEverythingWorks<uint?>((uint)1, dbType);
TestBigIntForEverythingWorks<short?>((short)1, dbType);
TestBigIntForEverythingWorks<ushort?>((ushort)1, dbType);
TestBigIntForEverythingWorks<long?>((long)1, dbType);
TestBigIntForEverythingWorks<ulong?>((ulong)1, dbType);
TestBigIntForEverythingWorks<float?>((float)1, dbType);
TestBigIntForEverythingWorks<double?>((double)1, dbType);
TestBigIntForEverythingWorks<decimal?>((decimal)1, dbType);
TestBigIntForEverythingWorks<LotsOfNumerics.E_Byte?>(LotsOfNumerics.E_Byte.B, dbType);
TestBigIntForEverythingWorks<LotsOfNumerics.E_SByte?>(LotsOfNumerics.E_SByte.B, dbType);
TestBigIntForEverythingWorks<LotsOfNumerics.E_Int?>(LotsOfNumerics.E_Int.B, dbType);
TestBigIntForEverythingWorks<LotsOfNumerics.E_UInt?>(LotsOfNumerics.E_UInt.B, dbType);
TestBigIntForEverythingWorks<LotsOfNumerics.E_Short?>(LotsOfNumerics.E_Short.B, dbType);
TestBigIntForEverythingWorks<LotsOfNumerics.E_UShort?>(LotsOfNumerics.E_UShort.B, dbType);
TestBigIntForEverythingWorks<LotsOfNumerics.E_Long?>(LotsOfNumerics.E_Long.B, dbType);
TestBigIntForEverythingWorks<LotsOfNumerics.E_ULong?>(LotsOfNumerics.E_ULong.B, dbType);
}
private void TestBigIntForEverythingWorks<T>(T expected, string dbType)
{
var query = connection.Query<T>("select cast(1 as " + dbType + ")").Single();
query.IsEqualTo(expected);
var scalar = connection.ExecuteScalar<T>("select cast(1 as " + dbType + ")");
scalar.IsEqualTo(expected);
}
#if POSTGRESQL #if POSTGRESQL
class Cat class Cat
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata schemaVersion="2"> <metadata schemaVersion="2">
<id>Dapper</id> <id>Dapper</id>
<version>1.27</version> <version>1.28</version>
<title>Dapper dot net</title> <title>Dapper dot net</title>
<authors>Sam Saffron,Marc Gravell</authors> <authors>Sam Saffron,Marc Gravell</authors>
<owners>Sam Saffron,Marc Gravell</owners> <owners>Sam Saffron,Marc Gravell</owners>
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
<frameworkAssembly assemblyName="Microsoft.CSharp" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" /> <frameworkAssembly assemblyName="Microsoft.CSharp" targetFramework=".NETFramework4.0-Client, .NETFramework4.0" />
</frameworkAssemblies> </frameworkAssemblies>
<releaseNotes> <releaseNotes>
* 1.28 - Much better numeric type conversion; fix for large oracle strings; map Foo_Bar to FooBar (etc); ExecuteScalar added; stability fixes
* 1.27 - Fixes for type-handler parse; ensure type-handlers get last dibs on configuring parameters * 1.27 - Fixes for type-handler parse; ensure type-handlers get last dibs on configuring parameters
* 1.26 - New type handler API for extension support * 1.26 - New type handler API for extension support
* 1.25 - Command recycling and disposing during pipelined async multi-exec; enable pipeline (via sync-over-async) for sync API * 1.25 - Command recycling and disposing during pipelined async multi-exec; enable pipeline (via sync-over-async) for sync API
......
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