Dapper的完整扩展(转)
真心想说:其实。。。我不想用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 CSHARP30
- this IDbConnection cnn, string sql,Type type, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
- #else
- this 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 CSHARP30
- this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
- #else
- this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
- #endif
- )
- 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 CSHARP30
- this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
- #else
- this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
- #endif
- )
- {
- 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 CSHARP30
- this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType
- #else
- this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null
- #endif
- )
- {
- var data = QueryInternal<T>(cnn, sql, param as object, transaction, commandTimeout, commandType);
- return buffered ? data.ToList() : data;
- }
- /// <summary>
- /// Execute a command that returns multiple result sets, and access each in turn
- /// </summary>
- public static GridReader QueryMultiple(
- #if CSHARP30
- this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
- #else
- this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
- #endif
- )
- {
- Identity identity = new Identity(sql, commandType, cnn, typeof(GridReader), (object)param == null ? null : ((object)param).GetType(), null);
- CacheInfo info = GetCacheInfo(identity);
- IDbCommand cmd = null;
- IDataReader reader = null;
- bool wasClosed = cnn.State == ConnectionState.Closed;
- try
- {
- if (wasClosed) cnn.Open();
- cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType);
- reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
- var result = new GridReader(cmd, reader, identity);
- wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
- // with the CloseConnection flag, so the reader will deal with the connection; we
- // still need something in the "finally" to ensure that broken SQL still results
- // in the connection closing itself
- return result;
- }
- catch
- {
- if (reader != null)
- {
- if (!reader.IsClosed) try { cmd.Cancel(); }
- catch { /* don't spoil the existing exception */ }
- reader.Dispose();
- }
- if (cmd != null) cmd.Dispose();
- if (wasClosed) cnn.Close();
- throw;
- }
- }
- /// <summary>
- /// Return a typed list of objects, reader is closed after the call
- /// </summary>
- private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType)
- {
- var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
- var info = GetCacheInfo(identity);
- IDbCommand cmd = null;
- IDataReader reader = null;
- bool wasClosed = cnn.State == ConnectionState.Closed;
- try
- {
- cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType);
- if (wasClosed) cnn.Open();
- reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
- wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
- // with the CloseConnection flag, so the reader will deal with the connection; we
- // still need something in the "finally" to ensure that broken SQL still results
- // in the connection closing itself
- var tuple = info.Deserializer;
- int hash = GetColumnHash(reader);
- if (tuple.Func == null || tuple.Hash != hash)
- {
- tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false));
- SetQueryCache(identity, info);
- }
- var func = tuple.Func;
- while (reader.Read())
- {
- yield return (T)func(reader);
- }
- // happy path; close the reader cleanly - no
- // need for "Cancel" etc
- reader.Dispose();
- reader = null;
- }
- finally
- {
- if (reader != null)
- {
- if (!reader.IsClosed) try { cmd.Cancel(); }
- catch { /* don't spoil the existing exception */ }
- reader.Dispose();
- }
- if (wasClosed) cnn.Close();
- if (cmd != null) cmd.Dispose();
- }
- }
- /// <summary>
- /// Maps a query to objects
- /// </summary>
- /// <typeparam name="TFirst">The first type in the recordset</typeparam>
- /// <typeparam name="TSecond">The second type in the recordset</typeparam>
- /// <typeparam name="TReturn">The return type</typeparam>
- /// <param name="cnn"></param>
- /// <param name="sql"></param>
- /// <param name="map"></param>
- /// <param name="param"></param>
- /// <param name="transaction"></param>
- /// <param name="buffered"></param>
- /// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
- /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
- /// <param name="commandType">Is it a stored proc or a batch?</param>
- /// <returns></returns>
- public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(
- #if CSHARP30
- this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
- #else
- this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
- #endif
- )
- {
- return MultiMap<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
- }
- /// <summary>
- /// Maps a query to objects
- /// </summary>
- /// <typeparam name="TFirst"></typeparam>
- /// <typeparam name="TSecond"></typeparam>
- /// <typeparam name="TThird"></typeparam>
- /// <typeparam name="TReturn"></typeparam>
- /// <param name="cnn"></param>
- /// <param name="sql"></param>
- /// <param name="map"></param>
- /// <param name="param"></param>
- /// <param name="transaction"></param>
- /// <param name="buffered"></param>
- /// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
- /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
- /// <param name="commandType"></param>
- /// <returns></returns>
- public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(
- #if CSHARP30
- this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
- #else
- this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
- #endif
- )
- {
- return MultiMap<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
- }
- /// <summary>
- /// Perform a multi mapping query with 4 input parameters
- /// </summary>
- /// <typeparam name="TFirst"></typeparam>
- /// <typeparam name="TSecond"></typeparam>
- /// <typeparam name="TThird"></typeparam>
- /// <typeparam name="TFourth"></typeparam>
- /// <typeparam name="TReturn"></typeparam>
- /// <param name="cnn"></param>
- /// <param name="sql"></param>
- /// <param name="map"></param>
- /// <param name="param"></param>
- /// <param name="transaction"></param>
- /// <param name="buffered"></param>
- /// <param name="splitOn"></param>
- /// <param name="commandTimeout"></param>
- /// <param name="commandType"></param>
- /// <returns></returns>
- public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>(
- #if CSHARP30
- this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
- #else
- this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
- #endif
- )
- {
- return MultiMap<TFirst, TSecond, TThird, TFourth, DontMap, 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 CSHARP30
- Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
- #else
- Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false
- #endif
- )
- {
- var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(IDataReader) }, true);
- var il = dm.GetILGenerator();
- il.DeclareLocal(typeof(int));
- il.DeclareLocal(type);
- il.Emit(OpCodes.Ldc_I4_0);
- il.Emit(OpCodes.Stloc_0);
- if (length == -1)
- {
- length = reader.FieldCount - startBound;
- }
- if (reader.FieldCount <= startBound)
- {
- throw new ArgumentException(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 CSHARP30
- object param
- #else
- dynamic param
- #endif
- )
- {
- var obj = param as object;
- if (obj != null)
- {
- var subDynamic = obj as DynamicParameters;
- if (subDynamic == null)
- {
- var dictionary = obj as IEnumerable<KeyValuePair<string, object>>;
- if (dictionary == null)
- {
- templates = templates ?? new List<object>();
- templates.Add(obj);
- }
- else
- {
- foreach (var kvp in dictionary)
- {
- #if CSHARP30
- Add(kvp.Key, kvp.Value, null, null, null);
- #else
- Add(kvp.Key, kvp.Value);
- #endif
- }
- }
- }
- else
- {
- if (subDynamic.parameters != null)
- {
- foreach (var kvp in subDynamic.parameters)
- {
- parameters.Add(kvp.Key, kvp.Value);
- }
- }
- if (subDynamic.templates != null)
- {
- templates = templates ?? new List<object>();
- foreach (var t in subDynamic.templates)
- {
- templates.Add(t);
- }
- }
- }
- }
- }
- /// <summary>
- /// Add a parameter to this dynamic parameter list
- /// </summary>
- /// <param name="name"></param>
- /// <param name="value"></param>
- /// <param name="dbType"></param>
- /// <param name="direction"></param>
- /// <param name="size"></param>
- public void Add(
- #if CSHARP30
- string name, object value, DbType? dbType, ParameterDirection? direction, int? size
- #else
- string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null
- #endif
- )
- {
- parameters[Clean(name)] = new ParamInfo() { Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType, Size = size };
- }
- static string Clean(string name)
- {
- if (!string.IsNullOrEmpty(name))
- {
- switch (name[0])
- {
- case '@':
- case ':':
- case '?':
- return name.Substring(1);
- }
- }
- return name;
- }
- void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
- {
- AddParameters(command, identity);
- }
- /// <summary>
- /// 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);
- }
- }
- }