真心想说:其实。。。我不想用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
{
///
/// Dapper扩展POCO信息类,为应用此扩展,在原Dapper的第1965行增加了对此类的应用
///
public class DapperPocoInfo
{
private Type _type;
private ConcurrentDictionary> _stringColumns
= new ConcurrentDictionary>();
private IEnumerable _typeProperties;
private IList _keyProperties = new List();
private IEnumerable _defaultKeyPropeties;
private string _tableName;
///
/// 对应表名
///
public string TableName
{
get
{
if (string.IsNullOrWhiteSpace(this._tableName))
{
this._tableName = this._type.Name;
}
return this._tableName;
}
set
{
this._tableName = value;
}
}
///
/// 所有Public的属性集合
///
public IEnumerable TypeProperties
{
get { return this._typeProperties; }
}
///
/// 获取主键集合,如果主键数量大于1,则认为是联合主键,等于0认为不存在主键,等于1并且类型为数字型则认为自增主键
///
///
public IEnumerable 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;
}
}
}
///
/// .oct
///
///
internal DapperPocoInfo(Type type)
{
if (type == null)
{
throw new ArgumentNullException();
}
this._type = type;
this._typeProperties = type.GetProperties();
}
///
/// 添加字符串参数化映射
///
/// 属性名
/// 必须为AnsiString、AnsiStringFixedLength、String、StringFixedLength
/// 值范围为1~8000
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, len));
}
///
/// 获取字符串映射
///
///
///
public KeyValuePair? GetStringColumnMap(string propertyName)
{
KeyValuePair 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;
}
///
/// 添加主键映射
///
///
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;//赋值时取消已经确认的是否可写键值
}
}
///
/// 不需要插入数据的主键类型,除了Guid,其它均认为自增
///
private static Type[] UnWriteTypes = { typeof(int), typeof(short), typeof(long), typeof(byte), typeof(Guid) };
private bool? _unWriteKey;
///
/// 主键是否可写
///
///
public bool IsUnWriteKey()
{
if (!this._unWriteKey.HasValue)
{
this._unWriteKey = false;
IList keys = this.KeyProperties.ToList();
if (keys.Count == 1)
{
this._unWriteKey = UnWriteTypes.Contains(keys[0].PropertyType);
}
}
return this._unWriteKey.Value;
}
}
///
/// Dapper扩展类
///
public static class DapperExtensions
{
private static ConcurrentDictionary PocoInfos
= new ConcurrentDictionary();
///
/// 已实现的ISqlAdapter集合
///
private static readonly Dictionary AdapterDictionary
= new Dictionary() {
{"sqlconnection", new MsSqlServerAdapter()}
};
public static DapperPocoInfo GetPocoInfo()
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;
}
///
/// 新增数据,如果T只有一个主键,且新增的主键为数字,则会将新增的主键赋值给entity相应的字段,如果为多主键,或主键不是数字和Guid,则主键需输入
/// 如果要进行匿名类型新增,因为匿名类型无法赋值,需调用ISqlAdapter的Insert,由数据库新增的主键需自己写方法查询
///
///
///
///
///
///
///
public static bool Insert(this IDbConnection connection, T entity, IDbTransaction transaction = null, int? commandTimeout = null)
where T : class
{
ISqlAdapter adapter = GetSqlAdapter(connection);
return adapter.Insert(connection, entity, transaction, commandTimeout);
}
///
/// 更新数据,如果entity为T,则全字段更新,如果为匿名类型,则修改包含的字段,但匿名类型必须包含主键对应的字段
///
///
///
///
///
///
///
public static bool Update(this IDbConnection connection, object entity, IDbTransaction transaction = null, int? commandTimeout = null)
where T : class
{
ISqlAdapter adapter = GetSqlAdapter(connection);
return adapter.UpdateByKey(connection, entity, transaction, commandTimeout);
}
///
/// 删除数据,支持匿名,但匿名类型必须包含主键对应的字段
///
///
///
///
///
///
///
public static bool DeleteByKey(this IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null)
where T : class
{
ISqlAdapter adapter = GetSqlAdapter(connection);
return adapter.DeleteByKey(connection, param, transaction, commandTimeout);
}
///
/// 查询数据,支持匿名,但匿名类型必须包含主键对应的字段
///
///
///
///
///
///
///
public static T QueryByKey(this IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null)
where T : class
{
ISqlAdapter adapter = GetSqlAdapter(connection);
return adapter.QueryByKey(connection, param, transaction, commandTimeout);
}
///
/// 获取最后新增的ID值,仅对Indentity有效
///
///
///
///
public static long GetLastInsertIndentityID(this IDbConnection connection)
where T : class
{
ISqlAdapter adapter = GetSqlAdapter(connection);
return adapter.GetLastInsertID(connection);
}
}
public interface ISqlAdapter
{
string GetFullQueryByKeySql(Type type);
string GetFullInsertSql(Type type);
string GetFullUpdateByKeySql(Type type);
string GetDeleteByKeySql(Type type);
bool Insert(IDbConnection connection, object entity, IDbTransaction transaction, int? commandTimeout)
where T : class;
bool UpdateByKey(IDbConnection connection, object entity, IDbTransaction transaction, int? commandTimeout)
where T : class;
bool DeleteByKey(IDbConnection connection, object param, IDbTransaction transaction, int? commandTimeout)
where T : class;
T QueryByKey(IDbConnection connection, object param, IDbTransaction transaction, int? commandTimeout)
where T : class;
long GetLastInsertID(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 BasicSqls
= new ConcurrentDictionary();
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 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 props)
{
StringBuilder tmp = new StringBuilder();
tmp.Append("INSERT INTO ");
tmp.AppendLine(dpi.TableName);
tmp.Append('(');
IEnumerable 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 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 GetExceptProps(IEnumerable props1, IEnumerable props2)
{
//return props1.Except(props2, new EqualityCompareProperty()).ToArray();
IList list = new List();
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 props)
{
StringBuilder tmp = new StringBuilder();
tmp.Append("UPDATE ");
tmp.AppendLine(dpi.TableName);
tmp.Append("SET ");
IEnumerable 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(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(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(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(updateSql, entity, transaction, commandTimeout) > 0;
}
public bool DeleteByKey(IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
string deleteSql = this.GetDeleteByKeySql(typeof(T));
return connection.Execute(deleteSql, param, transaction: transaction, commandTimeout: commandTimeout) > 0;
}
public T QueryByKey(IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
string querySql = this.GetFullQueryByKeySql(typeof(T));
return connection.Query(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(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
{
///
/// Dapper, a light weight object mapper for ADO.NET
///
static partial class SqlMapper
{
///
/// Implement this interface to pass an arbitrary db specific set of parameters to Dapper
///
public partial interface IDynamicParameters
{
///
/// Add all the parameters needed to the command just before it executes
///
/// The raw command prior to execution
/// Information about the query
void AddParameters(IDbCommand command, Identity identity);
}
///
/// Implement this interface to pass an arbitrary db specific parameter to Dapper
///
public interface ICustomQueryParameter
{
///
/// Add the parameter needed to the command before it executes
///
/// The raw command prior to execution
/// Parameter name
void AddParameter(IDbCommand command, string name);
}
///
/// Implement this interface to change default mapping of reader columns to type memebers
///
public interface ITypeMap
{
///
/// Finds best constructor
///
/// DataReader column names
/// DataReader column types
/// Matching constructor or default one
ConstructorInfo FindConstructor(string[] names, Type[] types);
///
/// Gets mapping for constructor parameter
///
/// Constructor to resolve
/// DataReader column name
/// Mapping implementation
IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName);
///
/// Gets member mapping for column
///
/// DataReader column name
/// Mapping implementation
IMemberMap GetMember(string columnName);
}
///
/// Implements this interface to provide custom member mapping
///
public interface IMemberMap
{
///
/// Source DataReader column name
///
string ColumnName { get; }
///
/// Target member type
///
Type MemberType { get; }
///
/// Target property
///
PropertyInfo Property { get; }
///
/// Target field
///
FieldInfo Field { get; }
///
/// Target constructor parameter
///
ParameterInfo Parameter { get; }
}
static Link> bindByNameCache;
static Action GetBindByName(Type commandType)
{
if (commandType == null) return null; // GIGO
Action action;
if (Link>.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)method.CreateDelegate(typeof(Action));
}
// cache it
Link>.TryAdd(ref bindByNameCache, commandType, ref action);
return action;
}
///
/// 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.
///
partial class Link where TKey : class
{
public static bool TryGet(Link 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 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(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 tail)
{
Key = key;
Value = value;
Tail = tail;
}
public TKey Key { get; private set; }
public TValue Value { get; private set; }
public Link Tail { get; private set; }
}
partial class CacheInfo
{
public DeserializerState Deserializer { get; set; }
public Func[] OtherDeserializers { get; set; }
public Action 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 Func;
public DeserializerState(int hash, Func func)
{
Hash = hash;
Func = func;
}
}
///
/// Called if the query cache is purged via PurgeQueryCache
///
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 _queryCache = new Dictionary();
// 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);
}
}
///
/// Purge the query cache
///
public static void PurgeQueryCache()
{
lock (_queryCache)
{
_queryCache.Clear();
}
OnQueryCachePurged();
}
#else
static readonly System.Collections.Concurrent.ConcurrentDictionary _queryCache = new System.Collections.Concurrent.ConcurrentDictionary();
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;
}
///
/// Purge the query cache
///
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);
}
}
///
/// Return a count of all the cached queries by dapper
///
///
public static int GetCachedSQLCount()
{
return _queryCache.Count;
}
///
/// Return a list of all the queries cached by dapper
///
///
///
public static IEnumerable> 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;
}
///
/// Deep diagnostics only: find any hash collisions in the cache
///
///
public static IEnumerable> GetHashCollissions()
{
var counts = new Dictionary();
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 typeMap;
static SqlMapper()
{
typeMap = new Dictionary();
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;
}
///
/// Configire the specified type to be mapped to a given db-type
///
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));
}
///
/// Identity of a cached query in Dapper, used for extensability
///
public partial class Identity : IEquatable
{
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);
}
///
/// Create an identity for use with DynamicParameters, internal use only
///
///
///
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());
}
}
///
///
///
///
///
public override bool Equals(object obj)
{
return Equals(obj as Identity);
}
///
/// The sql
///
public readonly string sql;
///
/// The command type
///
public readonly CommandType? commandType;
///
///
///
public readonly int hashCode, gridIndex;
///
///
///
public readonly Type type;
///
///
///
public readonly string connectionString;
///
///
///
public readonly Type parametersType;
///
///
///
///
public override int GetHashCode()
{
return hashCode;
}
///
/// Compare 2 Identity objects
///
///
///
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
///
/// Execute parameterized SQL
///
/// Number of rows affected
public static int Execute(this IDbConnection cnn, string sql, object param)
{
return Execute(cnn, sql, param, null, null, null);
}
///
/// Execute parameterized SQL
///
/// Number of rows affected
public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return Execute(cnn, sql, param, transaction, null, null);
}
///
/// Execute parameterized SQL
///
/// Number of rows affected
public static int Execute(this IDbConnection cnn, string sql, object param, CommandType commandType)
{
return Execute(cnn, sql, param, null, null, commandType);
}
///
/// Execute parameterized SQL
///
/// Number of rows affected
public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
{
return Execute(cnn, sql, param, transaction, null, commandType);
}
///
/// Executes a query, returning the data typed as per T
///
/// 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).
///
public static IEnumerable Query(this IDbConnection cnn, string sql, object param)
{
return Query(cnn, sql, param, null, true, null, null);
}
///
/// Executes a query, returning the data typed as per T
///
/// 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).
///
public static IEnumerable Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return Query(cnn, sql, param, transaction, true, null, null);
}
///
/// Executes a query, returning the data typed as per T
///
/// 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).
///
public static IEnumerable Query(this IDbConnection cnn, string sql, object param, CommandType commandType)
{
return Query(cnn, sql, param, null, true, null, commandType);
}
///
/// Executes a query, returning the data typed as per T
///
/// 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).
///
public static IEnumerable Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
{
return Query(cnn, sql, param, transaction, true, null, commandType);
}
///
/// Execute a command that returns multiple result sets, and access each in turn
///
public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return QueryMultiple(cnn, sql, param, transaction, null, null);
}
///
/// Execute a command that returns multiple result sets, and access each in turn
///
public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, CommandType commandType)
{
return QueryMultiple(cnn, sql, param, null, null, commandType);
}
///
/// Execute a command that returns multiple result sets, and access each in turn
///
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);
}
///
/// Execute parameterized SQL
///
/// Number of rows affected
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
)
where T : class
{
return Execute(cnn, sql, typeof(T), param, transaction, commandTimeout, commandType);
}
///
/// Execute parameterized SQL
///
/// Number of rows affected
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
///
/// Return a list of dynamic objects, reader is closed after the call
///
public static IEnumerable Query(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
return Query(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType);
}
#else
///
/// Return a list of dynamic objects, reader is closed after the call
///
public static IEnumerable> Query(this IDbConnection cnn, string sql, object param)
{
return Query(cnn, sql, param, null, true, null, null);
}
///
/// Return a list of dynamic objects, reader is closed after the call
///
public static IEnumerable> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
{
return Query(cnn, sql, param, transaction, true, null, null);
}
///
/// Return a list of dynamic objects, reader is closed after the call
///
public static IEnumerable> Query(this IDbConnection cnn, string sql, object param, CommandType? commandType)
{
return Query(cnn, sql, param, null, true, null, commandType);
}
///
/// Return a list of dynamic objects, reader is closed after the call
///
public static IEnumerable> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType? commandType)
{
return Query(cnn, sql, param, transaction, true, null, commandType);
}
///
/// Return a list of dynamic objects, reader is closed after the call
///
public static IEnumerable> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType)
{
return Query>(cnn, sql, param, transaction, buffered, commandTimeout, commandType);
}
#endif
///
/// Executes a query, returning the data typed as per T
///
/// 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
/// 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).
///
public static IEnumerable Query(
#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(cnn, sql, param as object, transaction, commandTimeout, commandType);
return buffered ? data.ToList() : data;
}
///
/// Execute a command that returns multiple result sets, and access each in turn
///
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;
}
}
///
/// Return a typed list of objects, reader is closed after the call
///
private static IEnumerable QueryInternal(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();
}
}
///
/// Maps a query to objects
///
/// The first type in the recordset
/// The second type in the recordset
/// The return type
///
///
///
///
///
///
/// The Field we should split and read the second object from (default: id)
/// Number of seconds before command execution timeout
/// Is it a stored proc or a batch?
///
public static IEnumerable Query(
#if CSHARP30
this IDbConnection cnn, string sql, Func map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
return MultiMap(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
}
///
/// Maps a query to objects
///
///
///
///
///
///
///
///
///
///
///
/// The Field we should split and read the second object from (default: id)
/// Number of seconds before command execution timeout
///
///
public static IEnumerable Query(
#if CSHARP30
this IDbConnection cnn, string sql, Func map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
return MultiMap(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
}
///
/// Perform a multi mapping query with 4 input parameters
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
public static IEnumerable Query(
#if CSHARP30
this IDbConnection cnn, string sql, Func map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
#else
this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
#endif
)
{
return MultiMap(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
}
#if !CSHARP30
///
/// Perform a multi mapping query with 5 input parameters
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
public static IEnumerable Query(
this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
)
{
return MultiMap(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
}
///
/// Perform a multi mapping query with 6 input parameters
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
public static IEnumerable Query(
this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
)
{
return MultiMap(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
}
///
/// Perform a multi mapping query with 7 input parameters
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return MultiMap(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
}
#endif
partial class DontMap { }
static IEnumerable MultiMap(
this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType)
{
var results = MultiMapImpl(cnn, sql, map, param, transaction, splitOn, commandTimeout, commandType, null, null);
return buffered ? results.ToList() : results;
}
static IEnumerable MultiMapImpl(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[] 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 mapIt = GenerateMapper(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 GenerateMapper(Func deserializer, Func[] otherDeserializers, object map)
{
switch (otherDeserializers.Length)
{
case 1:
return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r));
case 2:
return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r));
case 3:
return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r));
#if !CSHARP30
case 4:
return r => ((Func)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)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)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[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader)
{
int current = 0;
var splits = splitOn.Split(',').ToArray();
var splitIndex = 0;
Func 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>();
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>).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 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)))
{
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 fieldNameLookup;
internal string[] FieldNames { get { return fieldNames; } }
public DapperTable(string[] fieldNames)
{
if (fieldNames == null) throw new ArgumentNullException("fieldNames");
this.fieldNames = fieldNames;
fieldNameLookup = new Dictionary(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).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
{
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>.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> 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(names[i], value);
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#region Implementation of ICollection>
void ICollection>.Add(KeyValuePair item)
{
IDictionary dic = this;
dic.Add(item.Key, item.Value);
}
void ICollection>.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>.Contains(KeyValuePair item)
{
object value;
return TryGetValue(item.Key, out value) && Equals(value, item.Value);
}
void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
{
foreach (var kv in this)
{
array[arrayIndex++] = kv; // if they didn't leave enough space; not our fault
}
}
bool ICollection>.Remove(KeyValuePair item)
{
IDictionary dic = this;
return dic.Remove(item.Key);
}
bool ICollection>.IsReadOnly
{
get { return false; }
}
#endregion
#region Implementation of IDictionary
bool IDictionary.ContainsKey(string key)
{
int index = table.IndexOfName(key);
if (index < 0 || index >= values.Length || values[index] is DeadValue) return false;
return true;
}
void IDictionary.Add(string key, object value)
{
SetValue(key, value, true);
}
bool IDictionary.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.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 IDictionary.Keys
{
get { return this.Select(kv => kv.Key).ToArray(); }
}
ICollection