Skip to content

Instantly share code, notes, and snippets.

@brendanmckenzie
Created February 15, 2013 16:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save brendanmckenzie/4961483 to your computer and use it in GitHub Desktop.
Save brendanmckenzie/4961483 to your computer and use it in GitHub Desktop.
/*
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.Data.SqlClient;
namespace Dapper
{
/// <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>
/// Implement this interface to change default mapping of reader columns to type memebers
/// </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>
/// 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; }
}
static Link<Type, Action<IDbCommand, bool>> bindByNameCache;
static Action<IDbCommand, bool> GetBindByName(Type commandType)
{
if (commandType == null) return null; // GIGO
Action<IDbCommand, bool> action;
if (Link<Type, Action<IDbCommand, bool>>.TryGet(bindByNameCache, commandType, out action))
{
return action;
}
var prop = commandType.GetProperty("BindByName", BindingFlags.Public | BindingFlags.Instance);
action = null;
ParameterInfo[] indexers;
MethodInfo setter;
if (prop != null && prop.CanWrite && prop.PropertyType == typeof(bool)
&& ((indexers = prop.GetIndexParameters()) == null || indexers.Length == 0)
&& (setter = prop.GetSetMethod()) != null
)
{
var method = new DynamicMethod(commandType.Name + "_BindByName", null, new Type[] { typeof(IDbCommand), typeof(bool) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldarg_1);
il.EmitCall(OpCodes.Callvirt, setter, null);
il.Emit(OpCodes.Ret);
action = (Action<IDbCommand, bool>)method.CreateDelegate(typeof(Action<IDbCommand, bool>));
}
// cache it
Link<Type, Action<IDbCommand, bool>>.TryAdd(ref bindByNameCache, commandType, ref action);
return action;
}
/// <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>
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 readonly 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;
}
internal const string LinqBinary = "System.Data.Linq.Binary";
internal static DbType LookupDbType(Type type, string name)
{
DbType dbType;
var nullUnderlyingType = Nullable.GetUnderlyingType(type);
if (nullUnderlyingType != null) type = nullUnderlyingType;
if (type.IsEnum)
{
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 (type.FullName == "Microsoft.SqlServer.Types.SqlGeography")
{
return DynamicParameters.SqlGeographyParameter;
}
throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type));
}
/// <summary>
/// Identity of a cached query in Dapper, used for extensability
/// </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 : connectionString.GetHashCode());
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 &&
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>
/// 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, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
IEnumerable multiExec = (object)param as IEnumerable;
Identity identity;
CacheInfo info = null;
if (multiExec != null && !(multiExec is string))
{
bool isFirst = true;
int total = 0;
using (var cmd = SetupCommand(cnn, transaction, sql, null, null, commandTimeout, commandType))
{
string masterSql = null;
foreach (var obj in multiExec)
{
if (isFirst)
{
masterSql = cmd.CommandText;
isFirst = false;
identity = new Identity(sql, cmd.CommandType, cnn, null, obj.GetType(), null);
info = GetCacheInfo(identity);
}
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();
}
}
return total;
}
// nice and simple
if ((object)param != null)
{
identity = new Identity(sql, commandType, cnn, null, (object)param == null ? null : ((object)param).GetType(), null);
info = GetCacheInfo(identity);
}
return ExecuteCommand(cnn, transaction, sql, (object)param == null ? null : info.ParamReader, (object)param, commandTimeout, commandType);
}
#if !CSHARP30
/// <summary>
/// Return a list of dynamic objects, reader is closed after the call
/// </summary>
public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dynamic 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, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
var data = QueryInternal<T>(cnn, sql, param as object, transaction, commandTimeout, commandType);
return 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, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
Identity identity = new Identity(sql, commandType, cnn, typeof(GridReader), (object)param == null ? null : ((object)param).GetType(), null);
CacheInfo info = GetCacheInfo(identity);
IDbCommand cmd = null;
IDataReader reader = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) cnn.Open();
cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType);
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
var result = new GridReader(cmd, reader, identity);
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;
}
}
/// <summary>
/// Return a typed list of objects, reader is closed after the call
/// </summary>
private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType)
{
var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity);
IDbCommand cmd = null;
IDataReader reader = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType);
if (wasClosed) cnn.Open();
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
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)
{
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false));
SetQueryCache(identity, info);
}
var func = tuple.Func;
while (reader.Read())
{
yield return (T)func(reader);
}
// happy path; close the reader cleanly - no
// need for "Cancel" etc
reader.Dispose();
reader = null;
}
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 recordset</typeparam>
/// <typeparam name="TSecond">The second type in the recordset</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, dynamic 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, 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, dynamic 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, 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, dynamic 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, 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, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
}
#endif
partial class DontMap { }
static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(
this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType)
{
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(cnn, sql, map, param, transaction, splitOn, commandTimeout, commandType, null, null);
return buffered ? results.ToList() : results;
}
static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, string splitOn, int? commandTimeout, CommandType? commandType, IDataReader reader, Identity identity)
{
identity = identity ?? new Identity(sql, commandType, cnn, typeof(TFirst), (object)param == null ? null : ((object)param).GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth) });
CacheInfo cinfo = GetCacheInfo(identity);
IDbCommand ownedCommand = null;
IDataReader ownedReader = null;
try
{
if (reader == null)
{
ownedCommand = SetupCommand(cnn, transaction, sql, cinfo.ParamReader, (object)param, commandTimeout, commandType);
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(new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth) }, 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<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(deserializer.Func, otherDeserializers, map);
if (mapIt != null)
{
while (reader.Read())
{
yield return mapIt(reader);
}
}
}
finally
{
try
{
if (ownedReader != null)
{
ownedReader.Dispose();
}
}
finally
{
if (ownedCommand != null)
{
ownedCommand.Dispose();
}
}
}
}
private static Func<IDataReader, TReturn> GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, 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));
#endif
default:
throw new NotSupportedException();
}
}
private static Func<IDataReader, object>[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader)
{
int current = 0;
var splits = splitOn.Split(',').ToArray();
var splitIndex = 0;
Func<Type, int> nextSplit = type =>
{
var currentSplit = splits[splitIndex].Trim();
if (splits.Length > splitIndex + 1)
{
splitIndex++;
}
bool skipFirst = false;
int startingPos = current + 1;
// if our current type has the split, skip the first time you see it.
if (type != typeof(Object))
{
var props = DefaultTypeMap.GetSettableProps(type);
var fields = DefaultTypeMap.GetSettableFields(type);
foreach (var name in props.Select(p => p.Name).Concat(fields.Select(f => f.Name)))
{
if (string.Equals(name, currentSplit, StringComparison.OrdinalIgnoreCase))
{
skipFirst = true;
startingPos = current;
break;
}
}
}
int pos;
for (pos = startingPos; pos < reader.FieldCount; pos++)
{
// some people like ID some id ... assuming case insensitive splits for now
if (splitOn == "*")
{
break;
}
if (string.Equals(reader.GetName(pos), currentSplit, StringComparison.OrdinalIgnoreCase))
{
if (skipFirst)
{
skipFirst = false;
}
else
{
break;
}
}
}
current = pos;
return pos;
};
var deserializers = new List<Func<IDataReader, object>>();
int split = 0;
bool first = true;
foreach (var type in types)
{
if (type != typeof(DontMap))
{
int next = nextSplit(type);
deserializers.Add(GetDeserializer(type, reader, split, next - split, /* returnNullIfFirstMissing: */ !first));
first = false;
split = next;
}
}
return deserializers.ToArray();
}
private static CacheInfo GetCacheInfo(Identity identity)
{
CacheInfo info;
if (!TryGetQueryCache(identity, out info))
{
info = new CacheInfo();
if (identity.parametersType != null)
{
if (typeof(IDynamicParameters).IsAssignableFrom(identity.parametersType))
{
info.ParamReader = (cmd, obj) => { (obj as IDynamicParameters).AddParameters(cmd, identity); };
}
#if !CSHARP30
else if (typeof(IEnumerable<KeyValuePair<string, object>>).IsAssignableFrom(identity.parametersType) && typeof(System.Dynamic.IDynamicMetaObjectProvider).IsAssignableFrom(identity.parametersType))
{
info.ParamReader = (cmd, obj) =>
{
IDynamicParameters mapped = new DynamicParameters(obj);
mapped.AddParameters(cmd, identity);
};
}
#endif
else
{
info.ParamReader = CreateParamInfoGenerator(identity, false);
}
}
SetQueryCache(identity, info);
}
return info;
}
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)))
{
return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing);
}
return GetStructDeserializer(type, underlyingType ?? type, startBound);
}
#if !CSHARP30
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
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");
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;
}
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;
}
}
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 = new StringBuilder("{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('}').ToString();
}
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)
{
IDictionary<string, object> dic = this;
dic[key] = value;
}
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); }
}
public object SetValue(string key, object value)
{
if (key == null) throw new ArgumentNullException("key");
int index = table.IndexOfName(key);
if (index < 0)
{
index = table.AddField(key);
}
if (values.Length <= 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);
}
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
#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 new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn");
}
var effectiveFieldCount = fieldCount - startBound;
DapperTable table = null;
return
r =>
{
if (table == null)
{
string[] names = new string[effectiveFieldCount];
for (int i = 0; i < effectiveFieldCount; i++)
{
names[i] = reader.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);
}
else
{
var begin = returnNullIfFirstMissing ? 1 : 0;
for (var iter = begin; iter < effectiveFieldCount; ++iter)
{
values[iter] = r.GetValue(iter + startBound);
}
}
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 new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn");
}
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 PackSqlGeographyParameter(IDbCommand command, string namePrefix, object value)
{
var parameter = (SqlParameter)command.CreateParameter();
parameter.ParameterName = namePrefix;
parameter.UdtTypeName = "geography";
parameter.Value = value ?? DBNull.Value;
command.Parameters.Add(parameter);
}
/// <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
var list = value as IEnumerable;
var count = 0;
if (list != null)
{
if (FeatureSupport.Get(command.Connection).Arrays)
{
var arrayParm = command.CreateParameter();
arrayParm.Value = list;
arrayParm.ParameterName = namePrefix;
command.Parameters.Add(arrayParm);
}
else
{
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;
listParam.Value = item ?? DBNull.Value;
if (isString)
{
listParam.Size = 4000;
if (item != null && ((string)item).Length > 4000)
{
listParam.Size = -1;
}
}
if (isDbString && item as DbString != null)
{
var str = item as DbString;
str.AddParameter(command, listParam.ParameterName);
}
else
{
command.Parameters.Add(listParam);
}
}
if (count == 0)
{
command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), "(SELECT NULL WHERE 1 = 0)");
}
else
{
command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), match =>
{
var grp = match.Value;
var sb = new StringBuilder("(").Append(grp).Append(1);
for (int i = 2; i <= count; i++)
{
sb.Append(',').Append(grp).Append(i);
}
return sb.Append(')').ToString();
});
}
}
}
}
private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyInfo> parameters, string sql)
{
return parameters.Where(p => Regex.IsMatch(sql, "[@:]" + p.Name + "([^a-zA-Z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline));
}
/// <summary>
/// Internal use only
/// </summary>
public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates)
{
Type type = identity.parametersType;
bool filterParams = identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text;
var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true);
var il = dm.GetILGenerator();
il.DeclareLocal(type); // 0
bool haveInt32Arg1 = false;
il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param]
il.Emit(OpCodes.Unbox_Any, 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]
IEnumerable<PropertyInfo> props = type.GetProperties().Where(p => p.GetIndexParameters().Length == 0).OrderBy(p => p.Name);
if (filterParams)
{
props = FilterParameters(props, identity.sql);
}
foreach (var prop in props)
{
if (filterParams)
{
if (identity.sql.IndexOf("@" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0
&& identity.sql.IndexOf(":" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0)
{ // can't see the parameter in the text (even in a comment, etc) - burn it with fire
continue;
}
}
if (prop.PropertyType == typeof(DbString))
{
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param]
il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [dbstring]
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [dbstring] [command]
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [dbstring] [command] [name]
il.EmitCall(OpCodes.Callvirt, typeof(DbString).GetMethod("AddParameter"), null); // stack is now [parameters]
continue;
}
DbType dbType = LookupDbType(prop.PropertyType, prop.Name);
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(OpCodes.Callvirt, 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;
}
if (dbType == DynamicParameters.SqlGeographyParameter)
{
// 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(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value]
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("PackSqlGeographyParameter"), 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) // 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]
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(OpCodes.Callvirt, 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 && !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 ? 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)
{
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, 4000); // [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, 4000); // [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]
}
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
il.Emit(OpCodes.Ret);
return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>));
}
private static IDbCommand SetupCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType)
{
var cmd = cnn.CreateCommand();
var bindByName = GetBindByName(cmd.GetType());
if (bindByName != null) bindByName(cmd, true);
if (transaction != null)
cmd.Transaction = transaction;
cmd.CommandText = sql;
if (commandTimeout.HasValue)
cmd.CommandTimeout = commandTimeout.Value;
if (commandType.HasValue)
cmd.CommandType = commandType.Value;
if (paramReader != null)
{
paramReader(cmd, obj);
}
return cmd;
}
private static int ExecuteCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType)
{
IDbCommand cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
cmd = SetupCommand(cnn, transaction, sql, paramReader, obj, commandTimeout, commandType);
if (wasClosed) cnn.Open();
return cmd.ExecuteNonQuery();
}
finally
{
if (wasClosed) cnn.Close();
if (cmd != null) cmd.Dispose();
}
}
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);
return val is DBNull ? null : Enum.ToObject(effectiveType, val);
};
}
return r =>
{
var val = r.GetValue(index);
return val is DBNull ? null : val;
};
}
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 new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn");
}
var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray();
ITypeMap typeMap = GetTypeMap(type);
int index = startBound;
ConstructorInfo specializedConstructor = null;
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);
}
if (type.IsValueType)
{
il.Emit(OpCodes.Ldloca_S, (byte)1);
il.Emit(OpCodes.Initobj, type);
}
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);
}
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;
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]
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)
{
if (enumDeclareLocal == -1)
{
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
}
Label isNotString = il.DefineLabel();
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null]
StoreLocal(il, enumDeclareLocal); // stack is now [target][target][value-as-object][string or null]
il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object]
il.Emit(OpCodes.Pop); // stack is now [target][target]
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string]
il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
il.MarkLabel(isNotString);
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
if (nullUnderlyingType != null)
{
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][enum-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
{
Type dataType = reader.GetFieldType(index);
TypeCode dataTypeCode = Type.GetTypeCode(dataType), unboxTypeCode = Type.GetTypeCode(unboxType);
if (dataType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType))
{
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
}
else
{
// not a direct match; need to tweak the unbox
bool handled = true;
OpCode opCode = default(OpCode);
if (dataTypeCode == TypeCode.Decimal || unboxTypeCode == TypeCode.Decimal)
{ // no IL level conversions to/from decimal; I guess we could use the static operators, but
// this feels an edge-case
handled = false;
}
else
{
switch (unboxTypeCode)
{
case TypeCode.Byte:
opCode = OpCodes.Conv_Ovf_I1_Un; break;
case TypeCode.SByte:
opCode = OpCodes.Conv_Ovf_I1; break;
case TypeCode.UInt16:
opCode = OpCodes.Conv_Ovf_I2_Un; break;
case TypeCode.Int16:
opCode = OpCodes.Conv_Ovf_I2; break;
case TypeCode.UInt32:
opCode = OpCodes.Conv_Ovf_I4_Un; break;
case TypeCode.Boolean: // boolean is basically an int, at least at this level
case TypeCode.Int32:
opCode = OpCodes.Conv_Ovf_I4; break;
case TypeCode.UInt64:
opCode = OpCodes.Conv_Ovf_I8_Un; break;
case TypeCode.Int64:
opCode = OpCodes.Conv_Ovf_I8; break;
case TypeCode.Single:
opCode = OpCodes.Conv_R4; break;
case TypeCode.Double:
opCode = OpCodes.Conv_R8; break;
default:
handled = false;
break;
}
}
if (handled)
{ // unbox as the data-type, then use IL-level convert
il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]
il.Emit(opCode); // stack is now [target][target][typed-value]
if (unboxTypeCode == TypeCode.Boolean)
{ // compare to zero; I checked "csc" - this is the trick it uses; nice
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Ceq);
}
}
else
{ // use flexible conversion
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][value][member-type-token]
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // stack is now [target][target][value][member-type]
il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value]
il.Emit(OpCodes.Unbox_Any, unboxType); // 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
}
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
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 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>
/// <param name="ex"></param>
/// <param name="index"></param>
/// <param name="reader"></param>
public static void ThrowDataException(Exception ex, int index, IDataReader reader)
{
Exception toThrow;
try
{
string name = "(n/a)", value = "(n/a)";
if (reader != null && index >= 0 && index < reader.FieldCount)
{
name = reader.GetName(index);
object val = reader.GetValue(index);
if (val == null || val is DBNull)
{
value = "<null>";
}
else
{
value = Convert.ToString(val) + " - " + Type.GetTypeCode(val.GetType());
}
}
toThrow = new DataException(string.Format("Error parsing column {0} ({1}={2})", index, name, value), 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>
/// 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)
{
this.command = command;
this.reader = reader;
this.identity = identity;
}
#if !CSHARP30
/// <summary>
/// Read the next grid of results, returned as a dynamic object
/// </summary>
public IEnumerable<dynamic> Read(bool buffered = true)
{
return Read<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
{
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(typeof(T), gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity);
var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), 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, TReturn>(object func, string splitOn)
{
var identity = this.identity.ForGrid(typeof(TReturn), new Type[] {
typeof(TFirst),
typeof(TSecond),
typeof(TThird),
typeof(TFourth),
typeof(TFifth)
}, gridIndex);
try
{
foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(null, null, func, null, null, splitOn, null, null, reader, identity))
{
yield return r;
}
}
finally
{
NextResult();
}
}
#if CSHARP30
/// <summary>
/// Read multiple objects from a single recordset 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 recordset 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, TReturn>(func, splitOn);
return buffered ? result.ToList() : result;
}
#if CSHARP30
/// <summary>
/// Read multiple objects from a single recordset 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 recordset 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, 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, 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, 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 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;
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>
/// A bag of parameters that can be passed to the Dapper Query and Execute methods
/// </summary>
partial class DynamicParameters : SqlMapper.IDynamicParameters
{
internal const DbType EnumerableMultiParameter = (DbType)(-1);
internal const DbType SqlGeographyParameter = (DbType)(-2);
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;
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; }
}
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
public DynamicParameters() { }
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
/// <param name="template">can be an anonymous type or a DynamicParameters bag</param>
public DynamicParameters(object template)
{
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(
#if CSHARP30
object param
#else
dynamic param
#endif
)
{
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)
{
#if CSHARP30
Add(kvp.Key, kvp.Value, null, null, null);
#else
Add(kvp.Key, kvp.Value);
#endif
}
}
}
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>
/// 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)
{
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);
paramReaderCache[newIdent] = appender;
}
}
appender(command, template);
}
}
foreach (var param in parameters.Values)
{
var dbType = param.DbType;
var val = param.Value;
string name = Clean(param.Name);
if (dbType == null && val != null) dbType = SqlMapper.LookupDbType(val.GetType(), name);
if (dbType == DynamicParameters.EnumerableMultiParameter)
{
#pragma warning disable 612, 618
SqlMapper.PackListParameters(command, name, val);
#pragma warning restore 612, 618
}
else
{
bool add = !command.Parameters.Contains(name);
IDbDataParameter p;
if (add)
{
p = command.CreateParameter();
p.ParameterName = name;
}
else
{
p = (IDbDataParameter)command.Parameters[name];
}
p.Value = val ?? DBNull.Value;
p.Direction = param.ParameterDirection;
var s = val as string;
if (s != null)
{
if (s.Length <= 4000)
{
p.Size = 4000;
}
}
if (param.Size != null)
{
p.Size = param.Size.Value;
}
if (dbType != null)
{
p.DbType = dbType.Value;
}
if (add)
{
command.Parameters.Add(p);
}
param.AttachedParam = p;
}
}
}
/// <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>
/// 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
{
/// <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 <= 4000)
{
param.Size = 4000;
}
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
{
/// <summary>
/// Dictionary of supported features index by connection type name
/// </summary>
private static readonly Dictionary<string, FeatureSupport> FeatureList = new Dictionary<string, FeatureSupport>(StringComparer.InvariantCultureIgnoreCase) {
{"sqlserverconnection", new FeatureSupport { Arrays = false}},
{"npgsqlconnection", new FeatureSupport {Arrays = true}}
};
/// <summary>
/// Gets the featureset based on the passed connection
/// </summary>
public static FeatureSupport Get(IDbConnection connection)
{
string name = connection.GetType().Name;
FeatureSupport features;
return FeatureList.TryGetValue(name, out features) ? features : FeatureList.Values.First();
}
/// <summary>
/// True if the db supports array columns e.g. Postgresql
/// </summary>
public bool Arrays { get; set; }
}
/// <summary>
/// Represents simple memeber 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).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>
/// 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)
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)
return new SimpleMemberMap(columnName, field);
return null;
}
}
/// <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>
/// Not impelmeneted 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;
}
}
// 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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment