Dapper的完整扩展(含Nunit单元测试)

来源:互联网 发布:ubuntu 黑屏 编辑:程序博客网 时间:2024/05/24 06:27

真心想说:其实。。。我不想用Dapper,如果OrmLite.Net支持参数化的话,也就没Dapper的什么事情了,对于OrmLite.Net只能做后续跟踪......

这个其实是看了Dapper作者的扩展后觉得不爽,然后自己按照他的设计思路重写了代码,只支持单个数据的增删改查,根据Expression来查的真心无能为力......

另外作者似乎已经支持了属性、字段等与数据库中的映射.....

具体包含了

1、对字符串的扩展

2、对主键的定义,支持单或多主键,当单主键并且类型为数字时,认为该主键为自增列

3、对表名的定义


实际代码如下:

DapperExtensions部分

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Data;using System.Collections.Concurrent;using System.Reflection;namespace Dapper{    /// <summary>    /// Dapper扩展POCO信息类,为应用此扩展,在原Dapper的第1965行增加了对此类的应用    /// </summary>    public class DapperPocoInfo    {        private Type _type;        private ConcurrentDictionary<string, KeyValuePair<DbType, int>> _stringColumns            = new ConcurrentDictionary<string, KeyValuePair<DbType, int>>();        private IEnumerable<PropertyInfo> _typeProperties;        private IList<PropertyInfo> _keyProperties = new List<PropertyInfo>();        private IEnumerable<PropertyInfo> _defaultKeyPropeties;        private string _tableName;        /// <summary>        /// 对应表名        /// </summary>        public string TableName        {            get            {                if (string.IsNullOrWhiteSpace(this._tableName))                {                    this._tableName = this._type.Name;                }                return this._tableName;            }            set            {                this._tableName = value;            }        }        /// <summary>        /// 所有Public的属性集合        /// </summary>        public IEnumerable<PropertyInfo> TypeProperties        {            get { return this._typeProperties; }        }        /// <summary>        /// 获取主键集合,如果主键数量大于1,则认为是联合主键,等于0认为不存在主键,等于1并且类型为数字型则认为自增主键        /// </summary>        /// <returns></returns>        public IEnumerable<PropertyInfo> KeyProperties        {            get            {                if (this._keyProperties.Count == 0)                {//如果未设定KeyProperties,则默认认为第一个后缀为ID的PropertyInfo为主键                    if (this._defaultKeyPropeties == null)                    {                        this._defaultKeyPropeties = this._typeProperties.Where(p => p.Name.ToLower().EndsWith("id")).Take(1).ToArray();                    }                    return this._defaultKeyPropeties;                }                else                {                    return this._keyProperties;                }            }        }        /// <summary>        /// .oct        /// </summary>        /// <param name="type"></param>        internal DapperPocoInfo(Type type)        {            if (type == null)            {                throw new ArgumentNullException();            }            this._type = type;            this._typeProperties = type.GetProperties();        }        /// <summary>        /// 添加字符串参数化映射        /// </summary>        /// <param name="propertyName">属性名</param>        /// <param name="dbType">必须为AnsiString、AnsiStringFixedLength、String、StringFixedLength</param>        /// <param name="len">值范围为1~8000</param>        public virtual void AddStringColumnMap(string propertyName, DbType dbType = DbType.AnsiString, int len = 50)        {            this.GetProperty(propertyName);            if (len <= 0 || len > 8000)            {//长度范围1~8000,此处暂时对应sql,如果其它关系型数据库长度范围与此不一致,可继承修改                throw new ArgumentException("The param len's value must between 1 and 8000.");            }            if (dbType != DbType.AnsiString && dbType != DbType.AnsiStringFixedLength && dbType != DbType.String && dbType != DbType.StringFixedLength)            {                return;            }            this._stringColumns.TryAdd(propertyName, new KeyValuePair<DbType, int>(dbType, len));        }        /// <summary>        /// 获取字符串映射        /// </summary>        /// <param name="propertyName"></param>        /// <returns></returns>        public KeyValuePair<DbType, int>? GetStringColumnMap(string propertyName)        {            KeyValuePair<DbType, int> kvp;            if (this._stringColumns.TryGetValue(propertyName, out kvp))            {                return kvp;            }            return null;        }        private PropertyInfo GetProperty(string propertyName)        {            if (string.IsNullOrWhiteSpace(propertyName))            {                throw new ArgumentNullException("propertyName can not be null or empty value");            }            PropertyInfo pi = this._typeProperties.Where(p => p.Name.ToLower() == propertyName.ToLower()).FirstOrDefault();            if (pi == null)            {                throw new ArgumentOutOfRangeException(string.Format("The class '{0}' does not contains property '{1}'.", this._type.FullName, propertyName));            }            return pi;        }        /// <summary>        /// 添加主键映射        /// </summary>        /// <param name="propertyName"></param>        public void AddKeyMap(string propertyName)        {            var pi = this.GetProperty(propertyName);            if (this._keyProperties.Where(p => p.Name == pi.Name).FirstOrDefault() == null)            {                this._keyProperties.Add(pi);                this._unWriteKey = null;//赋值时取消已经确认的是否可写键值            }        }        /// <summary>        /// 不需要插入数据的主键类型,除了Guid,其它均认为自增        /// </summary>        private static Type[] UnWriteTypes = { typeof(int), typeof(short), typeof(long), typeof(byte), typeof(Guid) };        private bool? _unWriteKey;        /// <summary>        /// 主键是否可写        /// </summary>        /// <returns></returns>        public bool IsUnWriteKey()        {            if (!this._unWriteKey.HasValue)            {                this._unWriteKey = false;                IList<PropertyInfo> keys = this.KeyProperties.ToList();                if (keys.Count == 1)                {                    this._unWriteKey = UnWriteTypes.Contains(keys[0].PropertyType);                }            }            return this._unWriteKey.Value;        }    }    /// <summary>    /// Dapper扩展类    /// </summary>    public static class DapperExtensions    {        private static ConcurrentDictionary<RuntimeTypeHandle, DapperPocoInfo> PocoInfos            = new ConcurrentDictionary<RuntimeTypeHandle, DapperPocoInfo>();        /// <summary>        /// 已实现的ISqlAdapter集合        /// </summary>        private static readonly Dictionary<string, ISqlAdapter> AdapterDictionary            = new Dictionary<string, ISqlAdapter>() {            {"sqlconnection", new MsSqlServerAdapter()}            };        public static DapperPocoInfo GetPocoInfo<T>()            where T : class        {            return GetPocoInfo(typeof(T));        }        public static DapperPocoInfo GetPocoInfo(this Type type)        {            DapperPocoInfo pi;            RuntimeTypeHandle hd = type.TypeHandle;            if (!PocoInfos.TryGetValue(hd, out pi))            {                pi = new DapperPocoInfo(type);                PocoInfos[hd] = pi;            }            return pi;        }        public static ISqlAdapter GetSqlAdapter(this IDbConnection connection)        {            string name = connection.GetType().Name.ToLower();            ISqlAdapter adapter;            if (!AdapterDictionary.TryGetValue(name, out adapter))            {                throw new NotImplementedException(string.Format("Unknow sql connection '{0}'", name));            }            return adapter;        }        /// <summary>        /// 新增数据,如果T只有一个主键,且新增的主键为数字,则会将新增的主键赋值给entity相应的字段,如果为多主键,或主键不是数字和Guid,则主键需输入        /// 如果要进行匿名类型新增,因为匿名类型无法赋值,需调用ISqlAdapter的Insert,由数据库新增的主键需自己写方法查询        /// </summary>        /// <typeparam name="T"></typeparam>        /// <param name="connection"></param>        /// <param name="entity"></param>        /// <param name="transaction"></param>        /// <param name="commandTimeout"></param>        /// <returns></returns>        public static bool Insert<T>(this IDbConnection connection, T entity, IDbTransaction transaction = null, int? commandTimeout = null)            where T : class        {            ISqlAdapter adapter = GetSqlAdapter(connection);            return adapter.Insert<T>(connection, entity, transaction, commandTimeout);        }        /// <summary>        /// 更新数据,如果entity为T,则全字段更新,如果为匿名类型,则修改包含的字段,但匿名类型必须包含主键对应的字段        /// </summary>        /// <typeparam name="T"></typeparam>        /// <param name="connection"></param>        /// <param name="entity"></param>        /// <param name="transaction"></param>        /// <param name="commandTimeout"></param>        /// <returns></returns>        public static bool Update<T>(this IDbConnection connection, object entity, IDbTransaction transaction = null, int? commandTimeout = null)            where T : class        {            ISqlAdapter adapter = GetSqlAdapter(connection);            return adapter.UpdateByKey<T>(connection, entity, transaction, commandTimeout);        }        /// <summary>        /// 删除数据,支持匿名,但匿名类型必须包含主键对应的字段        /// </summary>        /// <typeparam name="T"></typeparam>        /// <param name="connection"></param>        /// <param name="param"></param>        /// <param name="transaction"></param>        /// <param name="commandTimeout"></param>        /// <returns></returns>        public static bool DeleteByKey<T>(this IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null)            where T : class        {            ISqlAdapter adapter = GetSqlAdapter(connection);            return adapter.DeleteByKey<T>(connection, param, transaction, commandTimeout);        }        /// <summary>        /// 查询数据,支持匿名,但匿名类型必须包含主键对应的字段        /// </summary>        /// <typeparam name="T"></typeparam>        /// <param name="connection"></param>        /// <param name="param"></param>        /// <param name="transaction"></param>        /// <param name="commandTimeout"></param>        /// <returns></returns>        public static T QueryByKey<T>(this IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null)            where T : class        {            ISqlAdapter adapter = GetSqlAdapter(connection);            return adapter.QueryByKey<T>(connection, param, transaction, commandTimeout);        }        /// <summary>        /// 获取最后新增的ID值,仅对Indentity有效        /// </summary>        /// <typeparam name="T"></typeparam>        /// <param name="connection"></param>        /// <returns></returns>        public static long GetLastInsertIndentityID<T>(this IDbConnection connection)            where T : class        {            ISqlAdapter adapter = GetSqlAdapter(connection);            return adapter.GetLastInsertID<T>(connection);        }    }    public interface ISqlAdapter    {        string GetFullQueryByKeySql(Type type);        string GetFullInsertSql(Type type);        string GetFullUpdateByKeySql(Type type);        string GetDeleteByKeySql(Type type);        bool Insert<T>(IDbConnection connection, object entity, IDbTransaction transaction, int? commandTimeout)            where T : class;        bool UpdateByKey<T>(IDbConnection connection, object entity, IDbTransaction transaction, int? commandTimeout)            where T : class;        bool DeleteByKey<T>(IDbConnection connection, object param, IDbTransaction transaction, int? commandTimeout)            where T : class;        T QueryByKey<T>(IDbConnection connection, object param, IDbTransaction transaction, int? commandTimeout)            where T : class;        long GetLastInsertID<T>(IDbConnection connection)            where T : class;    }    internal class BasicSql    {        public string FullQueryByKeySql { get; set; }        public string FullInsertSql { get; set; }        public string FullUpdateByKeySql { get; set; }        public string DeleteByKeySql { get; set; }    }    public class MsSqlServerAdapter : ISqlAdapter    {        private static ConcurrentDictionary<RuntimeTypeHandle, BasicSql> BasicSqls            = new ConcurrentDictionary<RuntimeTypeHandle, BasicSql>();        private static readonly char SqlParameterChar = '@';        internal MsSqlServerAdapter() { }        private string GetParameterName(PropertyInfo pi)        {            return string.Format("{0}{1}", SqlParameterChar, pi.Name);        }        private BasicSql GetBasicSql(Type type)        {            BasicSql basicSql;            RuntimeTypeHandle hd = type.TypeHandle;            if (!BasicSqls.TryGetValue(hd, out basicSql))            {                basicSql = new BasicSql();                BasicSqls[hd] = basicSql;            }            return basicSql;        }        private void AppendKeyParameter(StringBuilder tmp, IEnumerable<PropertyInfo> keys)        {            if (keys.Any())            {                tmp.AppendLine(" WHERE");                foreach (PropertyInfo key in keys)                {                    tmp.Append(key.Name);                    tmp.Append("=");                    tmp.Append(this.GetParameterName(key));                    tmp.Append(" AND ");                }                tmp.Remove(tmp.Length - 5, 5);            }        }        public string GetFullQueryByKeySql(Type type)        {            BasicSql basicSql = this.GetBasicSql(type);            if (string.IsNullOrEmpty(basicSql.FullQueryByKeySql))            {                DapperPocoInfo dpi = type.GetPocoInfo();                StringBuilder tmp = new StringBuilder();                tmp.Append("SELECT * FROM ");                tmp.Append(dpi.TableName);                tmp.Append(" (NOLOCK) ");                this.AppendKeyParameter(tmp, dpi.KeyProperties);                basicSql.FullQueryByKeySql = tmp.ToString();            }            return basicSql.FullQueryByKeySql;        }        public string GetFullInsertSql(Type type)        {            BasicSql basicSql = this.GetBasicSql(type);            if (string.IsNullOrEmpty(basicSql.FullInsertSql))            {                DapperPocoInfo dpi = type.GetPocoInfo();                basicSql.FullInsertSql = this.GetInsertSql(dpi, dpi.TypeProperties);            }            return basicSql.FullInsertSql;        }        private string GetInsertSql(DapperPocoInfo dpi, IEnumerable<PropertyInfo> props)        {            StringBuilder tmp = new StringBuilder();            tmp.Append("INSERT INTO ");            tmp.AppendLine(dpi.TableName);            tmp.Append('(');            IEnumerable<PropertyInfo> valueProps = props;            if (dpi.IsUnWriteKey())            {                valueProps = this.GetExceptProps(props, dpi.KeyProperties);            }            this.AppendColumnList(tmp, valueProps, '\0');            tmp.Append(')');            tmp.AppendLine(" VALUES ");            tmp.Append('(');            this.AppendColumnList(tmp, valueProps, SqlParameterChar);            tmp.Append(')');            return tmp.ToString();        }        private void AppendColumnList(StringBuilder tmp, IEnumerable<PropertyInfo> valueProps, char addChar)        {            foreach (PropertyInfo p in valueProps)            {                tmp.Append(addChar);                tmp.Append(p.Name);                tmp.Append(',');            }            tmp.Remove(tmp.Length - 1, 1);        }        private IEnumerable<PropertyInfo> GetExceptProps(IEnumerable<PropertyInfo> props1, IEnumerable<PropertyInfo> props2)        {            //return props1.Except(props2, new EqualityCompareProperty()).ToArray();            IList<PropertyInfo> list = new List<PropertyInfo>();            foreach (PropertyInfo pi in props1)            {                string name = pi.Name.ToLower();                if (!props2.Any(p => p.Name.ToLower() == name))                {                    list.Add(pi);                }            }            return list;        }        private string GetUpdateSql(DapperPocoInfo dpi, IEnumerable<PropertyInfo> props)        {            StringBuilder tmp = new StringBuilder();            tmp.Append("UPDATE ");            tmp.AppendLine(dpi.TableName);            tmp.Append("SET ");            IEnumerable<PropertyInfo> valueProps = this.GetExceptProps(props, dpi.KeyProperties);            foreach (PropertyInfo p in valueProps)            {                tmp.Append(p.Name);                tmp.Append('=');                tmp.Append(SqlParameterChar);                tmp.Append(p.Name);                tmp.Append(',');            }            tmp.Remove(tmp.Length - 1, 1);            tmp.AppendLine();            this.AppendKeyParameter(tmp, dpi.KeyProperties);            return tmp.ToString();        }        public string GetFullUpdateByKeySql(Type type)        {            BasicSql basicSql = this.GetBasicSql(type);            if (string.IsNullOrEmpty(basicSql.FullUpdateByKeySql))            {                DapperPocoInfo dpi = type.GetPocoInfo();                basicSql.FullUpdateByKeySql = this.GetUpdateSql(dpi, dpi.TypeProperties);            }            return basicSql.FullUpdateByKeySql;        }        public string GetDeleteByKeySql(Type type)        {            BasicSql basicSql = this.GetBasicSql(type);            if (string.IsNullOrEmpty(basicSql.DeleteByKeySql))            {                DapperPocoInfo dpi = type.GetPocoInfo();                StringBuilder tmp = new StringBuilder();                tmp.Append("DELETE FROM ");                tmp.AppendLine(dpi.TableName);                this.AppendKeyParameter(tmp, dpi.KeyProperties);                basicSql.DeleteByKeySql = tmp.ToString();            }            return basicSql.DeleteByKeySql;        }        public bool Insert<T>(IDbConnection connection, object entity, IDbTransaction transaction = null, int? commandTimeout = null) where T : class        {            Type type = typeof(T);            string insertSql;            Type entityType = entity.GetType();            DapperPocoInfo dpi = type.GetPocoInfo();            if (entityType.IsAssignableFrom(type))            {                insertSql = this.GetFullInsertSql(type);            }            else            {                insertSql = this.GetInsertSql(dpi, entityType.GetProperties());            }            if (connection.Execute<T>(insertSql, entity, transaction, commandTimeout) > 0)            {                if (entityType.IsAssignableFrom(type) && dpi.IsUnWriteKey())                {                    PropertyInfo key = dpi.KeyProperties.First();                    if (key.PropertyType != typeof(Guid))                    {                        var idValue = this.GetLastInsertID(connection, dpi, transaction, commandTimeout);                        key.SetValue(entity, idValue, null);                    }                }                return true;            }            return false;        }        public bool UpdateByKey<T>(IDbConnection connection, object entity, IDbTransaction transaction = null, int? commandTimeout = null) where T : class        {            Type type = typeof(T);            string updateSql;            Type entityType = entity.GetType();            DapperPocoInfo dpi = type.GetPocoInfo();            if (entityType.IsAssignableFrom(type))            {                updateSql = this.GetFullUpdateByKeySql(type);            }            else            {                updateSql = this.GetUpdateSql(dpi, entityType.GetProperties());            }            return connection.Execute<T>(updateSql, entity, transaction, commandTimeout) > 0;        }        public bool DeleteByKey<T>(IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null) where T : class        {            string deleteSql = this.GetDeleteByKeySql(typeof(T));            return connection.Execute<T>(deleteSql, param, transaction: transaction, commandTimeout: commandTimeout) > 0;        }        public T QueryByKey<T>(IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null) where T : class        {            string querySql = this.GetFullQueryByKeySql(typeof(T));            return connection.Query<T>(querySql, param, transaction: transaction, commandTimeout: commandTimeout).FirstOrDefault();        }        private object GetLastInsertID(IDbConnection connection, DapperPocoInfo dpi, IDbTransaction transaction = null, int? commandTimeout = null)        {            var r = connection.Query("SELECT IDENT_CURRENT('" + dpi.TableName + "') ID", transaction: transaction, commandTimeout: commandTimeout);            return Convert.ChangeType(r.First().ID, dpi.KeyProperties.First().PropertyType);        }        public long GetLastInsertID<T>(IDbConnection connection)            where T : class        {            DapperPocoInfo dpi = typeof(T).GetPocoInfo();            return Convert.ToInt64(this.GetLastInsertID(connection, dpi));        }    }}

原Dapper部分,在1965行开始做了些修改:

/* 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;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 pass an arbitrary db specific parameter to Dapper        /// </summary>        public interface ICustomQueryParameter        {            /// <summary>            /// Add the parameter needed to the command before it executes            /// </summary>            /// <param name="command">The raw command prior to execution</param>            /// <param name="name">Parameter name</param>            void AddParameter(IDbCommand command, string name);        }        /// <summary>        /// Implement this interface to 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;        }        /// <summary>        /// Configire the specified type to be mapped to a given db-type        /// </summary>        public static void AddTypeMap(Type type, DbType dbType)        {            typeMap[type] = dbType;        }        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 && !typeMap.ContainsKey(type))            {                type = Enum.GetUnderlyingType(type);            }            if (typeMap.TryGetValue(type, out dbType))            {                return dbType;            }            if (type.FullName == LinqBinary)            {                return DbType.Binary;            }            if (typeof(IEnumerable).IsAssignableFrom(type))            {                return DynamicParameters.EnumerableMultiParameter;            }            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 : SqlMapper.connectionStringComparer.GetHashCode(connectionString));                    hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode());                }            }            /// <summary>            ///             /// </summary>            /// <param name="obj"></param>            /// <returns></returns>            public override bool Equals(object obj)            {                return Equals(obj as Identity);            }            /// <summary>            /// The sql            /// </summary>            public readonly string sql;            /// <summary>            /// The command type             /// </summary>            public readonly CommandType? commandType;            /// <summary>            ///             /// </summary>            public readonly int hashCode, gridIndex;            /// <summary>            ///             /// </summary>            public readonly Type type;            /// <summary>            ///             /// </summary>            public readonly string connectionString;            /// <summary>            ///             /// </summary>            public readonly Type parametersType;            /// <summary>            ///             /// </summary>            /// <returns></returns>            public override int GetHashCode()            {                return hashCode;            }            /// <summary>            /// Compare 2 Identity objects            /// </summary>            /// <param name="other"></param>            /// <returns></returns>            public bool Equals(Identity other)            {                return                    other != null &&                    gridIndex == other.gridIndex &&                    type == other.type &&                    sql == other.sql &&                    commandType == other.commandType &&                    SqlMapper.connectionStringComparer.Equals(connectionString, other.connectionString) &&                    parametersType == other.parametersType;            }        }#if CSHARP30        /// <summary>        /// Execute parameterized SQL          /// </summary>        /// <returns>Number of rows affected</returns>        public static int Execute(this IDbConnection cnn, string sql, object param)        {            return Execute(cnn, sql, param, null, null, null);        }        /// <summary>        /// Execute parameterized SQL        /// </summary>        /// <returns>Number of rows affected</returns>        public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)        {            return Execute(cnn, sql, param, transaction, null, null);        }        /// <summary>        /// Execute parameterized SQL        /// </summary>        /// <returns>Number of rows affected</returns>        public static int Execute(this IDbConnection cnn, string sql, object param, CommandType commandType)        {            return Execute(cnn, sql, param, null, null, commandType);        }        /// <summary>        /// Execute parameterized SQL        /// </summary>        /// <returns>Number of rows affected</returns>        public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)        {            return Execute(cnn, sql, param, transaction, null, commandType);        }        /// <summary>        /// 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        private static int Execute(#if CSHARP30this IDbConnection cnn, string sql,Type type, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType#elsethis IDbConnection cnn, string sql, Type type, 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, type, 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, type, (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);        }        /// <summary>        /// Execute parameterized SQL          /// </summary>        /// <returns>Number of rows affected</returns>        public static int Execute<T>(#if CSHARP30this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType#elsethis IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null#endif)            where T : class        {            return Execute(cnn, sql, typeof(T), param, transaction, commandTimeout, commandType);        }        /// <summary>        /// Execute parameterized SQL          /// </summary>        /// <returns>Number of rows affected</returns>        public static int Execute(#if CSHARP30this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType#elsethis IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null#endif)        {            return Execute(cnn, sql, null, param, transaction, 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 CSHARP30this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType#elsethis 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 CSHARP30this 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 CSHARP30this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType#elsethis 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, 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 CSHARP30this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType#elsethis 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, 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 CSHARP30this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType#elsethis 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, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);        }#if !CSHARP30        /// <summary>        /// Perform a multi mapping query with 5 input parameters        /// </summary>        /// <typeparam name="TFirst"></typeparam>        /// <typeparam name="TSecond"></typeparam>        /// <typeparam name="TThird"></typeparam>        /// <typeparam name="TFourth"></typeparam>        /// <typeparam name="TFifth"></typeparam>        /// <typeparam name="TReturn"></typeparam>        /// <param name="cnn"></param>        /// <param name="sql"></param>        /// <param name="map"></param>        /// <param name="param"></param>        /// <param name="transaction"></param>        /// <param name="buffered"></param>        /// <param name="splitOn"></param>        /// <param name="commandTimeout"></param>        /// <param name="commandType"></param>        /// <returns></returns>        public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(            this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, 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, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);        }        /// <summary>        /// Perform a multi mapping query with 6 input parameters        /// </summary>        /// <typeparam name="TFirst"></typeparam>        /// <typeparam name="TSecond"></typeparam>        /// <typeparam name="TThird"></typeparam>        /// <typeparam name="TFourth"></typeparam>        /// <typeparam name="TFifth"></typeparam>        /// <typeparam name="TSixth"></typeparam>        /// <typeparam name="TReturn"></typeparam>        /// <param name="cnn"></param>        /// <param name="sql"></param>        /// <param name="map"></param>        /// <param name="param"></param>        /// <param name="transaction"></param>        /// <param name="buffered"></param>        /// <param name="splitOn"></param>        /// <param name="commandTimeout"></param>        /// <param name="commandType"></param>        /// <returns></returns>        public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(            this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> map, 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, TSixth, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);        }        /// <summary>        /// Perform a multi mapping query with 7 input parameters        /// </summary>        /// <typeparam name="TFirst"></typeparam>        /// <typeparam name="TSecond"></typeparam>        /// <typeparam name="TThird"></typeparam>        /// <typeparam name="TFourth"></typeparam>        /// <typeparam name="TFifth"></typeparam>        /// <typeparam name="TSixth"></typeparam>        /// <typeparam name="TSeventh"></typeparam>        /// <typeparam name="TReturn"></typeparam>        /// <param name="cnn"></param>        /// <param name="sql"></param>        /// <param name="map"></param>        /// <param name="param"></param>        /// <param name="transaction"></param>        /// <param name="buffered"></param>        /// <param name="splitOn"></param>        /// <param name="commandTimeout"></param>        /// <param name="commandType"></param>        /// <returns></returns>        public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, 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, TSixth, TSeventh, 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, TSixth, TSeventh, 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, TSixth, TSeventh, 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, TSixth, TSeventh, 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), typeof(TSixth), typeof(TSeventh) });            CacheInfo cinfo = GetCacheInfo(identity);            IDbCommand ownedCommand = null;            IDataReader ownedReader = null;            bool wasClosed = cnn != null && cnn.State == ConnectionState.Closed;            try            {                if (reader == null)                {                    ownedCommand = SetupCommand(cnn, transaction, sql, cinfo.ParamReader, (object)param, commandTimeout, commandType);                    if (wasClosed) cnn.Open();                    ownedReader = ownedCommand.ExecuteReader();                    reader = ownedReader;                }                DeserializerState deserializer = default(DeserializerState);                Func<IDataReader, object>[] otherDeserializers = null;                int hash = GetColumnHash(reader);                if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash)                {                    var deserializers = GenerateDeserializers(new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }, splitOn, reader);                    deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]);                    otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray();                    SetQueryCache(identity, cinfo);                }                Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(deserializer.Func, otherDeserializers, map);                if (mapIt != null)                {                    while (reader.Read())                    {                        yield return mapIt(reader);                    }                }            }            finally            {                try                {                    if (ownedReader != null)                    {                        ownedReader.Dispose();                    }                }                finally                {                    if (ownedCommand != null)                    {                        ownedCommand.Dispose();                    }                    if (wasClosed) cnn.Close();                }            }        }        private static Func<IDataReader, TReturn> GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<IDataReader, object> deserializer, Func<IDataReader, object>[] otherDeserializers, object map)        {            switch (otherDeserializers.Length)            {                case 1:                    return r => ((Func<TFirst, TSecond, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r));                case 2:                    return r => ((Func<TFirst, TSecond, TThird, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r));                case 3:                    return r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r));#if !CSHARP30                case 4:                    return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r));                case 5:                    return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r));                case 6:                    return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r), (TSeventh)otherDeserializers[5](r));#endif                default:                    throw new NotSupportedException();            }        }        private static Func<IDataReader, 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, true);                    }                }                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        private sealed partial class DapperTable        {            string[] fieldNames;            readonly Dictionary<string, int> fieldNameLookup;            internal string[] FieldNames { get { return fieldNames; } }            public DapperTable(string[] fieldNames)            {                if (fieldNames == null) throw new ArgumentNullException("fieldNames");                this.fieldNames = fieldNames;                fieldNameLookup = new Dictionary<string, int>(fieldNames.Length, StringComparer.Ordinal);                // if there are dups, we want the **first** key to be the "winner" - so iterate backwards                for (int i = fieldNames.Length - 1; i >= 0; i--)                {                    string key = fieldNames[i];                    if (key != null) fieldNameLookup[key] = i;                }            }            internal int IndexOfName(string name)            {                int result;                return (name != null && fieldNameLookup.TryGetValue(name, out result)) ? result : -1;            }            internal int AddField(string name)            {                if (name == null) throw new ArgumentNullException("name");                if (fieldNameLookup.ContainsKey(name)) throw new InvalidOperationException("Field already exists: " + name);                int oldLen = fieldNames.Length;                Array.Resize(ref fieldNames, oldLen + 1); // yes, this is sub-optimal, but this is not the expected common case                fieldNames[oldLen] = name;                fieldNameLookup[name] = oldLen;                return oldLen;            }            internal bool FieldExists(string key)            {                return key != null && fieldNameLookup.ContainsKey(key);            }            public int FieldCount { get { return fieldNames.Length; } }        }        sealed partial class DapperRowMetaObject : System.Dynamic.DynamicMetaObject        {            static readonly MethodInfo getValueMethod = typeof(IDictionary<string, object>).GetProperty("Item").GetGetMethod();            static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) });            public DapperRowMetaObject(                System.Linq.Expressions.Expression expression,                System.Dynamic.BindingRestrictions restrictions                )                : base(expression, restrictions)            {            }            public DapperRowMetaObject(                System.Linq.Expressions.Expression expression,                System.Dynamic.BindingRestrictions restrictions,                object value                )                : base(expression, restrictions, value)            {            }            System.Dynamic.DynamicMetaObject CallMethod(                MethodInfo method,                System.Linq.Expressions.Expression[] parameters                )            {                var callMethod = new System.Dynamic.DynamicMetaObject(                    System.Linq.Expressions.Expression.Call(                        System.Linq.Expressions.Expression.Convert(Expression, LimitType),                        method,                        parameters),                    System.Dynamic.BindingRestrictions.GetTypeRestriction(Expression, LimitType)                    );                return callMethod;            }            public override System.Dynamic.DynamicMetaObject BindGetMember(System.Dynamic.GetMemberBinder binder)            {                var parameters = new System.Linq.Expressions.Expression[]                                     {                                         System.Linq.Expressions.Expression.Constant(binder.Name)                                     };                var callMethod = CallMethod(getValueMethod, parameters);                return callMethod;            }            // Needed for Visual basic dynamic support            public override System.Dynamic.DynamicMetaObject BindInvokeMember(System.Dynamic.InvokeMemberBinder binder, System.Dynamic.DynamicMetaObject[] args)            {                var parameters = new System.Linq.Expressions.Expression[]                                     {                                         System.Linq.Expressions.Expression.Constant(binder.Name)                                     };                var callMethod = CallMethod(getValueMethod, parameters);                return callMethod;            }            public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.SetMemberBinder binder, System.Dynamic.DynamicMetaObject value)            {                var parameters = new System.Linq.Expressions.Expression[]                                     {                                         System.Linq.Expressions.Expression.Constant(binder.Name),                                         value.Expression,                                     };                var callMethod = CallMethod(setValueMethod, parameters);                return callMethod;            }        }        private sealed partial class DapperRow            : System.Dynamic.IDynamicMetaObjectProvider            , IDictionary<string, object>        {            readonly DapperTable table;            object[] values;            public DapperRow(DapperTable table, object[] values)            {                if (table == null) throw new ArgumentNullException("table");                if (values == null) throw new ArgumentNullException("values");                this.table = table;                this.values = values;            }            private sealed class DeadValue            {                public static readonly DeadValue Default = new DeadValue();                private DeadValue() { }            }            int ICollection<KeyValuePair<string, object>>.Count            {                get                {                    int count = 0;                    for (int i = 0; i < values.Length; i++)                    {                        if (!(values[i] is DeadValue)) count++;                    }                    return count;                }            }            public bool TryGetValue(string name, out object value)            {                var index = table.IndexOfName(name);                if (index < 0)                { // doesn't exist                    value = null;                    return false;                }                // exists, **even if** we don't have a value; consider table rows heterogeneous                value = index < values.Length ? values[index] : null;                if (value is DeadValue)                { // pretend it isn't here                    value = null;                    return false;                }                return true;            }            public override string ToString()            {                var sb = 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)            {                SetValue(key, value, true);            }            bool IDictionary<string, object>.Remove(string key)            {                int index = table.IndexOfName(key);                if (index < 0 || index >= values.Length || values[index] is DeadValue) return false;                values[index] = DeadValue.Default;                return true;            }            object IDictionary<string, object>.this[string key]            {                get { object val; TryGetValue(key, out val); return val; }                set { SetValue(key, value, false); }            }            public object SetValue(string key, object value)            {                return SetValue(key, value, false);            }            private object SetValue(string key, object value, bool isAdd)            {                if (key == null) throw new ArgumentNullException("key");                int index = table.IndexOfName(key);                if (index < 0)                {                    index = table.AddField(key);                }                else if (isAdd && index < values.Length && !(values[index] is DeadValue))                {                    // then semantically, this value already exists                    throw new ArgumentException("An item with the same key has already been added", "key");                }                int oldLength = values.Length;                if (oldLength <= index)                {                    // we'll assume they're doing lots of things, and                    // grow it to the full width of the table                    Array.Resize(ref values, table.FieldCount);                    for (int i = oldLength; i < values.Length; i++)                    {                        values[i] = DeadValue.Default;                    }                }                return values[index] = value;            }            ICollection<string> IDictionary<string, object>.Keys            {                get { return this.Select(kv => kv.Key).ToArray(); }            }            ICollection<object> IDictionary<string, object>.Values            {                get { return this.Select(kv => kv.Value).ToArray(); }            }            #endregion        }#endif        private const string MultiMapSplitExceptionMessage = "When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id";#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(MultiMapSplitExceptionMessage, "splitOn");            }            var effectiveFieldCount = Math.Min(fieldCount - startBound, length);            DapperTable table = null;            return                r =>                {                    if (table == null)                    {                        string[] names = new string[effectiveFieldCount];                        for (int i = 0; i < effectiveFieldCount; i++)                        {                            names[i] = r.GetName(i + startBound);                        }                        table = new DapperTable(names);                    }                    var values = new object[effectiveFieldCount];                    if (returnNullIfFirstMissing)                    {                        values[0] = r.GetValue(startBound);                        if (values[0] is DBNull)                        {                            return null;                        }                    }                    if (startBound == 0)                    {                        r.GetValues(values);                        for (int i = 0; i < values.Length; i++)                            if (values[i] is DBNull) values[i] = null;                    }                    else                    {                        var begin = returnNullIfFirstMissing ? 1 : 0;                        for (var iter = begin; iter < effectiveFieldCount; ++iter)                        {                            object obj = r.GetValue(iter + startBound);                            values[iter] = obj is DBNull ? null : obj;                        }                    }                    return new DapperRow(table, values);                };        }#else        internal static Func<IDataReader, object> GetDictionaryDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)        {            var fieldCount = reader.FieldCount;            if (length == -1)            {                length = fieldCount - startBound;            }            if (fieldCount <= startBound)            {                throw new ArgumentException(MultiMapSplitExceptionMessage, "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];        }        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]        [Obsolete("This method is for internal usage only", false)]        public static bool ReadBool(object value)        {            if (value.GetType() != typeof(bool))            {                return Convert.ToBoolean(value);            }            else                return (bool)value;        }        /// <summary>        /// Internal use only        /// </summary>        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]        [Obsolete("This method is for internal usage only", false)]        public static char? ReadNullableChar(object value)        {            if (value == null || value is DBNull) return null;            string s = value as string;            if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value");            return s[0];        }        /// <summary>        /// Internal use only        /// </summary>        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]        [Obsolete("This method is for internal usage only", true)]        public static IDbDataParameter FindOrAddParameter(IDataParameterCollection parameters, IDbCommand command, string name)        {            IDbDataParameter result;            if (parameters.Contains(name))            {                result = (IDbDataParameter)parameters[name];            }            else            {                result = command.CreateParameter();                result.ParameterName = name;                parameters.Add(result);            }            return result;        }        /// <summary>        /// Internal use only        /// </summary>        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]        [Obsolete("This method is for internal usage only", false)]        public static void PackListParameters(IDbCommand command, string namePrefix, object value)        {            // initially we tried TVP, however it performs quite poorly.            // keep in mind SQL support up to 2000 params easily in sp_executesql, needing more is rare            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));        }        // look for ? / @ / : *by itself*        static readonly Regex smellsLikeOleDb = new Regex(@"(?<![a-zA-Z0-9_])[?@:](?![a-zA-Z0-9_])", RegexOptions.Compiled);                /// <summary>        /// Internal use only        /// </summary>        public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused)        {            Type type = identity.parametersType;                        bool filterParams = false;            if (removeUnused && identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text)            {                filterParams = !smellsLikeOleDb.IsMatch(identity.sql);            }            var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true);            var il = dm.GetILGenerator();            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]            var propsArr = type.GetProperties().Where(p => p.GetIndexParameters().Length == 0).ToArray();            var ctors = type.GetConstructors();            ParameterInfo[] ctorParams;            IEnumerable<PropertyInfo> props = null;            // try to detect tuple patterns, e.g. anon-types, and use that to choose the order            // otherwise: alphabetical            if (ctors.Length == 1 && propsArr.Length == (ctorParams = ctors[0].GetParameters()).Length)            {                // check if reflection was kind enough to put everything in the right order for us                bool ok = true;                for (int i = 0; i < propsArr.Length; i++)                {                    if (!string.Equals(propsArr[i].Name, ctorParams[i].Name, StringComparison.InvariantCultureIgnoreCase))                    {                        ok = false;                        break;                    }                }                if(ok)                {                    // pre-sorted; the reflection gods have smiled upon us                    props = propsArr;                }                else { // might still all be accounted for; check the hard way                    var positionByName = new Dictionary<string,int>(StringComparer.InvariantCultureIgnoreCase);                    foreach(var param in ctorParams)                    {                        positionByName[param.Name] = param.Position;                    }                    if (positionByName.Count == propsArr.Length)                    {                        int[] positions = new int[propsArr.Length];                        ok = true;                        for (int i = 0; i < propsArr.Length; i++)                        {                            int pos;                            if (!positionByName.TryGetValue(propsArr[i].Name, out pos))                            {                                ok = false;                                break;                            }                            positions[i] = pos;                        }                        if (ok)                        {                            Array.Sort(positions, propsArr);                            props = propsArr;                        }                    }                }            }            if(props == null) props = propsArr.OrderBy(x => x.Name);            if (filterParams)            {                props = FilterParameters(props, identity.sql);            }            DapperPocoInfo dpi = null;            if (identity.type != null)            {                dpi = identity.type.GetPocoInfo();            }            foreach (var prop in props)            {                if (filterParams)                {                    if (identity.sql.IndexOf("@" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0                        && 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 (typeof(ICustomQueryParameter).IsAssignableFrom(prop.PropertyType))                {                    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, prop.PropertyType.GetMethod("AddParameter"), null); // stack is now [parameters]                    continue;                }                DbType dbType = LookupDbType(prop.PropertyType, prop.Name);                KeyValuePair<DbType, int>? kvp = null;                if (dbType == DbType.String && dpi != null)//默认所有字符串在Dapper中被param成 DbType.String                {                    kvp = dpi.GetStringColumnMap(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;                }                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                {                    //string parameter extensions  对于字符串参数化的扩展                    int dbTypeValue = (int)dbType;                    if (kvp.HasValue)                    {                        dbTypeValue = (int)kvp.Value.Key;                    }                    il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]                    EmitInt32(il, dbTypeValue);// 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.Clt); // [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 (kvp.HasValue)                    {                        il.Emit(OpCodes.Dup);                        EmitInt32(il, kvp.Value.Value);                        il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty("Size").GetSetMethod(), null); // stack is now [parameters] [[parameters]] [parameter]                    }                }                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 CSHARP30Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing#elseType 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(MultiMapSplitExceptionMessage, "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                                MethodInfo op;                                if ((op = GetOperator(dataType, nullUnderlyingType ?? unboxType)) != null)                                { // this is handy for things like decimal <===> double                                    il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]                                    il.Emit(OpCodes.Call, op); // stack is now [target][target][typed-value]                                }                                else                                {                                    bool handled = true;                                    OpCode opCode = default(OpCode);                                    if (dataTypeCode == TypeCode.Decimal || unboxTypeCode == TypeCode.Decimal)                                    {   // no IL level conversions to/from decimal; I guess we could use the static operators, but                                        // this feels an edge-case                                        handled = false;                                    }                                    else                                    {                                        switch (unboxTypeCode)                                        {                                            case TypeCode.Byte:                                                opCode = OpCodes.Conv_Ovf_I1_Un; break;                                            case TypeCode.SByte:                                                opCode = OpCodes.Conv_Ovf_I1; break;                                            case TypeCode.UInt16:                                                opCode = OpCodes.Conv_Ovf_I2_Un; break;                                            case TypeCode.Int16:                                                opCode = OpCodes.Conv_Ovf_I2; break;                                            case TypeCode.UInt32:                                                opCode = OpCodes.Conv_Ovf_I4_Un; break;                                            case TypeCode.Boolean: // boolean is basically an int, at least at this level                                            case TypeCode.Int32:                                                opCode = OpCodes.Conv_Ovf_I4; break;                                            case TypeCode.UInt64:                                                opCode = OpCodes.Conv_Ovf_I8_Un; break;                                            case TypeCode.Int64:                                                opCode = OpCodes.Conv_Ovf_I8; break;                                            case TypeCode.Single:                                                opCode = OpCodes.Conv_R4; break;                                            case TypeCode.Double:                                                opCode = OpCodes.Conv_R8; break;                                            default:                                                handled = false;                                                break;                                        }                                    }                                    if (handled)                                    { // unbox as the data-type, then use IL-level convert                                        il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]                                        il.Emit(opCode); // stack is now [target][target][typed-value]                                        if (unboxTypeCode == TypeCode.Boolean)                                        { // compare to zero; I checked "csc" - this is the trick it uses; nice                                            il.Emit(OpCodes.Ldc_I4_0);                                            il.Emit(OpCodes.Ceq);                                            il.Emit(OpCodes.Ldc_I4_0);                                            il.Emit(OpCodes.Ceq);                                        }                                    }                                    else                                    { // use flexible conversion                                        il.Emit(OpCodes.Ldtoken, nullUnderlyingType ?? unboxType); // stack is now [target][target][value][member-type-token]                                        il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // stack is now [target][target][value][member-type]                                        il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value]                                        il.Emit(OpCodes.Unbox_Any, nullUnderlyingType ?? unboxType); // stack is now [target][target][typed-value]                                    }                                }                                if (nullUnderlyingType != null)                                {                                    il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]                                }                            }                        }                    }                    if (specializedConstructor == null)                    {                        // Store the value in the property/field                        if (item.Property != null)                        {                            if (type.IsValueType)                            {                                il.Emit(OpCodes.Call, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]                            }                            else                            {                                il.Emit(OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]                            }                        }                        else                        {                            il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]                        }                    }                    il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target]                    il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]                    if (specializedConstructor != null)                    {                        il.Emit(OpCodes.Pop);                        if (item.MemberType.IsValueType)                        {                            int localIndex = il.DeclareLocal(item.MemberType).LocalIndex;                            LoadLocalAddress(il, localIndex);                            il.Emit(OpCodes.Initobj, item.MemberType);                            LoadLocal(il, localIndex);                        }                        else                        {                            il.Emit(OpCodes.Ldnull);                        }                    }                    else                    {                        il.Emit(OpCodes.Pop); // stack is now [target][target]                        il.Emit(OpCodes.Pop); // stack is now [target]                    }                    if (first && returnNullIfFirstMissing)                    {                        il.Emit(OpCodes.Pop);                        il.Emit(OpCodes.Ldnull); // stack is now [null]                        il.Emit(OpCodes.Stloc_1);                        il.Emit(OpCodes.Br, allDone);                    }                    il.MarkLabel(finishLabel);                }                first = false;                index += 1;            }            if (type.IsValueType)            {                il.Emit(OpCodes.Pop);            }            else            {                if (specializedConstructor != null)                {                    il.Emit(OpCodes.Newobj, specializedConstructor);                }                il.Emit(OpCodes.Stloc_1); // stack is empty            }            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>));        }        static MethodInfo GetOperator(Type from, Type to)        {            if (to == null) return null;            MethodInfo[] fromMethods, toMethods;            return ResolveOperator(fromMethods = from.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit")                ?? ResolveOperator(toMethods = to.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit")                ?? ResolveOperator(fromMethods, from, to, "op_Explicit")                ?? ResolveOperator(toMethods, from, to, "op_Explicit");        }        static MethodInfo ResolveOperator(MethodInfo[] methods, Type from, Type to, string name)        {            for (int i = 0; i < methods.Length; i++)            {                if (methods[i].Name != name || methods[i].ReturnType != to) continue;                var args = methods[i].GetParameters();                if (args.Length != 1 || args[0].ParameterType != from) continue;                return methods[i];            }            return null;        }        private static void LoadLocal(ILGenerator il, int index)        {            if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");            switch (index)            {                case 0: il.Emit(OpCodes.Ldloc_0); break;                case 1: il.Emit(OpCodes.Ldloc_1); break;                case 2: il.Emit(OpCodes.Ldloc_2); break;                case 3: il.Emit(OpCodes.Ldloc_3); break;                default:                    if (index <= 255)                    {                        il.Emit(OpCodes.Ldloc_S, (byte)index);                    }                    else                    {                        il.Emit(OpCodes.Ldloc, (short)index);                    }                    break;            }        }        private static void StoreLocal(ILGenerator il, int index)        {            if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");            switch (index)            {                case 0: il.Emit(OpCodes.Stloc_0); break;                case 1: il.Emit(OpCodes.Stloc_1); break;                case 2: il.Emit(OpCodes.Stloc_2); break;                case 3: il.Emit(OpCodes.Stloc_3); break;                default:                    if (index <= 255)                    {                        il.Emit(OpCodes.Stloc_S, (byte)index);                    }                    else                    {                        il.Emit(OpCodes.Stloc, (short)index);                    }                    break;            }        }        private static void LoadLocalAddress(ILGenerator il, int index)        {            if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");            if (index <= 255)            {                il.Emit(OpCodes.Ldloca_S, (byte)index);            }            else            {                il.Emit(OpCodes.Ldloca, (short)index);            }        }        /// <summary>        /// Throws a data exception, only used internally        /// </summary>        /// <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>        /// How should connection strings be compared for equivalence? Defaults to StringComparer.Ordinal.        /// Providing a custom implementation can be useful for allowing multi-tenancy databases with identical        /// schema to share startegies. Note that usual equivalence rules apply: any equivalent connection strings        /// <b>MUST</b> yield the same hash-code.        /// </summary>        public static IEqualityComparer<string> ConnectionStringComparer        {            get { return connectionStringComparer; }            set { connectionStringComparer = value ?? StringComparer.Ordinal; }        }        private static IEqualityComparer<string> connectionStringComparer = StringComparer.Ordinal;        /// <summary>        /// The grid reader provides interfaces for reading multiple result sets from a Dapper query         /// </summary>        public partial class GridReader : IDisposable        {            private IDataReader reader;            private IDbCommand command;            private Identity identity;            internal GridReader(IDbCommand command, IDataReader reader, Identity identity)            {                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, TSixth, TSeventh, TReturn>(object func, string splitOn)            {                var identity = this.identity.ForGrid(typeof(TReturn), new Type[] {                     typeof(TFirst),                     typeof(TSecond),                    typeof(TThird),                    typeof(TFourth),                    typeof(TFifth),                    typeof(TSixth),                    typeof(TSeventh)                }, gridIndex);                try                {                    foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, 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, 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, DontMap, DontMap, TReturn>(func, splitOn);                return buffered ? result.ToList() : result;            }#if CSHARP30            /// <summary>            /// Read multiple objects from a single record set on the grid            /// </summary>            public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn)            {                return Read<TFirst, TSecond, TThird, TFourth, TReturn>(func, splitOn, true);            }#endif            /// <summary>            /// Read multiple objects from a single record set on the grid            /// </summary>#if CSHARP30            public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn, bool buffered)#else            public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id", bool buffered = true)#endif            {                var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(func, splitOn);                return buffered ? result.ToList() : result;            }#if !CSHARP30            /// <summary>            /// Read multiple objects from a single record set on the grid            /// </summary>            public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> func, string splitOn = "id", bool buffered = true)            {                var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(func, splitOn);                return buffered ? result.ToList() : result;            }            /// <summary>            /// Read multiple objects from a single record set on the grid            /// </summary>            public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> func, string splitOn = "id", bool buffered = true)            {                var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(func, splitOn);                return buffered ? result.ToList() : result;            }            /// <summary>            /// Read multiple objects from a single record set on the grid            /// </summary>            public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> func, string splitOn = "id", bool buffered = true)            {                var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(func, splitOn);                return buffered ? result.ToList() : result;            }#endif            private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity)            {                try                {                    while (index == gridIndex && reader.Read())                    {                        yield return (T)deserializer(reader);                    }                }                finally // finally so that First etc progresses things even when multiple rows                {                    if (index == gridIndex)                    {                        NextResult();                    }                }            }            private int gridIndex, readCount;            private bool consumed;            private 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);        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()        {            RemoveUnused = true;        }        /// <summary>        /// construct a dynamic parameter bag        /// </summary>        /// <param name="template">can be an anonymous type or a DynamicParameters bag</param>        public DynamicParameters(object template)        {            RemoveUnused = true;            AddDynamicParams(template);        }        /// <summary>        /// Append a whole object full of params to the dynamic        /// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic        /// </summary>        /// <param name="param"></param>        public void AddDynamicParams(#if CSHARP30object param#elsedynamic 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 CSHARP30string name, object value, DbType? dbType, ParameterDirection? direction, int? size#elsestring name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null#endif)        {            parameters[Clean(name)] = new ParamInfo() { Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType, Size = size };        }        static string Clean(string name)        {            if (!string.IsNullOrEmpty(name))            {                switch (name[0])                {                    case '@':                    case ':':                    case '?':                        return name.Substring(1);                }            }            return name;        }        void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)        {            AddParameters(command, identity);        }        /// <summary>        /// If true, the command-text is inspected and only values that are clearly used are included on the connection        /// </summary>        public bool RemoveUnused { get; set; }        /// <summary>        /// Add all the parameters needed to the command just before it executes        /// </summary>        /// <param name="command">The raw command prior to execution</param>        /// <param name="identity">Information about the query</param>        protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)        {            if (templates != null)            {                foreach (var template in templates)                {                    var newIdent = identity.ForDynamicParameters(template.GetType());                    Action<IDbCommand, object> appender;                    lock (paramReaderCache)                    {                        if (!paramReaderCache.TryGetValue(newIdent, out appender))                        {                            appender = SqlMapper.CreateParamInfoGenerator(newIdent, true, RemoveUnused);                            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 : Dapper.SqlMapper.ICustomQueryParameter    {        /// <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}

Nunits测试代码

1、对数据操作部分的测试

using System;using System.Linq;using NUnit.Framework;using System.Data.SqlClient;using Dapper;using System.Collections;using System.Collections.Generic;namespace DapperTest.DapperExtensions{    [Serializable]    public class AutoCreate    {        public int ID { get; set; }        public string Name { get; set; }    }    [TestFixture]    public class DataOperateTest    {        private static string connectionStr = @"Server=W-PC\DEMO; Database=ByteartRetail; Integrated Security=True; MultipleActiveResultSets=True;";        SqlConnection conn;        [TestFixtureSetUp]        public void InitSqlConnection()        {            DapperPocoInfo dpi = typeof(AutoCreate).GetPocoInfo();            dpi.AddStringColumnMap("Name");            dpi.TableName = "Tbl_AutoCreate";            conn = new SqlConnection(connectionStr);            conn.Open();        }        [TestFixtureTearDown]        public void CloseConnection()        {            conn.Close();        }        [Test]        public void IUQDTypedTest()        {            AutoCreate poco = new AutoCreate();            poco.Name = DateTime.Now.ToString("yyMMddHHmmssfff");            bool success = conn.Insert<AutoCreate>(poco);            Assert.AreEqual(success, true);            Assert.AreNotEqual(0, poco.ID);            poco.Name = "UU" + DateTime.Now.ToString("yyMMddHHmmssfff");            success = conn.Update<AutoCreate>(poco);            Assert.AreEqual(success, true);            AutoCreate pocoQuery = conn.QueryByKey<AutoCreate>(new { ID = poco.ID });            Assert.AreEqual(poco.Name, pocoQuery.Name);            success = conn.DeleteByKey<AutoCreate>(new { ID = poco.ID });            Assert.AreEqual(true, success);            pocoQuery = conn.QueryByKey<AutoCreate>(poco);            Assert.IsNull(pocoQuery);        }        [Test]        public void IUQDDynamicTest()        {            //AutoCreate tmp = new AutoCreate();            string name = DateTime.Now.ToString("yyMMddHHmmssfff");            var poco = new { ID = 0, Name = name };            MsSqlServerAdapter adapter = (MsSqlServerAdapter)conn.GetSqlAdapter();            bool success = adapter.Insert<AutoCreate>(conn, poco);            Assert.AreEqual(success, true);            //Assert.AreNotEqual(0, poco.ID);            int id;            //id = adapter.GetLastInsertID<AutoCreate>(conn);            id = (int)conn.GetLastInsertIndentityID<AutoCreate>();            Assert.Greater(id, 0);            AutoCreate pocoQuery = conn.QueryByKey<AutoCreate>(new { ID = id });            Assert.AreEqual(poco.Name, pocoQuery.Name);//测试插入的数据是否正确            name = "UU" + DateTime.Now.ToString("yyMMddHHmmssfff");            //success = adapter.UpdateByKey<AutoCreate>(conn, new { ID = id, Name = name });            success = conn.Update<AutoCreate>(new { ID = id, Name = name });            Assert.AreEqual(success, true);            pocoQuery = conn.QueryByKey<AutoCreate>(new { ID = id });            Assert.AreEqual(name, pocoQuery.Name);//测试插入的数据是否正确            success = conn.DeleteByKey<AutoCreate>(new { ID = id });            Assert.AreEqual(true, success);            pocoQuery = conn.QueryByKey<AutoCreate>(new { ID = id });            Assert.IsNull(pocoQuery);        }    }}

2、对POCO扩展部分的测试

using System;namespace DapperTest{    [Serializable]    public class TestEndWithIDModel    {        public byte ID { get; set; }        public string Name { get; set; }    }    [Serializable]    public class TestNotEndWithIDModel    {        public int OrderNum { get; set; }        public string OrderName { get; set; }    }}

对ID结尾的主键测试

using System.Linq;using NUnit.Framework;using Dapper;namespace DapperTest.PocoInfo{    [TestFixture]    public class PocoInfoEndWithIDKeyTest    {        DapperPocoInfo dpi;        [SetUp]        public void GetDapperPocoInfo()        {            dpi = typeof(TestEndWithIDModel).GetPocoInfo();            //dpi.AddKeyMap("ID");//此行代码注销以下测试也是正确的        }        [Test]        public void TestModelKeyOnlyOneTest()        {            Assert.AreEqual(1, dpi.KeyProperties.Count());            Assert.AreEqual("ID", dpi.KeyProperties.First().Name);            Assert.AreEqual(true, dpi.IsUnWriteKey());        }    }}

非ID结尾的主键测试

using System.Linq;using NUnit.Framework;using Dapper;namespace DapperTest.PocoInfo{    [TestFixture]    public class PocoInfoNotEndWithIDKeyTest    {        DapperPocoInfo dpi;        [SetUp]        public void GetDapperPocoInfo()        {            dpi = typeof(TestNotEndWithIDModel).GetPocoInfo();            dpi.AddKeyMap("OrderNum");        }        [Test]        public void TestModelKeyOnlyOneTest()        {            Assert.AreEqual(1, dpi.KeyProperties.Count());            Assert.AreEqual("OrderNum", dpi.KeyProperties.First().Name);            Assert.AreEqual(true, dpi.IsUnWriteKey());        }    }}

字符串映射测试

using System.Collections.Generic;using NUnit.Framework;using Dapper;using System.Data;namespace DapperTest.PocoInfo{    [TestFixture]    public class PocoInfoStringColumnMapTest    {        KeyValuePair<DbType, int>? kvp;        [SetUp]        public void GetBasicInfo()        {            DapperPocoInfo dpi = typeof(TestEndWithIDModel).GetPocoInfo();            dpi.AddStringColumnMap("Name");            kvp = dpi.GetStringColumnMap("Name");        }        [Test]        public void TestModelMappedStringColumnMapTest()        {            Assert.NotNull(kvp);            Assert.AreEqual(DbType.AnsiString, kvp.Value.Key);            Assert.AreEqual(50, kvp.Value.Value);        }    }}

原创粉丝点击