Created
February 15, 2013 16:25
-
-
Save brendanmckenzie/4961483 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
License: http://www.apache.org/licenses/LICENSE-2.0 | |
Home page: http://code.google.com/p/dapper-dot-net/ | |
Note: to build on C# 3.0 + .NET 3.5, include the CSHARP30 compiler symbol (and yes, | |
I know the difference between language and runtime versions; this is a compromise). | |
*/ | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Data; | |
using System.Linq; | |
using System.Reflection; | |
using System.Reflection.Emit; | |
using System.Text; | |
using System.Threading; | |
using System.Text.RegularExpressions; | |
using System.Diagnostics; | |
using System.Data.SqlClient; | |
namespace Dapper | |
{ | |
/// <summary> | |
/// Dapper, a light weight object mapper for ADO.NET | |
/// </summary> | |
static partial class SqlMapper | |
{ | |
/// <summary> | |
/// Implement this interface to pass an arbitrary db specific set of parameters to Dapper | |
/// </summary> | |
public partial interface IDynamicParameters | |
{ | |
/// <summary> | |
/// Add all the parameters needed to the command just before it executes | |
/// </summary> | |
/// <param name="command">The raw command prior to execution</param> | |
/// <param name="identity">Information about the query</param> | |
void AddParameters(IDbCommand command, Identity identity); | |
} | |
/// <summary> | |
/// Implement this interface to change default mapping of reader columns to type memebers | |
/// </summary> | |
public interface ITypeMap | |
{ | |
/// <summary> | |
/// Finds best constructor | |
/// </summary> | |
/// <param name="names">DataReader column names</param> | |
/// <param name="types">DataReader column types</param> | |
/// <returns>Matching constructor or default one</returns> | |
ConstructorInfo FindConstructor(string[] names, Type[] types); | |
/// <summary> | |
/// Gets mapping for constructor parameter | |
/// </summary> | |
/// <param name="constructor">Constructor to resolve</param> | |
/// <param name="columnName">DataReader column name</param> | |
/// <returns>Mapping implementation</returns> | |
IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName); | |
/// <summary> | |
/// Gets member mapping for column | |
/// </summary> | |
/// <param name="columnName">DataReader column name</param> | |
/// <returns>Mapping implementation</returns> | |
IMemberMap GetMember(string columnName); | |
} | |
/// <summary> | |
/// Implements this interface to provide custom member mapping | |
/// </summary> | |
public interface IMemberMap | |
{ | |
/// <summary> | |
/// Source DataReader column name | |
/// </summary> | |
string ColumnName { get; } | |
/// <summary> | |
/// Target member type | |
/// </summary> | |
Type MemberType { get; } | |
/// <summary> | |
/// Target property | |
/// </summary> | |
PropertyInfo Property { get; } | |
/// <summary> | |
/// Target field | |
/// </summary> | |
FieldInfo Field { get; } | |
/// <summary> | |
/// Target constructor parameter | |
/// </summary> | |
ParameterInfo Parameter { get; } | |
} | |
static Link<Type, Action<IDbCommand, bool>> bindByNameCache; | |
static Action<IDbCommand, bool> GetBindByName(Type commandType) | |
{ | |
if (commandType == null) return null; // GIGO | |
Action<IDbCommand, bool> action; | |
if (Link<Type, Action<IDbCommand, bool>>.TryGet(bindByNameCache, commandType, out action)) | |
{ | |
return action; | |
} | |
var prop = commandType.GetProperty("BindByName", BindingFlags.Public | BindingFlags.Instance); | |
action = null; | |
ParameterInfo[] indexers; | |
MethodInfo setter; | |
if (prop != null && prop.CanWrite && prop.PropertyType == typeof(bool) | |
&& ((indexers = prop.GetIndexParameters()) == null || indexers.Length == 0) | |
&& (setter = prop.GetSetMethod()) != null | |
) | |
{ | |
var method = new DynamicMethod(commandType.Name + "_BindByName", null, new Type[] { typeof(IDbCommand), typeof(bool) }); | |
var il = method.GetILGenerator(); | |
il.Emit(OpCodes.Ldarg_0); | |
il.Emit(OpCodes.Castclass, commandType); | |
il.Emit(OpCodes.Ldarg_1); | |
il.EmitCall(OpCodes.Callvirt, setter, null); | |
il.Emit(OpCodes.Ret); | |
action = (Action<IDbCommand, bool>)method.CreateDelegate(typeof(Action<IDbCommand, bool>)); | |
} | |
// cache it | |
Link<Type, Action<IDbCommand, bool>>.TryAdd(ref bindByNameCache, commandType, ref action); | |
return action; | |
} | |
/// <summary> | |
/// This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example), | |
/// and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE** | |
/// equality. The type is fully thread-safe. | |
/// </summary> | |
partial class Link<TKey, TValue> where TKey : class | |
{ | |
public static bool TryGet(Link<TKey, TValue> link, TKey key, out TValue value) | |
{ | |
while (link != null) | |
{ | |
if ((object)key == (object)link.Key) | |
{ | |
value = link.Value; | |
return true; | |
} | |
link = link.Tail; | |
} | |
value = default(TValue); | |
return false; | |
} | |
public static bool TryAdd(ref Link<TKey, TValue> head, TKey key, ref TValue value) | |
{ | |
bool tryAgain; | |
do | |
{ | |
var snapshot = Interlocked.CompareExchange(ref head, null, null); | |
TValue found; | |
if (TryGet(snapshot, key, out found)) | |
{ // existing match; report the existing value instead | |
value = found; | |
return false; | |
} | |
var newNode = new Link<TKey, TValue>(key, value, snapshot); | |
// did somebody move our cheese? | |
tryAgain = Interlocked.CompareExchange(ref head, newNode, snapshot) != snapshot; | |
} while (tryAgain); | |
return true; | |
} | |
private Link(TKey key, TValue value, Link<TKey, TValue> tail) | |
{ | |
Key = key; | |
Value = value; | |
Tail = tail; | |
} | |
public TKey Key { get; private set; } | |
public TValue Value { get; private set; } | |
public Link<TKey, TValue> Tail { get; private set; } | |
} | |
partial class CacheInfo | |
{ | |
public DeserializerState Deserializer { get; set; } | |
public Func<IDataReader, object>[] OtherDeserializers { get; set; } | |
public Action<IDbCommand, object> ParamReader { get; set; } | |
private int hitCount; | |
public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); } | |
public void RecordHit() { Interlocked.Increment(ref hitCount); } | |
} | |
static int GetColumnHash(IDataReader reader) | |
{ | |
unchecked | |
{ | |
int colCount = reader.FieldCount, hash = colCount; | |
for (int i = 0; i < colCount; i++) | |
{ // binding code is only interested in names - not types | |
object tmp = reader.GetName(i); | |
hash = (hash * 31) + (tmp == null ? 0 : tmp.GetHashCode()); | |
} | |
return hash; | |
} | |
} | |
struct DeserializerState | |
{ | |
public readonly int Hash; | |
public readonly Func<IDataReader, object> Func; | |
public DeserializerState(int hash, Func<IDataReader, object> func) | |
{ | |
Hash = hash; | |
Func = func; | |
} | |
} | |
/// <summary> | |
/// Called if the query cache is purged via PurgeQueryCache | |
/// </summary> | |
public static event EventHandler QueryCachePurged; | |
private static void OnQueryCachePurged() | |
{ | |
var handler = QueryCachePurged; | |
if (handler != null) handler(null, EventArgs.Empty); | |
} | |
#if CSHARP30 | |
private static readonly Dictionary<Identity, CacheInfo> _queryCache = new Dictionary<Identity, CacheInfo>(); | |
// note: conflicts between readers and writers are so short-lived that it isn't worth the overhead of | |
// ReaderWriterLockSlim etc; a simple lock is faster | |
private static void SetQueryCache(Identity key, CacheInfo value) | |
{ | |
lock (_queryCache) { _queryCache[key] = value; } | |
} | |
private static bool TryGetQueryCache(Identity key, out CacheInfo value) | |
{ | |
lock (_queryCache) { return _queryCache.TryGetValue(key, out value); } | |
} | |
private static void PurgeQueryCacheByType(Type type) | |
{ | |
lock (_queryCache) | |
{ | |
var toRemove = _queryCache.Keys.Where(id => id.type == type).ToArray(); | |
foreach (var key in toRemove) | |
_queryCache.Remove(key); | |
} | |
} | |
/// <summary> | |
/// Purge the query cache | |
/// </summary> | |
public static void PurgeQueryCache() | |
{ | |
lock (_queryCache) | |
{ | |
_queryCache.Clear(); | |
} | |
OnQueryCachePurged(); | |
} | |
#else | |
static readonly System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo> _queryCache = new System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo>(); | |
private static void SetQueryCache(Identity key, CacheInfo value) | |
{ | |
if (Interlocked.Increment(ref collect) == COLLECT_PER_ITEMS) | |
{ | |
CollectCacheGarbage(); | |
} | |
_queryCache[key] = value; | |
} | |
private static void CollectCacheGarbage() | |
{ | |
try | |
{ | |
foreach (var pair in _queryCache) | |
{ | |
if (pair.Value.GetHitCount() <= COLLECT_HIT_COUNT_MIN) | |
{ | |
CacheInfo cache; | |
_queryCache.TryRemove(pair.Key, out cache); | |
} | |
} | |
} | |
finally | |
{ | |
Interlocked.Exchange(ref collect, 0); | |
} | |
} | |
private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0; | |
private static int collect; | |
private static bool TryGetQueryCache(Identity key, out CacheInfo value) | |
{ | |
if (_queryCache.TryGetValue(key, out value)) | |
{ | |
value.RecordHit(); | |
return true; | |
} | |
value = null; | |
return false; | |
} | |
/// <summary> | |
/// Purge the query cache | |
/// </summary> | |
public static void PurgeQueryCache() | |
{ | |
_queryCache.Clear(); | |
OnQueryCachePurged(); | |
} | |
private static void PurgeQueryCacheByType(Type type) | |
{ | |
foreach (var entry in _queryCache) | |
{ | |
CacheInfo cache; | |
if (entry.Key.type == type) | |
_queryCache.TryRemove(entry.Key, out cache); | |
} | |
} | |
/// <summary> | |
/// Return a count of all the cached queries by dapper | |
/// </summary> | |
/// <returns></returns> | |
public static int GetCachedSQLCount() | |
{ | |
return _queryCache.Count; | |
} | |
/// <summary> | |
/// Return a list of all the queries cached by dapper | |
/// </summary> | |
/// <param name="ignoreHitCountAbove"></param> | |
/// <returns></returns> | |
public static IEnumerable<Tuple<string, string, int>> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue) | |
{ | |
var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount())); | |
if (ignoreHitCountAbove < int.MaxValue) data = data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove); | |
return data; | |
} | |
/// <summary> | |
/// Deep diagnostics only: find any hash collisions in the cache | |
/// </summary> | |
/// <returns></returns> | |
public static IEnumerable<Tuple<int, int>> GetHashCollissions() | |
{ | |
var counts = new Dictionary<int, int>(); | |
foreach (var key in _queryCache.Keys) | |
{ | |
int count; | |
if (!counts.TryGetValue(key.hashCode, out count)) | |
{ | |
counts.Add(key.hashCode, 1); | |
} | |
else | |
{ | |
counts[key.hashCode] = count + 1; | |
} | |
} | |
return from pair in counts | |
where pair.Value > 1 | |
select Tuple.Create(pair.Key, pair.Value); | |
} | |
#endif | |
static readonly Dictionary<Type, DbType> typeMap; | |
static SqlMapper() | |
{ | |
typeMap = new Dictionary<Type, DbType>(); | |
typeMap[typeof(byte)] = DbType.Byte; | |
typeMap[typeof(sbyte)] = DbType.SByte; | |
typeMap[typeof(short)] = DbType.Int16; | |
typeMap[typeof(ushort)] = DbType.UInt16; | |
typeMap[typeof(int)] = DbType.Int32; | |
typeMap[typeof(uint)] = DbType.UInt32; | |
typeMap[typeof(long)] = DbType.Int64; | |
typeMap[typeof(ulong)] = DbType.UInt64; | |
typeMap[typeof(float)] = DbType.Single; | |
typeMap[typeof(double)] = DbType.Double; | |
typeMap[typeof(decimal)] = DbType.Decimal; | |
typeMap[typeof(bool)] = DbType.Boolean; | |
typeMap[typeof(string)] = DbType.String; | |
typeMap[typeof(char)] = DbType.StringFixedLength; | |
typeMap[typeof(Guid)] = DbType.Guid; | |
typeMap[typeof(DateTime)] = DbType.DateTime; | |
typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset; | |
typeMap[typeof(TimeSpan)] = DbType.Time; | |
typeMap[typeof(byte[])] = DbType.Binary; | |
typeMap[typeof(byte?)] = DbType.Byte; | |
typeMap[typeof(sbyte?)] = DbType.SByte; | |
typeMap[typeof(short?)] = DbType.Int16; | |
typeMap[typeof(ushort?)] = DbType.UInt16; | |
typeMap[typeof(int?)] = DbType.Int32; | |
typeMap[typeof(uint?)] = DbType.UInt32; | |
typeMap[typeof(long?)] = DbType.Int64; | |
typeMap[typeof(ulong?)] = DbType.UInt64; | |
typeMap[typeof(float?)] = DbType.Single; | |
typeMap[typeof(double?)] = DbType.Double; | |
typeMap[typeof(decimal?)] = DbType.Decimal; | |
typeMap[typeof(bool?)] = DbType.Boolean; | |
typeMap[typeof(char?)] = DbType.StringFixedLength; | |
typeMap[typeof(Guid?)] = DbType.Guid; | |
typeMap[typeof(DateTime?)] = DbType.DateTime; | |
typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset; | |
typeMap[typeof(TimeSpan?)] = DbType.Time; | |
typeMap[typeof(Object)] = DbType.Object; | |
} | |
internal const string LinqBinary = "System.Data.Linq.Binary"; | |
internal static DbType LookupDbType(Type type, string name) | |
{ | |
DbType dbType; | |
var nullUnderlyingType = Nullable.GetUnderlyingType(type); | |
if (nullUnderlyingType != null) type = nullUnderlyingType; | |
if (type.IsEnum) | |
{ | |
type = Enum.GetUnderlyingType(type); | |
} | |
if (typeMap.TryGetValue(type, out dbType)) | |
{ | |
return dbType; | |
} | |
if (type.FullName == LinqBinary) | |
{ | |
return DbType.Binary; | |
} | |
if (typeof(IEnumerable).IsAssignableFrom(type)) | |
{ | |
return DynamicParameters.EnumerableMultiParameter; | |
} | |
if (type.FullName == "Microsoft.SqlServer.Types.SqlGeography") | |
{ | |
return DynamicParameters.SqlGeographyParameter; | |
} | |
throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type)); | |
} | |
/// <summary> | |
/// Identity of a cached query in Dapper, used for extensability | |
/// </summary> | |
public partial class Identity : IEquatable<Identity> | |
{ | |
internal Identity ForGrid(Type primaryType, int gridIndex) | |
{ | |
return new Identity(sql, commandType, connectionString, primaryType, parametersType, null, gridIndex); | |
} | |
internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) | |
{ | |
return new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); | |
} | |
/// <summary> | |
/// Create an identity for use with DynamicParameters, internal use only | |
/// </summary> | |
/// <param name="type"></param> | |
/// <returns></returns> | |
public Identity ForDynamicParameters(Type type) | |
{ | |
return new Identity(sql, commandType, connectionString, this.type, type, null, -1); | |
} | |
internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes) | |
: this(sql, commandType, connection.ConnectionString, type, parametersType, otherTypes, 0) | |
{ } | |
private Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, Type[] otherTypes, int gridIndex) | |
{ | |
this.sql = sql; | |
this.commandType = commandType; | |
this.connectionString = connectionString; | |
this.type = type; | |
this.parametersType = parametersType; | |
this.gridIndex = gridIndex; | |
unchecked | |
{ | |
hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this | |
hashCode = hashCode * 23 + commandType.GetHashCode(); | |
hashCode = hashCode * 23 + gridIndex.GetHashCode(); | |
hashCode = hashCode * 23 + (sql == null ? 0 : sql.GetHashCode()); | |
hashCode = hashCode * 23 + (type == null ? 0 : type.GetHashCode()); | |
if (otherTypes != null) | |
{ | |
foreach (var t in otherTypes) | |
{ | |
hashCode = hashCode * 23 + (t == null ? 0 : t.GetHashCode()); | |
} | |
} | |
hashCode = hashCode * 23 + (connectionString == null ? 0 : connectionString.GetHashCode()); | |
hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode()); | |
} | |
} | |
/// <summary> | |
/// | |
/// </summary> | |
/// <param name="obj"></param> | |
/// <returns></returns> | |
public override bool Equals(object obj) | |
{ | |
return Equals(obj as Identity); | |
} | |
/// <summary> | |
/// The sql | |
/// </summary> | |
public readonly string sql; | |
/// <summary> | |
/// The command type | |
/// </summary> | |
public readonly CommandType? commandType; | |
/// <summary> | |
/// | |
/// </summary> | |
public readonly int hashCode, gridIndex; | |
/// <summary> | |
/// | |
/// </summary> | |
public readonly Type type; | |
/// <summary> | |
/// | |
/// </summary> | |
public readonly string connectionString; | |
/// <summary> | |
/// | |
/// </summary> | |
public readonly Type parametersType; | |
/// <summary> | |
/// | |
/// </summary> | |
/// <returns></returns> | |
public override int GetHashCode() | |
{ | |
return hashCode; | |
} | |
/// <summary> | |
/// Compare 2 Identity objects | |
/// </summary> | |
/// <param name="other"></param> | |
/// <returns></returns> | |
public bool Equals(Identity other) | |
{ | |
return | |
other != null && | |
gridIndex == other.gridIndex && | |
type == other.type && | |
sql == other.sql && | |
commandType == other.commandType && | |
connectionString == other.connectionString && | |
parametersType == other.parametersType; | |
} | |
} | |
#if CSHARP30 | |
/// <summary> | |
/// Execute parameterized SQL | |
/// </summary> | |
/// <returns>Number of rows affected</returns> | |
public static int Execute(this IDbConnection cnn, string sql, object param) | |
{ | |
return Execute(cnn, sql, param, null, null, null); | |
} | |
/// <summary> | |
/// Execute parameterized SQL | |
/// </summary> | |
/// <returns>Number of rows affected</returns> | |
public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction) | |
{ | |
return Execute(cnn, sql, param, transaction, null, null); | |
} | |
/// <summary> | |
/// Execute parameterized SQL | |
/// </summary> | |
/// <returns>Number of rows affected</returns> | |
public static int Execute(this IDbConnection cnn, string sql, object param, CommandType commandType) | |
{ | |
return Execute(cnn, sql, param, null, null, commandType); | |
} | |
/// <summary> | |
/// Execute parameterized SQL | |
/// </summary> | |
/// <returns>Number of rows affected</returns> | |
public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType) | |
{ | |
return Execute(cnn, sql, param, transaction, null, commandType); | |
} | |
/// <summary> | |
/// Executes a query, returning the data typed as per T | |
/// </summary> | |
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is | |
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). | |
/// </returns> | |
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param) | |
{ | |
return Query<T>(cnn, sql, param, null, true, null, null); | |
} | |
/// <summary> | |
/// Executes a query, returning the data typed as per T | |
/// </summary> | |
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is | |
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). | |
/// </returns> | |
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction) | |
{ | |
return Query<T>(cnn, sql, param, transaction, true, null, null); | |
} | |
/// <summary> | |
/// Executes a query, returning the data typed as per T | |
/// </summary> | |
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is | |
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). | |
/// </returns> | |
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, CommandType commandType) | |
{ | |
return Query<T>(cnn, sql, param, null, true, null, commandType); | |
} | |
/// <summary> | |
/// Executes a query, returning the data typed as per T | |
/// </summary> | |
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is | |
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). | |
/// </returns> | |
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType) | |
{ | |
return Query<T>(cnn, sql, param, transaction, true, null, commandType); | |
} | |
/// <summary> | |
/// Execute a command that returns multiple result sets, and access each in turn | |
/// </summary> | |
public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, IDbTransaction transaction) | |
{ | |
return QueryMultiple(cnn, sql, param, transaction, null, null); | |
} | |
/// <summary> | |
/// Execute a command that returns multiple result sets, and access each in turn | |
/// </summary> | |
public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, CommandType commandType) | |
{ | |
return QueryMultiple(cnn, sql, param, null, null, commandType); | |
} | |
/// <summary> | |
/// Execute a command that returns multiple result sets, and access each in turn | |
/// </summary> | |
public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType) | |
{ | |
return QueryMultiple(cnn, sql, param, transaction, null, commandType); | |
} | |
#endif | |
/// <summary> | |
/// Execute parameterized SQL | |
/// </summary> | |
/// <returns>Number of rows affected</returns> | |
public static int Execute( | |
#if CSHARP30 | |
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType | |
#else | |
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null | |
#endif | |
) | |
{ | |
IEnumerable multiExec = (object)param as IEnumerable; | |
Identity identity; | |
CacheInfo info = null; | |
if (multiExec != null && !(multiExec is string)) | |
{ | |
bool isFirst = true; | |
int total = 0; | |
using (var cmd = SetupCommand(cnn, transaction, sql, null, null, commandTimeout, commandType)) | |
{ | |
string masterSql = null; | |
foreach (var obj in multiExec) | |
{ | |
if (isFirst) | |
{ | |
masterSql = cmd.CommandText; | |
isFirst = false; | |
identity = new Identity(sql, cmd.CommandType, cnn, null, obj.GetType(), null); | |
info = GetCacheInfo(identity); | |
} | |
else | |
{ | |
cmd.CommandText = masterSql; // because we do magic replaces on "in" etc | |
cmd.Parameters.Clear(); // current code is Add-tastic | |
} | |
info.ParamReader(cmd, obj); | |
total += cmd.ExecuteNonQuery(); | |
} | |
} | |
return total; | |
} | |
// nice and simple | |
if ((object)param != null) | |
{ | |
identity = new Identity(sql, commandType, cnn, null, (object)param == null ? null : ((object)param).GetType(), null); | |
info = GetCacheInfo(identity); | |
} | |
return ExecuteCommand(cnn, transaction, sql, (object)param == null ? null : info.ParamReader, (object)param, commandTimeout, commandType); | |
} | |
#if !CSHARP30 | |
/// <summary> | |
/// Return a list of dynamic objects, reader is closed after the call | |
/// </summary> | |
public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) | |
{ | |
return Query<DapperRow>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType); | |
} | |
#else | |
/// <summary> | |
/// Return a list of dynamic objects, reader is closed after the call | |
/// </summary> | |
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param) { | |
return Query(cnn, sql, param, null, true, null, null); | |
} | |
/// <summary> | |
/// Return a list of dynamic objects, reader is closed after the call | |
/// </summary> | |
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction) { | |
return Query(cnn, sql, param, transaction, true, null, null); | |
} | |
/// <summary> | |
/// Return a list of dynamic objects, reader is closed after the call | |
/// </summary> | |
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, CommandType? commandType) { | |
return Query(cnn, sql, param, null, true, null, commandType); | |
} | |
/// <summary> | |
/// Return a list of dynamic objects, reader is closed after the call | |
/// </summary> | |
public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType? commandType) { | |
return Query(cnn, sql, param, transaction, true, null, commandType); | |
} | |
/// <summary> | |
/// Return a list of dynamic objects, reader is closed after the call | |
/// </summary> | |
public static IEnumerable<IDictionary<string,object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType) { | |
return Query<IDictionary<string, object>>(cnn, sql, param, transaction, buffered, commandTimeout, commandType); | |
} | |
#endif | |
/// <summary> | |
/// Executes a query, returning the data typed as per T | |
/// </summary> | |
/// <remarks>the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object</remarks> | |
/// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is | |
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). | |
/// </returns> | |
public static IEnumerable<T> Query<T>( | |
#if CSHARP30 | |
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType | |
#else | |
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null | |
#endif | |
) | |
{ | |
var data = QueryInternal<T>(cnn, sql, param as object, transaction, commandTimeout, commandType); | |
return buffered ? data.ToList() : data; | |
} | |
/// <summary> | |
/// Execute a command that returns multiple result sets, and access each in turn | |
/// </summary> | |
public static GridReader QueryMultiple( | |
#if CSHARP30 | |
this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType | |
#else | |
this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null | |
#endif | |
) | |
{ | |
Identity identity = new Identity(sql, commandType, cnn, typeof(GridReader), (object)param == null ? null : ((object)param).GetType(), null); | |
CacheInfo info = GetCacheInfo(identity); | |
IDbCommand cmd = null; | |
IDataReader reader = null; | |
bool wasClosed = cnn.State == ConnectionState.Closed; | |
try | |
{ | |
if (wasClosed) cnn.Open(); | |
cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType); | |
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default); | |
var result = new GridReader(cmd, reader, identity); | |
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader | |
// with the CloseConnection flag, so the reader will deal with the connection; we | |
// still need something in the "finally" to ensure that broken SQL still results | |
// in the connection closing itself | |
return result; | |
} | |
catch | |
{ | |
if (reader != null) | |
{ | |
if (!reader.IsClosed) try { cmd.Cancel(); } | |
catch { /* don't spoil the existing exception */ } | |
reader.Dispose(); | |
} | |
if (cmd != null) cmd.Dispose(); | |
if (wasClosed) cnn.Close(); | |
throw; | |
} | |
} | |
/// <summary> | |
/// Return a typed list of objects, reader is closed after the call | |
/// </summary> | |
private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType) | |
{ | |
var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null); | |
var info = GetCacheInfo(identity); | |
IDbCommand cmd = null; | |
IDataReader reader = null; | |
bool wasClosed = cnn.State == ConnectionState.Closed; | |
try | |
{ | |
cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType); | |
if (wasClosed) cnn.Open(); | |
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default); | |
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader | |
// with the CloseConnection flag, so the reader will deal with the connection; we | |
// still need something in the "finally" to ensure that broken SQL still results | |
// in the connection closing itself | |
var tuple = info.Deserializer; | |
int hash = GetColumnHash(reader); | |
if (tuple.Func == null || tuple.Hash != hash) | |
{ | |
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false)); | |
SetQueryCache(identity, info); | |
} | |
var func = tuple.Func; | |
while (reader.Read()) | |
{ | |
yield return (T)func(reader); | |
} | |
// happy path; close the reader cleanly - no | |
// need for "Cancel" etc | |
reader.Dispose(); | |
reader = null; | |
} | |
finally | |
{ | |
if (reader != null) | |
{ | |
if (!reader.IsClosed) try { cmd.Cancel(); } | |
catch { /* don't spoil the existing exception */ } | |
reader.Dispose(); | |
} | |
if (wasClosed) cnn.Close(); | |
if (cmd != null) cmd.Dispose(); | |
} | |
} | |
/// <summary> | |
/// Maps a query to objects | |
/// </summary> | |
/// <typeparam name="TFirst">The first type in the recordset</typeparam> | |
/// <typeparam name="TSecond">The second type in the recordset</typeparam> | |
/// <typeparam name="TReturn">The return type</typeparam> | |
/// <param name="cnn"></param> | |
/// <param name="sql"></param> | |
/// <param name="map"></param> | |
/// <param name="param"></param> | |
/// <param name="transaction"></param> | |
/// <param name="buffered"></param> | |
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param> | |
/// <param name="commandTimeout">Number of seconds before command execution timeout</param> | |
/// <param name="commandType">Is it a stored proc or a batch?</param> | |
/// <returns></returns> | |
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>( | |
#if CSHARP30 | |
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType | |
#else | |
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null | |
#endif | |
) | |
{ | |
return MultiMap<TFirst, TSecond, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); | |
} | |
/// <summary> | |
/// Maps a query to objects | |
/// </summary> | |
/// <typeparam name="TFirst"></typeparam> | |
/// <typeparam name="TSecond"></typeparam> | |
/// <typeparam name="TThird"></typeparam> | |
/// <typeparam name="TReturn"></typeparam> | |
/// <param name="cnn"></param> | |
/// <param name="sql"></param> | |
/// <param name="map"></param> | |
/// <param name="param"></param> | |
/// <param name="transaction"></param> | |
/// <param name="buffered"></param> | |
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param> | |
/// <param name="commandTimeout">Number of seconds before command execution timeout</param> | |
/// <param name="commandType"></param> | |
/// <returns></returns> | |
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>( | |
#if CSHARP30 | |
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType | |
#else | |
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null | |
#endif | |
) | |
{ | |
return MultiMap<TFirst, TSecond, TThird, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); | |
} | |
/// <summary> | |
/// Perform a multi mapping query with 4 input parameters | |
/// </summary> | |
/// <typeparam name="TFirst"></typeparam> | |
/// <typeparam name="TSecond"></typeparam> | |
/// <typeparam name="TThird"></typeparam> | |
/// <typeparam name="TFourth"></typeparam> | |
/// <typeparam name="TReturn"></typeparam> | |
/// <param name="cnn"></param> | |
/// <param name="sql"></param> | |
/// <param name="map"></param> | |
/// <param name="param"></param> | |
/// <param name="transaction"></param> | |
/// <param name="buffered"></param> | |
/// <param name="splitOn"></param> | |
/// <param name="commandTimeout"></param> | |
/// <param name="commandType"></param> | |
/// <returns></returns> | |
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>( | |
#if CSHARP30 | |
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType | |
#else | |
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null | |
#endif | |
) | |
{ | |
return MultiMap<TFirst, TSecond, TThird, TFourth, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); | |
} | |
#if !CSHARP30 | |
/// <summary> | |
/// Perform a multi mapping query with 5 input parameters | |
/// </summary> | |
/// <typeparam name="TFirst"></typeparam> | |
/// <typeparam name="TSecond"></typeparam> | |
/// <typeparam name="TThird"></typeparam> | |
/// <typeparam name="TFourth"></typeparam> | |
/// <typeparam name="TFifth"></typeparam> | |
/// <typeparam name="TReturn"></typeparam> | |
/// <param name="cnn"></param> | |
/// <param name="sql"></param> | |
/// <param name="map"></param> | |
/// <param name="param"></param> | |
/// <param name="transaction"></param> | |
/// <param name="buffered"></param> | |
/// <param name="splitOn"></param> | |
/// <param name="commandTimeout"></param> | |
/// <param name="commandType"></param> | |
/// <returns></returns> | |
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) | |
{ | |
return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); | |
} | |
#endif | |
partial class DontMap { } | |
static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>( | |
this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType) | |
{ | |
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(cnn, sql, map, param, transaction, splitOn, commandTimeout, commandType, null, null); | |
return buffered ? results.ToList() : results; | |
} | |
static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, string splitOn, int? commandTimeout, CommandType? commandType, IDataReader reader, Identity identity) | |
{ | |
identity = identity ?? new Identity(sql, commandType, cnn, typeof(TFirst), (object)param == null ? null : ((object)param).GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth) }); | |
CacheInfo cinfo = GetCacheInfo(identity); | |
IDbCommand ownedCommand = null; | |
IDataReader ownedReader = null; | |
try | |
{ | |
if (reader == null) | |
{ | |
ownedCommand = SetupCommand(cnn, transaction, sql, cinfo.ParamReader, (object)param, commandTimeout, commandType); | |
ownedReader = ownedCommand.ExecuteReader(); | |
reader = ownedReader; | |
} | |
DeserializerState deserializer = default(DeserializerState); | |
Func<IDataReader, object>[] otherDeserializers = null; | |
int hash = GetColumnHash(reader); | |
if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash) | |
{ | |
var deserializers = GenerateDeserializers(new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth) }, splitOn, reader); | |
deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); | |
otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray(); | |
SetQueryCache(identity, cinfo); | |
} | |
Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(deserializer.Func, otherDeserializers, map); | |
if (mapIt != null) | |
{ | |
while (reader.Read()) | |
{ | |
yield return mapIt(reader); | |
} | |
} | |
} | |
finally | |
{ | |
try | |
{ | |
if (ownedReader != null) | |
{ | |
ownedReader.Dispose(); | |
} | |
} | |
finally | |
{ | |
if (ownedCommand != null) | |
{ | |
ownedCommand.Dispose(); | |
} | |
} | |
} | |
} | |
private static Func<IDataReader, TReturn> GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<IDataReader, object> deserializer, Func<IDataReader, object>[] otherDeserializers, object map) | |
{ | |
switch (otherDeserializers.Length) | |
{ | |
case 1: | |
return r => ((Func<TFirst, TSecond, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r)); | |
case 2: | |
return r => ((Func<TFirst, TSecond, TThird, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r)); | |
case 3: | |
return r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r)); | |
#if !CSHARP30 | |
case 4: | |
return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r)); | |
#endif | |
default: | |
throw new NotSupportedException(); | |
} | |
} | |
private static Func<IDataReader, object>[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader) | |
{ | |
int current = 0; | |
var splits = splitOn.Split(',').ToArray(); | |
var splitIndex = 0; | |
Func<Type, int> nextSplit = type => | |
{ | |
var currentSplit = splits[splitIndex].Trim(); | |
if (splits.Length > splitIndex + 1) | |
{ | |
splitIndex++; | |
} | |
bool skipFirst = false; | |
int startingPos = current + 1; | |
// if our current type has the split, skip the first time you see it. | |
if (type != typeof(Object)) | |
{ | |
var props = DefaultTypeMap.GetSettableProps(type); | |
var fields = DefaultTypeMap.GetSettableFields(type); | |
foreach (var name in props.Select(p => p.Name).Concat(fields.Select(f => f.Name))) | |
{ | |
if (string.Equals(name, currentSplit, StringComparison.OrdinalIgnoreCase)) | |
{ | |
skipFirst = true; | |
startingPos = current; | |
break; | |
} | |
} | |
} | |
int pos; | |
for (pos = startingPos; pos < reader.FieldCount; pos++) | |
{ | |
// some people like ID some id ... assuming case insensitive splits for now | |
if (splitOn == "*") | |
{ | |
break; | |
} | |
if (string.Equals(reader.GetName(pos), currentSplit, StringComparison.OrdinalIgnoreCase)) | |
{ | |
if (skipFirst) | |
{ | |
skipFirst = false; | |
} | |
else | |
{ | |
break; | |
} | |
} | |
} | |
current = pos; | |
return pos; | |
}; | |
var deserializers = new List<Func<IDataReader, object>>(); | |
int split = 0; | |
bool first = true; | |
foreach (var type in types) | |
{ | |
if (type != typeof(DontMap)) | |
{ | |
int next = nextSplit(type); | |
deserializers.Add(GetDeserializer(type, reader, split, next - split, /* returnNullIfFirstMissing: */ !first)); | |
first = false; | |
split = next; | |
} | |
} | |
return deserializers.ToArray(); | |
} | |
private static CacheInfo GetCacheInfo(Identity identity) | |
{ | |
CacheInfo info; | |
if (!TryGetQueryCache(identity, out info)) | |
{ | |
info = new CacheInfo(); | |
if (identity.parametersType != null) | |
{ | |
if (typeof(IDynamicParameters).IsAssignableFrom(identity.parametersType)) | |
{ | |
info.ParamReader = (cmd, obj) => { (obj as IDynamicParameters).AddParameters(cmd, identity); }; | |
} | |
#if !CSHARP30 | |
else if (typeof(IEnumerable<KeyValuePair<string, object>>).IsAssignableFrom(identity.parametersType) && typeof(System.Dynamic.IDynamicMetaObjectProvider).IsAssignableFrom(identity.parametersType)) | |
{ | |
info.ParamReader = (cmd, obj) => | |
{ | |
IDynamicParameters mapped = new DynamicParameters(obj); | |
mapped.AddParameters(cmd, identity); | |
}; | |
} | |
#endif | |
else | |
{ | |
info.ParamReader = CreateParamInfoGenerator(identity, false); | |
} | |
} | |
SetQueryCache(identity, info); | |
} | |
return info; | |
} | |
private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) | |
{ | |
#if !CSHARP30 | |
// dynamic is passed in as Object ... by c# design | |
if (type == typeof(object) | |
|| type == typeof(DapperRow)) | |
{ | |
return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing); | |
} | |
#else | |
if(type.IsAssignableFrom(typeof(Dictionary<string,object>))) | |
{ | |
return GetDictionaryDeserializer(reader, startBound, length, returnNullIfFirstMissing); | |
} | |
#endif | |
Type underlyingType = null; | |
if (!(typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary || | |
(type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum))) | |
{ | |
return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing); | |
} | |
return GetStructDeserializer(type, underlyingType ?? type, startBound); | |
} | |
#if !CSHARP30 | |
sealed partial class DapperTable | |
{ | |
string[] fieldNames; | |
readonly Dictionary<string, int> fieldNameLookup; | |
internal string[] FieldNames { get { return fieldNames; } } | |
public DapperTable(string[] fieldNames) | |
{ | |
if (fieldNames == null) throw new ArgumentNullException("fieldNames"); | |
this.fieldNames = fieldNames; | |
fieldNameLookup = new Dictionary<string, int>(fieldNames.Length, StringComparer.Ordinal); | |
// if there are dups, we want the **first** key to be the "winner" - so iterate backwards | |
for (int i = fieldNames.Length - 1; i >= 0; i--) | |
{ | |
string key = fieldNames[i]; | |
if (key != null) fieldNameLookup[key] = i; | |
} | |
} | |
internal int IndexOfName(string name) | |
{ | |
int result; | |
return (name != null && fieldNameLookup.TryGetValue(name, out result)) ? result : -1; | |
} | |
internal int AddField(string name) | |
{ | |
if (name == null) throw new ArgumentNullException("name"); | |
if (fieldNameLookup.ContainsKey(name)) throw new InvalidOperationException("Field already exists: " + name); | |
int oldLen = fieldNames.Length; | |
Array.Resize(ref fieldNames, oldLen + 1); // yes, this is sub-optimal, but this is not the expected common case | |
fieldNameLookup[name] = oldLen; | |
return oldLen; | |
} | |
internal bool FieldExists(string key) | |
{ | |
return key != null && fieldNameLookup.ContainsKey(key); | |
} | |
public int FieldCount { get { return fieldNames.Length; } } | |
} | |
sealed partial class DapperRowMetaObject : System.Dynamic.DynamicMetaObject | |
{ | |
static readonly MethodInfo getValueMethod = typeof(IDictionary<string, object>).GetProperty("Item").GetGetMethod(); | |
static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue"); | |
public DapperRowMetaObject( | |
System.Linq.Expressions.Expression expression, | |
System.Dynamic.BindingRestrictions restrictions | |
) | |
: base(expression, restrictions) | |
{ | |
} | |
public DapperRowMetaObject( | |
System.Linq.Expressions.Expression expression, | |
System.Dynamic.BindingRestrictions restrictions, | |
object value | |
) | |
: base(expression, restrictions, value) | |
{ | |
} | |
System.Dynamic.DynamicMetaObject CallMethod( | |
MethodInfo method, | |
System.Linq.Expressions.Expression[] parameters | |
) | |
{ | |
var callMethod = new System.Dynamic.DynamicMetaObject( | |
System.Linq.Expressions.Expression.Call( | |
System.Linq.Expressions.Expression.Convert(Expression, LimitType), | |
method, | |
parameters), | |
System.Dynamic.BindingRestrictions.GetTypeRestriction(Expression, LimitType) | |
); | |
return callMethod; | |
} | |
public override System.Dynamic.DynamicMetaObject BindGetMember(System.Dynamic.GetMemberBinder binder) | |
{ | |
var parameters = new System.Linq.Expressions.Expression[] | |
{ | |
System.Linq.Expressions.Expression.Constant(binder.Name) | |
}; | |
var callMethod = CallMethod(getValueMethod, parameters); | |
return callMethod; | |
} | |
public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.SetMemberBinder binder, System.Dynamic.DynamicMetaObject value) | |
{ | |
var parameters = new System.Linq.Expressions.Expression[] | |
{ | |
System.Linq.Expressions.Expression.Constant(binder.Name), | |
value.Expression, | |
}; | |
var callMethod = CallMethod(setValueMethod, parameters); | |
return callMethod; | |
} | |
} | |
sealed partial class DapperRow | |
: System.Dynamic.IDynamicMetaObjectProvider | |
, IDictionary<string, object> | |
{ | |
readonly DapperTable table; | |
object[] values; | |
public DapperRow(DapperTable table, object[] values) | |
{ | |
if (table == null) throw new ArgumentNullException("table"); | |
if (values == null) throw new ArgumentNullException("values"); | |
this.table = table; | |
this.values = values; | |
} | |
private sealed class DeadValue | |
{ | |
public static readonly DeadValue Default = new DeadValue(); | |
private DeadValue() { } | |
} | |
int ICollection<KeyValuePair<string, object>>.Count | |
{ | |
get | |
{ | |
int count = 0; | |
for (int i = 0; i < values.Length; i++) | |
{ | |
if (!(values[i] is DeadValue)) count++; | |
} | |
return count; | |
} | |
} | |
public bool TryGetValue(string name, out object value) | |
{ | |
var index = table.IndexOfName(name); | |
if (index < 0) | |
{ // doesn't exist | |
value = null; | |
return false; | |
} | |
// exists, **even if** we don't have a value; consider table rows heterogeneous | |
value = index < values.Length ? values[index] : null; | |
if (value is DeadValue) | |
{ // pretend it isn't here | |
value = null; | |
return false; | |
} | |
return true; | |
} | |
public override string ToString() | |
{ | |
var sb = new StringBuilder("{DapperRow"); | |
foreach (var kv in this) | |
{ | |
var value = kv.Value; | |
sb.Append(", ").Append(kv.Key); | |
if (value != null) | |
{ | |
sb.Append(" = '").Append(kv.Value).Append('\''); | |
} | |
else | |
{ | |
sb.Append(" = NULL"); | |
} | |
} | |
return sb.Append('}').ToString(); | |
} | |
System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject( | |
System.Linq.Expressions.Expression parameter) | |
{ | |
return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this); | |
} | |
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() | |
{ | |
var names = table.FieldNames; | |
for (var i = 0; i < names.Length; i++) | |
{ | |
object value = i < values.Length ? values[i] : null; | |
if (!(value is DeadValue)) | |
{ | |
yield return new KeyValuePair<string, object>(names[i], value); | |
} | |
} | |
} | |
IEnumerator IEnumerable.GetEnumerator() | |
{ | |
return GetEnumerator(); | |
} | |
#region Implementation of ICollection<KeyValuePair<string,object>> | |
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) | |
{ | |
IDictionary<string, object> dic = this; | |
dic.Add(item.Key, item.Value); | |
} | |
void ICollection<KeyValuePair<string, object>>.Clear() | |
{ // removes values for **this row**, but doesn't change the fundamental table | |
for (int i = 0; i < values.Length; i++) | |
values[i] = DeadValue.Default; | |
} | |
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item) | |
{ | |
object value; | |
return TryGetValue(item.Key, out value) && Equals(value, item.Value); | |
} | |
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) | |
{ | |
foreach (var kv in this) | |
{ | |
array[arrayIndex++] = kv; // if they didn't leave enough space; not our fault | |
} | |
} | |
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item) | |
{ | |
IDictionary<string, object> dic = this; | |
return dic.Remove(item.Key); | |
} | |
bool ICollection<KeyValuePair<string, object>>.IsReadOnly | |
{ | |
get { return false; } | |
} | |
#endregion | |
#region Implementation of IDictionary<string,object> | |
bool IDictionary<string, object>.ContainsKey(string key) | |
{ | |
int index = table.IndexOfName(key); | |
if (index < 0 || index >= values.Length || values[index] is DeadValue) return false; | |
return true; | |
} | |
void IDictionary<string, object>.Add(string key, object value) | |
{ | |
IDictionary<string, object> dic = this; | |
dic[key] = value; | |
} | |
bool IDictionary<string, object>.Remove(string key) | |
{ | |
int index = table.IndexOfName(key); | |
if (index < 0 || index >= values.Length || values[index] is DeadValue) return false; | |
values[index] = DeadValue.Default; | |
return true; | |
} | |
object IDictionary<string, object>.this[string key] | |
{ | |
get { object val; TryGetValue(key, out val); return val; } | |
set { SetValue(key, value); } | |
} | |
public object SetValue(string key, object value) | |
{ | |
if (key == null) throw new ArgumentNullException("key"); | |
int index = table.IndexOfName(key); | |
if (index < 0) | |
{ | |
index = table.AddField(key); | |
} | |
if (values.Length <= index) | |
{ // we'll assume they're doing lots of things, and | |
// grow it to the full width of the table | |
Array.Resize(ref values, table.FieldCount); | |
} | |
return values[index] = value; | |
} | |
ICollection<string> IDictionary<string, object>.Keys | |
{ | |
get { return this.Select(kv => kv.Key).ToArray(); } | |
} | |
ICollection<object> IDictionary<string, object>.Values | |
{ | |
get { return this.Select(kv => kv.Value).ToArray(); } | |
} | |
#endregion | |
} | |
#endif | |
#if !CSHARP30 | |
internal static Func<IDataReader, object> GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing) | |
{ | |
var fieldCount = reader.FieldCount; | |
if (length == -1) | |
{ | |
length = fieldCount - startBound; | |
} | |
if (fieldCount <= startBound) | |
{ | |
throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); | |
} | |
var effectiveFieldCount = fieldCount - startBound; | |
DapperTable table = null; | |
return | |
r => | |
{ | |
if (table == null) | |
{ | |
string[] names = new string[effectiveFieldCount]; | |
for (int i = 0; i < effectiveFieldCount; i++) | |
{ | |
names[i] = reader.GetName(i + startBound); | |
} | |
table = new DapperTable(names); | |
} | |
var values = new object[effectiveFieldCount]; | |
if (returnNullIfFirstMissing) | |
{ | |
values[0] = r.GetValue(startBound); | |
if (values[0] is DBNull) | |
{ | |
return null; | |
} | |
} | |
if (startBound == 0) | |
{ | |
r.GetValues(values); | |
} | |
else | |
{ | |
var begin = returnNullIfFirstMissing ? 1 : 0; | |
for (var iter = begin; iter < effectiveFieldCount; ++iter) | |
{ | |
values[iter] = r.GetValue(iter + startBound); | |
} | |
} | |
return new DapperRow(table, values); | |
}; | |
} | |
#else | |
internal static Func<IDataReader, object> GetDictionaryDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing) | |
{ | |
var fieldCount = reader.FieldCount; | |
if (length == -1) | |
{ | |
length = fieldCount - startBound; | |
} | |
if (fieldCount <= startBound) | |
{ | |
throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); | |
} | |
return | |
r => | |
{ | |
IDictionary<string, object> row = new Dictionary<string, object>(length); | |
for (var i = startBound; i < startBound + length; i++) | |
{ | |
var tmp = r.GetValue(i); | |
tmp = tmp == DBNull.Value ? null : tmp; | |
row[r.GetName(i)] = tmp; | |
if (returnNullIfFirstMissing && i == startBound && tmp == null) | |
{ | |
return null; | |
} | |
} | |
return row; | |
}; | |
} | |
#endif | |
/// <summary> | |
/// Internal use only | |
/// </summary> | |
/// <param name="value"></param> | |
/// <returns></returns> | |
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] | |
[Obsolete("This method is for internal usage only", false)] | |
public static char ReadChar(object value) | |
{ | |
if (value == null || value is DBNull) throw new ArgumentNullException("value"); | |
string s = value as string; | |
if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value"); | |
return s[0]; | |
} | |
/// <summary> | |
/// Internal use only | |
/// </summary> | |
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] | |
[Obsolete("This method is for internal usage only", false)] | |
public static char? ReadNullableChar(object value) | |
{ | |
if (value == null || value is DBNull) return null; | |
string s = value as string; | |
if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value"); | |
return s[0]; | |
} | |
/// <summary> | |
/// Internal use only | |
/// </summary> | |
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] | |
[Obsolete("This method is for internal usage only", true)] | |
public static IDbDataParameter FindOrAddParameter(IDataParameterCollection parameters, IDbCommand command, string name) | |
{ | |
IDbDataParameter result; | |
if (parameters.Contains(name)) | |
{ | |
result = (IDbDataParameter)parameters[name]; | |
} | |
else | |
{ | |
result = command.CreateParameter(); | |
result.ParameterName = name; | |
parameters.Add(result); | |
} | |
return result; | |
} | |
/// <summary> | |
/// Internal use only | |
/// </summary> | |
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] | |
[Obsolete("This method is for internal usage only", false)] | |
public static void PackSqlGeographyParameter(IDbCommand command, string namePrefix, object value) | |
{ | |
var parameter = (SqlParameter)command.CreateParameter(); | |
parameter.ParameterName = namePrefix; | |
parameter.UdtTypeName = "geography"; | |
parameter.Value = value ?? DBNull.Value; | |
command.Parameters.Add(parameter); | |
} | |
/// <summary> | |
/// Internal use only | |
/// </summary> | |
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] | |
[Obsolete("This method is for internal usage only", false)] | |
public static void PackListParameters(IDbCommand command, string namePrefix, object value) | |
{ | |
// initially we tried TVP, however it performs quite poorly. | |
// keep in mind SQL support up to 2000 params easily in sp_executesql, needing more is rare | |
var list = value as IEnumerable; | |
var count = 0; | |
if (list != null) | |
{ | |
if (FeatureSupport.Get(command.Connection).Arrays) | |
{ | |
var arrayParm = command.CreateParameter(); | |
arrayParm.Value = list; | |
arrayParm.ParameterName = namePrefix; | |
command.Parameters.Add(arrayParm); | |
} | |
else | |
{ | |
bool isString = value is IEnumerable<string>; | |
bool isDbString = value is IEnumerable<DbString>; | |
foreach (var item in list) | |
{ | |
count++; | |
var listParam = command.CreateParameter(); | |
listParam.ParameterName = namePrefix + count; | |
listParam.Value = item ?? DBNull.Value; | |
if (isString) | |
{ | |
listParam.Size = 4000; | |
if (item != null && ((string)item).Length > 4000) | |
{ | |
listParam.Size = -1; | |
} | |
} | |
if (isDbString && item as DbString != null) | |
{ | |
var str = item as DbString; | |
str.AddParameter(command, listParam.ParameterName); | |
} | |
else | |
{ | |
command.Parameters.Add(listParam); | |
} | |
} | |
if (count == 0) | |
{ | |
command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), "(SELECT NULL WHERE 1 = 0)"); | |
} | |
else | |
{ | |
command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), match => | |
{ | |
var grp = match.Value; | |
var sb = new StringBuilder("(").Append(grp).Append(1); | |
for (int i = 2; i <= count; i++) | |
{ | |
sb.Append(',').Append(grp).Append(i); | |
} | |
return sb.Append(')').ToString(); | |
}); | |
} | |
} | |
} | |
} | |
private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyInfo> parameters, string sql) | |
{ | |
return parameters.Where(p => Regex.IsMatch(sql, "[@:]" + p.Name + "([^a-zA-Z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline)); | |
} | |
/// <summary> | |
/// Internal use only | |
/// </summary> | |
public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates) | |
{ | |
Type type = identity.parametersType; | |
bool filterParams = identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text; | |
var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true); | |
var il = dm.GetILGenerator(); | |
il.DeclareLocal(type); // 0 | |
bool haveInt32Arg1 = false; | |
il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param] | |
il.Emit(OpCodes.Unbox_Any, type); // stack is now [typed-param] | |
il.Emit(OpCodes.Stloc_0);// stack is now empty | |
il.Emit(OpCodes.Ldarg_0); // stack is now [command] | |
il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty("Parameters").GetGetMethod(), null); // stack is now [parameters] | |
IEnumerable<PropertyInfo> props = type.GetProperties().Where(p => p.GetIndexParameters().Length == 0).OrderBy(p => p.Name); | |
if (filterParams) | |
{ | |
props = FilterParameters(props, identity.sql); | |
} | |
foreach (var prop in props) | |
{ | |
if (filterParams) | |
{ | |
if (identity.sql.IndexOf("@" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0 | |
&& identity.sql.IndexOf(":" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0) | |
{ // can't see the parameter in the text (even in a comment, etc) - burn it with fire | |
continue; | |
} | |
} | |
if (prop.PropertyType == typeof(DbString)) | |
{ | |
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param] | |
il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [dbstring] | |
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [dbstring] [command] | |
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [dbstring] [command] [name] | |
il.EmitCall(OpCodes.Callvirt, typeof(DbString).GetMethod("AddParameter"), null); // stack is now [parameters] | |
continue; | |
} | |
DbType dbType = LookupDbType(prop.PropertyType, prop.Name); | |
if (dbType == DynamicParameters.EnumerableMultiParameter) | |
{ | |
// this actually represents special handling for list types; | |
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command] | |
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name] | |
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param] | |
il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value] | |
if (prop.PropertyType.IsValueType) | |
{ | |
il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value] | |
} | |
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("PackListParameters"), null); // stack is [parameters] | |
continue; | |
} | |
if (dbType == DynamicParameters.SqlGeographyParameter) | |
{ | |
// this actually represents special handling for list types; | |
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command] | |
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name] | |
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param] | |
il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value] | |
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("PackSqlGeographyParameter"), null); // stack is [parameters] | |
continue; | |
} | |
il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters] | |
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [parameters] [command] | |
if (checkForDuplicates) | |
{ | |
// need to be a little careful about adding; use a utility method | |
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [command] [name] | |
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("FindOrAddParameter"), null); // stack is [parameters] [parameter] | |
} | |
else | |
{ | |
// no risk of duplicates; just blindly add | |
il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod("CreateParameter"), null);// stack is now [parameters] [parameters] [parameter] | |
il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter] | |
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name] | |
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("ParameterName").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter] | |
} | |
if (dbType != DbType.Time) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time | |
{ | |
il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] | |
EmitInt32(il, (int)dbType);// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] | |
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("DbType").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] | |
} | |
il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] | |
EmitInt32(il, (int)ParameterDirection.Input);// stack is now [parameters] [[parameters]] [parameter] [parameter] [dir] | |
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Direction").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] | |
il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] | |
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] | |
il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value] | |
bool checkForNull = true; | |
if (prop.PropertyType.IsValueType) | |
{ | |
il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value] | |
if (Nullable.GetUnderlyingType(prop.PropertyType) == null) | |
{ // struct but not Nullable<T>; boxed value cannot be null | |
checkForNull = false; | |
} | |
} | |
if (checkForNull) | |
{ | |
if (dbType == DbType.String && !haveInt32Arg1) | |
{ | |
il.DeclareLocal(typeof(int)); | |
haveInt32Arg1 = true; | |
} | |
// relative stack: [boxed value] | |
il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value] | |
Label notNull = il.DefineLabel(); | |
Label? allDone = dbType == DbType.String ? il.DefineLabel() : (Label?)null; | |
il.Emit(OpCodes.Brtrue_S, notNull); | |
// relative stack [boxed value = null] | |
il.Emit(OpCodes.Pop); // relative stack empty | |
il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); // relative stack [DBNull] | |
if (dbType == DbType.String) | |
{ | |
EmitInt32(il, 0); | |
il.Emit(OpCodes.Stloc_1); | |
} | |
if (allDone != null) il.Emit(OpCodes.Br_S, allDone.Value); | |
il.MarkLabel(notNull); | |
if (prop.PropertyType == typeof(string)) | |
{ | |
il.Emit(OpCodes.Dup); // [string] [string] | |
il.EmitCall(OpCodes.Callvirt, typeof(string).GetProperty("Length").GetGetMethod(), null); // [string] [length] | |
EmitInt32(il, 4000); // [string] [length] [4000] | |
il.Emit(OpCodes.Cgt); // [string] [0 or 1] | |
Label isLong = il.DefineLabel(), lenDone = il.DefineLabel(); | |
il.Emit(OpCodes.Brtrue_S, isLong); | |
EmitInt32(il, 4000); // [string] [4000] | |
il.Emit(OpCodes.Br_S, lenDone); | |
il.MarkLabel(isLong); | |
EmitInt32(il, -1); // [string] [-1] | |
il.MarkLabel(lenDone); | |
il.Emit(OpCodes.Stloc_1); // [string] | |
} | |
if (prop.PropertyType.FullName == LinqBinary) | |
{ | |
il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance), null); | |
} | |
if (allDone != null) il.MarkLabel(allDone.Value); | |
// relative stack [boxed value or DBNull] | |
} | |
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Value").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] | |
if (prop.PropertyType == typeof(string)) | |
{ | |
var endOfSize = il.DefineLabel(); | |
// don't set if 0 | |
il.Emit(OpCodes.Ldloc_1); // [parameters] [[parameters]] [parameter] [size] | |
il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [[parameters]] [parameter] | |
il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] | |
il.Emit(OpCodes.Ldloc_1); // stack is now [parameters] [[parameters]] [parameter] [parameter] [size] | |
il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty("Size").GetSetMethod(), null); // stack is now [parameters] [[parameters]] [parameter] | |
il.MarkLabel(endOfSize); | |
} | |
if (checkForDuplicates) | |
{ | |
// stack is now [parameters] [parameter] | |
il.Emit(OpCodes.Pop); // don't need parameter any more | |
} | |
else | |
{ | |
// stack is now [parameters] [parameters] [parameter] | |
// blindly add | |
il.EmitCall(OpCodes.Callvirt, typeof(IList).GetMethod("Add"), null); // stack is now [parameters] | |
il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care | |
} | |
} | |
// stack is currently [parameters] | |
il.Emit(OpCodes.Pop); // stack is now empty | |
il.Emit(OpCodes.Ret); | |
return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>)); | |
} | |
private static IDbCommand SetupCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType) | |
{ | |
var cmd = cnn.CreateCommand(); | |
var bindByName = GetBindByName(cmd.GetType()); | |
if (bindByName != null) bindByName(cmd, true); | |
if (transaction != null) | |
cmd.Transaction = transaction; | |
cmd.CommandText = sql; | |
if (commandTimeout.HasValue) | |
cmd.CommandTimeout = commandTimeout.Value; | |
if (commandType.HasValue) | |
cmd.CommandType = commandType.Value; | |
if (paramReader != null) | |
{ | |
paramReader(cmd, obj); | |
} | |
return cmd; | |
} | |
private static int ExecuteCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType) | |
{ | |
IDbCommand cmd = null; | |
bool wasClosed = cnn.State == ConnectionState.Closed; | |
try | |
{ | |
cmd = SetupCommand(cnn, transaction, sql, paramReader, obj, commandTimeout, commandType); | |
if (wasClosed) cnn.Open(); | |
return cmd.ExecuteNonQuery(); | |
} | |
finally | |
{ | |
if (wasClosed) cnn.Close(); | |
if (cmd != null) cmd.Dispose(); | |
} | |
} | |
private static Func<IDataReader, object> GetStructDeserializer(Type type, Type effectiveType, int index) | |
{ | |
// no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!) | |
#pragma warning disable 618 | |
if (type == typeof(char)) | |
{ // this *does* need special handling, though | |
return r => SqlMapper.ReadChar(r.GetValue(index)); | |
} | |
if (type == typeof(char?)) | |
{ | |
return r => SqlMapper.ReadNullableChar(r.GetValue(index)); | |
} | |
if (type.FullName == LinqBinary) | |
{ | |
return r => Activator.CreateInstance(type, r.GetValue(index)); | |
} | |
#pragma warning restore 618 | |
if (effectiveType.IsEnum) | |
{ // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum | |
return r => | |
{ | |
var val = r.GetValue(index); | |
return val is DBNull ? null : Enum.ToObject(effectiveType, val); | |
}; | |
} | |
return r => | |
{ | |
var val = r.GetValue(index); | |
return val is DBNull ? null : val; | |
}; | |
} | |
static readonly MethodInfo | |
enumParse = typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string), typeof(bool) }), | |
getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public) | |
.Where(p => p.GetIndexParameters().Any() && p.GetIndexParameters()[0].ParameterType == typeof(int)) | |
.Select(p => p.GetGetMethod()).First(); | |
/// <summary> | |
/// Gets type-map for the given type | |
/// </summary> | |
/// <returns>Type map implementation, DefaultTypeMap instance if no override present</returns> | |
public static ITypeMap GetTypeMap(Type type) | |
{ | |
if (type == null) throw new ArgumentNullException("type"); | |
var map = (ITypeMap)_typeMaps[type]; | |
if (map == null) | |
{ | |
lock (_typeMaps) | |
{ // double-checked; store this to avoid reflection next time we see this type | |
// since multiple queries commonly use the same domain-entity/DTO/view-model type | |
map = (ITypeMap)_typeMaps[type]; | |
if (map == null) | |
{ | |
map = new DefaultTypeMap(type); | |
_typeMaps[type] = map; | |
} | |
} | |
} | |
return map; | |
} | |
// use Hashtable to get free lockless reading | |
private static readonly Hashtable _typeMaps = new Hashtable(); | |
/// <summary> | |
/// Set custom mapping for type deserializers | |
/// </summary> | |
/// <param name="type">Entity type to override</param> | |
/// <param name="map">Mapping rules impementation, null to remove custom map</param> | |
public static void SetTypeMap(Type type, ITypeMap map) | |
{ | |
if (type == null) | |
throw new ArgumentNullException("type"); | |
if (map == null || map is DefaultTypeMap) | |
{ | |
lock (_typeMaps) | |
{ | |
_typeMaps.Remove(type); | |
} | |
} | |
else | |
{ | |
lock (_typeMaps) | |
{ | |
_typeMaps[type] = map; | |
} | |
} | |
PurgeQueryCacheByType(type); | |
} | |
/// <summary> | |
/// Internal use only | |
/// </summary> | |
/// <param name="type"></param> | |
/// <param name="reader"></param> | |
/// <param name="startBound"></param> | |
/// <param name="length"></param> | |
/// <param name="returnNullIfFirstMissing"></param> | |
/// <returns></returns> | |
public static Func<IDataReader, object> GetTypeDeserializer( | |
#if CSHARP30 | |
Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing | |
#else | |
Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false | |
#endif | |
) | |
{ | |
var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(IDataReader) }, true); | |
var il = dm.GetILGenerator(); | |
il.DeclareLocal(typeof(int)); | |
il.DeclareLocal(type); | |
il.Emit(OpCodes.Ldc_I4_0); | |
il.Emit(OpCodes.Stloc_0); | |
if (length == -1) | |
{ | |
length = reader.FieldCount - startBound; | |
} | |
if (reader.FieldCount <= startBound) | |
{ | |
throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); | |
} | |
var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray(); | |
ITypeMap typeMap = GetTypeMap(type); | |
int index = startBound; | |
ConstructorInfo specializedConstructor = null; | |
if (type.IsValueType) | |
{ | |
il.Emit(OpCodes.Ldloca_S, (byte)1); | |
il.Emit(OpCodes.Initobj, type); | |
} | |
else | |
{ | |
var types = new Type[length]; | |
for (int i = startBound; i < startBound + length; i++) | |
{ | |
types[i - startBound] = reader.GetFieldType(i); | |
} | |
if (type.IsValueType) | |
{ | |
il.Emit(OpCodes.Ldloca_S, (byte)1); | |
il.Emit(OpCodes.Initobj, type); | |
} | |
else | |
{ | |
var ctor = typeMap.FindConstructor(names, types); | |
if (ctor == null) | |
{ | |
string proposedTypes = "(" + String.Join(", ", types.Select((t, i) => t.FullName + " " + names[i]).ToArray()) + ")"; | |
throw new InvalidOperationException(String.Format("A parameterless default constructor or one matching signature {0} is required for {1} materialization", proposedTypes, type.FullName)); | |
} | |
if (ctor.GetParameters().Length == 0) | |
{ | |
il.Emit(OpCodes.Newobj, ctor); | |
il.Emit(OpCodes.Stloc_1); | |
} | |
else | |
specializedConstructor = ctor; | |
} | |
} | |
il.BeginExceptionBlock(); | |
if (type.IsValueType) | |
{ | |
il.Emit(OpCodes.Ldloca_S, (byte)1);// [target] | |
} | |
else if (specializedConstructor == null) | |
{ | |
il.Emit(OpCodes.Ldloc_1);// [target] | |
} | |
var members = (specializedConstructor != null | |
? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n)) | |
: names.Select(n => typeMap.GetMember(n))).ToList(); | |
// stack is now [target] | |
bool first = true; | |
var allDone = il.DefineLabel(); | |
int enumDeclareLocal = -1; | |
foreach (var item in members) | |
{ | |
if (item != null) | |
{ | |
if (specializedConstructor == null) | |
il.Emit(OpCodes.Dup); // stack is now [target][target] | |
Label isDbNullLabel = il.DefineLabel(); | |
Label finishLabel = il.DefineLabel(); | |
il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader] | |
EmitInt32(il, index); // stack is now [target][target][reader][index] | |
il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index] | |
il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index] | |
il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object] | |
Type memberType = item.MemberType; | |
if (memberType == typeof(char) || memberType == typeof(char?)) | |
{ | |
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod( | |
memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value] | |
} | |
else | |
{ | |
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value] | |
il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null] | |
il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object] | |
// unbox nullable enums as the primitive, i.e. byte etc | |
var nullUnderlyingType = Nullable.GetUnderlyingType(memberType); | |
var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType; | |
if (unboxType.IsEnum) | |
{ | |
if (enumDeclareLocal == -1) | |
{ | |
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex; | |
} | |
Label isNotString = il.DefineLabel(); | |
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value] | |
il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null] | |
il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null] | |
StoreLocal(il, enumDeclareLocal); // stack is now [target][target][value-as-object][string or null] | |
il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object] | |
il.Emit(OpCodes.Pop); // stack is now [target][target] | |
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token] | |
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type] | |
il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string] | |
il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true] | |
il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object] | |
il.MarkLabel(isNotString); | |
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] | |
if (nullUnderlyingType != null) | |
{ | |
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][enum-value] | |
} | |
} | |
else if (memberType.FullName == LinqBinary) | |
{ | |
il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array] | |
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary] | |
} | |
else | |
{ | |
Type dataType = reader.GetFieldType(index); | |
TypeCode dataTypeCode = Type.GetTypeCode(dataType), unboxTypeCode = Type.GetTypeCode(unboxType); | |
if (dataType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType)) | |
{ | |
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] | |
} | |
else | |
{ | |
// not a direct match; need to tweak the unbox | |
bool handled = true; | |
OpCode opCode = default(OpCode); | |
if (dataTypeCode == TypeCode.Decimal || unboxTypeCode == TypeCode.Decimal) | |
{ // no IL level conversions to/from decimal; I guess we could use the static operators, but | |
// this feels an edge-case | |
handled = false; | |
} | |
else | |
{ | |
switch (unboxTypeCode) | |
{ | |
case TypeCode.Byte: | |
opCode = OpCodes.Conv_Ovf_I1_Un; break; | |
case TypeCode.SByte: | |
opCode = OpCodes.Conv_Ovf_I1; break; | |
case TypeCode.UInt16: | |
opCode = OpCodes.Conv_Ovf_I2_Un; break; | |
case TypeCode.Int16: | |
opCode = OpCodes.Conv_Ovf_I2; break; | |
case TypeCode.UInt32: | |
opCode = OpCodes.Conv_Ovf_I4_Un; break; | |
case TypeCode.Boolean: // boolean is basically an int, at least at this level | |
case TypeCode.Int32: | |
opCode = OpCodes.Conv_Ovf_I4; break; | |
case TypeCode.UInt64: | |
opCode = OpCodes.Conv_Ovf_I8_Un; break; | |
case TypeCode.Int64: | |
opCode = OpCodes.Conv_Ovf_I8; break; | |
case TypeCode.Single: | |
opCode = OpCodes.Conv_R4; break; | |
case TypeCode.Double: | |
opCode = OpCodes.Conv_R8; break; | |
default: | |
handled = false; | |
break; | |
} | |
} | |
if (handled) | |
{ // unbox as the data-type, then use IL-level convert | |
il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value] | |
il.Emit(opCode); // stack is now [target][target][typed-value] | |
if (unboxTypeCode == TypeCode.Boolean) | |
{ // compare to zero; I checked "csc" - this is the trick it uses; nice | |
il.Emit(OpCodes.Ldc_I4_0); | |
il.Emit(OpCodes.Ceq); | |
il.Emit(OpCodes.Ldc_I4_0); | |
il.Emit(OpCodes.Ceq); | |
} | |
} | |
else | |
{ // use flexible conversion | |
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][value][member-type-token] | |
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // stack is now [target][target][value][member-type] | |
il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value] | |
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] | |
} | |
} | |
} | |
} | |
if (specializedConstructor == null) | |
{ | |
// Store the value in the property/field | |
if (item.Property != null) | |
{ | |
if (type.IsValueType) | |
{ | |
il.Emit(OpCodes.Call, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target] | |
} | |
else | |
{ | |
il.Emit(OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target] | |
} | |
} | |
else | |
{ | |
il.Emit(OpCodes.Stfld, item.Field); // stack is now [target] | |
} | |
} | |
il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target] | |
il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value] | |
if (specializedConstructor != null) | |
{ | |
il.Emit(OpCodes.Pop); | |
if (item.MemberType.IsValueType) | |
{ | |
int localIndex = il.DeclareLocal(item.MemberType).LocalIndex; | |
LoadLocalAddress(il, localIndex); | |
il.Emit(OpCodes.Initobj, item.MemberType); | |
LoadLocal(il, localIndex); | |
} | |
else | |
{ | |
il.Emit(OpCodes.Ldnull); | |
} | |
} | |
else | |
{ | |
il.Emit(OpCodes.Pop); // stack is now [target][target] | |
il.Emit(OpCodes.Pop); // stack is now [target] | |
} | |
if (first && returnNullIfFirstMissing) | |
{ | |
il.Emit(OpCodes.Pop); | |
il.Emit(OpCodes.Ldnull); // stack is now [null] | |
il.Emit(OpCodes.Stloc_1); | |
il.Emit(OpCodes.Br, allDone); | |
} | |
il.MarkLabel(finishLabel); | |
} | |
first = false; | |
index += 1; | |
} | |
if (type.IsValueType) | |
{ | |
il.Emit(OpCodes.Pop); | |
} | |
else | |
{ | |
if (specializedConstructor != null) | |
{ | |
il.Emit(OpCodes.Newobj, specializedConstructor); | |
} | |
il.Emit(OpCodes.Stloc_1); // stack is empty | |
} | |
il.MarkLabel(allDone); | |
il.BeginCatchBlock(typeof(Exception)); // stack is Exception | |
il.Emit(OpCodes.Ldloc_0); // stack is Exception, index | |
il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader | |
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null); | |
il.EndExceptionBlock(); | |
il.Emit(OpCodes.Ldloc_1); // stack is [rval] | |
if (type.IsValueType) | |
{ | |
il.Emit(OpCodes.Box, type); | |
} | |
il.Emit(OpCodes.Ret); | |
return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>)); | |
} | |
private static void LoadLocal(ILGenerator il, int index) | |
{ | |
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index"); | |
switch (index) | |
{ | |
case 0: il.Emit(OpCodes.Ldloc_0); break; | |
case 1: il.Emit(OpCodes.Ldloc_1); break; | |
case 2: il.Emit(OpCodes.Ldloc_2); break; | |
case 3: il.Emit(OpCodes.Ldloc_3); break; | |
default: | |
if (index <= 255) | |
{ | |
il.Emit(OpCodes.Ldloc_S, (byte)index); | |
} | |
else | |
{ | |
il.Emit(OpCodes.Ldloc, (short)index); | |
} | |
break; | |
} | |
} | |
private static void StoreLocal(ILGenerator il, int index) | |
{ | |
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index"); | |
switch (index) | |
{ | |
case 0: il.Emit(OpCodes.Stloc_0); break; | |
case 1: il.Emit(OpCodes.Stloc_1); break; | |
case 2: il.Emit(OpCodes.Stloc_2); break; | |
case 3: il.Emit(OpCodes.Stloc_3); break; | |
default: | |
if (index <= 255) | |
{ | |
il.Emit(OpCodes.Stloc_S, (byte)index); | |
} | |
else | |
{ | |
il.Emit(OpCodes.Stloc, (short)index); | |
} | |
break; | |
} | |
} | |
private static void LoadLocalAddress(ILGenerator il, int index) | |
{ | |
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index"); | |
if (index <= 255) | |
{ | |
il.Emit(OpCodes.Ldloca_S, (byte)index); | |
} | |
else | |
{ | |
il.Emit(OpCodes.Ldloca, (short)index); | |
} | |
} | |
/// <summary> | |
/// Throws a data exception, only used internally | |
/// </summary> | |
/// <param name="ex"></param> | |
/// <param name="index"></param> | |
/// <param name="reader"></param> | |
public static void ThrowDataException(Exception ex, int index, IDataReader reader) | |
{ | |
Exception toThrow; | |
try | |
{ | |
string name = "(n/a)", value = "(n/a)"; | |
if (reader != null && index >= 0 && index < reader.FieldCount) | |
{ | |
name = reader.GetName(index); | |
object val = reader.GetValue(index); | |
if (val == null || val is DBNull) | |
{ | |
value = "<null>"; | |
} | |
else | |
{ | |
value = Convert.ToString(val) + " - " + Type.GetTypeCode(val.GetType()); | |
} | |
} | |
toThrow = new DataException(string.Format("Error parsing column {0} ({1}={2})", index, name, value), ex); | |
} | |
catch | |
{ // throw the **original** exception, wrapped as DataException | |
toThrow = new DataException(ex.Message, ex); | |
} | |
throw toThrow; | |
} | |
private static void EmitInt32(ILGenerator il, int value) | |
{ | |
switch (value) | |
{ | |
case -1: il.Emit(OpCodes.Ldc_I4_M1); break; | |
case 0: il.Emit(OpCodes.Ldc_I4_0); break; | |
case 1: il.Emit(OpCodes.Ldc_I4_1); break; | |
case 2: il.Emit(OpCodes.Ldc_I4_2); break; | |
case 3: il.Emit(OpCodes.Ldc_I4_3); break; | |
case 4: il.Emit(OpCodes.Ldc_I4_4); break; | |
case 5: il.Emit(OpCodes.Ldc_I4_5); break; | |
case 6: il.Emit(OpCodes.Ldc_I4_6); break; | |
case 7: il.Emit(OpCodes.Ldc_I4_7); break; | |
case 8: il.Emit(OpCodes.Ldc_I4_8); break; | |
default: | |
if (value >= -128 && value <= 127) | |
{ | |
il.Emit(OpCodes.Ldc_I4_S, (sbyte)value); | |
} | |
else | |
{ | |
il.Emit(OpCodes.Ldc_I4, value); | |
} | |
break; | |
} | |
} | |
/// <summary> | |
/// The grid reader provides interfaces for reading multiple result sets from a Dapper query | |
/// </summary> | |
public partial class GridReader : IDisposable | |
{ | |
private IDataReader reader; | |
private IDbCommand command; | |
private Identity identity; | |
internal GridReader(IDbCommand command, IDataReader reader, Identity identity) | |
{ | |
this.command = command; | |
this.reader = reader; | |
this.identity = identity; | |
} | |
#if !CSHARP30 | |
/// <summary> | |
/// Read the next grid of results, returned as a dynamic object | |
/// </summary> | |
public IEnumerable<dynamic> Read(bool buffered = true) | |
{ | |
return Read<DapperRow>(buffered); | |
} | |
#endif | |
#if CSHARP30 | |
/// <summary> | |
/// Read the next grid of results | |
/// </summary> | |
public IEnumerable<T> Read<T>() | |
{ | |
return Read<T>(true); | |
} | |
#endif | |
/// <summary> | |
/// Read the next grid of results | |
/// </summary> | |
#if CSHARP30 | |
public IEnumerable<T> Read<T>(bool buffered) | |
#else | |
public IEnumerable<T> Read<T>(bool buffered = true) | |
#endif | |
{ | |
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); | |
if (consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); | |
var typedIdentity = identity.ForGrid(typeof(T), gridIndex); | |
CacheInfo cache = GetCacheInfo(typedIdentity); | |
var deserializer = cache.Deserializer; | |
int hash = GetColumnHash(reader); | |
if (deserializer.Func == null || deserializer.Hash != hash) | |
{ | |
deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false)); | |
cache.Deserializer = deserializer; | |
} | |
consumed = true; | |
var result = ReadDeferred<T>(gridIndex, deserializer.Func, typedIdentity); | |
return buffered ? result.ToList() : result; | |
} | |
private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(object func, string splitOn) | |
{ | |
var identity = this.identity.ForGrid(typeof(TReturn), new Type[] { | |
typeof(TFirst), | |
typeof(TSecond), | |
typeof(TThird), | |
typeof(TFourth), | |
typeof(TFifth) | |
}, gridIndex); | |
try | |
{ | |
foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(null, null, func, null, null, splitOn, null, null, reader, identity)) | |
{ | |
yield return r; | |
} | |
} | |
finally | |
{ | |
NextResult(); | |
} | |
} | |
#if CSHARP30 | |
/// <summary> | |
/// Read multiple objects from a single recordset on the grid | |
/// </summary> | |
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn) | |
{ | |
return Read<TFirst, TSecond, TReturn>(func, splitOn, true); | |
} | |
#endif | |
/// <summary> | |
/// Read multiple objects from a single recordset on the grid | |
/// </summary> | |
#if CSHARP30 | |
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn, bool buffered) | |
#else | |
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn = "id", bool buffered = true) | |
#endif | |
{ | |
var result = MultiReadInternal<TFirst, TSecond, DontMap, DontMap, DontMap, TReturn>(func, splitOn); | |
return buffered ? result.ToList() : result; | |
} | |
#if CSHARP30 | |
/// <summary> | |
/// Read multiple objects from a single recordset on the grid | |
/// </summary> | |
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn) | |
{ | |
return Read<TFirst, TSecond, TThird, TReturn>(func, splitOn, true); | |
} | |
#endif | |
/// <summary> | |
/// Read multiple objects from a single recordset on the grid | |
/// </summary> | |
#if CSHARP30 | |
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn, bool buffered) | |
#else | |
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn = "id", bool buffered = true) | |
#endif | |
{ | |
var result = MultiReadInternal<TFirst, TSecond, TThird, DontMap, DontMap, TReturn>(func, splitOn); | |
return buffered ? result.ToList() : result; | |
} | |
#if CSHARP30 | |
/// <summary> | |
/// Read multiple objects from a single record set on the grid | |
/// </summary> | |
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn) | |
{ | |
return Read<TFirst, TSecond, TThird, TFourth, TReturn>(func, splitOn, true); | |
} | |
#endif | |
/// <summary> | |
/// Read multiple objects from a single record set on the grid | |
/// </summary> | |
#if CSHARP30 | |
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn, bool buffered) | |
#else | |
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id", bool buffered = true) | |
#endif | |
{ | |
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, TReturn>(func, splitOn); | |
return buffered ? result.ToList() : result; | |
} | |
#if !CSHARP30 | |
/// <summary> | |
/// Read multiple objects from a single record set on the grid | |
/// </summary> | |
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> func, string splitOn = "id", bool buffered = true) | |
{ | |
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(func, splitOn); | |
return buffered ? result.ToList() : result; | |
} | |
#endif | |
private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity) | |
{ | |
try | |
{ | |
while (index == gridIndex && reader.Read()) | |
{ | |
yield return (T)deserializer(reader); | |
} | |
} | |
finally // finally so that First etc progresses things even when multiple rows | |
{ | |
if (index == gridIndex) | |
{ | |
NextResult(); | |
} | |
} | |
} | |
private int gridIndex, readCount; | |
private bool consumed; | |
private void NextResult() | |
{ | |
if (reader.NextResult()) | |
{ | |
readCount++; | |
gridIndex++; | |
consumed = false; | |
} | |
else | |
{ | |
// happy path; close the reader cleanly - no | |
// need for "Cancel" etc | |
reader.Dispose(); | |
reader = null; | |
Dispose(); | |
} | |
} | |
/// <summary> | |
/// Dispose the grid, closing and disposing both the underlying reader and command. | |
/// </summary> | |
public void Dispose() | |
{ | |
if (reader != null) | |
{ | |
if (!reader.IsClosed && command != null) command.Cancel(); | |
reader.Dispose(); | |
reader = null; | |
} | |
if (command != null) | |
{ | |
command.Dispose(); | |
command = null; | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// A bag of parameters that can be passed to the Dapper Query and Execute methods | |
/// </summary> | |
partial class DynamicParameters : SqlMapper.IDynamicParameters | |
{ | |
internal const DbType EnumerableMultiParameter = (DbType)(-1); | |
internal const DbType SqlGeographyParameter = (DbType)(-2); | |
static Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>(); | |
Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>(); | |
List<object> templates; | |
partial class ParamInfo | |
{ | |
public string Name { get; set; } | |
public object Value { get; set; } | |
public ParameterDirection ParameterDirection { get; set; } | |
public DbType? DbType { get; set; } | |
public int? Size { get; set; } | |
public IDbDataParameter AttachedParam { get; set; } | |
} | |
/// <summary> | |
/// construct a dynamic parameter bag | |
/// </summary> | |
public DynamicParameters() { } | |
/// <summary> | |
/// construct a dynamic parameter bag | |
/// </summary> | |
/// <param name="template">can be an anonymous type or a DynamicParameters bag</param> | |
public DynamicParameters(object template) | |
{ | |
AddDynamicParams(template); | |
} | |
/// <summary> | |
/// Append a whole object full of params to the dynamic | |
/// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic | |
/// </summary> | |
/// <param name="param"></param> | |
public void AddDynamicParams( | |
#if CSHARP30 | |
object param | |
#else | |
dynamic param | |
#endif | |
) | |
{ | |
var obj = param as object; | |
if (obj != null) | |
{ | |
var subDynamic = obj as DynamicParameters; | |
if (subDynamic == null) | |
{ | |
var dictionary = obj as IEnumerable<KeyValuePair<string, object>>; | |
if (dictionary == null) | |
{ | |
templates = templates ?? new List<object>(); | |
templates.Add(obj); | |
} | |
else | |
{ | |
foreach (var kvp in dictionary) | |
{ | |
#if CSHARP30 | |
Add(kvp.Key, kvp.Value, null, null, null); | |
#else | |
Add(kvp.Key, kvp.Value); | |
#endif | |
} | |
} | |
} | |
else | |
{ | |
if (subDynamic.parameters != null) | |
{ | |
foreach (var kvp in subDynamic.parameters) | |
{ | |
parameters.Add(kvp.Key, kvp.Value); | |
} | |
} | |
if (subDynamic.templates != null) | |
{ | |
templates = templates ?? new List<object>(); | |
foreach (var t in subDynamic.templates) | |
{ | |
templates.Add(t); | |
} | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// Add a parameter to this dynamic parameter list | |
/// </summary> | |
/// <param name="name"></param> | |
/// <param name="value"></param> | |
/// <param name="dbType"></param> | |
/// <param name="direction"></param> | |
/// <param name="size"></param> | |
public void Add( | |
#if CSHARP30 | |
string name, object value, DbType? dbType, ParameterDirection? direction, int? size | |
#else | |
string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null | |
#endif | |
) | |
{ | |
parameters[Clean(name)] = new ParamInfo() { Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType, Size = size }; | |
} | |
static string Clean(string name) | |
{ | |
if (!string.IsNullOrEmpty(name)) | |
{ | |
switch (name[0]) | |
{ | |
case '@': | |
case ':': | |
case '?': | |
return name.Substring(1); | |
} | |
} | |
return name; | |
} | |
void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity) | |
{ | |
AddParameters(command, identity); | |
} | |
/// <summary> | |
/// Add all the parameters needed to the command just before it executes | |
/// </summary> | |
/// <param name="command">The raw command prior to execution</param> | |
/// <param name="identity">Information about the query</param> | |
protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) | |
{ | |
if (templates != null) | |
{ | |
foreach (var template in templates) | |
{ | |
var newIdent = identity.ForDynamicParameters(template.GetType()); | |
Action<IDbCommand, object> appender; | |
lock (paramReaderCache) | |
{ | |
if (!paramReaderCache.TryGetValue(newIdent, out appender)) | |
{ | |
appender = SqlMapper.CreateParamInfoGenerator(newIdent, true); | |
paramReaderCache[newIdent] = appender; | |
} | |
} | |
appender(command, template); | |
} | |
} | |
foreach (var param in parameters.Values) | |
{ | |
var dbType = param.DbType; | |
var val = param.Value; | |
string name = Clean(param.Name); | |
if (dbType == null && val != null) dbType = SqlMapper.LookupDbType(val.GetType(), name); | |
if (dbType == DynamicParameters.EnumerableMultiParameter) | |
{ | |
#pragma warning disable 612, 618 | |
SqlMapper.PackListParameters(command, name, val); | |
#pragma warning restore 612, 618 | |
} | |
else | |
{ | |
bool add = !command.Parameters.Contains(name); | |
IDbDataParameter p; | |
if (add) | |
{ | |
p = command.CreateParameter(); | |
p.ParameterName = name; | |
} | |
else | |
{ | |
p = (IDbDataParameter)command.Parameters[name]; | |
} | |
p.Value = val ?? DBNull.Value; | |
p.Direction = param.ParameterDirection; | |
var s = val as string; | |
if (s != null) | |
{ | |
if (s.Length <= 4000) | |
{ | |
p.Size = 4000; | |
} | |
} | |
if (param.Size != null) | |
{ | |
p.Size = param.Size.Value; | |
} | |
if (dbType != null) | |
{ | |
p.DbType = dbType.Value; | |
} | |
if (add) | |
{ | |
command.Parameters.Add(p); | |
} | |
param.AttachedParam = p; | |
} | |
} | |
} | |
/// <summary> | |
/// All the names of the param in the bag, use Get to yank them out | |
/// </summary> | |
public IEnumerable<string> ParameterNames | |
{ | |
get | |
{ | |
return parameters.Select(p => p.Key); | |
} | |
} | |
/// <summary> | |
/// Get the value of a parameter | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
/// <param name="name"></param> | |
/// <returns>The value, note DBNull.Value is not returned, instead the value is returned as null</returns> | |
public T Get<T>(string name) | |
{ | |
var val = parameters[Clean(name)].AttachedParam.Value; | |
if (val == DBNull.Value) | |
{ | |
if (default(T) != null) | |
{ | |
throw new ApplicationException("Attempting to cast a DBNull to a non nullable type!"); | |
} | |
return default(T); | |
} | |
return (T)val; | |
} | |
} | |
/// <summary> | |
/// This class represents a SQL string, it can be used if you need to denote your parameter is a Char vs VarChar vs nVarChar vs nChar | |
/// </summary> | |
sealed partial class DbString | |
{ | |
/// <summary> | |
/// Create a new DbString | |
/// </summary> | |
public DbString() { Length = -1; } | |
/// <summary> | |
/// Ansi vs Unicode | |
/// </summary> | |
public bool IsAnsi { get; set; } | |
/// <summary> | |
/// Fixed length | |
/// </summary> | |
public bool IsFixedLength { get; set; } | |
/// <summary> | |
/// Length of the string -1 for max | |
/// </summary> | |
public int Length { get; set; } | |
/// <summary> | |
/// The value of the string | |
/// </summary> | |
public string Value { get; set; } | |
/// <summary> | |
/// Add the parameter to the command... internal use only | |
/// </summary> | |
/// <param name="command"></param> | |
/// <param name="name"></param> | |
public void AddParameter(IDbCommand command, string name) | |
{ | |
if (IsFixedLength && Length == -1) | |
{ | |
throw new InvalidOperationException("If specifying IsFixedLength, a Length must also be specified"); | |
} | |
var param = command.CreateParameter(); | |
param.ParameterName = name; | |
param.Value = (object)Value ?? DBNull.Value; | |
if (Length == -1 && Value != null && Value.Length <= 4000) | |
{ | |
param.Size = 4000; | |
} | |
else | |
{ | |
param.Size = Length; | |
} | |
param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String); | |
command.Parameters.Add(param); | |
} | |
} | |
/// <summary> | |
/// Handles variances in features per DBMS | |
/// </summary> | |
partial class FeatureSupport | |
{ | |
/// <summary> | |
/// Dictionary of supported features index by connection type name | |
/// </summary> | |
private static readonly Dictionary<string, FeatureSupport> FeatureList = new Dictionary<string, FeatureSupport>(StringComparer.InvariantCultureIgnoreCase) { | |
{"sqlserverconnection", new FeatureSupport { Arrays = false}}, | |
{"npgsqlconnection", new FeatureSupport {Arrays = true}} | |
}; | |
/// <summary> | |
/// Gets the featureset based on the passed connection | |
/// </summary> | |
public static FeatureSupport Get(IDbConnection connection) | |
{ | |
string name = connection.GetType().Name; | |
FeatureSupport features; | |
return FeatureList.TryGetValue(name, out features) ? features : FeatureList.Values.First(); | |
} | |
/// <summary> | |
/// True if the db supports array columns e.g. Postgresql | |
/// </summary> | |
public bool Arrays { get; set; } | |
} | |
/// <summary> | |
/// Represents simple memeber map for one of target parameter or property or field to source DataReader column | |
/// </summary> | |
sealed partial class SimpleMemberMap : SqlMapper.IMemberMap | |
{ | |
private readonly string _columnName; | |
private readonly PropertyInfo _property; | |
private readonly FieldInfo _field; | |
private readonly ParameterInfo _parameter; | |
/// <summary> | |
/// Creates instance for simple property mapping | |
/// </summary> | |
/// <param name="columnName">DataReader column name</param> | |
/// <param name="property">Target property</param> | |
public SimpleMemberMap(string columnName, PropertyInfo property) | |
{ | |
if (columnName == null) | |
throw new ArgumentNullException("columnName"); | |
if (property == null) | |
throw new ArgumentNullException("property"); | |
_columnName = columnName; | |
_property = property; | |
} | |
/// <summary> | |
/// Creates instance for simple field mapping | |
/// </summary> | |
/// <param name="columnName">DataReader column name</param> | |
/// <param name="field">Target property</param> | |
public SimpleMemberMap(string columnName, FieldInfo field) | |
{ | |
if (columnName == null) | |
throw new ArgumentNullException("columnName"); | |
if (field == null) | |
throw new ArgumentNullException("field"); | |
_columnName = columnName; | |
_field = field; | |
} | |
/// <summary> | |
/// Creates instance for simple constructor parameter mapping | |
/// </summary> | |
/// <param name="columnName">DataReader column name</param> | |
/// <param name="parameter">Target constructor parameter</param> | |
public SimpleMemberMap(string columnName, ParameterInfo parameter) | |
{ | |
if (columnName == null) | |
throw new ArgumentNullException("columnName"); | |
if (parameter == null) | |
throw new ArgumentNullException("parameter"); | |
_columnName = columnName; | |
_parameter = parameter; | |
} | |
/// <summary> | |
/// DataReader column name | |
/// </summary> | |
public string ColumnName | |
{ | |
get { return _columnName; } | |
} | |
/// <summary> | |
/// Target member type | |
/// </summary> | |
public Type MemberType | |
{ | |
get | |
{ | |
if (_field != null) | |
return _field.FieldType; | |
if (_property != null) | |
return _property.PropertyType; | |
if (_parameter != null) | |
return _parameter.ParameterType; | |
return null; | |
} | |
} | |
/// <summary> | |
/// Target property | |
/// </summary> | |
public PropertyInfo Property | |
{ | |
get { return _property; } | |
} | |
/// <summary> | |
/// Target field | |
/// </summary> | |
public FieldInfo Field | |
{ | |
get { return _field; } | |
} | |
/// <summary> | |
/// Target constructor parameter | |
/// </summary> | |
public ParameterInfo Parameter | |
{ | |
get { return _parameter; } | |
} | |
} | |
/// <summary> | |
/// Represents default type mapping strategy used by Dapper | |
/// </summary> | |
sealed partial class DefaultTypeMap : SqlMapper.ITypeMap | |
{ | |
private readonly List<FieldInfo> _fields; | |
private readonly List<PropertyInfo> _properties; | |
private readonly Type _type; | |
/// <summary> | |
/// Creates default type map | |
/// </summary> | |
/// <param name="type">Entity type</param> | |
public DefaultTypeMap(Type type) | |
{ | |
if (type == null) | |
throw new ArgumentNullException("type"); | |
_fields = GetSettableFields(type); | |
_properties = GetSettableProps(type); | |
_type = type; | |
} | |
internal static MethodInfo GetPropertySetter(PropertyInfo propertyInfo, Type type) | |
{ | |
return propertyInfo.DeclaringType == type ? | |
propertyInfo.GetSetMethod(true) : | |
propertyInfo.DeclaringType.GetProperty(propertyInfo.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetSetMethod(true); | |
} | |
internal static List<PropertyInfo> GetSettableProps(Type t) | |
{ | |
return t | |
.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) | |
.Where(p => GetPropertySetter(p, t) != null) | |
.ToList(); | |
} | |
internal static List<FieldInfo> GetSettableFields(Type t) | |
{ | |
return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList(); | |
} | |
/// <summary> | |
/// Finds best constructor | |
/// </summary> | |
/// <param name="names">DataReader column names</param> | |
/// <param name="types">DataReader column types</param> | |
/// <returns>Matching constructor or default one</returns> | |
public ConstructorInfo FindConstructor(string[] names, Type[] types) | |
{ | |
var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); | |
foreach (ConstructorInfo ctor in constructors.OrderBy(c => c.IsPublic ? 0 : (c.IsPrivate ? 2 : 1)).ThenBy(c => c.GetParameters().Length)) | |
{ | |
ParameterInfo[] ctorParameters = ctor.GetParameters(); | |
if (ctorParameters.Length == 0) | |
return ctor; | |
if (ctorParameters.Length != types.Length) | |
continue; | |
int i = 0; | |
for (; i < ctorParameters.Length; i++) | |
{ | |
if (!String.Equals(ctorParameters[i].Name, names[i], StringComparison.OrdinalIgnoreCase)) | |
break; | |
if (types[i] == typeof(byte[]) && ctorParameters[i].ParameterType.FullName == SqlMapper.LinqBinary) | |
continue; | |
var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType; | |
if (unboxedType != types[i] | |
&& !(unboxedType.IsEnum && Enum.GetUnderlyingType(unboxedType) == types[i]) | |
&& !(unboxedType == typeof(char) && types[i] == typeof(string))) | |
break; | |
} | |
if (i == ctorParameters.Length) | |
return ctor; | |
} | |
return null; | |
} | |
/// <summary> | |
/// Gets mapping for constructor parameter | |
/// </summary> | |
/// <param name="constructor">Constructor to resolve</param> | |
/// <param name="columnName">DataReader column name</param> | |
/// <returns>Mapping implementation</returns> | |
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName) | |
{ | |
var parameters = constructor.GetParameters(); | |
return new SimpleMemberMap(columnName, parameters.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase))); | |
} | |
/// <summary> | |
/// Gets member mapping for column | |
/// </summary> | |
/// <param name="columnName">DataReader column name</param> | |
/// <returns>Mapping implementation</returns> | |
public SqlMapper.IMemberMap GetMember(string columnName) | |
{ | |
var property = _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) | |
?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)); | |
if (property != null) | |
return new SimpleMemberMap(columnName, property); | |
var field = _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) | |
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)); | |
if (field != null) | |
return new SimpleMemberMap(columnName, field); | |
return null; | |
} | |
} | |
/// <summary> | |
/// Implements custom property mapping by user provided criteria (usually presence of some custom attribute with column to member mapping) | |
/// </summary> | |
sealed partial class CustomPropertyTypeMap : SqlMapper.ITypeMap | |
{ | |
private readonly Type _type; | |
private readonly Func<Type, string, PropertyInfo> _propertySelector; | |
/// <summary> | |
/// Creates custom property mapping | |
/// </summary> | |
/// <param name="type">Target entity type</param> | |
/// <param name="propertySelector">Property selector based on target type and DataReader column name</param> | |
public CustomPropertyTypeMap(Type type, Func<Type, string, PropertyInfo> propertySelector) | |
{ | |
if (type == null) | |
throw new ArgumentNullException("type"); | |
if (propertySelector == null) | |
throw new ArgumentNullException("propertySelector"); | |
_type = type; | |
_propertySelector = propertySelector; | |
} | |
/// <summary> | |
/// Always returns default constructor | |
/// </summary> | |
/// <param name="names">DataReader column names</param> | |
/// <param name="types">DataReader column types</param> | |
/// <returns>Default constructor</returns> | |
public ConstructorInfo FindConstructor(string[] names, Type[] types) | |
{ | |
return _type.GetConstructor(new Type[0]); | |
} | |
/// <summary> | |
/// Not impelmeneted as far as default constructor used for all cases | |
/// </summary> | |
/// <param name="constructor"></param> | |
/// <param name="columnName"></param> | |
/// <returns></returns> | |
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName) | |
{ | |
throw new NotSupportedException(); | |
} | |
/// <summary> | |
/// Returns property based on selector strategy | |
/// </summary> | |
/// <param name="columnName">DataReader column name</param> | |
/// <returns>Poperty member map</returns> | |
public SqlMapper.IMemberMap GetMember(string columnName) | |
{ | |
var prop = _propertySelector(_type, columnName); | |
return prop != null ? new SimpleMemberMap(columnName, prop) : null; | |
} | |
} | |
// Define DAPPER_MAKE_PRIVATE if you reference Dapper by source | |
// and you like to make the Dapper types private (in order to avoid | |
// conflicts with other projects that also reference Dapper by source) | |
#if !DAPPER_MAKE_PRIVATE | |
public partial class SqlMapper | |
{ | |
} | |
public partial class DynamicParameters | |
{ | |
} | |
public partial class DbString | |
{ | |
} | |
public partial class SimpleMemberMap | |
{ | |
} | |
public partial class DefaultTypeMap | |
{ | |
} | |
public partial class CustomPropertyTypeMap | |
{ | |
} | |
public partial class FeatureSupport | |
{ | |
} | |
#endif | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment