|
/* |
|
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; |
|
|