/*
License: http://www.apache.org/licenses/LICENSE-2.0
Home page: http://code.google.com/p/dapper-dot-net/
Note: to build on C# 3.0 + .NET 3.5, include the CSHARP30 compiler symbol (and yes,
I know the difference between language and runtime versions; this is a compromise).
*/ using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Globalization;
using System.Linq.Expressions; namespace Dapper
{
[AssemblyNeutral, AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
internal sealed class AssemblyNeutralAttribute : Attribute { } /// <summary>
/// Additional state flags that control command behaviour
/// </summary>
[Flags]
public enum CommandFlags
{
/// <summary>
/// No additional flags
/// </summary>
None = 0,
/// <summary>
/// Should data be buffered before returning?
/// </summary>
Buffered = 1,
/// <summary>
/// Can async queries be pipelined?
/// </summary>
Pipelined = 2,
/// <summary>
/// Should the plan cache be bypassed?
/// </summary>
NoCache = 4,
}
/// <summary>
/// Represents the key aspects of a sql operation
/// </summary>
public struct CommandDefinition
{
internal static CommandDefinition ForCallback(object parameters)
{
if(parameters is DynamicParameters)
{
return new CommandDefinition(parameters);
}
else
{
return default(CommandDefinition);
}
}
private readonly string commandText;
private readonly object parameters;
private readonly IDbTransaction transaction;
private readonly int? commandTimeout;
private readonly CommandType? commandType;
private readonly CommandFlags flags; internal void OnCompleted()
{
if (parameters is SqlMapper.IParameterCallbacks)
{
((SqlMapper.IParameterCallbacks)parameters).OnCompleted();
}
}
/// <summary>
/// The command (sql or a stored-procedure name) to execute
/// </summary>
public string CommandText { get { return commandText; } }
/// <summary>
/// The parameters associated with the command
/// </summary>
public object Parameters { get { return parameters; } }
/// <summary>
/// The active transaction for the command
/// </summary>
public IDbTransaction Transaction { get { return transaction; } }
/// <summary>
/// The effective timeout for the command
/// </summary>
public int? CommandTimeout { get { return commandTimeout; } }
/// <summary>
/// The type of command that the command-text represents
/// </summary>
public CommandType? CommandType { get { return commandType; } } /// <summary>
/// Should data be buffered before returning?
/// </summary>
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>
/// Additional state flags against this command
/// </summary>
public CommandFlags Flags { get { return flags; } } /// <summary>
/// Can async queries be pipelined?
/// </summary>
public bool Pipelined { get { return (flags & CommandFlags.Pipelined) != 0; } } /// <summary>
/// Initialize the command definition
/// </summary>
#if CSHARP30
public CommandDefinition(string commandText, object parameters, IDbTransaction transaction, int? commandTimeout,
CommandType? commandType, CommandFlags flags)
#else
public CommandDefinition(string commandText, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null,
CommandType? commandType = null, CommandFlags flags = CommandFlags.Buffered
#if ASYNC
, CancellationToken cancellationToken = default(CancellationToken)
#endif
)
#endif
{
this.commandText = commandText;
this.parameters = parameters;
this.transaction = transaction;
this.commandTimeout = commandTimeout;
this.commandType = commandType;
this.flags = flags;
#if ASYNC
this.cancellationToken = cancellationToken;
#endif
} private CommandDefinition(object parameters) : this()
{
this.parameters = parameters;
} #if ASYNC
private readonly CancellationToken cancellationToken;
/// <summary>
/// For asynchronous operations, the cancellation-token
/// </summary>
public CancellationToken CancellationToken { get { return cancellationToken; } }
#endif internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object> paramReader)
{
var cmd = cnn.CreateCommand();
var init = GetInit(cmd.GetType());
if (init != null) init(cmd);
if (transaction != null)
cmd.Transaction = transaction;
cmd.CommandText = commandText;
if (commandTimeout.HasValue)
cmd.CommandTimeout = commandTimeout.Value;
if (commandType.HasValue)
cmd.CommandType = commandType.Value;
if (paramReader != null)
{
paramReader(cmd, parameters);
}
return cmd;
} static SqlMapper.Link<Type, Action<IDbCommand>> commandInitCache;
static Action<IDbCommand> GetInit(Type commandType)
{
if (commandType == null) return null; // GIGO
Action<IDbCommand> action;
if (SqlMapper.Link<Type, Action<IDbCommand>>.TryGet(commandInitCache, commandType, out action))
{
return action;
}
var bindByName = GetBasicPropertySetter(commandType, "BindByName", typeof(bool));
var initialLongFetchSize = GetBasicPropertySetter(commandType, "InitialLONGFetchSize", typeof(int)); action = null;
if (bindByName != null || initialLongFetchSize != null)
{
var method = new DynamicMethod(commandType.Name + "_init", null, new Type[] { typeof(IDbCommand) });
var il = method.GetILGenerator(); if (bindByName != 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);
action = (Action<IDbCommand>)method.CreateDelegate(typeof(Action<IDbCommand>));
}
// cache it
SqlMapper.Link<Type, Action<IDbCommand>>.TryAdd(ref commandInitCache, commandType, ref 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>
/// Dapper, a light weight object mapper for ADO.NET
/// </summary>
static partial class SqlMapper
{
/// <summary>
/// Implement this interface to pass an arbitrary db specific set of parameters to Dapper
/// </summary>
public partial interface IDynamicParameters
{
/// <summary>
/// Add all the parameters needed to the command just before it executes
/// </summary>
/// <param name="command">The raw command prior to execution</param>
/// <param name="identity">Information about the query</param>
void AddParameters(IDbCommand command, Identity identity);
} /// <summary>
/// Extends IDynamicParameters providing by-name lookup of parameter values
/// </summary>
public interface IParameterLookup : IDynamicParameters
{
/// <summary>
/// Get the value of the specified parameter (return null if not found)
/// </summary>
object this[string name] { get; }
} /// <summary>
/// Extends IDynamicParameters with facilities for executing callbacks after commands have completed
/// </summary>
public partial interface IParameterCallbacks : IDynamicParameters
{
/// <summary>
/// Invoked when the command has executed
/// </summary>
void OnCompleted();
} /// <summary>
/// Implement this interface to pass an arbitrary db specific parameter to Dapper
/// </summary>
[AssemblyNeutral]
public interface ICustomQueryParameter
{
/// <summary>
/// Add the parameter needed to the command before it executes
/// </summary>
/// <param name="command">The raw command prior to execution</param>
/// <param name="name">Parameter name</param>
void AddParameter(IDbCommand command, string name);
} /// <summary>
/// Implement this interface to perform custom type-based parameter handling and value parsing
/// </summary>
[AssemblyNeutral]
public interface ITypeHandler
{
/// <summary>
/// Assign the value of a parameter before a command executes
/// </summary>
/// <param name="parameter">The parameter to configure</param>
/// <param name="value">Parameter value</param>
void SetValue(IDbDataParameter parameter, object value); /// <summary>
/// Parse a database value back to a typed value
/// </summary>
/// <param name="value">The value from the database</param>
/// <param name="destinationType">The type to parse to</param>
/// <returns>The typed value</returns>
object Parse(Type destinationType, object value);
} /// <summary>
/// A type handler for data-types that are supported by the underlying provider, but which need
/// a well-known UdtTypeName to be specified
/// </summary>
public class UdtTypeHandler : ITypeHandler
{
private readonly string udtTypeName;
/// <summary>
/// Creates a new instance of UdtTypeHandler with the specified UdtTypeName
/// </summary>
public UdtTypeHandler(string udtTypeName)
{
if (string.IsNullOrEmpty(udtTypeName)) throw new ArgumentException("Cannot be null or empty", udtTypeName);
this.udtTypeName = udtTypeName;
}
object ITypeHandler.Parse(Type destinationType, object value)
{
return value is DBNull ? null : value;
} void ITypeHandler.SetValue(IDbDataParameter parameter, object value)
{
parameter.Value = ((object)value) ?? DBNull.Value;
if (parameter is System.Data.SqlClient.SqlParameter)
{
((System.Data.SqlClient.SqlParameter)parameter).UdtTypeName = udtTypeName;
}
}
} /// <summary>
/// Base-class for simple type-handlers
/// </summary>
public abstract class TypeHandler<T> : ITypeHandler
{
/// <summary>
/// Assign the value of a parameter before a command executes
/// </summary>
/// <param name="parameter">The parameter to configure</param>
/// <param name="value">Parameter value</param>
public abstract void SetValue(IDbDataParameter parameter, T value); /// <summary>
/// Parse a database value back to a typed value
/// </summary>
/// <param name="value">The value from the database</param>
/// <returns>The typed value</returns>
public abstract T Parse(object value); void ITypeHandler.SetValue(IDbDataParameter parameter, object value)
{
if (value is DBNull)
{
parameter.Value = value;
}
else
{
SetValue(parameter, (T)value);
}
} object ITypeHandler.Parse(Type destinationType, object value)
{
return Parse(value);
}
} /// <summary>
/// Implement this interface to change default mapping of reader columns to type members
/// </summary>
public interface ITypeMap
{
/// <summary>
/// Finds best constructor
/// </summary>
/// <param name="names">DataReader column names</param>
/// <param name="types">DataReader column types</param>
/// <returns>Matching constructor or default one</returns>
ConstructorInfo FindConstructor(string[] names, Type[] types); /// <summary>
/// Returns a constructor which should *always* be used.
///
/// Parameters will be default values, nulls for reference types and zero'd for value types.
///
/// Use this class to force object creation away from parameterless constructors you don't control.
/// </summary>
ConstructorInfo FindExplicitConstructor(); /// <summary>
/// Gets mapping for constructor parameter
/// </summary>
/// <param name="constructor">Constructor to resolve</param>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName); /// <summary>
/// Gets member mapping for column
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
IMemberMap GetMember(string columnName);
} /// <summary>
/// Implements this interface to provide custom member mapping
/// </summary>
public interface IMemberMap
{
/// <summary>
/// Source DataReader column name
/// </summary>
string ColumnName { get; } /// <summary>
/// Target member type
/// </summary>
Type MemberType { get; } /// <summary>
/// Target property
/// </summary>
PropertyInfo Property { get; } /// <summary>
/// Target field
/// </summary>
FieldInfo Field { get; } /// <summary>
/// Target constructor parameter
/// </summary>
ParameterInfo Parameter { get; }
} /// <summary>
/// This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example),
/// and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE**
/// equality. The type is fully thread-safe.
/// </summary>
internal partial class Link<TKey, TValue> where TKey : class
{
public static bool TryGet(Link<TKey, TValue> link, TKey key, out TValue value)
{
while (link != null)
{
if ((object)key == (object)link.Key)
{
value = link.Value;
return true;
}
link = link.Tail;
}
value = default(TValue);
return false;
}
public static bool TryAdd(ref Link<TKey, TValue> head, TKey key, ref TValue value)
{
bool tryAgain;
do
{
var snapshot = Interlocked.CompareExchange(ref head, null, null);
TValue found;
if (TryGet(snapshot, key, out found))
{ // existing match; report the existing value instead
value = found;
return false;
}
var newNode = new Link<TKey, TValue>(key, value, snapshot);
// did somebody move our cheese?
tryAgain = Interlocked.CompareExchange(ref head, newNode, snapshot) != snapshot;
} while (tryAgain);
return true;
}
private Link(TKey key, TValue value, Link<TKey, TValue> tail)
{
Key = key;
Value = value;
Tail = tail;
}
public TKey Key { get; private set; }
public TValue Value { get; private set; }
public Link<TKey, TValue> Tail { get; private set; }
}
partial class CacheInfo
{
public DeserializerState Deserializer { get; set; }
public Func<IDataReader, object>[] OtherDeserializers { get; set; }
public Action<IDbCommand, object> ParamReader { get; set; }
private int hitCount;
public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); }
public void RecordHit() { Interlocked.Increment(ref hitCount); }
}
static int GetColumnHash(IDataReader reader)
{
unchecked
{
int colCount = reader.FieldCount, hash = colCount;
for (int i = 0; i < colCount; i++)
{ // binding code is only interested in names - not types
object tmp = reader.GetName(i);
hash = (hash * 31) + (tmp == null ? 0 : tmp.GetHashCode());
}
return hash;
}
}
struct DeserializerState
{
public readonly int Hash;
public readonly Func<IDataReader, object> Func; public DeserializerState(int hash, Func<IDataReader, object> func)
{
Hash = hash;
Func = func;
}
} /// <summary>
/// Called if the query cache is purged via PurgeQueryCache
/// </summary>
public static event EventHandler QueryCachePurged;
private static void OnQueryCachePurged()
{
var handler = QueryCachePurged;
if (handler != null) handler(null, EventArgs.Empty);
}
#if CSHARP30
private static readonly Dictionary<Identity, CacheInfo> _queryCache = new Dictionary<Identity, CacheInfo>();
// note: conflicts between readers and writers are so short-lived that it isn't worth the overhead of
// ReaderWriterLockSlim etc; a simple lock is faster
private static void SetQueryCache(Identity key, CacheInfo value)
{
lock (_queryCache) { _queryCache[key] = value; }
}
private static bool TryGetQueryCache(Identity key, out CacheInfo value)
{
lock (_queryCache) { return _queryCache.TryGetValue(key, out value); }
}
private static void PurgeQueryCacheByType(Type type)
{
lock (_queryCache)
{
var toRemove = _queryCache.Keys.Where(id => id.type == type).ToArray();
foreach (var key in toRemove)
_queryCache.Remove(key);
}
}
/// <summary>
/// Purge the query cache
/// </summary>
public static void PurgeQueryCache()
{
lock (_queryCache)
{
_queryCache.Clear();
}
OnQueryCachePurged();
}
#else
static readonly System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo> _queryCache = new System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo>();
private static void SetQueryCache(Identity key, CacheInfo value)
{
if (Interlocked.Increment(ref collect) == COLLECT_PER_ITEMS)
{
CollectCacheGarbage();
}
_queryCache[key] = value;
} private static void CollectCacheGarbage()
{
try
{
foreach (var pair in _queryCache)
{
if (pair.Value.GetHitCount() <= COLLECT_HIT_COUNT_MIN)
{
CacheInfo cache;
_queryCache.TryRemove(pair.Key, out cache);
}
}
} finally
{
Interlocked.Exchange(ref collect, 0);
}
} private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0;
private static int collect;
private static bool TryGetQueryCache(Identity key, out CacheInfo value)
{
if (_queryCache.TryGetValue(key, out value))
{
value.RecordHit();
return true;
}
value = null;
return false;
} /// <summary>
/// Purge the query cache
/// </summary>
public static void PurgeQueryCache()
{
_queryCache.Clear();
OnQueryCachePurged();
} private static void PurgeQueryCacheByType(Type type)
{
foreach (var entry in _queryCache)
{
CacheInfo cache;
if (entry.Key.type == type)
_queryCache.TryRemove(entry.Key, out cache);
}
} /// <summary>
/// Return a count of all the cached queries by dapper
/// </summary>
/// <returns></returns>
public static int GetCachedSQLCount()
{
return _queryCache.Count;
} /// <summary>
/// Return a list of all the queries cached by dapper
/// </summary>
/// <param name="ignoreHitCountAbove"></param>
/// <returns></returns>
public static IEnumerable<Tuple<string, string, int>> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue)
{
var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount()));
if (ignoreHitCountAbove < int.MaxValue) data = data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove);
return data;
} /// <summary>
/// Deep diagnostics only: find any hash collisions in the cache
/// </summary>
/// <returns></returns>
public static IEnumerable<Tuple<int, int>> GetHashCollissions()
{
var counts = new Dictionary<int, int>();
foreach (var key in _queryCache.Keys)
{
int count;
if (!counts.TryGetValue(key.hashCode, out count))
{
counts.Add(key.hashCode, 1);
}
else
{
counts[key.hashCode] = count + 1;
}
}
return from pair in counts
where pair.Value > 1
select Tuple.Create(pair.Key, pair.Value); }
#endif static Dictionary<Type, DbType> typeMap; static SqlMapper()
{
typeMap = new Dictionary<Type, DbType>();
typeMap[typeof(byte)] = DbType.Byte;
typeMap[typeof(sbyte)] = DbType.SByte;
typeMap[typeof(short)] = DbType.Int16;
typeMap[typeof(ushort)] = DbType.UInt16;
typeMap[typeof(int)] = DbType.Int32;
typeMap[typeof(uint)] = DbType.UInt32;
typeMap[typeof(long)] = DbType.Int64;
typeMap[typeof(ulong)] = DbType.UInt64;
typeMap[typeof(float)] = DbType.Single;
typeMap[typeof(double)] = DbType.Double;
typeMap[typeof(decimal)] = DbType.Decimal;
typeMap[typeof(bool)] = DbType.Boolean;
typeMap[typeof(string)] = DbType.String;
typeMap[typeof(char)] = DbType.StringFixedLength;
typeMap[typeof(Guid)] = DbType.Guid;
typeMap[typeof(DateTime)] = DbType.DateTime;
typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset;
typeMap[typeof(TimeSpan)] = DbType.Time;
typeMap[typeof(byte[])] = DbType.Binary;
typeMap[typeof(byte?)] = DbType.Byte;
typeMap[typeof(sbyte?)] = DbType.SByte;
typeMap[typeof(short?)] = DbType.Int16;
typeMap[typeof(ushort?)] = DbType.UInt16;
typeMap[typeof(int?)] = DbType.Int32;
typeMap[typeof(uint?)] = DbType.UInt32;
typeMap[typeof(long?)] = DbType.Int64;
typeMap[typeof(ulong?)] = DbType.UInt64;
typeMap[typeof(float?)] = DbType.Single;
typeMap[typeof(double?)] = DbType.Double;
typeMap[typeof(decimal?)] = DbType.Decimal;
typeMap[typeof(bool?)] = DbType.Boolean;
typeMap[typeof(char?)] = DbType.StringFixedLength;
typeMap[typeof(Guid?)] = DbType.Guid;
typeMap[typeof(DateTime?)] = DbType.DateTime;
typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset;
typeMap[typeof(TimeSpan?)] = DbType.Time;
typeMap[typeof(object)] = DbType.Object; 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>
/// Configure the specified type to be mapped to a given db-type
/// </summary>
public static void AddTypeMap(Type type, DbType 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>
/// Configure the specified type to be processed by a custom handler
/// </summary>
public static void AddTypeHandler(Type type, ITypeHandler handler)
{
AddTypeHandlerImpl(type, handler, true);
} /// <summary>
/// Configure 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"); 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
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
if (handler == null)
{
newCopy.Remove(type);
if (secondary != null) newCopy.Remove(secondary);
}
else
{
newCopy[type] = handler;
if(secondary != null) newCopy[secondary] = handler;
}
typeHandlers = newCopy;
} /// <summary>
/// Configure the specified type to be processed by a custom handler
/// </summary>
public static void AddTypeHandler<T>(TypeHandler<T> handler)
{
AddTypeHandlerImpl(typeof(T), handler, true);
} /// <summary>
/// Not intended for direct usage
/// </summary>
[Obsolete("Not intended for direct usage", false)]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public static class TypeHandlerCache<T>
{
/// <summary>
/// Not intended for direct usage
/// </summary>
[Obsolete("Not intended for direct usage", true)]
public static T Parse(object value)
{
return (T)handler.Parse(typeof(T), value); } /// <summary>
/// Not intended for direct usage
/// </summary>
[Obsolete("Not intended for direct usage", true)]
public static void SetValue(IDbDataParameter parameter, object value)
{
handler.SetValue(parameter, value);
} internal static void SetHandler(ITypeHandler handler)
{
#pragma warning disable 618
TypeHandlerCache<T>.handler = handler;
#pragma warning restore 618
} private static ITypeHandler handler;
} private static Dictionary<Type, ITypeHandler> typeHandlers = new Dictionary<Type, ITypeHandler>(); internal const string LinqBinary = "System.Data.Linq.Binary"; /// <summary>
/// Get the DbType that maps to a given value
/// </summary>
[Obsolete("This method is for internal use only"), Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public static DbType GetDbType(object value)
{
if (value == null || value is DBNull) return DbType.Object; ITypeHandler handler;
return LookupDbType(value.GetType(), "n/a", false, out handler); }
internal static DbType LookupDbType(Type type, string name, bool demand, out ITypeHandler handler)
{
DbType dbType;
handler = null;
var nullUnderlyingType = Nullable.GetUnderlyingType(type);
if (nullUnderlyingType != null) type = nullUnderlyingType;
if (type.IsEnum && !typeMap.ContainsKey(type))
{
type = Enum.GetUnderlyingType(type);
}
if (typeMap.TryGetValue(type, out dbType))
{
return dbType;
}
if (type.FullName == LinqBinary)
{
return DbType.Binary;
}
if (typeof(IEnumerable).IsAssignableFrom(type))
{
return DynamicParameters.EnumerableMultiParameter;
} if (typeHandlers.TryGetValue(type, out handler))
{
return DbType.Object;
}
switch (type.FullName)
{
case "Microsoft.SqlServer.Types.SqlGeography":
AddTypeHandler(type, handler = new UdtTypeHandler("GEOGRAPHY"));
return DbType.Object;
case "Microsoft.SqlServer.Types.SqlGeometry":
AddTypeHandler(type, handler = new UdtTypeHandler("GEOMETRY"));
return DbType.Object;
case "Microsoft.SqlServer.Types.SqlHierarchyId":
AddTypeHandler(type, handler = new UdtTypeHandler("HIERARCHYID"));
return DbType.Object;
}
if(demand)
throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type.FullName));
return DbType.Object; } /// <summary>
/// Identity of a cached query in Dapper, used for extensibility
/// </summary>
public partial class Identity : IEquatable<Identity>
{
internal Identity ForGrid(Type primaryType, int gridIndex)
{
return new Identity(sql, commandType, connectionString, primaryType, parametersType, null, gridIndex);
} internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex)
{
return new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex);
}
/// <summary>
/// Create an identity for use with DynamicParameters, internal use only
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public Identity ForDynamicParameters(Type type)
{
return new Identity(sql, commandType, connectionString, this.type, type, null, -1);
} internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes)
: this(sql, commandType, connection.ConnectionString, type, parametersType, otherTypes, 0)
{ }
private Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, Type[] otherTypes, int gridIndex)
{
this.sql = sql;
this.commandType = commandType;
this.connectionString = connectionString;
this.type = type;
this.parametersType = parametersType;
this.gridIndex = gridIndex;
unchecked
{
hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this
hashCode = hashCode * 23 + commandType.GetHashCode();
hashCode = hashCode * 23 + gridIndex.GetHashCode();
hashCode = hashCode * 23 + (sql == null ? 0 : sql.GetHashCode());
hashCode = hashCode * 23 + (type == null ? 0 : type.GetHashCode());
if (otherTypes != null)
{
foreach (var t in otherTypes)
{
hashCode = hashCode * 23 + (t == null ? 0 : t.GetHashCode());
}
}
hashCode = hashCode * 23 + (connectionString == null ? 0 : SqlMapper.connectionStringComparer.GetHashCode(connectionString));
hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode());
}
} /// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
return Equals(obj as Identity);
}
/// <summary>
/// The sql
/// </summary>
public readonly string sql;
/// <summary>
/// The command type
/// </summary>
public readonly CommandType? commandType; /// <summary>
///
/// </summary>
public readonly int hashCode, gridIndex;
/// <summary>
///
/// </summary>
public readonly Type type;
/// <summary>
///
/// </summary>
public readonly string connectionString;
/// <summary>
///
/// </summary>
public readonly Type parametersType;
/// <summary>
///
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return hashCode;
}
/// <summary>
/// Compare 2 Identity objects
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public bool Equals(Identity other)
{
return
other != null &&
gridIndex == other.gridIndex &&
type == other.type &&
sql == other.sql &&
commandType == other.commandType &&
SqlMapper.connectionStringComparer.Equals(connectionString, other.connectionString) &&
parametersType == other.parametersType;
}
} #if CSHARP30
/// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(this IDbConnection cnn, string sql, object param)
{
return Execute(cnn, sql, param, null, null, null);
} /// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return Execute(cnn, sql, param, transaction, null, null);
} /// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(this IDbConnection cnn, string sql, object param, CommandType commandType)
{
return Execute(cnn, sql, param, null, null, commandType);
} /// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
{
return Execute(cnn, sql, param, transaction, null, commandType);
} /// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param)
{
return ExecuteReader(cnn, sql, param, null, null, null);
} /// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return ExecuteReader(cnn, sql, param, transaction, null, null);
} /// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param, CommandType commandType)
{
return ExecuteReader(cnn, sql, param, null, null, commandType);
} /// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
{
return ExecuteReader(cnn, sql, param, transaction, null, commandType);
} /// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param)
{
return Query<T>(cnn, sql, param, null, true, null, null);
} /// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return Query<T>(cnn, sql, param, transaction, true, null, null);
} /// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, CommandType commandType)
{
return Query<T>(cnn, sql, param, null, true, null, commandType);
} /// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
{
return Query<T>(cnn, sql, param, transaction, true, null, commandType);
} /// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return QueryMultiple(cnn, sql, param, transaction, null, null);
} /// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, CommandType commandType)
{
return QueryMultiple(cnn, sql, param, null, null, commandType);
} /// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
{
return QueryMultiple(cnn, sql, param, transaction, null, commandType);
}
#endif /// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
public static int Execute(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, object 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 ExecuteImpl(cnn, ref command);
}
/// <summary>
/// Execute parameterized SQL
/// </summary>
/// <returns>Number of rows affected</returns>
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, object 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, object 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 IEnumerable GetMultiExec(object param)
{
return (param is IEnumerable
&& !(param is string || param is IEnumerable<KeyValuePair<string, object>>
)) ? (IEnumerable)param : null;
} private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition command)
{
object param = command.Parameters;
IEnumerable multiExec = GetMultiExec(param);
Identity identity;
CacheInfo info = null;
if (multiExec != null)
{
#if ASYNC
if((command.Flags & CommandFlags.Pipelined) != 0)
{
// this includes all the code for concurrent/overlapped query
return ExecuteMultiImplAsync(cnn, command, multiExec).Result;
}
#endif
bool isFirst = true;
int total = 0;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) cnn.Open();
using (var cmd = command.SetupCommand(cnn, null))
{
string masterSql = null;
foreach (var obj in multiExec)
{
if (isFirst)
{
masterSql = cmd.CommandText;
isFirst = false;
identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
info = GetCacheInfo(identity, obj, command.AddToCache);
}
else
{
cmd.CommandText = masterSql; // because we do magic replaces on "in" etc
cmd.Parameters.Clear(); // current code is Add-tastic
}
info.ParamReader(cmd, obj);
total += cmd.ExecuteNonQuery();
}
}
command.OnCompleted();
} finally
{
if (wasClosed) cnn.Close();
}
return total;
} // nice and simple
if (param != null)
{
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
info = GetCacheInfo(identity, param, command.AddToCache);
}
return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader);
} /// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
/// <remarks>
/// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a <see cref="DataTable"/>
/// or <see cref="DataSet"/>.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// DataTable table = new DataTable("MyTable");
/// using (var reader = ExecuteReader(cnn, sql, param))
/// {
/// table.Load(reader);
/// }
/// ]]>
/// </code>
/// </example>
public static IDataReader ExecuteReader(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
IDbCommand dbcmd;
var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out dbcmd);
return new WrappedReader(dbcmd, reader);
} /// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
/// <remarks>
/// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a <see cref="DataTable"/>
/// or <see cref="DataSet"/>.
/// </remarks>
public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command)
{
IDbCommand dbcmd;
var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out dbcmd);
return new WrappedReader(dbcmd, reader);
}
/// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
/// </summary>
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
/// <remarks>
/// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a <see cref="DataTable"/>
/// or <see cref="DataSet"/>.
/// </remarks>
public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior)
{
IDbCommand dbcmd;
var reader = ExecuteReaderImpl(cnn, ref command, commandBehavior, out dbcmd);
return new WrappedReader(dbcmd, reader);
} #if !CSHARP30
/// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object></remarks>
public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
return Query<DapperRow>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType);
}
#else
/// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param)
{
return Query(cnn, sql, param, null, true, null, null);
} /// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return Query(cnn, sql, param, transaction, true, null, null);
} /// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, CommandType? commandType)
{
return Query(cnn, sql, param, null, true, null, commandType);
} /// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType? commandType)
{
return Query(cnn, sql, param, transaction, true, null, commandType);
} /// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType)
{
return Query<IDictionary<string, object>>(cnn, sql, param, transaction, buffered, commandTimeout, commandType);
}
#endif /// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
/// <remarks>the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object</remarks>
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns>
public static IEnumerable<T> Query<T>(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var data = QueryImpl<T>(cnn, command, typeof(T));
return command.Buffered ? data.ToList() : data;
} /// <summary>
/// Executes a query, returning the data typed as per the Type suggested
/// </summary>
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns>
public static IEnumerable<object> Query(
#if CSHARP30
this IDbConnection cnn, Type type, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
if (type == null) throw new ArgumentNullException("type");
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var data = QueryImpl<object>(cnn, command, type);
return command.Buffered ? data.ToList() : data;
}
/// <summary>
/// Executes a query, returning the data typed as per T
/// </summary>
/// <remarks>the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object</remarks>
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition command)
{
var data = QueryImpl<T>(cnn, command, typeof(T));
return command.Buffered ? data.ToList() : data;
} /// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static GridReader QueryMultiple(
#if CSHARP30
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, object 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 QueryMultipleImpl(cnn, ref command);
}
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static GridReader QueryMultiple(this IDbConnection cnn, CommandDefinition command)
{
return QueryMultipleImpl(cnn, ref command);
}
private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandDefinition command)
{
object param = command.Parameters;
Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null);
CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand cmd = null;
IDataReader reader = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) cnn.Open();
cmd = command.SetupCommand(cnn, info.ParamReader);
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess); var result = new GridReader(cmd, reader, identity, command.Parameters as DynamicParameters);
cmd = null; // now owned by result
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
// with the CloseConnection flag, so the reader will deal with the connection; we
// still need something in the "finally" to ensure that broken SQL still results
// in the connection closing itself
return result;
}
catch
{
if (reader != null)
{
if (!reader.IsClosed) try { cmd.Cancel(); }
catch { /* don't spoil the existing exception */ }
reader.Dispose();
}
if (cmd != null) cmd.Dispose();
if (wasClosed) cnn.Close();
throw;
}
} private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command, Type effectiveType)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand cmd = null;
IDataReader reader = null; bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
cmd = command.SetupCommand(cnn, info.ParamReader); if (wasClosed) cnn.Open();
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess);
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
// with the CloseConnection flag, so the reader will deal with the connection; we
// still need something in the "finally" to ensure that broken SQL still results
// in the connection closing itself
var tuple = info.Deserializer;
int hash = GetColumnHash(reader);
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));
if(command.AddToCache) SetQueryCache(identity, info);
} var func = tuple.Func;
var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
while (reader.Read())
{
object val = func(reader);
if (val == null || val is T) {
yield return (T)val;
} else {
yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
}
}
while (reader.NextResult()) { }
// happy path; close the reader cleanly - no
// need for "Cancel" etc
reader.Dispose();
reader = null; command.OnCompleted();
}
finally
{
if (reader != null)
{
if (!reader.IsClosed) try { cmd.Cancel(); }
catch { /* don't spoil the existing exception */ }
reader.Dispose();
}
if (wasClosed) cnn.Close();
if (cmd != null) cmd.Dispose();
}
} /// <summary>
/// Maps a query to objects
/// </summary>
/// <typeparam name="TFirst">The first type in the record set</typeparam>
/// <typeparam name="TSecond">The second type in the record set</typeparam>
/// <typeparam name="TReturn">The return type</typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType">Is it a stored proc or a batch?</param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(
#if CSHARP30
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
return MultiMap<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
} /// <summary>
/// Maps a query to objects
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType"></param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(
#if CSHARP30
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
return MultiMap<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
} /// <summary>
/// Perform a multi mapping query with 4 input parameters
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>(
#if CSHARP30
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
return MultiMap<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
} #if !CSHARP30
/// <summary>
/// Perform a multi mapping query with 5 input parameters
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TFifth"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
)
{
return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
} /// <summary>
/// Perform a multi mapping query with 6 input parameters
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TFifth"></typeparam>
/// <typeparam name="TSixth"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
)
{
return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
} /// <summary>
/// Perform a multi mapping query with 7 input parameters
/// </summary>
/// <typeparam name="TFirst"></typeparam>
/// <typeparam name="TSecond"></typeparam>
/// <typeparam name="TThird"></typeparam>
/// <typeparam name="TFourth"></typeparam>
/// <typeparam name="TFifth"></typeparam>
/// <typeparam name="TSixth"></typeparam>
/// <typeparam name="TSeventh"></typeparam>
/// <typeparam name="TReturn"></typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn"></param>
/// <param name="commandTimeout"></param>
/// <param name="commandType"></param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
} /// <summary>
/// Perform a multi mapping query with arbitrary input parameters
/// </summary>
/// <typeparam name="TReturn">The return type</typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="types">array of types in the record set</param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType">Is it a stored proc or a batch?</param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var results = MultiMapImpl<TReturn>(cnn, command, types, map, splitOn, null, null, true);
return buffered ? results.ToList() : results;
}
#endif
partial class DontMap { }
static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(
this IDbConnection cnn, string sql, Delegate map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, command, map, splitOn, null, null, true);
return buffered ? results.ToList() : results;
} static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity, bool finalize)
{
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) });
CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand ownedCommand = null;
IDataReader ownedReader = null; bool wasClosed = cnn != null && cnn.State == ConnectionState.Closed;
try
{
if (reader == null)
{
ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader);
if (wasClosed) cnn.Open();
ownedReader = ownedCommand.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess);
reader = ownedReader;
}
DeserializerState deserializer = default(DeserializerState);
Func<IDataReader, object>[] otherDeserializers = null; int hash = GetColumnHash(reader);
if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash)
{
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();
if(command.AddToCache) SetQueryCache(identity, cinfo);
} Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(deserializer.Func, otherDeserializers, map); if (mapIt != null)
{
while (reader.Read())
{
yield return mapIt(reader);
}
if(finalize)
{
while (reader.NextResult()) { }
command.OnCompleted();
}
}
}
finally
{
try
{
if (ownedReader != null)
{
ownedReader.Dispose();
}
}
finally
{
if (ownedCommand != null)
{
ownedCommand.Dispose();
}
if (wasClosed) cnn.Close();
}
}
} static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn, CommandDefinition command, Type[] types, Func<object[], TReturn> map, string splitOn, IDataReader reader, Identity identity, bool finalize)
{
if (types.Length < 1)
{
throw new ArgumentException("you must provide at least one type to deserialize");
} object param = command.Parameters;
identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, types[0], param == null ? null : param.GetType(), types);
CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand ownedCommand = null;
IDataReader ownedReader = null; bool wasClosed = cnn != null && cnn.State == ConnectionState.Closed;
try
{
if (reader == null)
{
ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader);
if (wasClosed) cnn.Open();
ownedReader = ownedCommand.ExecuteReader();
reader = ownedReader;
}
DeserializerState deserializer = default(DeserializerState);
Func<IDataReader, object>[] otherDeserializers = null; int hash = GetColumnHash(reader);
if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash)
{
var deserializers = GenerateDeserializers(types, splitOn, reader);
deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]);
otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray();
SetQueryCache(identity, cinfo);
} Func<IDataReader, TReturn> mapIt = GenerateMapper(types.Length, deserializer.Func, otherDeserializers, map); if (mapIt != null)
{
while (reader.Read())
{
yield return mapIt(reader);
}
if (finalize)
{
while (reader.NextResult()) { }
command.OnCompleted();
}
}
}
finally
{
try
{
if (ownedReader != null)
{
ownedReader.Dispose();
}
}
finally
{
if (ownedCommand != null)
{
ownedCommand.Dispose();
}
if (wasClosed) cnn.Close();
}
}
} private static Func<IDataReader, TReturn> GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<IDataReader, object> deserializer, Func<IDataReader, object>[] otherDeserializers, object map)
{
switch (otherDeserializers.Length)
{
case 1:
return r => ((Func<TFirst, TSecond, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r));
case 2:
return r => ((Func<TFirst, TSecond, TThird, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r));
case 3:
return r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r));
#if !CSHARP30
case 4:
return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r));
case 5:
return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r));
case 6:
return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r), (TSeventh)otherDeserializers[5](r));
#endif
default:
throw new NotSupportedException();
}
} private static Func<IDataReader, TReturn> GenerateMapper<TReturn>(int length, Func<IDataReader, object> deserializer, Func<IDataReader, object>[] otherDeserializers, Func<object[], TReturn> map)
{
return r =>
{
var objects = new object[length];
objects[0] = deserializer(r); for (var i = 1; i < length; ++i)
{
objects[i] = otherDeserializers[i - 1](r);
} return map(objects);
};
} private static Func<IDataReader, object>[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader)
{
var deserializers = new List<Func<IDataReader, object>>();
var splits = splitOn.Split(',').Select(s => s.Trim()).ToArray();
bool isMultiSplit = splits.Length > 1;
if (types.First() == typeof(Object))
{
// we go left to right for dynamic multi-mapping so that the madness of TestMultiMappingVariations
// is supported
bool first = true;
int currentPos = 0;
int splitIdx = 0;
string currentSplit = splits[splitIdx];
foreach (var type in types)
{
if (type == typeof(DontMap))
{
break;
} int splitPoint = GetNextSplitDynamic(currentPos, currentSplit, reader);
if (isMultiSplit && splitIdx < splits.Length - 1)
{
currentSplit = splits[++splitIdx];
}
deserializers.Add((GetDeserializer(type, reader, currentPos, splitPoint - currentPos, !first)));
currentPos = splitPoint;
first = false;
}
}
else
{
// in this we go right to left through the data reader in order to cope with properties that are
// named the same as a subsequent primary key that we split on
int currentPos = reader.FieldCount;
int splitIdx = splits.Length - 1;
var currentSplit = splits[splitIdx];
for (var typeIdx = types.Length - 1; typeIdx >= 0; --typeIdx)
{
var type = types[typeIdx];
if (type == typeof (DontMap))
{
continue;
} int splitPoint = 0;
if (typeIdx > 0)
{
splitPoint = GetNextSplit(currentPos, currentSplit, reader);
if (isMultiSplit && splitIdx > 0)
{
currentSplit = splits[--splitIdx];
}
} deserializers.Add((GetDeserializer(type, reader, splitPoint, currentPos - splitPoint, typeIdx > 0)));
currentPos = splitPoint;
} deserializers.Reverse(); }
return deserializers.ToArray();
} private static int GetNextSplitDynamic(int startIdx, string splitOn, IDataReader reader)
{
if (startIdx == reader.FieldCount)
{
throw MultiMapException(reader);
} if (splitOn == "*")
{
return ++startIdx;
} for (var i = startIdx + 1; i < reader.FieldCount; ++i)
{
if (string.Equals(splitOn, reader.GetName(i), StringComparison.OrdinalIgnoreCase))
{
return i;
}
} return reader.FieldCount;
} private static int GetNextSplit(int startIdx, string splitOn, IDataReader reader)
{
if (splitOn == "*")
{
return --startIdx;
} for (var i = startIdx - 1; i > 0; --i)
{
if (string.Equals(splitOn, reader.GetName(i), StringComparison.OrdinalIgnoreCase))
{
return i;
}
} throw MultiMapException(reader);
} private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters, bool addToCache)
{
CacheInfo info;
if (!TryGetQueryCache(identity, out info))
{
info = new CacheInfo();
if (identity.parametersType != null)
{
Action<IDbCommand, object> reader;
if (exampleParameters is IDynamicParameters)
{
reader = (cmd, obj) => { ((IDynamicParameters)obj).AddParameters(cmd, identity); };
}
else if (exampleParameters is IEnumerable<KeyValuePair<string, object>>)
{
reader = (cmd, obj) =>
{
IDynamicParameters mapped = new DynamicParameters(obj);
mapped.AddParameters(cmd, identity);
};
}
else
{
var literals = GetLiteralTokens(identity.sql);
reader = CreateParamInfoGenerator(identity, false, true, literals);
}
if((identity.commandType == null || identity.commandType == CommandType.Text) && ShouldPassByPosition(identity.sql))
{
var tail = reader;
var sql = identity.sql;
reader = (cmd, obj) =>
{
tail(cmd, obj);
PassByPosition(cmd);
};
}
info.ParamReader = reader;
}
if(addToCache) SetQueryCache(identity, info);
}
return info;
} private static bool ShouldPassByPosition(string sql)
{
return sql != null && sql.IndexOf('?') >= 0 && pseudoPositional.IsMatch(sql);
} private static void PassByPosition(IDbCommand cmd)
{
if (cmd.Parameters.Count == 0) return; Dictionary<string, IDbDataParameter> parameters = new Dictionary<string, IDbDataParameter>(StringComparer.InvariantCulture); foreach(IDbDataParameter param in cmd.Parameters)
{
if (!string.IsNullOrEmpty(param.ParameterName)) parameters[param.ParameterName] = param;
}
HashSet<string> consumed = new HashSet<string>(StringComparer.InvariantCulture);
bool firstMatch = true;
cmd.CommandText = pseudoPositional.Replace(cmd.CommandText, match =>
{
string key = match.Groups[1].Value;
IDbDataParameter param;
if (!consumed.Add(key))
{
throw new InvalidOperationException("When passing parameters by position, each parameter can only be referenced once");
}
else if (parameters.TryGetValue(key, out param))
{
if(firstMatch)
{
firstMatch = false;
cmd.Parameters.Clear(); // only clear if we are pretty positive that we've found this pattern successfully
}
// if found, return the anonymous token "?"
cmd.Parameters.Add(param);
parameters.Remove(key);
consumed.Add(key);
return "?";
}
else
{
// otherwise, leave alone for simple debugging
return match.Value;
}
});
} private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
#if !CSHARP30
// dynamic is passed in as Object ... by c# design
if (type == typeof(object)
|| type == typeof(DapperRow))
{
return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing);
}
#else
if (type.IsAssignableFrom(typeof(Dictionary<string, object>)))
{
return GetDictionaryDeserializer(reader, startBound, length, returnNullIfFirstMissing);
}
#endif
Type underlyingType = null;
if (!(typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary ||
(type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum)))
{
ITypeHandler handler;
if (typeHandlers.TryGetValue(type, out handler))
{
return GetHandlerDeserializer(handler, type, startBound);
}
return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing);
}
return GetStructDeserializer(type, underlyingType ?? type, startBound);
}
static Func<IDataReader, object> GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound)
{
return (IDataReader reader) =>
handler.Parse(type, reader.GetValue(startBound));
} #if !CSHARP30
private sealed partial class DapperTable
{
string[] fieldNames;
readonly Dictionary<string, int> fieldNameLookup; internal string[] FieldNames { get { return fieldNames; } } public DapperTable(string[] fieldNames)
{
if (fieldNames == null) throw new ArgumentNullException("fieldNames");
this.fieldNames = fieldNames; fieldNameLookup = new Dictionary<string, int>(fieldNames.Length, StringComparer.Ordinal);
// if there are dups, we want the **first** key to be the "winner" - so iterate backwards
for (int i = fieldNames.Length - 1; i >= 0; i--)
{
string key = fieldNames[i];
if (key != null) fieldNameLookup[key] = i;
}
} internal int IndexOfName(string name)
{
int result;
return (name != null && fieldNameLookup.TryGetValue(name, out result)) ? result : -1;
}
internal int AddField(string name)
{
if (name == null) throw new ArgumentNullException("name");
if (fieldNameLookup.ContainsKey(name)) throw new InvalidOperationException("Field already exists: " + name);
int oldLen = fieldNames.Length;
Array.Resize(ref fieldNames, oldLen + 1); // yes, this is sub-optimal, but this is not the expected common case
fieldNames[oldLen] = name;
fieldNameLookup[name] = oldLen;
return oldLen;
} internal bool FieldExists(string key)
{
return key != null && fieldNameLookup.ContainsKey(key);
} public int FieldCount { get { return fieldNames.Length; } }
} sealed partial class DapperRowMetaObject : System.Dynamic.DynamicMetaObject
{
static readonly MethodInfo getValueMethod = typeof(IDictionary<string, object>).GetProperty("Item").GetGetMethod();
static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) }); public DapperRowMetaObject(
System.Linq.Expressions.Expression expression,
System.Dynamic.BindingRestrictions restrictions
)
: base(expression, restrictions)
{
} public DapperRowMetaObject(
System.Linq.Expressions.Expression expression,
System.Dynamic.BindingRestrictions restrictions,
object value
)
: base(expression, restrictions, value)
{
} System.Dynamic.DynamicMetaObject CallMethod(
MethodInfo method,
System.Linq.Expressions.Expression[] parameters
)
{
var callMethod = new System.Dynamic.DynamicMetaObject(
System.Linq.Expressions.Expression.Call(
System.Linq.Expressions.Expression.Convert(Expression, LimitType),
method,
parameters),
System.Dynamic.BindingRestrictions.GetTypeRestriction(Expression, LimitType)
);
return callMethod;
} public override System.Dynamic.DynamicMetaObject BindGetMember(System.Dynamic.GetMemberBinder binder)
{
var parameters = new System.Linq.Expressions.Expression[]
{
System.Linq.Expressions.Expression.Constant(binder.Name)
}; var callMethod = CallMethod(getValueMethod, parameters); return callMethod;
} // Needed for Visual basic dynamic support
public override System.Dynamic.DynamicMetaObject BindInvokeMember(System.Dynamic.InvokeMemberBinder binder, System.Dynamic.DynamicMetaObject[] args)
{
var parameters = new System.Linq.Expressions.Expression[]
{
System.Linq.Expressions.Expression.Constant(binder.Name)
}; var callMethod = CallMethod(getValueMethod, parameters); return callMethod;
} public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.SetMemberBinder binder, System.Dynamic.DynamicMetaObject value)
{
var parameters = new System.Linq.Expressions.Expression[]
{
System.Linq.Expressions.Expression.Constant(binder.Name),
value.Expression,
}; var callMethod = CallMethod(setValueMethod, parameters); return callMethod;
}
} private sealed partial class DapperRow
: System.Dynamic.IDynamicMetaObjectProvider
, IDictionary<string, object>
{
readonly DapperTable table;
object[] values; public DapperRow(DapperTable table, object[] values)
{
if (table == null) throw new ArgumentNullException("table");
if (values == null) throw new ArgumentNullException("values");
this.table = table;
this.values = values;
}
private sealed class DeadValue
{
public static readonly DeadValue Default = new DeadValue();
private DeadValue() { }
}
int ICollection<KeyValuePair<string, object>>.Count
{
get
{
int count = 0;
for (int i = 0; i < values.Length; i++)
{
if (!(values[i] is DeadValue)) count++;
}
return count;
}
} public bool TryGetValue(string name, out object value)
{
var index = table.IndexOfName(name);
if (index < 0)
{ // doesn't exist
value = null;
return false;
}
// exists, **even if** we don't have a value; consider table rows heterogeneous
value = index < values.Length ? values[index] : null;
if (value is DeadValue)
{ // pretend it isn't here
value = null;
return false;
}
return true;
} public override string ToString()
{
var sb = GetStringBuilder().Append("{DapperRow");
foreach (var kv in this)
{
var value = kv.Value;
sb.Append(", ").Append(kv.Key);
if (value != null)
{
sb.Append(" = '").Append(kv.Value).Append('\'');
}
else
{
sb.Append(" = NULL");
}
} return sb.Append('}').__ToStringRecycle();
} System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(
System.Linq.Expressions.Expression parameter)
{
return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this);
} public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
var names = table.FieldNames;
for (var i = 0; i < names.Length; i++)
{
object value = i < values.Length ? values[i] : null;
if (!(value is DeadValue))
{
yield return new KeyValuePair<string, object>(names[i], value);
}
}
} IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
} #region Implementation of ICollection<KeyValuePair<string,object>> void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
IDictionary<string, object> dic = this;
dic.Add(item.Key, item.Value);
} void ICollection<KeyValuePair<string, object>>.Clear()
{ // removes values for **this row**, but doesn't change the fundamental table
for (int i = 0; i < values.Length; i++)
values[i] = DeadValue.Default;
} bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
object value;
return TryGetValue(item.Key, out value) && Equals(value, item.Value);
} void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
foreach (var kv in this)
{
array[arrayIndex++] = kv; // if they didn't leave enough space; not our fault
}
} bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
IDictionary<string, object> dic = this;
return dic.Remove(item.Key);
} bool ICollection<KeyValuePair<string, object>>.IsReadOnly
{
get { return false; }
} #endregion #region Implementation of IDictionary<string,object> bool IDictionary<string, object>.ContainsKey(string key)
{
int index = table.IndexOfName(key);
if (index < 0 || index >= values.Length || values[index] is DeadValue) return false;
return true;
} void IDictionary<string, object>.Add(string key, object value)
{
SetValue(key, value, true);
} bool IDictionary<string, object>.Remove(string key)
{
int index = table.IndexOfName(key);
if (index < 0 || index >= values.Length || values[index] is DeadValue) return false;
values[index] = DeadValue.Default;
return true;
} object IDictionary<string, object>.this[string key]
{
get { object val; TryGetValue(key, out val); return val; }
set { SetValue(key, value, false); }
} public object SetValue(string key, object value)
{
return SetValue(key, value, false);
}
private object SetValue(string key, object value, bool isAdd)
{
if (key == null) throw new ArgumentNullException("key");
int index = table.IndexOfName(key);
if (index < 0)
{
index = table.AddField(key);
}
else if (isAdd && index < values.Length && !(values[index] is DeadValue))
{
// then semantically, this value already exists
throw new ArgumentException("An item with the same key has already been added", "key");
}
int oldLength = values.Length;
if (oldLength <= index)
{
// we'll assume they're doing lots of things, and
// grow it to the full width of the table
Array.Resize(ref values, table.FieldCount);
for (int i = oldLength; i < values.Length; i++)
{
values[i] = DeadValue.Default;
}
}
return values[index] = value;
} ICollection<string> IDictionary<string, object>.Keys
{
get { return this.Select(kv => kv.Key).ToArray(); }
} ICollection<object> IDictionary<string, object>.Values
{
get { return this.Select(kv => kv.Value).ToArray(); }
} #endregion
}
#endif
private static Exception MultiMapException(IDataRecord reader)
{
bool hasFields = false;
try {
hasFields = reader != null && reader.FieldCount != 0;
} catch { }
if (hasFields)
return new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn");
else
return new InvalidOperationException("No columns were selected");
} #if !CSHARP30
internal static Func<IDataReader, object> GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)
{
var fieldCount = reader.FieldCount;
if (length == -1)
{
length = fieldCount - startBound;
} if (fieldCount <= startBound)
{
throw MultiMapException(reader);
} var effectiveFieldCount = Math.Min(fieldCount - startBound, length); DapperTable table = null; return
r =>
{
if (table == null)
{
string[] names = new string[effectiveFieldCount];
for (int i = 0; i < effectiveFieldCount; i++)
{
names[i] = r.GetName(i + startBound);
}
table = new DapperTable(names);
} var values = new object[effectiveFieldCount]; if (returnNullIfFirstMissing)
{
values[0] = r.GetValue(startBound);
if (values[0] is DBNull)
{
return null;
}
} if (startBound == 0)
{
r.GetValues(values);
for (int i = 0; i < values.Length; i++)
if (values[i] is DBNull) values[i] = null;
}
else
{
var begin = returnNullIfFirstMissing ? 1 : 0;
for (var iter = begin; iter < effectiveFieldCount; ++iter)
{
object obj = r.GetValue(iter + startBound);
values[iter] = obj is DBNull ? null : obj;
}
}
return new DapperRow(table, values);
};
}
#else
internal static Func<IDataReader, object> GetDictionaryDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)
{
var fieldCount = reader.FieldCount;
if (length == -1)
{
length = fieldCount - startBound;
} if (fieldCount <= startBound)
{
throw MultiMapException(reader);
} return
r =>
{
IDictionary<string, object> row = new Dictionary<string, object>(length);
for (var i = startBound; i < startBound + length; i++)
{
var tmp = r.GetValue(i);
tmp = tmp == DBNull.Value ? null : tmp;
row[r.GetName(i)] = tmp;
if (returnNullIfFirstMissing && i == startBound && tmp == null)
{
return null;
}
}
return row;
};
}
#endif
/// <summary>
/// Internal use only
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is for internal usage only", false)]
public static char ReadChar(object value)
{
if (value == null || value is DBNull) throw new ArgumentNullException("value");
string s = value as string;
if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value");
return s[0];
} /// <summary>
/// Internal use only
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is for internal usage only", false)]
public static char? ReadNullableChar(object value)
{
if (value == null || value is DBNull) return null;
string s = value as string;
if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value");
return s[0];
} /// <summary>
/// Internal use only
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is for internal usage only", true)]
public static IDbDataParameter FindOrAddParameter(IDataParameterCollection parameters, IDbCommand command, string name)
{
IDbDataParameter result;
if (parameters.Contains(name))
{
result = (IDbDataParameter)parameters[name];
}
else
{
result = command.CreateParameter();
result.ParameterName = name;
parameters.Add(result);
}
return result;
} /// <summary>
/// Internal use only
/// </summary>
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is for internal usage only", false)]
public static void PackListParameters(IDbCommand command, string namePrefix, object value)
{
// initially we tried TVP, however it performs quite poorly.
// keep in mind SQL support up to 2000 params easily in sp_executesql, needing more is rare if (FeatureSupport.Get(command.Connection).Arrays)
{
var arrayParm = command.CreateParameter();
arrayParm.Value = value ?? DBNull.Value;
arrayParm.ParameterName = namePrefix;
command.Parameters.Add(arrayParm);
}
else
{
var list = value as IEnumerable;
var count = 0;
bool isString = value is IEnumerable<string>;
bool isDbString = value is IEnumerable<DbString>;
foreach (var item in list)
{
count++;
var listParam = command.CreateParameter();
listParam.ParameterName = namePrefix + count;
if (isString)
{
listParam.Size = DbString.DefaultLength;
if (item != null && ((string)item).Length > DbString.DefaultLength)
{
listParam.Size = -1;
}
}
if (isDbString && item as DbString != null)
{
var str = item as DbString;
str.AddParameter(command, listParam.ParameterName);
}
else
{
listParam.Value = item ?? DBNull.Value;
command.Parameters.Add(listParam);
}
} var regexIncludingUnknown = @"([?@:]" + Regex.Escape(namePrefix) + @")(?!\w)(\s+(?i)unknown(?-i))?";
if (count == 0)
{
command.CommandText = Regex.Replace(command.CommandText, regexIncludingUnknown, match =>
{
var variableName = match.Groups[1].Value;
if (match.Groups[2].Success)
{
// looks like an optimize hint; leave it alone!
return match.Value;
}
else
{
return "(SELECT " + variableName + " WHERE 1 = 0)";
}
}, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant);
var dummyParam = command.CreateParameter();
dummyParam.ParameterName = namePrefix;
dummyParam.Value = DBNull.Value;
command.Parameters.Add(dummyParam);
}
else
{
command.CommandText = Regex.Replace(command.CommandText, regexIncludingUnknown, match =>
{
var variableName = match.Groups[1].Value;
if (match.Groups[2].Success)
{
// looks like an optimize hint; expand it
var suffix = match.Groups[2].Value; var sb = GetStringBuilder().Append(variableName).Append(1).Append(suffix);
for (int i = 2; i <= count; i++)
{
sb.Append(',').Append(variableName).Append(i).Append(suffix);
}
return sb.__ToStringRecycle();
}
else
{
var sb = GetStringBuilder().Append('(').Append(variableName).Append(1);
for (int i = 2; i <= count; i++)
{
sb.Append(',').Append(variableName).Append(i);
}
return sb.Append(')').__ToStringRecycle();
}
}, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant);
}
} } private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyInfo> parameters, string sql)
{
return parameters.Where(p => Regex.IsMatch(sql, @"[?@:]" + p.Name + "([^a-z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant));
} // look for ? / @ / : *by itself*
static readonly Regex smellsLikeOleDb = new Regex(@"(?<![a-z0-9@_])[?@:](?![a-z0-9@_])", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled),
literalTokens = new Regex(@"(?<![a-z0-9_])\{=([a-z0-9_]+)\}", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled),
pseudoPositional = new Regex(@"\?([a-z_][a-z0-9_]*)\?", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); /// <summary>
/// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql
/// </summary>
internal struct LiteralToken
{
private readonly string token, member;
/// <summary>
/// The text in the original command that should be replaced
/// </summary>
public string Token { get { return token; } } /// <summary>
/// The name of the member referred to by the token
/// </summary>
public string Member { get { return member; } }
internal LiteralToken(string token, string member)
{
this.token = token;
this.member = member;
} internal static readonly IList<LiteralToken> None = new LiteralToken[0];
} /// <summary>
/// Replace all literal tokens with their text form
/// </summary>
public static void ReplaceLiterals(this IParameterLookup parameters, IDbCommand command)
{
var tokens = GetLiteralTokens(command.CommandText);
if (tokens.Count != 0) ReplaceLiterals(parameters, command, tokens);
} internal static readonly MethodInfo format = typeof(SqlMapper).GetMethod("Format", BindingFlags.Public | BindingFlags.Static);
/// <summary>
/// Convert numeric values to their string form for SQL literal purposes
/// </summary>
[Obsolete("This is intended for internal usage only")]
public static string Format(object value)
{
if (value == null)
{
return "null";
}
else
{
switch (Type.GetTypeCode(value.GetType()))
{
case TypeCode.DBNull:
return "null";
case TypeCode.Boolean:
return ((bool)value) ? "1" : "0";
case TypeCode.Byte:
return ((byte)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.SByte:
return ((sbyte)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.UInt16:
return ((ushort)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Int16:
return ((short)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.UInt32:
return ((uint)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Int32:
return ((int)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.UInt64:
return ((ulong)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Int64:
return ((long)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Single:
return ((float)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Double:
return ((double)value).ToString(CultureInfo.InvariantCulture);
case TypeCode.Decimal:
return ((decimal)value).ToString(CultureInfo.InvariantCulture);
default:
var multiExec = GetMultiExec(value);
if(multiExec != null)
{
StringBuilder sb = null;
bool first = true;
foreach (object subval in multiExec)
{
if(first)
{
sb = GetStringBuilder().Append('(');
first = false;
}
else
{
sb.Append(',');
}
sb.Append(Format(subval));
}
if(first)
{
return "(select null where 1=0)";
}
else
{
return sb.Append(')').__ToStringRecycle();
}
}
throw new NotSupportedException(value.GetType().Name);
}
}
} internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand command, IList<LiteralToken> tokens)
{
var sql = command.CommandText;
foreach (var token in tokens)
{
object value = parameters[token.Member];
#pragma warning disable 0618
string text = Format(value);
#pragma warning restore 0618
sql = sql.Replace(token.Token, text);
}
command.CommandText = sql;
} internal static IList<LiteralToken> GetLiteralTokens(string sql)
{
if (string.IsNullOrEmpty(sql)) return LiteralToken.None;
if (!literalTokens.IsMatch(sql)) return LiteralToken.None; var matches = literalTokens.Matches(sql);
var found = new HashSet<string>(StringComparer.InvariantCulture);
List<LiteralToken> list = new List<LiteralToken>(matches.Count);
foreach(Match match in matches)
{
string token = match.Value;
if(found.Add(match.Value))
{
list.Add(new LiteralToken(token, match.Groups[1].Value));
}
}
return list.Count == 0 ? LiteralToken.None : list;
} /// <summary>
/// Internal use only
/// </summary>
public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused)
{
return CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql));
} internal static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList<LiteralToken> literals)
{
Type type = identity.parametersType; bool filterParams = false;
if (removeUnused && identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text)
{
filterParams = !smellsLikeOleDb.IsMatch(identity.sql);
}
var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true); var il = dm.GetILGenerator(); bool isStruct = type.IsValueType;
bool haveInt32Arg1 = false;
il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param]
if (isStruct)
{
il.DeclareLocal(type.MakePointerType());
il.Emit(OpCodes.Unbox, type); // stack is now [typed-param]
}
else
{
il.DeclareLocal(type); // 0
il.Emit(OpCodes.Castclass, type); // stack is now [typed-param]
}
il.Emit(OpCodes.Stloc_0);// stack is now empty il.Emit(OpCodes.Ldarg_0); // stack is now [command]
il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty("Parameters").GetGetMethod(), null); // stack is now [parameters] var propsArr = type.GetProperties().Where(p => p.GetIndexParameters().Length == 0).ToArray();
var ctors = type.GetConstructors();
ParameterInfo[] ctorParams;
IEnumerable<PropertyInfo> props = null;
// try to detect tuple patterns, e.g. anon-types, and use that to choose the order
// otherwise: alphabetical
if (ctors.Length == 1 && propsArr.Length == (ctorParams = ctors[0].GetParameters()).Length)
{
// check if reflection was kind enough to put everything in the right order for us
bool ok = true;
for (int i = 0; i < propsArr.Length; i++)
{
if (!string.Equals(propsArr[i].Name, ctorParams[i].Name, StringComparison.InvariantCultureIgnoreCase))
{
ok = false;
break;
}
}
if(ok)
{
// pre-sorted; the reflection gods have smiled upon us
props = propsArr;
}
else { // might still all be accounted for; check the hard way
var positionByName = new Dictionary<string,int>(StringComparer.InvariantCultureIgnoreCase);
foreach(var param in ctorParams)
{
positionByName[param.Name] = param.Position;
}
if (positionByName.Count == propsArr.Length)
{
int[] positions = new int[propsArr.Length];
ok = true;
for (int i = 0; i < propsArr.Length; i++)
{
int pos;
if (!positionByName.TryGetValue(propsArr[i].Name, out pos))
{
ok = false;
break;
}
positions[i] = pos;
}
if (ok)
{
Array.Sort(positions, propsArr);
props = propsArr;
}
}
}
}
if(props == null) props = propsArr.OrderBy(x => x.Name);
if (filterParams)
{
props = FilterParameters(props, identity.sql);
} var callOpCode = isStruct ? OpCodes.Call : OpCodes.Callvirt;
foreach (var prop in props)
{
if (typeof(ICustomQueryParameter).IsAssignableFrom(prop.PropertyType))
{
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param]
il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [custom]
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [custom] [command]
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [custom] [command] [name]
il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod("AddParameter"), null); // stack is now [parameters]
continue;
}
ITypeHandler handler;
DbType dbType = LookupDbType(prop.PropertyType, prop.Name, true, out handler);
if (dbType == DynamicParameters.EnumerableMultiParameter)
{
// this actually represents special handling for list types;
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command]
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name]
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param]
il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value]
if (prop.PropertyType.IsValueType)
{
il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value]
}
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("PackListParameters"), null); // stack is [parameters]
continue;
}
il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters] il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [parameters] [command] if (checkForDuplicates)
{
// need to be a little careful about adding; use a utility method
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [command] [name]
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("FindOrAddParameter"), null); // stack is [parameters] [parameter]
}
else
{
// no risk of duplicates; just blindly add
il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod("CreateParameter"), null);// stack is now [parameters] [parameters] [parameter] il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name]
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("ParameterName").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
}
if (dbType != DbType.Time && handler == null) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time
{
il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
if (dbType == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic
{
// look it up from the param value
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param]
il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [object-value]
il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod("GetDbType", BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type]
}
else
{
// constant value; nice and simple
EmitInt32(il, (int)dbType);// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type]
}
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("DbType").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter]
} il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
EmitInt32(il, (int)ParameterDirection.Input);// stack is now [parameters] [[parameters]] [parameter] [parameter] [dir]
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Direction").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param]
il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value]
bool checkForNull = true;
if (prop.PropertyType.IsValueType)
{
il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value]
if (Nullable.GetUnderlyingType(prop.PropertyType) == null)
{ // struct but not Nullable<T>; boxed value cannot be null
checkForNull = false;
}
}
if (checkForNull)
{
if ((dbType == DbType.String || dbType == DbType.AnsiString) && !haveInt32Arg1)
{
il.DeclareLocal(typeof(int));
haveInt32Arg1 = true;
}
// relative stack: [boxed value]
il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value]
Label notNull = il.DefineLabel();
Label? allDone = (dbType == DbType.String || dbType == DbType.AnsiString) ? il.DefineLabel() : (Label?)null;
il.Emit(OpCodes.Brtrue_S, notNull);
// relative stack [boxed value = null]
il.Emit(OpCodes.Pop); // relative stack empty
il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); // relative stack [DBNull]
if (dbType == DbType.String || dbType == DbType.AnsiString)
{
EmitInt32(il, 0);
il.Emit(OpCodes.Stloc_1);
}
if (allDone != null) il.Emit(OpCodes.Br_S, allDone.Value);
il.MarkLabel(notNull);
if (prop.PropertyType == typeof(string))
{
il.Emit(OpCodes.Dup); // [string] [string]
il.EmitCall(OpCodes.Callvirt, typeof(string).GetProperty("Length").GetGetMethod(), null); // [string] [length]
EmitInt32(il, DbString.DefaultLength); // [string] [length] [4000]
il.Emit(OpCodes.Cgt); // [string] [0 or 1]
Label isLong = il.DefineLabel(), lenDone = il.DefineLabel();
il.Emit(OpCodes.Brtrue_S, isLong);
EmitInt32(il, DbString.DefaultLength); // [string] [4000]
il.Emit(OpCodes.Br_S, lenDone);
il.MarkLabel(isLong);
EmitInt32(il, -1); // [string] [-1]
il.MarkLabel(lenDone);
il.Emit(OpCodes.Stloc_1); // [string]
}
if (prop.PropertyType.FullName == LinqBinary)
{
il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance), null);
}
if (allDone != null) il.MarkLabel(allDone.Value);
// relative stack [boxed value or DBNull]
} if (handler != null)
{
#pragma warning disable 618
il.Emit(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(prop.PropertyType).GetMethod("SetValue")); // stack is now [parameters] [[parameters]] [parameter]
#pragma warning restore 618
}
else
{
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Value").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter]
} if (prop.PropertyType == typeof(string))
{
var endOfSize = il.DefineLabel();
// don't set if 0
il.Emit(OpCodes.Ldloc_1); // [parameters] [[parameters]] [parameter] [size]
il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [[parameters]] [parameter] il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
il.Emit(OpCodes.Ldloc_1); // stack is now [parameters] [[parameters]] [parameter] [parameter] [size]
il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty("Size").GetSetMethod(), null); // stack is now [parameters] [[parameters]] [parameter] il.MarkLabel(endOfSize);
}
if (checkForDuplicates)
{
// stack is now [parameters] [parameter]
il.Emit(OpCodes.Pop); // don't need parameter any more
}
else
{
// stack is now [parameters] [parameters] [parameter]
// blindly add
il.EmitCall(OpCodes.Callvirt, typeof(IList).GetMethod("Add"), null); // stack is now [parameters]
il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care
}
} // stack is currently [parameters]
il.Emit(OpCodes.Pop); // stack is now empty if(literals.Count != 0 && propsArr != null)
{
il.Emit(OpCodes.Ldarg_0); // command
il.Emit(OpCodes.Ldarg_0); // command, command
var cmdText = typeof(IDbCommand).GetProperty("CommandText");
il.EmitCall(OpCodes.Callvirt, cmdText.GetGetMethod(), null); // command, sql
Dictionary<Type, LocalBuilder> locals = null;
LocalBuilder local = null;
foreach (var literal in literals)
{
// find the best member, preferring case-sensitive
PropertyInfo exact = null, fallback = null;
string huntName = literal.Member;
for(int i = 0; i < propsArr.Length;i++)
{
string thisName = propsArr[i].Name;
if(string.Equals(thisName, huntName, StringComparison.InvariantCultureIgnoreCase))
{
fallback = propsArr[i];
if(string.Equals(thisName, huntName, StringComparison.InvariantCulture))
{
exact = fallback;
break;
}
}
}
var prop = exact ?? fallback; if(prop != null)
{
il.Emit(OpCodes.Ldstr, literal.Token);
il.Emit(OpCodes.Ldloc_0); // command, sql, typed parameter
il.EmitCall(callOpCode, prop.GetGetMethod(), null); // command, sql, typed value
Type propType = prop.PropertyType;
var typeCode = Type.GetTypeCode(propType);
switch (typeCode)
{
case TypeCode.Boolean:
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.Int16:
case TypeCode.UInt32:
case TypeCode.Int32:
case TypeCode.UInt64:
case TypeCode.Int64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
// need to stloc, ldloca, call
// re-use existing locals (both the last known, and via a dictionary)
var convert = GetToString(typeCode);
if (local == null || local.LocalType != propType)
{
if (locals == null)
{
locals = new Dictionary<Type, LocalBuilder>();
local = null;
}
else
{
if (!locals.TryGetValue(propType, out local)) local = null;
}
if (local == null)
{
local = il.DeclareLocal(propType);
locals.Add(propType, local);
}
}
il.Emit(OpCodes.Stloc, local); // command, sql
il.Emit(OpCodes.Ldloca, local); // command, sql, ref-to-value
il.EmitCall(OpCodes.Call, InvariantCulture, null); // command, sql, ref-to-value, culture
il.EmitCall(OpCodes.Call, convert, null); // command, sql, string value
break;
default:
if (propType.IsValueType) il.Emit(OpCodes.Box, propType); // command, sql, object value
il.EmitCall(OpCodes.Call, format, null); // command, sql, string value
break; }
il.EmitCall(OpCodes.Callvirt, StringReplace, null);
}
}
il.EmitCall(OpCodes.Callvirt, cmdText.GetSetMethod(), null); // empty
} il.Emit(OpCodes.Ret);
return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>));
}
static readonly Dictionary<TypeCode, MethodInfo> toStrings = new[]
{
typeof(bool), typeof(sbyte), typeof(byte), typeof(ushort), typeof(short),
typeof(uint), typeof(int), typeof(ulong), typeof(long), typeof(float), typeof(double), typeof(decimal)
}.ToDictionary(x => Type.GetTypeCode(x), x => x.GetMethod("ToString", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(IFormatProvider) }, null));
static MethodInfo GetToString(TypeCode typeCode)
{
MethodInfo method;
return toStrings.TryGetValue(typeCode, out method) ? method : null;
}
static readonly MethodInfo StringReplace = typeof(string).GetMethod("Replace", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string), typeof(string) }, null),
InvariantCulture = typeof(CultureInfo).GetProperty("InvariantCulture", BindingFlags.Public | BindingFlags.Static).GetGetMethod(); private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action<IDbCommand, object> paramReader)
{
IDbCommand cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
cmd = command.SetupCommand(cnn, paramReader);
if (wasClosed) cnn.Open();
int result = cmd.ExecuteNonQuery();
command.OnCompleted();
return result;
}
finally
{
if (wasClosed) cnn.Close();
if (cmd != null) cmd.Dispose();
}
} 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();
command.OnCompleted();
}
finally
{
if (wasClosed) cnn.Close();
if (cmd != null) cmd.Dispose();
}
return Parse<T>(result);
} private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command, CommandBehavior commandBehavior, out IDbCommand cmd)
{
Action<IDbCommand, object> paramReader = GetParameterReader(cnn, ref command);
cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed, disposeCommand = true;
try
{
cmd = command.SetupCommand(cnn, paramReader);
if (wasClosed) cnn.Open();
if (wasClosed) commandBehavior |= CommandBehavior.CloseConnection;
var reader = cmd.ExecuteReader(commandBehavior);
wasClosed = false; // don't dispose before giving it to them!
disposeCommand = false;
// note: command.FireOutputCallbacks(); would be useless here; parameters come at the **end** of the TDS stream
return reader;
}
finally
{
if (wasClosed) cnn.Close();
if (cmd != null && disposeCommand) cmd.Dispose();
}
} private static Action<IDbCommand, object> GetParameterReader(IDbConnection cnn, ref CommandDefinition command)
{
object param = command.Parameters;
IEnumerable multiExec = GetMultiExec(param);
Identity identity;
CacheInfo info = null;
if (multiExec != null)
{
throw new NotSupportedException("MultiExec is not supported by ExecuteReader");
} // nice and simple
if (param != null)
{
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
info = GetCacheInfo(identity, param, command.AddToCache);
}
var paramReader = info == null ? null : info.ParamReader;
return paramReader;
} private static Func<IDataReader, object> GetStructDeserializer(Type type, Type effectiveType, int index)
{
// no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!)
#pragma warning disable 618
if (type == typeof(char))
{ // this *does* need special handling, though
return r => SqlMapper.ReadChar(r.GetValue(index));
}
if (type == typeof(char?))
{
return r => SqlMapper.ReadNullableChar(r.GetValue(index));
}
if (type.FullName == LinqBinary)
{
return r => Activator.CreateInstance(type, r.GetValue(index));
}
#pragma warning restore 618 if (effectiveType.IsEnum)
{ // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum
return r =>
{
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);
};
}
ITypeHandler handler;
if(typeHandlers.TryGetValue(type, out handler))
{
return r =>
{
var val = r.GetValue(index);
return val is DBNull ? null : handler.Parse(type, val);
};
}
return r =>
{
var val = r.GetValue(index);
return val is DBNull ? null : val;
};
} 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
enumParse = typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string), typeof(bool) }),
getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.GetIndexParameters().Any() && p.GetIndexParameters()[0].ParameterType == typeof(int))
.Select(p => p.GetGetMethod()).First(); /// <summary>
/// Gets type-map for the given type
/// </summary>
/// <returns>Type map implementation, DefaultTypeMap instance if no override present</returns>
public static ITypeMap GetTypeMap(Type type)
{
if (type == null) throw new ArgumentNullException("type");
var map = (ITypeMap)_typeMaps[type];
if (map == null)
{
lock (_typeMaps)
{ // double-checked; store this to avoid reflection next time we see this type
// since multiple queries commonly use the same domain-entity/DTO/view-model type
map = (ITypeMap)_typeMaps[type];
if (map == null)
{
map = new DefaultTypeMap(type);
_typeMaps[type] = map;
}
}
}
return map;
} // use Hashtable to get free lockless reading
private static readonly Hashtable _typeMaps = new Hashtable(); /// <summary>
/// Set custom mapping for type deserializers
/// </summary>
/// <param name="type">Entity type to override</param>
/// <param name="map">Mapping rules impementation, null to remove custom map</param>
public static void SetTypeMap(Type type, ITypeMap map)
{
if (type == null)
throw new ArgumentNullException("type"); if (map == null || map is DefaultTypeMap)
{
lock (_typeMaps)
{
_typeMaps.Remove(type);
}
}
else
{
lock (_typeMaps)
{
_typeMaps[type] = map;
}
} PurgeQueryCacheByType(type);
} /// <summary>
/// Internal use only
/// </summary>
/// <param name="type"></param>
/// <param name="reader"></param>
/// <param name="startBound"></param>
/// <param name="length"></param>
/// <param name="returnNullIfFirstMissing"></param>
/// <returns></returns>
public static Func<IDataReader, object> GetTypeDeserializer(
#if CSHARP30
Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
#else
Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false
#endif
)
{ var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(IDataReader) }, true);
var il = dm.GetILGenerator();
il.DeclareLocal(typeof(int));
il.DeclareLocal(type);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Stloc_0); if (length == -1)
{
length = reader.FieldCount - startBound;
} if (reader.FieldCount <= startBound)
{
throw MultiMapException(reader);
} var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray(); ITypeMap typeMap = GetTypeMap(type); int index = startBound; ConstructorInfo specializedConstructor = null; bool supportInitialize = false;
if (type.IsValueType)
{
il.Emit(OpCodes.Ldloca_S, (byte)1);
il.Emit(OpCodes.Initobj, type);
}
else
{
var types = new Type[length];
for (int i = startBound; i < startBound + length; i++)
{
types[i - startBound] = reader.GetFieldType(i);
} var explicitConstr = typeMap.FindExplicitConstructor();
if (explicitConstr != null)
{
var structLocals = new Dictionary<Type, LocalBuilder>(); var consPs = explicitConstr.GetParameters();
foreach(var p in consPs)
{
if(!p.ParameterType.IsValueType)
{
il.Emit(OpCodes.Ldnull);
}
else
{
LocalBuilder loc;
if(!structLocals.TryGetValue(p.ParameterType, out loc))
{
structLocals[p.ParameterType] = loc = il.DeclareLocal(p.ParameterType);
} il.Emit(OpCodes.Ldloca, (short)loc.LocalIndex);
il.Emit(OpCodes.Initobj, p.ParameterType);
il.Emit(OpCodes.Ldloca, (short)loc.LocalIndex);
il.Emit(OpCodes.Ldobj, p.ParameterType);
}
} il.Emit(OpCodes.Newobj, explicitConstr);
il.Emit(OpCodes.Stloc_1);
supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type);
if (supportInitialize)
{
il.Emit(OpCodes.Ldloc_1);
il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("BeginInit"), null);
}
}
else
{
var ctor = typeMap.FindConstructor(names, types);
if (ctor == null)
{
string proposedTypes = "(" + string.Join(", ", types.Select((t, i) => t.FullName + " " + names[i]).ToArray()) + ")";
throw new InvalidOperationException(string.Format("A parameterless default constructor or one matching signature {0} is required for {1} materialization", proposedTypes, type.FullName));
} if (ctor.GetParameters().Length == 0)
{
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Stloc_1);
supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type);
if (supportInitialize)
{
il.Emit(OpCodes.Ldloc_1);
il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("BeginInit"), null);
}
}
else
{
specializedConstructor = ctor;
}
}
} il.BeginExceptionBlock();
if (type.IsValueType)
{
il.Emit(OpCodes.Ldloca_S, (byte)1);// [target]
}
else if (specializedConstructor == null)
{
il.Emit(OpCodes.Ldloc_1);// [target]
} var members = (specializedConstructor != null
? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n))
: names.Select(n => typeMap.GetMember(n))).ToList(); // stack is now [target] bool first = true;
var allDone = il.DefineLabel();
int enumDeclareLocal = -1, valueCopyLocal = il.DeclareLocal(typeof(object)).LocalIndex;
foreach (var item in members)
{
if (item != null)
{
if (specializedConstructor == null)
il.Emit(OpCodes.Dup); // stack is now [target][target]
Label isDbNullLabel = il.DefineLabel();
Label finishLabel = il.DefineLabel(); il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader]
EmitInt32(il, index); // stack is now [target][target][reader][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.Callvirt, getItem); // stack is now [target][target][value-as-object]
il.Emit(OpCodes.Dup); // stack is now [target][target][value-as-object][value-as-object]
StoreLocal(il, valueCopyLocal);
Type colType = reader.GetFieldType(index);
Type memberType = item.MemberType; if (memberType == typeof(char) || memberType == typeof(char?))
{
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(
memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value]
}
else
{
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null]
il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object] // unbox nullable enums as the primitive, i.e. byte etc var nullUnderlyingType = Nullable.GetUnderlyingType(memberType);
var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType; if (unboxType.IsEnum)
{
Type numericType = Enum.GetUnderlyingType(unboxType);
if(colType == typeof(string))
{
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]
LoadLocal(il, enumDeclareLocal); // 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);
} if (nullUnderlyingType != null)
{
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
}
}
else if (memberType.FullName == LinqBinary)
{
il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array]
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary]
}
else
{
TypeCode dataTypeCode = Type.GetTypeCode(colType), unboxTypeCode = Type.GetTypeCode(unboxType);
bool hasTypeHandler;
if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType))
{
if (hasTypeHandler)
{
#pragma warning disable 618
il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod("Parse"), null); // stack is now [target][target][typed-value]
#pragma warning restore 618
}
else
{
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
}
}
else
{
// not a direct match; need to tweak the unbox
FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null);
if (nullUnderlyingType != null)
{
il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
} } }
}
if (specializedConstructor == null)
{
// Store the value in the property/field
if (item.Property != null)
{
if (type.IsValueType)
{
il.Emit(OpCodes.Call, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
}
else
{
il.Emit(OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
}
}
else
{
il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
}
} il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target] il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]
if (specializedConstructor != null)
{
il.Emit(OpCodes.Pop);
if (item.MemberType.IsValueType)
{
int localIndex = il.DeclareLocal(item.MemberType).LocalIndex;
LoadLocalAddress(il, localIndex);
il.Emit(OpCodes.Initobj, item.MemberType);
LoadLocal(il, localIndex);
}
else
{
il.Emit(OpCodes.Ldnull);
}
}
else
{
il.Emit(OpCodes.Pop); // stack is now [target][target]
il.Emit(OpCodes.Pop); // stack is now [target]
} if (first && returnNullIfFirstMissing)
{
il.Emit(OpCodes.Pop);
il.Emit(OpCodes.Ldnull); // stack is now [null]
il.Emit(OpCodes.Stloc_1);
il.Emit(OpCodes.Br, allDone);
} il.MarkLabel(finishLabel);
}
first = false;
index += 1;
}
if (type.IsValueType)
{
il.Emit(OpCodes.Pop);
}
else
{
if (specializedConstructor != null)
{
il.Emit(OpCodes.Newobj, specializedConstructor);
}
il.Emit(OpCodes.Stloc_1); // stack is empty
if (supportInitialize)
{
il.Emit(OpCodes.Ldloc_1);
il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod("EndInit"), null);
}
}
il.MarkLabel(allDone);
il.BeginCatchBlock(typeof(Exception)); // stack is Exception
il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader
LoadLocal(il, valueCopyLocal); // stack is Exception, index, reader, value
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null);
il.EndExceptionBlock(); il.Emit(OpCodes.Ldloc_1); // stack is [rval]
if (type.IsValueType)
{
il.Emit(OpCodes.Box, type);
}
il.Emit(OpCodes.Ret); 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)
{
if (to == null) return null;
MethodInfo[] fromMethods, toMethods;
return ResolveOperator(fromMethods = from.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit")
?? ResolveOperator(toMethods = to.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit")
?? ResolveOperator(fromMethods, from, to, "op_Explicit")
?? ResolveOperator(toMethods, from, to, "op_Explicit"); }
static MethodInfo ResolveOperator(MethodInfo[] methods, Type from, Type to, string name)
{
for (int i = 0; i < methods.Length; i++)
{
if (methods[i].Name != name || methods[i].ReturnType != to) continue;
var args = methods[i].GetParameters();
if (args.Length != 1 || args[0].ParameterType != from) continue;
return methods[i];
}
return null;
} private static void LoadLocal(ILGenerator il, int index)
{
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
switch (index)
{
case 0: il.Emit(OpCodes.Ldloc_0); break;
case 1: il.Emit(OpCodes.Ldloc_1); break;
case 2: il.Emit(OpCodes.Ldloc_2); break;
case 3: il.Emit(OpCodes.Ldloc_3); break;
default:
if (index <= 255)
{
il.Emit(OpCodes.Ldloc_S, (byte)index);
}
else
{
il.Emit(OpCodes.Ldloc, (short)index);
}
break;
}
}
private static void StoreLocal(ILGenerator il, int index)
{
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
switch (index)
{
case 0: il.Emit(OpCodes.Stloc_0); break;
case 1: il.Emit(OpCodes.Stloc_1); break;
case 2: il.Emit(OpCodes.Stloc_2); break;
case 3: il.Emit(OpCodes.Stloc_3); break;
default:
if (index <= 255)
{
il.Emit(OpCodes.Stloc_S, (byte)index);
}
else
{
il.Emit(OpCodes.Stloc, (short)index);
}
break;
}
}
private static void LoadLocalAddress(ILGenerator il, int index)
{
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index"); if (index <= 255)
{
il.Emit(OpCodes.Ldloca_S, (byte)index);
}
else
{
il.Emit(OpCodes.Ldloca, (short)index);
}
}
/// <summary>
/// Throws a data exception, only used internally
/// </summary>
[Obsolete("Intended for internal use only")]
public static void ThrowDataException(Exception ex, int index, IDataReader reader, object value)
{
Exception toThrow;
try
{
string name = "(n/a)", formattedValue = "(n/a)";
if (reader != null && index >= 0 && index < reader.FieldCount)
{
name = reader.GetName(index);
try
{
if (value == null || value is DBNull)
{
formattedValue = "<null>";
}
else
{
formattedValue = Convert.ToString(value) + " - " + Type.GetTypeCode(value.GetType());
}
}
catch (Exception valEx)
{
formattedValue = valEx.Message;
}
}
toThrow = new DataException(string.Format("Error parsing column {0} ({1}={2})", index, name, formattedValue), ex);
}
catch
{ // throw the **original** exception, wrapped as DataException
toThrow = new DataException(ex.Message, ex);
}
throw toThrow;
}
private static void EmitInt32(ILGenerator il, int value)
{
switch (value)
{
case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
case 0: il.Emit(OpCodes.Ldc_I4_0); break;
case 1: il.Emit(OpCodes.Ldc_I4_1); break;
case 2: il.Emit(OpCodes.Ldc_I4_2); break;
case 3: il.Emit(OpCodes.Ldc_I4_3); break;
case 4: il.Emit(OpCodes.Ldc_I4_4); break;
case 5: il.Emit(OpCodes.Ldc_I4_5); break;
case 6: il.Emit(OpCodes.Ldc_I4_6); break;
case 7: il.Emit(OpCodes.Ldc_I4_7); break;
case 8: il.Emit(OpCodes.Ldc_I4_8); break;
default:
if (value >= -128 && value <= 127)
{
il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
}
else
{
il.Emit(OpCodes.Ldc_I4, value);
}
break;
}
} /// <summary>
/// Key used to indicate the type name associated with a DataTable
/// </summary>
private const string DataTableTypeNameKey = "dapper:TypeName"; /// <summary>
/// How should connection strings be compared for equivalence? Defaults to StringComparer.Ordinal.
/// Providing a custom implementation can be useful for allowing multi-tenancy databases with identical
/// schema to share strategies. Note that usual equivalence rules apply: any equivalent connection strings
/// <b>MUST</b> yield the same hash-code.
/// </summary>
public static IEqualityComparer<string> ConnectionStringComparer
{
get { return connectionStringComparer; }
set { connectionStringComparer = value ?? StringComparer.Ordinal; }
}
private static IEqualityComparer<string> connectionStringComparer = StringComparer.Ordinal; /// <summary>
/// The grid reader provides interfaces for reading multiple result sets from a Dapper query
/// </summary>
public partial class GridReader : IDisposable
{
private IDataReader reader;
private IDbCommand command;
private Identity identity; internal GridReader(IDbCommand command, IDataReader reader, Identity identity, SqlMapper.IParameterCallbacks callbacks)
{
this.command = command;
this.reader = reader;
this.identity = identity;
this.callbacks = callbacks;
} #if !CSHARP30 /// <summary>
/// Read the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object></remarks>
public IEnumerable<dynamic> Read(bool buffered = true)
{
return ReadImpl<dynamic>(typeof(DapperRow), buffered);
}
#endif #if CSHARP30
/// <summary>
/// Read the next grid of results
/// </summary>
public IEnumerable<T> Read<T>()
{
return Read<T>(true);
}
#endif
/// <summary>
/// Read the next grid of results
/// </summary>
#if CSHARP30
public IEnumerable<T> Read<T>(bool buffered)
#else
public IEnumerable<T> Read<T>(bool buffered = true)
#endif
{
return ReadImpl<T>(typeof(T), buffered);
} /// <summary>
/// Read the next grid of results
/// </summary>
#if CSHARP30
public IEnumerable<object> Read(Type type, bool buffered)
#else
public IEnumerable<object> Read(Type type, bool buffered = true)
#endif
{
if (type == null) throw new ArgumentNullException("type");
return ReadImpl<object>(type, buffered);
} private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, true);
var deserializer = cache.Deserializer; int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false));
cache.Deserializer = deserializer;
}
consumed = true;
var result = ReadDeferred<T>(gridIndex, deserializer.Func, typedIdentity);
return buffered ? result.ToList() : result;
} private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Delegate func, string splitOn)
{
var identity = this.identity.ForGrid(typeof(TReturn), new Type[] {
typeof(TFirst),
typeof(TSecond),
typeof(TThird),
typeof(TFourth),
typeof(TFifth),
typeof(TSixth),
typeof(TSeventh)
}, gridIndex);
try
{
foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default(CommandDefinition), func, splitOn, reader, identity, false))
{
yield return r;
}
}
finally
{
NextResult();
}
} #if CSHARP30
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn)
{
return Read<TFirst, TSecond, TReturn>(func, splitOn, true);
}
#endif
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
#if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn, bool buffered)
#else
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn = "id", bool buffered = true)
#endif
{
var result = MultiReadInternal<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
} #if CSHARP30
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn)
{
return Read<TFirst, TSecond, TThird, TReturn>(func, splitOn, true);
}
#endif
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
#if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn, bool buffered)
#else
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn = "id", bool buffered = true)
#endif
{
var result = MultiReadInternal<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
} #if CSHARP30
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn)
{
return Read<TFirst, TSecond, TThird, TFourth, TReturn>(func, splitOn, true);
}
#endif /// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
#if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn, bool buffered)
#else
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id", bool buffered = true)
#endif
{
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
} #if !CSHARP30
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> func, string splitOn = "id", bool buffered = true)
{
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
}
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> func, string splitOn = "id", bool buffered = true)
{
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
}
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> func, string splitOn = "id", bool buffered = true)
{
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
}
#endif private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity)
{
try
{
while (index == gridIndex && reader.Read())
{
yield return (T)deserializer(reader);
}
}
finally // finally so that First etc progresses things even when multiple rows
{
if (index == gridIndex)
{
NextResult();
}
}
}
private int gridIndex, readCount;
private bool consumed;
private SqlMapper.IParameterCallbacks callbacks; /// <summary>
/// Has the underlying reader been consumed?
/// </summary>
public bool IsConsumed
{
get
{
return consumed;
}
}
private void NextResult()
{
if (reader.NextResult())
{
readCount++;
gridIndex++;
consumed = false;
}
else
{
// happy path; close the reader cleanly - no
// need for "Cancel" etc
reader.Dispose();
reader = null;
if (callbacks != null) callbacks.OnCompleted();
Dispose();
}
}
/// <summary>
/// Dispose the grid, closing and disposing both the underlying reader and command.
/// </summary>
public void Dispose()
{
if (reader != null)
{
if (!reader.IsClosed && command != null) command.Cancel();
reader.Dispose();
reader = null;
}
if (command != null)
{
command.Dispose();
command = null;
}
}
} /// <summary>
/// Used to pass a DataTable as a TableValuedParameter
/// </summary>
public static ICustomQueryParameter AsTableValuedParameter(this DataTable table, string typeName
#if !CSHARP30
= null
#endif
)
{
return new TableValuedParameter(table, typeName);
} /// <summary>
/// Associate a DataTable with a type name
/// </summary>
public static void SetTypeName(this DataTable table, string typeName)
{
if (table != null)
{
if (string.IsNullOrEmpty(typeName))
table.ExtendedProperties.Remove(DataTableTypeNameKey);
else
table.ExtendedProperties[DataTableTypeNameKey] = typeName;
}
} /// <summary>
/// Fetch the type name associated with a DataTable
/// </summary>
public static string GetTypeName(this DataTable table)
{
return table == null ? null : table.ExtendedProperties[DataTableTypeNameKey] as string;
} // one per thread
[ThreadStatic]
private static StringBuilder perThreadStringBuilderCache;
private static StringBuilder GetStringBuilder()
{
var tmp = perThreadStringBuilderCache;
if (tmp != null)
{
perThreadStringBuilderCache = null;
tmp.Length = 0;
return tmp;
}
return new StringBuilder();
} private static string __ToStringRecycle(this StringBuilder obj)
{
if (obj == null) return "";
var s = obj.ToString();
if(perThreadStringBuilderCache == null)
{
perThreadStringBuilderCache = obj;
}
return s;
}
} /// <summary>
/// A bag of parameters that can be passed to the Dapper Query and Execute methods
/// </summary>
partial class DynamicParameters : SqlMapper.IDynamicParameters, SqlMapper.IParameterLookup, SqlMapper.IParameterCallbacks
{
internal const DbType EnumerableMultiParameter = (DbType)(-1);
static Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>(); Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>();
List<object> templates; object SqlMapper.IParameterLookup.this[string member]
{
get
{
ParamInfo param;
return parameters.TryGetValue(member, out param) ? param.Value : null;
}
} partial class ParamInfo
{
public string Name { get; set; }
public object Value { get; set; }
public ParameterDirection ParameterDirection { get; set; }
public DbType? DbType { get; set; }
public int? Size { get; set; }
public IDbDataParameter AttachedParam { get; set; }
internal Action<object, DynamicParameters> OutputCallback { get; set; }
internal object OutputTarget { get; set; }
internal bool CameFromTemplate { get; set; }
} /// <summary>
/// construct a dynamic parameter bag
/// </summary>
public DynamicParameters()
{
RemoveUnused = true;
} /// <summary>
/// construct a dynamic parameter bag
/// </summary>
/// <param name="template">can be an anonymous type or a DynamicParameters bag</param>
public DynamicParameters(object template)
{
RemoveUnused = true;
AddDynamicParams(template);
} /// <summary>
/// Append a whole object full of params to the dynamic
/// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic
/// </summary>
/// <param name="param"></param>
public void AddDynamicParams(object param)
{
var obj = param as object;
if (obj != null)
{
var subDynamic = obj as DynamicParameters;
if (subDynamic == null)
{
var dictionary = obj as IEnumerable<KeyValuePair<string, object>>;
if (dictionary == null)
{
templates = templates ?? new List<object>();
templates.Add(obj);
}
else
{
foreach (var kvp in dictionary)
{
Add(kvp.Key, kvp.Value, null, null, null);
}
}
}
else
{
if (subDynamic.parameters != null)
{
foreach (var kvp in subDynamic.parameters)
{
parameters.Add(kvp.Key, kvp.Value);
}
} if (subDynamic.templates != null)
{
templates = templates ?? new List<object>();
foreach (var t in subDynamic.templates)
{
templates.Add(t);
}
}
}
}
} /// <summary>
/// Add a parameter to this dynamic parameter list
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <param name="dbType"></param>
/// <param name="direction"></param>
/// <param name="size"></param>
public void Add(
#if CSHARP30
string name, object value, DbType? dbType, ParameterDirection? direction, int? size
#else
string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null
#endif
)
{
parameters[Clean(name)] = new ParamInfo() { Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType, Size = size };
} static string Clean(string name)
{
if (!string.IsNullOrEmpty(name))
{
switch (name[0])
{
case '@':
case ':':
case '?':
return name.Substring(1);
}
}
return name;
} void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
AddParameters(command, identity);
} /// <summary>
/// If true, the command-text is inspected and only values that are clearly used are included on the connection
/// </summary>
public bool RemoveUnused { get; set; } /// <summary>
/// Add all the parameters needed to the command just before it executes
/// </summary>
/// <param name="command">The raw command prior to execution</param>
/// <param name="identity">Information about the query</param>
protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
var literals = SqlMapper.GetLiteralTokens(identity.sql); if (templates != null)
{
foreach (var template in templates)
{
var newIdent = identity.ForDynamicParameters(template.GetType());
Action<IDbCommand, object> appender; lock (paramReaderCache)
{
if (!paramReaderCache.TryGetValue(newIdent, out appender))
{
appender = SqlMapper.CreateParamInfoGenerator(newIdent, true, RemoveUnused, literals);
paramReaderCache[newIdent] = appender;
}
} appender(command, template);
} // The parameters were added to the command, but not the
// DynamicParameters until now.
foreach (IDbDataParameter param in command.Parameters)
{
// If someone makes a DynamicParameters with a template,
// then explicitly adds a parameter of a matching name,
// it will already exist in 'parameters'.
if (!parameters.ContainsKey(param.ParameterName))
{
parameters.Add(param.ParameterName, new ParamInfo
{
AttachedParam = param,
CameFromTemplate = true,
DbType = param.DbType,
Name = param.ParameterName,
ParameterDirection = param.Direction,
Size = param.Size,
Value = param.Value
});
}
} // Now that the parameters are added to the command, let's place our output callbacks
var tmp = outputCallbacks;
if (tmp != null)
{
foreach (var generator in tmp)
{
generator();
}
}
} foreach (var param in parameters.Values)
{
if (param.CameFromTemplate) continue; var dbType = param.DbType;
var val = param.Value;
string name = Clean(param.Name);
var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter; SqlMapper.ITypeHandler handler = null;
if (dbType == null && val != null && !isCustomQueryParameter) dbType = SqlMapper.LookupDbType(val.GetType(), name, true, out handler);
if (dbType == DynamicParameters.EnumerableMultiParameter)
{
#pragma warning disable 612, 618
SqlMapper.PackListParameters(command, name, val);
#pragma warning restore 612, 618
}
else if (isCustomQueryParameter)
{
((SqlMapper.ICustomQueryParameter)val).AddParameter(command, name);
}
else
{ bool add = !command.Parameters.Contains(name);
IDbDataParameter p;
if (add)
{
p = command.CreateParameter();
p.ParameterName = name;
}
else
{
p = (IDbDataParameter)command.Parameters[name];
} p.Direction = param.ParameterDirection;
if (handler == null)
{
p.Value = val ?? DBNull.Value;
if (dbType != null && p.DbType != dbType)
{
p.DbType = dbType.Value;
}
var s = val as string;
if (s != null)
{
if (s.Length <= DbString.DefaultLength)
{
p.Size = DbString.DefaultLength;
}
}
if (param.Size != null)
{
p.Size = param.Size.Value;
}
}
else
{
if (dbType != null) p.DbType = dbType.Value;
if (param.Size != null) p.Size = param.Size.Value;
handler.SetValue(p, val ?? DBNull.Value);
} if (add)
{
command.Parameters.Add(p);
}
param.AttachedParam = p;
}
} // note: most non-priveleged implementations would use: this.ReplaceLiterals(command);
if(literals.Count != 0) SqlMapper.ReplaceLiterals(this, command, literals);
} /// <summary>
/// All the names of the param in the bag, use Get to yank them out
/// </summary>
public IEnumerable<string> ParameterNames
{
get
{
return parameters.Select(p => p.Key);
}
} /// <summary>
/// Get the value of a parameter
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns>The value, note DBNull.Value is not returned, instead the value is returned as null</returns>
public T Get<T>(string name)
{
var val = parameters[Clean(name)].AttachedParam.Value;
if (val == DBNull.Value)
{
if (default(T) != null)
{
throw new ApplicationException("Attempting to cast a DBNull to a non nullable type!");
}
return default(T);
}
return (T)val;
} /// <summary>
/// Allows you to automatically populate a target property/field from output parameters. It actually
/// creates an InputOutput parameter, so you can still pass data in.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="target">The object whose property/field you wish to populate.</param>
/// <param name="expression">A MemberExpression targeting a property/field of the target (or descendant thereof.)</param>
/// <param name="dbType"></param>
/// <param name="size">The size to set on the parameter. Defaults to 0, or DbString.DefaultLength in case of strings.</param>
/// <returns>The DynamicParameters instance</returns>
#if CSHARP30
public DynamicParameters Output<T>(T target, Expression<Func<T, object>> expression, DbType? dbType, int? size)
#else
public DynamicParameters Output<T>(T target, Expression<Func<T, object>> expression, DbType? dbType = null, int? size = null)
#endif
{
var failMessage = "Expression must be a property/field chain off of a(n) {0} instance";
failMessage = string.Format(failMessage, typeof(T).Name);
Action @throw = () => { throw new InvalidOperationException(failMessage); }; // Is it even a MemberExpression?
var lastMemberAccess = expression.Body as MemberExpression; if (lastMemberAccess == null ||
(lastMemberAccess.Member.MemberType != MemberTypes.Property &&
lastMemberAccess.Member.MemberType != MemberTypes.Field))
{
if (expression.Body.NodeType == ExpressionType.Convert &&
expression.Body.Type == typeof(object) &&
((UnaryExpression)expression.Body).Operand is MemberExpression)
{
// It's got to be unboxed
lastMemberAccess = (MemberExpression)((UnaryExpression)expression.Body).Operand;
}
else @throw();
} // Does the chain consist of MemberExpressions leading to a ParameterExpression of type T?
MemberExpression diving = lastMemberAccess;
ParameterExpression constant = null;
// Retain a list of member names and the member expressions so we can rebuild the chain.
List<string> names = new List<string>();
List<MemberExpression> chain = new List<MemberExpression>(); do
{
// Insert the names in the right order so expression
// "Post.Author.Name" becomes parameter "PostAuthorName"
names.Insert(0, diving.Member.Name);
chain.Insert(0, diving); constant = diving.Expression as ParameterExpression;
diving = diving.Expression as MemberExpression; if (constant != null &&
constant.Type == typeof(T))
{
break;
}
else if (diving == null ||
(diving.Member.MemberType != MemberTypes.Property &&
diving.Member.MemberType != MemberTypes.Field))
{
@throw();
}
}
while (diving != null); var dynamicParamName = string.Join(string.Empty, names.ToArray()); // Before we get all emitty...
var lookup = string.Join("|", names.ToArray()); var cache = CachedOutputSetters<T>.Cache;
var setter = (Action<object, DynamicParameters>)cache[lookup]; if (setter != null) goto MAKECALLBACK; // Come on let's build a method, let's build it, let's build it now!
var dm = new DynamicMethod(string.Format("ExpressionParam{0}", Guid.NewGuid()), null, new[] { typeof(object), this.GetType() }, true);
var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // [object]
il.Emit(OpCodes.Castclass, typeof(T)); // [T] // Count - 1 to skip the last member access
var i = 0;
for (; i < (chain.Count - 1); i++)
{
var member = chain[0].Member; if (member.MemberType == MemberTypes.Property)
{
var get = ((PropertyInfo)member).GetGetMethod(true);
il.Emit(OpCodes.Callvirt, get); // [Member{i}]
}
else // Else it must be a field!
{
il.Emit(OpCodes.Ldfld, ((FieldInfo)member)); // [Member{i}]
}
} var paramGetter = this.GetType().GetMethod("Get", new Type[] { typeof(string) }).MakeGenericMethod(lastMemberAccess.Type); il.Emit(OpCodes.Ldarg_1); // [target] [DynamicParameters]
il.Emit(OpCodes.Ldstr, dynamicParamName); // [target] [DynamicParameters] [ParamName]
il.Emit(OpCodes.Callvirt, paramGetter); // [target] [value], it's already typed thanks to generic method // GET READY
var lastMember = lastMemberAccess.Member;
if (lastMember.MemberType == MemberTypes.Property)
{
var set = ((PropertyInfo)lastMember).GetSetMethod(true);
il.Emit(OpCodes.Callvirt, set); // SET
}
else
{
il.Emit(OpCodes.Stfld, ((FieldInfo)lastMember)); // SET
} il.Emit(OpCodes.Ret); // GO setter = (Action<object, DynamicParameters>)dm.CreateDelegate(typeof(Action<object, DynamicParameters>));
lock (cache)
{
cache[lookup] = setter;
} // Queue the preparation to be fired off when adding parameters to the DbCommand
MAKECALLBACK:
(outputCallbacks ?? (outputCallbacks = new List<Action>())).Add(() =>
{
// Finally, prep the parameter and attach the callback to it
ParamInfo parameter;
var targetMemberType = lastMemberAccess.Type;
int sizeToSet = (!size.HasValue && targetMemberType == typeof(string)) ? DbString.DefaultLength : size ?? 0; if (this.parameters.TryGetValue(dynamicParamName, out parameter))
{
parameter.ParameterDirection = parameter.AttachedParam.Direction = ParameterDirection.InputOutput; if (parameter.AttachedParam.Size == 0)
{
parameter.Size = parameter.AttachedParam.Size = sizeToSet;
}
}
else
{
SqlMapper.ITypeHandler handler;
dbType = (!dbType.HasValue) ? SqlMapper.LookupDbType(targetMemberType, targetMemberType.Name, true, out handler) : dbType; // CameFromTemplate property would not apply here because this new param
// Still needs to be added to the command
this.Add(dynamicParamName, expression.Compile().Invoke(target), null, ParameterDirection.InputOutput, sizeToSet);
} parameter = this.parameters[dynamicParamName];
parameter.OutputCallback = setter;
parameter.OutputTarget = target;
}); return this;
} private List<Action> outputCallbacks; private readonly Dictionary<string, Action<object, DynamicParameters>> cachedOutputSetters = new Dictionary<string,Action<object,DynamicParameters>>(); internal static class CachedOutputSetters<T>
{
public static readonly Hashtable Cache = new Hashtable();
} void SqlMapper.IParameterCallbacks.OnCompleted()
{
foreach (var param in (from p in parameters select p.Value))
{
if (param.OutputCallback != null) param.OutputCallback(param.OutputTarget, this);
}
}
} sealed class DataTableHandler : Dapper.SqlMapper.ITypeHandler
{
public object Parse(Type destinationType, object value)
{
throw new NotImplementedException();
} public void SetValue(IDbDataParameter parameter, object value)
{
TableValuedParameter.Set(parameter, value as DataTable, null);
}
} /// <summary>
/// Used to pass a DataTable as a TableValuedParameter
/// </summary>
sealed partial class TableValuedParameter : Dapper.SqlMapper.ICustomQueryParameter
{
private readonly DataTable table;
private readonly string typeName; /// <summary>
/// Create a new instance of TableValuedParameter
/// </summary>
public TableValuedParameter(DataTable table) : this(table, null) { }
/// <summary>
/// Create a new instance of TableValuedParameter
/// </summary>
public TableValuedParameter(DataTable table, string typeName)
{
this.table = table;
this.typeName = typeName;
}
static readonly Action<System.Data.SqlClient.SqlParameter, string> setTypeName;
static TableValuedParameter()
{
var prop = typeof(System.Data.SqlClient.SqlParameter).GetProperty("TypeName", BindingFlags.Instance | BindingFlags.Public);
if(prop != null && prop.PropertyType == typeof(string) && prop.CanWrite)
{
setTypeName = (Action<System.Data.SqlClient.SqlParameter, string>)
Delegate.CreateDelegate(typeof(Action<System.Data.SqlClient.SqlParameter, string>), prop.GetSetMethod());
}
}
void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name)
{
var param = command.CreateParameter();
param.ParameterName = name;
Set(param, table, typeName);
command.Parameters.Add(param);
}
internal static void Set(IDbDataParameter parameter, DataTable table, string typeName)
{
parameter.Value = (object)table ?? DBNull.Value;
if (string.IsNullOrEmpty(typeName) && table != null)
{
typeName = SqlMapper.GetTypeName(table);
}
if (!string.IsNullOrEmpty(typeName))
{
var sqlParam = parameter as System.Data.SqlClient.SqlParameter;
if (sqlParam != null)
{
if (setTypeName != null) setTypeName(sqlParam, typeName);
sqlParam.SqlDbType = SqlDbType.Structured;
}
}
}
}
/// <summary>
/// This class represents a SQL string, it can be used if you need to denote your parameter is a Char vs VarChar vs nVarChar vs nChar
/// </summary>
sealed partial class DbString : Dapper.SqlMapper.ICustomQueryParameter
{
/// <summary>
/// A value to set the default value of strings
/// going through Dapper. Default is 4000, any value larger than this
/// field will not have the default value applied.
/// </summary>
public const int DefaultLength = 4000; /// <summary>
/// Create a new DbString
/// </summary>
public DbString() { Length = -1; }
/// <summary>
/// Ansi vs Unicode
/// </summary>
public bool IsAnsi { get; set; }
/// <summary>
/// Fixed length
/// </summary>
public bool IsFixedLength { get; set; }
/// <summary>
/// Length of the string -1 for max
/// </summary>
public int Length { get; set; }
/// <summary>
/// The value of the string
/// </summary>
public string Value { get; set; }
/// <summary>
/// Add the parameter to the command... internal use only
/// </summary>
/// <param name="command"></param>
/// <param name="name"></param>
public void AddParameter(IDbCommand command, string name)
{
if (IsFixedLength && Length == -1)
{
throw new InvalidOperationException("If specifying IsFixedLength, a Length must also be specified");
}
var param = command.CreateParameter();
param.ParameterName = name;
param.Value = (object)Value ?? DBNull.Value;
if (Length == -1 && Value != null && Value.Length <= DefaultLength)
{
param.Size = DefaultLength;
}
else
{
param.Size = Length;
}
param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String);
command.Parameters.Add(param);
}
} /// <summary>
/// Handles variances in features per DBMS
/// </summary>
partial class FeatureSupport
{
private static readonly FeatureSupport
@default = new FeatureSupport(false),
postgres = new FeatureSupport(true); /// <summary>
/// Gets the feature set based on the passed connection
/// </summary>
public static FeatureSupport Get(IDbConnection connection)
{
string name = connection == null ? null : connection.GetType().Name;
if (string.Equals(name, "npgsqlconnection", StringComparison.InvariantCultureIgnoreCase)) return postgres;
return @default;
}
private FeatureSupport(bool arrays)
{
Arrays = arrays;
}
/// <summary>
/// True if the db supports array columns e.g. Postgresql
/// </summary>
public bool Arrays { get; private set; }
} /// <summary>
/// Represents simple member map for one of target parameter or property or field to source DataReader column
/// </summary>
sealed partial class SimpleMemberMap : SqlMapper.IMemberMap
{
private readonly string _columnName;
private readonly PropertyInfo _property;
private readonly FieldInfo _field;
private readonly ParameterInfo _parameter; /// <summary>
/// Creates instance for simple property mapping
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <param name="property">Target property</param>
public SimpleMemberMap(string columnName, PropertyInfo property)
{
if (columnName == null)
throw new ArgumentNullException("columnName"); if (property == null)
throw new ArgumentNullException("property"); _columnName = columnName;
_property = property;
} /// <summary>
/// Creates instance for simple field mapping
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <param name="field">Target property</param>
public SimpleMemberMap(string columnName, FieldInfo field)
{
if (columnName == null)
throw new ArgumentNullException("columnName"); if (field == null)
throw new ArgumentNullException("field"); _columnName = columnName;
_field = field;
} /// <summary>
/// Creates instance for simple constructor parameter mapping
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <param name="parameter">Target constructor parameter</param>
public SimpleMemberMap(string columnName, ParameterInfo parameter)
{
if (columnName == null)
throw new ArgumentNullException("columnName"); if (parameter == null)
throw new ArgumentNullException("parameter"); _columnName = columnName;
_parameter = parameter;
} /// <summary>
/// DataReader column name
/// </summary>
public string ColumnName
{
get { return _columnName; }
} /// <summary>
/// Target member type
/// </summary>
public Type MemberType
{
get
{
if (_field != null)
return _field.FieldType; if (_property != null)
return _property.PropertyType; if (_parameter != null)
return _parameter.ParameterType; return null;
}
} /// <summary>
/// Target property
/// </summary>
public PropertyInfo Property
{
get { return _property; }
} /// <summary>
/// Target field
/// </summary>
public FieldInfo Field
{
get { return _field; }
} /// <summary>
/// Target constructor parameter
/// </summary>
public ParameterInfo Parameter
{
get { return _parameter; }
}
} /// <summary>
/// Represents default type mapping strategy used by Dapper
/// </summary>
sealed partial class DefaultTypeMap : SqlMapper.ITypeMap
{
private readonly List<FieldInfo> _fields;
private readonly List<PropertyInfo> _properties;
private readonly Type _type; /// <summary>
/// Creates default type map
/// </summary>
/// <param name="type">Entity type</param>
public DefaultTypeMap(Type type)
{
if (type == null)
throw new ArgumentNullException("type"); _fields = GetSettableFields(type);
_properties = GetSettableProps(type);
_type = type;
} internal static MethodInfo GetPropertySetter(PropertyInfo propertyInfo, Type type)
{
return propertyInfo.DeclaringType == type ?
propertyInfo.GetSetMethod(true) :
propertyInfo.DeclaringType.GetProperty(
propertyInfo.Name,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
Type.DefaultBinder,
propertyInfo.PropertyType,
propertyInfo.GetIndexParameters().Select(p => p.ParameterType).ToArray(),
null).GetSetMethod(true);
} internal static List<PropertyInfo> GetSettableProps(Type t)
{
return t
.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(p => GetPropertySetter(p, t) != null)
.ToList();
} internal static List<FieldInfo> GetSettableFields(Type t)
{
return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList();
} /// <summary>
/// Finds best constructor
/// </summary>
/// <param name="names">DataReader column names</param>
/// <param name="types">DataReader column types</param>
/// <returns>Matching constructor or default one</returns>
public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (ConstructorInfo ctor in constructors.OrderBy(c => c.IsPublic ? 0 : (c.IsPrivate ? 2 : 1)).ThenBy(c => c.GetParameters().Length))
{
ParameterInfo[] ctorParameters = ctor.GetParameters();
if (ctorParameters.Length == 0)
return ctor; if (ctorParameters.Length != types.Length)
continue; int i = 0;
for (; i < ctorParameters.Length; i++)
{
if (!String.Equals(ctorParameters[i].Name, names[i], StringComparison.OrdinalIgnoreCase))
break;
if (types[i] == typeof(byte[]) && ctorParameters[i].ParameterType.FullName == SqlMapper.LinqBinary)
continue;
var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType;
if (unboxedType != types[i]
&& !(unboxedType.IsEnum && Enum.GetUnderlyingType(unboxedType) == types[i])
&& !(unboxedType == typeof(char) && types[i] == typeof(string)))
break;
} if (i == ctorParameters.Length)
return ctor;
} return null;
} /// <summary>
/// Returns the constructor, if any, that has the ExplicitConstructorAttribute on it.
/// </summary>
public ConstructorInfo FindExplicitConstructor()
{
var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var withAttr = constructors.Where(c => c.GetCustomAttributes(typeof(ExplicitConstructorAttribute), true).Length > 0).ToList(); if (withAttr.Count == 1)
{
return withAttr[0];
} return null;
} /// <summary>
/// Gets mapping for constructor parameter
/// </summary>
/// <param name="constructor">Constructor to resolve</param>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
var parameters = constructor.GetParameters(); return new SimpleMemberMap(columnName, parameters.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)));
} /// <summary>
/// Gets member mapping for column
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
public SqlMapper.IMemberMap GetMember(string columnName)
{
var property = _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)); if (property == null && MatchNamesWithUnderscores)
{
property = _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)
return new SimpleMemberMap(columnName, property); var field = _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)); if (field == null && MatchNamesWithUnderscores)
{
field = _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)
return new SimpleMemberMap(columnName, field); return null;
}
/// <summary>
/// Should column names like User_Id be allowed to match properties/fields like UserId ?
/// </summary>
public static bool MatchNamesWithUnderscores { get; set; }
} /// <summary>
/// Implements custom property mapping by user provided criteria (usually presence of some custom attribute with column to member mapping)
/// </summary>
sealed partial class CustomPropertyTypeMap : SqlMapper.ITypeMap
{
private readonly Type _type;
private readonly Func<Type, string, PropertyInfo> _propertySelector; /// <summary>
/// Creates custom property mapping
/// </summary>
/// <param name="type">Target entity type</param>
/// <param name="propertySelector">Property selector based on target type and DataReader column name</param>
public CustomPropertyTypeMap(Type type, Func<Type, string, PropertyInfo> propertySelector)
{
if (type == null)
throw new ArgumentNullException("type"); if (propertySelector == null)
throw new ArgumentNullException("propertySelector"); _type = type;
_propertySelector = propertySelector;
} /// <summary>
/// Always returns default constructor
/// </summary>
/// <param name="names">DataReader column names</param>
/// <param name="types">DataReader column types</param>
/// <returns>Default constructor</returns>
public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
return _type.GetConstructor(new Type[0]);
} /// <summary>
/// Always returns null
/// </summary>
/// <returns></returns>
public ConstructorInfo FindExplicitConstructor()
{
return null;
} /// <summary>
/// Not implemented as far as default constructor used for all cases
/// </summary>
/// <param name="constructor"></param>
/// <param name="columnName"></param>
/// <returns></returns>
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
throw new NotSupportedException();
} /// <summary>
/// Returns property based on selector strategy
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <returns>Poperty member map</returns>
public SqlMapper.IMemberMap GetMember(string columnName)
{
var prop = _propertySelector(_type, columnName);
return prop != null ? new SimpleMemberMap(columnName, prop) : null;
}
} internal class WrappedReader : IDataReader, IWrappedDataReader
{
private IDataReader reader;
private IDbCommand cmd; public IDataReader Reader
{
get
{
var tmp = reader;
if (tmp == null) throw new ObjectDisposedException(GetType().Name);
return tmp;
}
}
IDbCommand IWrappedDataReader.Command
{
get
{
var tmp = cmd;
if (tmp == null) throw new ObjectDisposedException(GetType().Name);
return tmp;
}
}
public WrappedReader(IDbCommand cmd, IDataReader reader)
{
this.cmd = cmd;
this.reader = reader;
} void IDataReader.Close()
{
if(reader != null) reader.Close();
} int IDataReader.Depth
{
get { return Reader.Depth; }
} DataTable IDataReader.GetSchemaTable()
{
return Reader.GetSchemaTable();
} bool IDataReader.IsClosed
{
get { return reader == null ? true : reader.IsClosed; }
} bool IDataReader.NextResult()
{
return Reader.NextResult();
} bool IDataReader.Read()
{
return Reader.Read();
} int IDataReader.RecordsAffected
{
get { return Reader.RecordsAffected; }
} void IDisposable.Dispose()
{
if (reader != null) reader.Close();
if (reader != null) reader.Dispose();
reader = null;
if (cmd != null) cmd.Dispose();
cmd = null;
} int IDataRecord.FieldCount
{
get { return Reader.FieldCount; }
} bool IDataRecord.GetBoolean(int i)
{
return Reader.GetBoolean(i);
} byte IDataRecord.GetByte(int i)
{
return Reader.GetByte(i);
} long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
{
return Reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
} char IDataRecord.GetChar(int i)
{
return Reader.GetChar(i);
} long IDataRecord.GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
{
return Reader.GetChars(i, fieldoffset, buffer, bufferoffset, length);
} IDataReader IDataRecord.GetData(int i)
{
return Reader.GetData(i);
} string IDataRecord.GetDataTypeName(int i)
{
return Reader.GetDataTypeName(i);
} DateTime IDataRecord.GetDateTime(int i)
{
return Reader.GetDateTime(i);
} decimal IDataRecord.GetDecimal(int i)
{
return Reader.GetDecimal(i);
} double IDataRecord.GetDouble(int i)
{
return Reader.GetDouble(i);
} Type IDataRecord.GetFieldType(int i)
{
return Reader.GetFieldType(i);
} float IDataRecord.GetFloat(int i)
{
return Reader.GetFloat(i);
} Guid IDataRecord.GetGuid(int i)
{
return Reader.GetGuid(i);
} short IDataRecord.GetInt16(int i)
{
return Reader.GetInt16(i);
} int IDataRecord.GetInt32(int i)
{
return Reader.GetInt32(i);
} long IDataRecord.GetInt64(int i)
{
return Reader.GetInt64(i);
} string IDataRecord.GetName(int i)
{
return Reader.GetName(i);
} int IDataRecord.GetOrdinal(string name)
{
return Reader.GetOrdinal(name);
} string IDataRecord.GetString(int i)
{
return Reader.GetString(i);
} object IDataRecord.GetValue(int i)
{
return Reader.GetValue(i);
} int IDataRecord.GetValues(object[] values)
{
return Reader.GetValues(values);
} bool IDataRecord.IsDBNull(int i)
{
return Reader.IsDBNull(i);
} object IDataRecord.this[string name]
{
get { return Reader[name]; }
} object IDataRecord.this[int i]
{
get { return Reader[i]; }
}
} /// <summary>
/// Describes a reader that controls the lifetime of both a command and a reader,
/// exposing the downstream command/reader as properties.
/// </summary>
public interface IWrappedDataReader : IDataReader
{
/// <summary>
/// Obtain the underlying reader
/// </summary>
IDataReader Reader { get; }
/// <summary>
/// Obtain the underlying command
/// </summary>
IDbCommand Command { get; }
} /// <summary>
/// Tell Dapper to use an explicit constructor, passing nulls or 0s for all parameters
/// </summary>
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false)]
public sealed class ExplicitConstructorAttribute : Attribute
{ } // Define DAPPER_MAKE_PRIVATE if you reference Dapper by source
// and you like to make the Dapper types private (in order to avoid
// conflicts with other projects that also reference Dapper by source)
#if !DAPPER_MAKE_PRIVATE public partial class SqlMapper
{
} public partial class DynamicParameters
{ } public partial class DbString
{ } public partial class SimpleMemberMap
{ } public partial class DefaultTypeMap
{ } public partial class CustomPropertyTypeMap
{ } public partial class FeatureSupport
{ } #endif }

  

dapper-dot-net/Dapper NET40/SqlMapper.cs的更多相关文章

  1. ECommon.Dapper 轻量级的dapper扩展

    我们都知道Dapper这个orm框架,但是我们也知道他的扩展目前没有特别好的,今天我就推荐一个轻量级的很方便使用的一个扩展叫做 ECommon.Dapper,它是ECommon的一个类库,关于ECom ...

  2. Dapper扩展之~~~Dapper.Contrib

    平台之大势何人能挡? 带着你的Net飞奔吧!http://www.cnblogs.com/dunitian/p/4822808.html#skill 上一篇文章:Dapper逆天入门~强类型,动态类型 ...

  3. Dapper.Common基于Dapper的开源LINQ超轻量扩展

    Dapper.Common Dapper.Common是基于Dapper的LINQ实现,支持.net core,遵循Linq语法规则.链式调用.配置简单.上手快,支持Mysql,Sqlserver(目 ...

  4. Dapper学习笔记(2)-链接引用

    在研究Dapper源码时发现Dapper NET45类库中的SqlMapper.cs文件前面有个蓝色的箭头图标,发现在Dapper NET45文件夹下根本不存在SqlMapper.cs文件,其文件属性 ...

  5. Dapper.NET——轻量ORM

    Dapper.NET使用 http://www.cnblogs.com/yankliu-vip/p/4182892.html 本文目录 Dapper.NET使用 1.为什么选择Dapper 2.以Da ...

  6. 轻量级ORM框架——第一篇:Dapper快速学习

    我们都知道ORM全称叫做Object Relationship Mapper,也就是可以用object来map我们的db,而且市面上的orm框架有很多,其中有一个框架 叫做dapper,而且被称为th ...

  7. Dapper学习笔记(一)

    https://github.com/StackExchange/dapper-dot-net Dapper是对IDbConnection的扩展,需要使用Dapper提供的扩展只需要把SqlMappe ...

  8. Dapper

    前一段做一个技术分享关于dapper的,现在再总结一下,也好长时间没有更新博客了--,用到的东西 Dapper.AutoFac .AutoMapper.FluentValidation: 下面说一下D ...

  9. Dapper with MVC MiniProfiler

    Dapper是一个轻型的ORM类.代码就一个SqlMapper.cs文件,主要是IDbConnection的扩展方法,编译后就118K的一个很小的dll.官方站点http://code.google. ...

随机推荐

  1. kettle添加hadoop cluster时报错Caused by: java.lang.IllegalArgumentException: Does not contain a valid host:port authority: hadoop:password@node56:9000

    完整报错是: Caused by: java.lang.IllegalArgumentException: Does not contain a valid host:port authority: ...

  2. SDUT3145:Integer division 1(换零钱背包)

    题目:传送门 题目描述 整数划分是一个非常经典的数学问题. 所谓整数划分,是指把一个正整数n写成为n=m1+m2+...+mi的形式,其中mi为正整数,并且1<=mi<=n,此时,{m1, ...

  3. 面试题5:JS实现从尾到头打印单链表

    单链表,在内存中所占地址是不连续的.所以遍历单链表时:需要从头遍历.而题目要求输出的顺序:从尾到头.也就是说第一个遍历到的节点最后一个输出,而最后一个遍历到的节点第一个输出.这就是典型的“后进先出”, ...

  4. iis7.5安装配置php环境详细清晰教程,三步实现【图文】

    iis7.5安装配置php环境详细清晰教程,三步实现[图文] iis7.5是安装在win7.win8里的web服务器,win2003.win2000的web服务器使用的是iis6.0,由于win7.w ...

  5. The Cheap KD 8 is rumored to arrive online

    Nike and also the Oklahoma City Thunder star revealed the Cheap KD 8, which they are calling probabl ...

  6. The Jordan 3lab5 is the perfect sneaker for you

    The Jordan 5 3Lab5 Metallic Silver returns inside a mind-turning new iteration for that Spring/Summe ...

  7. k近邻 KNN

    KNN是通过测量对象的不同特征值之间的距离进行分类.它的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别,其中K通常是不大于20 ...

  8. EntityFramework 6 开篇

    本系列文章主要来讲解理解以及怎样使用EntityFramework,写这个系列主要是因为部门里面准备来使用EF,为了让大家一起来学习,我每天发布1-2篇文章让大家一块参与学习.之前一直写在有道云笔记里 ...

  9. HDFS datanode心跳与运维中的实际案例

    分布式系统的节点之间常采用心跳来维护节点的健康状态,如yarn的rm与nm之间,hdfs的nn与dn之间.DataNode会定期(dfs.heartbeat.interval配置项配置,默认是3秒)向 ...

  10. Java并发编程之CountDownLatch的用法

    一.含义 CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能.CountDownLatch是一个同步的辅助类,它可以允许一个或多个线程等待, ...