mirror of
https://github.com/DapperLib/Dapper.git
synced 2026-10-19 23:28:15 +08:00
4184 lines
181 KiB
C#
4184 lines
181 KiB
C#
/*
|
|
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.Globalization;
|
|
|
|
namespace Dapper
|
|
{
|
|
|
|
/// <summary>
|
|
/// Additional state flags that control command behaviour
|
|
/// </summary>
|
|
[Flags]
|
|
public enum CommandFlags
|
|
{
|
|
/// <summary>
|
|
/// No additonal flags
|
|
/// </summary>
|
|
None = 0,
|
|
/// <summary>
|
|
/// Should data be buffered before returning?
|
|
/// </summary>
|
|
Buffered = 1,
|
|
/// <summary>
|
|
/// Can async queries be pipelined?
|
|
/// </summary>
|
|
Pipelined = 2,
|
|
}
|
|
/// <summary>
|
|
/// Represents the key aspects of a sql operation
|
|
/// </summary>
|
|
public struct CommandDefinition
|
|
{
|
|
private readonly string commandText;
|
|
private readonly object parameters;
|
|
private readonly IDbTransaction transaction;
|
|
private readonly int? commandTimeout;
|
|
private readonly CommandType? commandType;
|
|
private readonly CommandFlags flags;
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// The command (sql or a stored-procedure name) to execute
|
|
/// </summary>
|
|
public string CommandText { get { return commandText; } }
|
|
/// <summary>
|
|
/// The parameters associated with the command
|
|
/// </summary>
|
|
public object Parameters { get { return parameters; } }
|
|
/// <summary>
|
|
/// The active transaction for the command
|
|
/// </summary>
|
|
public IDbTransaction Transaction { get { return transaction; } }
|
|
/// <summary>
|
|
/// The effective timeout for the command
|
|
/// </summary>
|
|
public int? CommandTimeout { get { return commandTimeout; } }
|
|
/// <summary>
|
|
/// The type of command that the command-text represents
|
|
/// </summary>
|
|
public CommandType? CommandType { get { return commandType; } }
|
|
|
|
/// <summary>
|
|
/// Should data be buffered before returning?
|
|
/// </summary>
|
|
public bool Buffered { get { return (flags & CommandFlags.Buffered) != 0; } }
|
|
|
|
/// <summary>
|
|
/// Additional state flags against this command
|
|
/// </summary>
|
|
public CommandFlags Flags { get { return flags; } }
|
|
|
|
/// <summary>
|
|
/// Can async queries be pipelined?
|
|
/// </summary>
|
|
public bool Pipelined { get { return (flags & CommandFlags.Pipelined) != 0; } }
|
|
|
|
/// <summary>
|
|
/// Initialize the command definition
|
|
/// </summary>
|
|
#if CSHARP30
|
|
public CommandDefinition(string commandText, object parameters, IDbTransaction transaction, int? commandTimeout,
|
|
CommandType? commandType, CommandFlags flags)
|
|
#else
|
|
public CommandDefinition(string commandText, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null,
|
|
CommandType? commandType = null, CommandFlags flags = CommandFlags.Buffered
|
|
#if ASYNC
|
|
, CancellationToken cancellationToken = default(CancellationToken)
|
|
#endif
|
|
)
|
|
#endif
|
|
{
|
|
this.commandText = commandText;
|
|
this.parameters = parameters;
|
|
this.transaction = transaction;
|
|
this.commandTimeout = commandTimeout;
|
|
this.commandType = commandType;
|
|
this.flags = flags;
|
|
#if ASYNC
|
|
this.cancellationToken = cancellationToken;
|
|
#endif
|
|
}
|
|
|
|
#if ASYNC
|
|
private readonly CancellationToken cancellationToken;
|
|
/// <summary>
|
|
/// For asynchronous operations, the cancellation-token
|
|
/// </summary>
|
|
public CancellationToken CancellationToken { get { return cancellationToken; } }
|
|
#endif
|
|
|
|
|
|
internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object> paramReader)
|
|
{
|
|
var cmd = cnn.CreateCommand();
|
|
var bindByName = GetBindByName(cmd.GetType());
|
|
if (bindByName != null) bindByName(cmd, true);
|
|
if (transaction != null)
|
|
cmd.Transaction = transaction;
|
|
cmd.CommandText = commandText;
|
|
if (commandTimeout.HasValue)
|
|
cmd.CommandTimeout = commandTimeout.Value;
|
|
if (commandType.HasValue)
|
|
cmd.CommandType = commandType.Value;
|
|
if (paramReader != null)
|
|
{
|
|
paramReader(cmd, parameters);
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
static SqlMapper.Link<Type, Action<IDbCommand, bool>> bindByNameCache;
|
|
static Action<IDbCommand, bool> GetBindByName(Type commandType)
|
|
{
|
|
if (commandType == null) return null; // GIGO
|
|
Action<IDbCommand, bool> action;
|
|
if (SqlMapper.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
|
|
SqlMapper.Link<Type, Action<IDbCommand, bool>>.TryAdd(ref bindByNameCache, commandType, ref action);
|
|
return action;
|
|
}
|
|
|
|
}
|
|
|
|
/// <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>
|
|
/// Extends IDynamicParameters providing by-name lookup of parameter values
|
|
/// </summary>
|
|
public interface IParameterLookup : IDynamicParameters
|
|
{
|
|
/// <summary>
|
|
/// Get the value of the specified parameter (return null if not found)
|
|
/// </summary>
|
|
object this[string name] { get; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implement this interface to pass an arbitrary db specific parameter to Dapper
|
|
/// </summary>
|
|
public interface ICustomQueryParameter
|
|
{
|
|
/// <summary>
|
|
/// Add the parameter needed to the command before it executes
|
|
/// </summary>
|
|
/// <param name="command">The raw command prior to execution</param>
|
|
/// <param name="name">Parameter name</param>
|
|
void AddParameter(IDbCommand command, string name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implement this interface to change default mapping of reader columns to type memebers
|
|
/// </summary>
|
|
public interface ITypeMap
|
|
{
|
|
/// <summary>
|
|
/// Finds best constructor
|
|
/// </summary>
|
|
/// <param name="names">DataReader column names</param>
|
|
/// <param name="types">DataReader column types</param>
|
|
/// <returns>Matching constructor or default one</returns>
|
|
ConstructorInfo FindConstructor(string[] names, Type[] types);
|
|
|
|
/// <summary>
|
|
/// Gets mapping for constructor parameter
|
|
/// </summary>
|
|
/// <param name="constructor">Constructor to resolve</param>
|
|
/// <param name="columnName">DataReader column name</param>
|
|
/// <returns>Mapping implementation</returns>
|
|
IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName);
|
|
|
|
/// <summary>
|
|
/// Gets member mapping for column
|
|
/// </summary>
|
|
/// <param name="columnName">DataReader column name</param>
|
|
/// <returns>Mapping implementation</returns>
|
|
IMemberMap GetMember(string columnName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implements this interface to provide custom member mapping
|
|
/// </summary>
|
|
public interface IMemberMap
|
|
{
|
|
/// <summary>
|
|
/// Source DataReader column name
|
|
/// </summary>
|
|
string ColumnName { get; }
|
|
|
|
/// <summary>
|
|
/// Target member type
|
|
/// </summary>
|
|
Type MemberType { get; }
|
|
|
|
/// <summary>
|
|
/// Target property
|
|
/// </summary>
|
|
PropertyInfo Property { get; }
|
|
|
|
/// <summary>
|
|
/// Target field
|
|
/// </summary>
|
|
FieldInfo Field { get; }
|
|
|
|
/// <summary>
|
|
/// Target constructor parameter
|
|
/// </summary>
|
|
ParameterInfo Parameter { get; }
|
|
}
|
|
|
|
/// <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>
|
|
internal partial class Link<TKey, TValue> where TKey : class
|
|
{
|
|
public static bool TryGet(Link<TKey, TValue> link, TKey key, out TValue value)
|
|
{
|
|
while (link != null)
|
|
{
|
|
if ((object)key == (object)link.Key)
|
|
{
|
|
value = link.Value;
|
|
return true;
|
|
}
|
|
link = link.Tail;
|
|
}
|
|
value = default(TValue);
|
|
return false;
|
|
}
|
|
public static bool TryAdd(ref Link<TKey, TValue> head, TKey key, ref TValue value)
|
|
{
|
|
bool tryAgain;
|
|
do
|
|
{
|
|
var snapshot = Interlocked.CompareExchange(ref head, null, null);
|
|
TValue found;
|
|
if (TryGet(snapshot, key, out found))
|
|
{ // existing match; report the existing value instead
|
|
value = found;
|
|
return false;
|
|
}
|
|
var newNode = new Link<TKey, TValue>(key, value, snapshot);
|
|
// did somebody move our cheese?
|
|
tryAgain = Interlocked.CompareExchange(ref head, newNode, snapshot) != snapshot;
|
|
} while (tryAgain);
|
|
return true;
|
|
}
|
|
private Link(TKey key, TValue value, Link<TKey, TValue> tail)
|
|
{
|
|
Key = key;
|
|
Value = value;
|
|
Tail = tail;
|
|
}
|
|
public TKey Key { get; private set; }
|
|
public TValue Value { get; private set; }
|
|
public Link<TKey, TValue> Tail { get; private set; }
|
|
}
|
|
partial class CacheInfo
|
|
{
|
|
public DeserializerState Deserializer { get; set; }
|
|
public Func<IDataReader, object>[] OtherDeserializers { get; set; }
|
|
public Action<IDbCommand, object> ParamReader { get; set; }
|
|
private int hitCount;
|
|
public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); }
|
|
public void RecordHit() { Interlocked.Increment(ref hitCount); }
|
|
}
|
|
static int GetColumnHash(IDataReader reader)
|
|
{
|
|
unchecked
|
|
{
|
|
int colCount = reader.FieldCount, hash = colCount;
|
|
for (int i = 0; i < colCount; i++)
|
|
{ // binding code is only interested in names - not types
|
|
object tmp = reader.GetName(i);
|
|
hash = (hash * 31) + (tmp == null ? 0 : tmp.GetHashCode());
|
|
}
|
|
return hash;
|
|
}
|
|
}
|
|
struct DeserializerState
|
|
{
|
|
public readonly int Hash;
|
|
public readonly Func<IDataReader, object> Func;
|
|
|
|
public DeserializerState(int hash, Func<IDataReader, object> func)
|
|
{
|
|
Hash = hash;
|
|
Func = func;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called if the query cache is purged via PurgeQueryCache
|
|
/// </summary>
|
|
public static event EventHandler QueryCachePurged;
|
|
private static void OnQueryCachePurged()
|
|
{
|
|
var handler = QueryCachePurged;
|
|
if (handler != null) handler(null, EventArgs.Empty);
|
|
}
|
|
#if CSHARP30
|
|
private static readonly Dictionary<Identity, CacheInfo> _queryCache = new Dictionary<Identity, CacheInfo>();
|
|
// note: conflicts between readers and writers are so short-lived that it isn't worth the overhead of
|
|
// ReaderWriterLockSlim etc; a simple lock is faster
|
|
private static void SetQueryCache(Identity key, CacheInfo value)
|
|
{
|
|
lock (_queryCache) { _queryCache[key] = value; }
|
|
}
|
|
private static bool TryGetQueryCache(Identity key, out CacheInfo value)
|
|
{
|
|
lock (_queryCache) { return _queryCache.TryGetValue(key, out value); }
|
|
}
|
|
private static void PurgeQueryCacheByType(Type type)
|
|
{
|
|
lock (_queryCache)
|
|
{
|
|
var toRemove = _queryCache.Keys.Where(id => id.type == type).ToArray();
|
|
foreach (var key in toRemove)
|
|
_queryCache.Remove(key);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Purge the query cache
|
|
/// </summary>
|
|
public static void PurgeQueryCache()
|
|
{
|
|
lock (_queryCache)
|
|
{
|
|
_queryCache.Clear();
|
|
}
|
|
OnQueryCachePurged();
|
|
}
|
|
#else
|
|
static readonly System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo> _queryCache = new System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo>();
|
|
private static void SetQueryCache(Identity key, CacheInfo value)
|
|
{
|
|
if (Interlocked.Increment(ref collect) == COLLECT_PER_ITEMS)
|
|
{
|
|
CollectCacheGarbage();
|
|
}
|
|
_queryCache[key] = value;
|
|
}
|
|
|
|
private static void CollectCacheGarbage()
|
|
{
|
|
try
|
|
{
|
|
foreach (var pair in _queryCache)
|
|
{
|
|
if (pair.Value.GetHitCount() <= COLLECT_HIT_COUNT_MIN)
|
|
{
|
|
CacheInfo cache;
|
|
_queryCache.TryRemove(pair.Key, out cache);
|
|
}
|
|
}
|
|
}
|
|
|
|
finally
|
|
{
|
|
Interlocked.Exchange(ref collect, 0);
|
|
}
|
|
}
|
|
|
|
private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0;
|
|
private static int collect;
|
|
private static bool TryGetQueryCache(Identity key, out CacheInfo value)
|
|
{
|
|
if (_queryCache.TryGetValue(key, out value))
|
|
{
|
|
value.RecordHit();
|
|
return true;
|
|
}
|
|
value = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Purge the query cache
|
|
/// </summary>
|
|
public static void PurgeQueryCache()
|
|
{
|
|
_queryCache.Clear();
|
|
OnQueryCachePurged();
|
|
}
|
|
|
|
private static void PurgeQueryCacheByType(Type type)
|
|
{
|
|
foreach (var entry in _queryCache)
|
|
{
|
|
CacheInfo cache;
|
|
if (entry.Key.type == type)
|
|
_queryCache.TryRemove(entry.Key, out cache);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a count of all the cached queries by dapper
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static int GetCachedSQLCount()
|
|
{
|
|
return _queryCache.Count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a list of all the queries cached by dapper
|
|
/// </summary>
|
|
/// <param name="ignoreHitCountAbove"></param>
|
|
/// <returns></returns>
|
|
public static IEnumerable<Tuple<string, string, int>> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue)
|
|
{
|
|
var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount()));
|
|
if (ignoreHitCountAbove < int.MaxValue) data = data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove);
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deep diagnostics only: find any hash collisions in the cache
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public static IEnumerable<Tuple<int, int>> GetHashCollissions()
|
|
{
|
|
var counts = new Dictionary<int, int>();
|
|
foreach (var key in _queryCache.Keys)
|
|
{
|
|
int count;
|
|
if (!counts.TryGetValue(key.hashCode, out count))
|
|
{
|
|
counts.Add(key.hashCode, 1);
|
|
}
|
|
else
|
|
{
|
|
counts[key.hashCode] = count + 1;
|
|
}
|
|
}
|
|
return from pair in counts
|
|
where pair.Value > 1
|
|
select Tuple.Create(pair.Key, pair.Value);
|
|
|
|
}
|
|
#endif
|
|
|
|
|
|
static readonly Dictionary<Type, DbType> typeMap;
|
|
|
|
static SqlMapper()
|
|
{
|
|
typeMap = new Dictionary<Type, DbType>();
|
|
typeMap[typeof(byte)] = DbType.Byte;
|
|
typeMap[typeof(sbyte)] = DbType.SByte;
|
|
typeMap[typeof(short)] = DbType.Int16;
|
|
typeMap[typeof(ushort)] = DbType.UInt16;
|
|
typeMap[typeof(int)] = DbType.Int32;
|
|
typeMap[typeof(uint)] = DbType.UInt32;
|
|
typeMap[typeof(long)] = DbType.Int64;
|
|
typeMap[typeof(ulong)] = DbType.UInt64;
|
|
typeMap[typeof(float)] = DbType.Single;
|
|
typeMap[typeof(double)] = DbType.Double;
|
|
typeMap[typeof(decimal)] = DbType.Decimal;
|
|
typeMap[typeof(bool)] = DbType.Boolean;
|
|
typeMap[typeof(string)] = DbType.String;
|
|
typeMap[typeof(char)] = DbType.StringFixedLength;
|
|
typeMap[typeof(Guid)] = DbType.Guid;
|
|
typeMap[typeof(DateTime)] = DbType.DateTime;
|
|
typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset;
|
|
typeMap[typeof(TimeSpan)] = DbType.Time;
|
|
typeMap[typeof(byte[])] = DbType.Binary;
|
|
typeMap[typeof(byte?)] = DbType.Byte;
|
|
typeMap[typeof(sbyte?)] = DbType.SByte;
|
|
typeMap[typeof(short?)] = DbType.Int16;
|
|
typeMap[typeof(ushort?)] = DbType.UInt16;
|
|
typeMap[typeof(int?)] = DbType.Int32;
|
|
typeMap[typeof(uint?)] = DbType.UInt32;
|
|
typeMap[typeof(long?)] = DbType.Int64;
|
|
typeMap[typeof(ulong?)] = DbType.UInt64;
|
|
typeMap[typeof(float?)] = DbType.Single;
|
|
typeMap[typeof(double?)] = DbType.Double;
|
|
typeMap[typeof(decimal?)] = DbType.Decimal;
|
|
typeMap[typeof(bool?)] = DbType.Boolean;
|
|
typeMap[typeof(char?)] = DbType.StringFixedLength;
|
|
typeMap[typeof(Guid?)] = DbType.Guid;
|
|
typeMap[typeof(DateTime?)] = DbType.DateTime;
|
|
typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset;
|
|
typeMap[typeof(TimeSpan?)] = DbType.Time;
|
|
typeMap[typeof(Object)] = DbType.Object;
|
|
}
|
|
/// <summary>
|
|
/// Configire the specified type to be mapped to a given db-type
|
|
/// </summary>
|
|
public static void AddTypeMap(Type type, DbType dbType)
|
|
{
|
|
typeMap[type] = dbType;
|
|
}
|
|
|
|
internal const string LinqBinary = "System.Data.Linq.Binary";
|
|
internal static DbType LookupDbType(Type type, string name)
|
|
{
|
|
DbType dbType;
|
|
var nullUnderlyingType = Nullable.GetUnderlyingType(type);
|
|
if (nullUnderlyingType != null) type = nullUnderlyingType;
|
|
if (type.IsEnum && !typeMap.ContainsKey(type))
|
|
{
|
|
type = Enum.GetUnderlyingType(type);
|
|
}
|
|
if (typeMap.TryGetValue(type, out dbType))
|
|
{
|
|
return dbType;
|
|
}
|
|
if (type.FullName == LinqBinary)
|
|
{
|
|
return DbType.Binary;
|
|
}
|
|
if (typeof(IEnumerable).IsAssignableFrom(type))
|
|
{
|
|
return DynamicParameters.EnumerableMultiParameter;
|
|
}
|
|
|
|
|
|
throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type));
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Identity of a cached query in Dapper, used for extensability
|
|
/// </summary>
|
|
public partial class Identity : IEquatable<Identity>
|
|
{
|
|
internal Identity ForGrid(Type primaryType, int gridIndex)
|
|
{
|
|
return new Identity(sql, commandType, connectionString, primaryType, parametersType, null, gridIndex);
|
|
}
|
|
|
|
internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex)
|
|
{
|
|
return new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex);
|
|
}
|
|
/// <summary>
|
|
/// Create an identity for use with DynamicParameters, internal use only
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <returns></returns>
|
|
public Identity ForDynamicParameters(Type type)
|
|
{
|
|
return new Identity(sql, commandType, connectionString, this.type, type, null, -1);
|
|
}
|
|
|
|
internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes)
|
|
: this(sql, commandType, connection.ConnectionString, type, parametersType, otherTypes, 0)
|
|
{ }
|
|
private Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, Type[] otherTypes, int gridIndex)
|
|
{
|
|
this.sql = sql;
|
|
this.commandType = commandType;
|
|
this.connectionString = connectionString;
|
|
this.type = type;
|
|
this.parametersType = parametersType;
|
|
this.gridIndex = gridIndex;
|
|
unchecked
|
|
{
|
|
hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this
|
|
hashCode = hashCode * 23 + commandType.GetHashCode();
|
|
hashCode = hashCode * 23 + gridIndex.GetHashCode();
|
|
hashCode = hashCode * 23 + (sql == null ? 0 : sql.GetHashCode());
|
|
hashCode = hashCode * 23 + (type == null ? 0 : type.GetHashCode());
|
|
if (otherTypes != null)
|
|
{
|
|
foreach (var t in otherTypes)
|
|
{
|
|
hashCode = hashCode * 23 + (t == null ? 0 : t.GetHashCode());
|
|
}
|
|
}
|
|
hashCode = hashCode * 23 + (connectionString == null ? 0 : SqlMapper.connectionStringComparer.GetHashCode(connectionString));
|
|
hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="obj"></param>
|
|
/// <returns></returns>
|
|
public override bool Equals(object obj)
|
|
{
|
|
return Equals(obj as Identity);
|
|
}
|
|
/// <summary>
|
|
/// The sql
|
|
/// </summary>
|
|
public readonly string sql;
|
|
/// <summary>
|
|
/// The command type
|
|
/// </summary>
|
|
public readonly CommandType? commandType;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public readonly int hashCode, gridIndex;
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public readonly Type type;
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public readonly string connectionString;
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public readonly Type parametersType;
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override int GetHashCode()
|
|
{
|
|
return hashCode;
|
|
}
|
|
/// <summary>
|
|
/// Compare 2 Identity objects
|
|
/// </summary>
|
|
/// <param name="other"></param>
|
|
/// <returns></returns>
|
|
public bool Equals(Identity other)
|
|
{
|
|
return
|
|
other != null &&
|
|
gridIndex == other.gridIndex &&
|
|
type == other.type &&
|
|
sql == other.sql &&
|
|
commandType == other.commandType &&
|
|
SqlMapper.connectionStringComparer.Equals(connectionString, other.connectionString) &&
|
|
parametersType == other.parametersType;
|
|
}
|
|
}
|
|
|
|
#if CSHARP30
|
|
/// <summary>
|
|
/// Execute parameterized SQL
|
|
/// </summary>
|
|
/// <returns>Number of rows affected</returns>
|
|
public static int Execute(this IDbConnection cnn, string sql, object param)
|
|
{
|
|
return Execute(cnn, sql, param, null, null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute parameterized SQL
|
|
/// </summary>
|
|
/// <returns>Number of rows affected</returns>
|
|
public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
|
|
{
|
|
return Execute(cnn, sql, param, transaction, null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute parameterized SQL
|
|
/// </summary>
|
|
/// <returns>Number of rows affected</returns>
|
|
public static int Execute(this IDbConnection cnn, string sql, object param, CommandType commandType)
|
|
{
|
|
return Execute(cnn, sql, param, null, null, commandType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute parameterized SQL
|
|
/// </summary>
|
|
/// <returns>Number of rows affected</returns>
|
|
public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
|
|
{
|
|
return Execute(cnn, sql, param, transaction, null, commandType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
|
|
/// </summary>
|
|
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
|
|
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param)
|
|
{
|
|
return ExecuteReader(cnn, sql, param, null, null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
|
|
/// </summary>
|
|
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
|
|
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
|
|
{
|
|
return ExecuteReader(cnn, sql, param, transaction, null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
|
|
/// </summary>
|
|
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
|
|
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param, CommandType commandType)
|
|
{
|
|
return ExecuteReader(cnn, sql, param, null, null, commandType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
|
|
/// </summary>
|
|
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
|
|
public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
|
|
{
|
|
return ExecuteReader(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
|
|
)
|
|
{
|
|
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
|
|
return ExecuteImpl(cnn, ref command);
|
|
}
|
|
/// <summary>
|
|
/// Execute parameterized SQL
|
|
/// </summary>
|
|
/// <returns>Number of rows affected</returns>
|
|
public static int Execute(this IDbConnection cnn, CommandDefinition command)
|
|
{
|
|
return ExecuteImpl(cnn, ref command);
|
|
}
|
|
private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition command)
|
|
{
|
|
object param = command.Parameters;
|
|
IEnumerable multiExec = param as IEnumerable;
|
|
Identity identity;
|
|
CacheInfo info = null;
|
|
if (multiExec != null && !(multiExec is string))
|
|
{
|
|
bool isFirst = true;
|
|
int total = 0;
|
|
bool wasClosed = cnn.State == ConnectionState.Closed;
|
|
try
|
|
{
|
|
if (wasClosed) cnn.Open();
|
|
using (var cmd = command.SetupCommand(cnn, null))
|
|
{
|
|
string masterSql = null;
|
|
foreach (var obj in multiExec)
|
|
{
|
|
if (isFirst)
|
|
{
|
|
masterSql = cmd.CommandText;
|
|
isFirst = false;
|
|
identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
|
|
info = GetCacheInfo(identity, obj);
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
} finally
|
|
{
|
|
if (wasClosed) cnn.Close();
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// nice and simple
|
|
if (param != null)
|
|
{
|
|
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
|
|
info = GetCacheInfo(identity, param);
|
|
}
|
|
return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
|
|
/// </summary>
|
|
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
|
|
/// <remarks>
|
|
/// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a <see cref="DataTable"/>
|
|
/// or <see cref="DataSet"/>.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <code>
|
|
/// <![CDATA[
|
|
/// DataTable table = new DataTable("MyTable");
|
|
/// using (var reader = ExecuteReader(cnn, sql, param))
|
|
/// {
|
|
/// table.Load(reader);
|
|
/// }
|
|
/// ]]>
|
|
/// </code>
|
|
/// </example>
|
|
public static IDataReader ExecuteReader(
|
|
#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
|
|
)
|
|
{
|
|
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
|
|
return ExecuteReaderImpl(cnn, ref command);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute parameterized SQL and return an <see cref="IDataReader"/>
|
|
/// </summary>
|
|
/// <returns>An <see cref="IDataReader"/> that can be used to iterate over the results of the SQL query.</returns>
|
|
/// <remarks>
|
|
/// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a <see cref="DataTable"/>
|
|
/// or <see cref="DataSet"/>.
|
|
/// </remarks>
|
|
public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command)
|
|
{
|
|
return ExecuteReaderImpl(cnn, ref command);
|
|
}
|
|
|
|
#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 command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
|
|
var data = QueryImpl<T>(cnn, command);
|
|
return command.Buffered ? data.ToList() : data;
|
|
}
|
|
|
|
/// <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>(this IDbConnection cnn, CommandDefinition command)
|
|
{
|
|
var data = QueryImpl<T>(cnn, command);
|
|
return command.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
|
|
)
|
|
{
|
|
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
|
|
return QueryMultipleImpl(cnn, ref command);
|
|
}
|
|
/// <summary>
|
|
/// Execute a command that returns multiple result sets, and access each in turn
|
|
/// </summary>
|
|
public static GridReader QueryMultiple(this IDbConnection cnn, CommandDefinition command)
|
|
{
|
|
return QueryMultipleImpl(cnn, ref command);
|
|
}
|
|
private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandDefinition command)
|
|
{
|
|
object param = command.Parameters;
|
|
Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null);
|
|
CacheInfo info = GetCacheInfo(identity, param);
|
|
|
|
IDbCommand cmd = null;
|
|
IDataReader reader = null;
|
|
bool wasClosed = cnn.State == ConnectionState.Closed;
|
|
try
|
|
{
|
|
if (wasClosed) cnn.Open();
|
|
cmd = command.SetupCommand(cnn, info.ParamReader);
|
|
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;
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command)
|
|
{
|
|
object param = command.Parameters;
|
|
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
|
|
var info = GetCacheInfo(identity, param);
|
|
|
|
IDbCommand cmd = null;
|
|
IDataReader reader = null;
|
|
|
|
bool wasClosed = cnn.State == ConnectionState.Closed;
|
|
try
|
|
{
|
|
cmd = command.SetupCommand(cnn, info.ParamReader);
|
|
|
|
if (wasClosed) cnn.Open();
|
|
reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
|
|
wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
|
|
// with the CloseConnection flag, so the reader will deal with the connection; we
|
|
// still need something in the "finally" to ensure that broken SQL still results
|
|
// in the connection closing itself
|
|
var tuple = info.Deserializer;
|
|
int hash = GetColumnHash(reader);
|
|
if (tuple.Func == null || tuple.Hash != hash)
|
|
{
|
|
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false));
|
|
SetQueryCache(identity, info);
|
|
}
|
|
|
|
var func = tuple.Func;
|
|
|
|
while (reader.Read())
|
|
{
|
|
yield return (T)func(reader);
|
|
}
|
|
// happy path; close the reader cleanly - no
|
|
// need for "Cancel" etc
|
|
reader.Dispose();
|
|
reader = null;
|
|
}
|
|
finally
|
|
{
|
|
if (reader != null)
|
|
{
|
|
if (!reader.IsClosed) try { cmd.Cancel(); }
|
|
catch { /* don't spoil the existing exception */ }
|
|
reader.Dispose();
|
|
}
|
|
if (wasClosed) cnn.Close();
|
|
if (cmd != null) cmd.Dispose();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Maps a query to objects
|
|
/// </summary>
|
|
/// <typeparam name="TFirst">The first type in the recordset</typeparam>
|
|
/// <typeparam name="TSecond">The second type in the recordset</typeparam>
|
|
/// <typeparam name="TReturn">The return type</typeparam>
|
|
/// <param name="cnn"></param>
|
|
/// <param name="sql"></param>
|
|
/// <param name="map"></param>
|
|
/// <param name="param"></param>
|
|
/// <param name="transaction"></param>
|
|
/// <param name="buffered"></param>
|
|
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
|
|
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
|
|
/// <param name="commandType">Is it a stored proc or a batch?</param>
|
|
/// <returns></returns>
|
|
public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(
|
|
#if CSHARP30
|
|
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
|
|
#else
|
|
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
|
|
#endif
|
|
)
|
|
{
|
|
return MultiMap<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Maps a query to objects
|
|
/// </summary>
|
|
/// <typeparam name="TFirst"></typeparam>
|
|
/// <typeparam name="TSecond"></typeparam>
|
|
/// <typeparam name="TThird"></typeparam>
|
|
/// <typeparam name="TReturn"></typeparam>
|
|
/// <param name="cnn"></param>
|
|
/// <param name="sql"></param>
|
|
/// <param name="map"></param>
|
|
/// <param name="param"></param>
|
|
/// <param name="transaction"></param>
|
|
/// <param name="buffered"></param>
|
|
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
|
|
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
|
|
/// <param name="commandType"></param>
|
|
/// <returns></returns>
|
|
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(
|
|
#if CSHARP30
|
|
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
|
|
#else
|
|
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
|
|
#endif
|
|
)
|
|
{
|
|
return MultiMap<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform a multi mapping query with 4 input parameters
|
|
/// </summary>
|
|
/// <typeparam name="TFirst"></typeparam>
|
|
/// <typeparam name="TSecond"></typeparam>
|
|
/// <typeparam name="TThird"></typeparam>
|
|
/// <typeparam name="TFourth"></typeparam>
|
|
/// <typeparam name="TReturn"></typeparam>
|
|
/// <param name="cnn"></param>
|
|
/// <param name="sql"></param>
|
|
/// <param name="map"></param>
|
|
/// <param name="param"></param>
|
|
/// <param name="transaction"></param>
|
|
/// <param name="buffered"></param>
|
|
/// <param name="splitOn"></param>
|
|
/// <param name="commandTimeout"></param>
|
|
/// <param name="commandType"></param>
|
|
/// <returns></returns>
|
|
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>(
|
|
#if CSHARP30
|
|
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
|
|
#else
|
|
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
|
|
#endif
|
|
)
|
|
{
|
|
return MultiMap<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
|
|
}
|
|
|
|
#if !CSHARP30
|
|
/// <summary>
|
|
/// Perform a multi mapping query with 5 input parameters
|
|
/// </summary>
|
|
/// <typeparam name="TFirst"></typeparam>
|
|
/// <typeparam name="TSecond"></typeparam>
|
|
/// <typeparam name="TThird"></typeparam>
|
|
/// <typeparam name="TFourth"></typeparam>
|
|
/// <typeparam name="TFifth"></typeparam>
|
|
/// <typeparam name="TReturn"></typeparam>
|
|
/// <param name="cnn"></param>
|
|
/// <param name="sql"></param>
|
|
/// <param name="map"></param>
|
|
/// <param name="param"></param>
|
|
/// <param name="transaction"></param>
|
|
/// <param name="buffered"></param>
|
|
/// <param name="splitOn"></param>
|
|
/// <param name="commandTimeout"></param>
|
|
/// <param name="commandType"></param>
|
|
/// <returns></returns>
|
|
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(
|
|
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
|
|
)
|
|
{
|
|
return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform a multi mapping query with 6 input parameters
|
|
/// </summary>
|
|
/// <typeparam name="TFirst"></typeparam>
|
|
/// <typeparam name="TSecond"></typeparam>
|
|
/// <typeparam name="TThird"></typeparam>
|
|
/// <typeparam name="TFourth"></typeparam>
|
|
/// <typeparam name="TFifth"></typeparam>
|
|
/// <typeparam name="TSixth"></typeparam>
|
|
/// <typeparam name="TReturn"></typeparam>
|
|
/// <param name="cnn"></param>
|
|
/// <param name="sql"></param>
|
|
/// <param name="map"></param>
|
|
/// <param name="param"></param>
|
|
/// <param name="transaction"></param>
|
|
/// <param name="buffered"></param>
|
|
/// <param name="splitOn"></param>
|
|
/// <param name="commandTimeout"></param>
|
|
/// <param name="commandType"></param>
|
|
/// <returns></returns>
|
|
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(
|
|
this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
|
|
)
|
|
{
|
|
return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Perform a multi mapping query with 7 input parameters
|
|
/// </summary>
|
|
/// <typeparam name="TFirst"></typeparam>
|
|
/// <typeparam name="TSecond"></typeparam>
|
|
/// <typeparam name="TThird"></typeparam>
|
|
/// <typeparam name="TFourth"></typeparam>
|
|
/// <typeparam name="TFifth"></typeparam>
|
|
/// <typeparam name="TSixth"></typeparam>
|
|
/// <typeparam name="TSeventh"></typeparam>
|
|
/// <typeparam name="TReturn"></typeparam>
|
|
/// <param name="cnn"></param>
|
|
/// <param name="sql"></param>
|
|
/// <param name="map"></param>
|
|
/// <param name="param"></param>
|
|
/// <param name="transaction"></param>
|
|
/// <param name="buffered"></param>
|
|
/// <param name="splitOn"></param>
|
|
/// <param name="commandTimeout"></param>
|
|
/// <param name="commandType"></param>
|
|
/// <returns></returns>
|
|
public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
|
|
{
|
|
return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
|
|
}
|
|
#endif
|
|
partial class DontMap { }
|
|
static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(
|
|
this IDbConnection cnn, string sql, Delegate map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType)
|
|
{
|
|
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
|
|
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, command, map, splitOn, null, null);
|
|
return buffered ? results.ToList() : results;
|
|
}
|
|
|
|
static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity)
|
|
{
|
|
object param = command.Parameters;
|
|
identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
|
|
CacheInfo cinfo = GetCacheInfo(identity, param);
|
|
|
|
IDbCommand ownedCommand = null;
|
|
IDataReader ownedReader = null;
|
|
|
|
bool wasClosed = cnn != null && cnn.State == ConnectionState.Closed;
|
|
try
|
|
{
|
|
if (reader == null)
|
|
{
|
|
ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader);
|
|
if (wasClosed) cnn.Open();
|
|
ownedReader = ownedCommand.ExecuteReader();
|
|
reader = ownedReader;
|
|
}
|
|
DeserializerState deserializer = default(DeserializerState);
|
|
Func<IDataReader, object>[] otherDeserializers = null;
|
|
|
|
int hash = GetColumnHash(reader);
|
|
if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash)
|
|
{
|
|
var deserializers = GenerateDeserializers(new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }, splitOn, reader);
|
|
deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]);
|
|
otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray();
|
|
SetQueryCache(identity, cinfo);
|
|
}
|
|
|
|
Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(deserializer.Func, otherDeserializers, map);
|
|
|
|
if (mapIt != null)
|
|
{
|
|
while (reader.Read())
|
|
{
|
|
yield return mapIt(reader);
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
try
|
|
{
|
|
if (ownedReader != null)
|
|
{
|
|
ownedReader.Dispose();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (ownedCommand != null)
|
|
{
|
|
ownedCommand.Dispose();
|
|
}
|
|
if (wasClosed) cnn.Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Func<IDataReader, TReturn> GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<IDataReader, object> deserializer, Func<IDataReader, object>[] otherDeserializers, object map)
|
|
{
|
|
switch (otherDeserializers.Length)
|
|
{
|
|
case 1:
|
|
return r => ((Func<TFirst, TSecond, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r));
|
|
case 2:
|
|
return r => ((Func<TFirst, TSecond, TThird, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r));
|
|
case 3:
|
|
return r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r));
|
|
#if !CSHARP30
|
|
case 4:
|
|
return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r));
|
|
case 5:
|
|
return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r));
|
|
case 6:
|
|
return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r), (TSeventh)otherDeserializers[5](r));
|
|
#endif
|
|
default:
|
|
throw new NotSupportedException();
|
|
}
|
|
}
|
|
|
|
private static Func<IDataReader, object>[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader)
|
|
{
|
|
int current = 0;
|
|
var splits = splitOn.Split(',').ToArray();
|
|
var splitIndex = 0;
|
|
|
|
Func<Type, int> nextSplit = type =>
|
|
{
|
|
var currentSplit = splits[splitIndex].Trim();
|
|
if (splits.Length > splitIndex + 1)
|
|
{
|
|
splitIndex++;
|
|
}
|
|
|
|
bool skipFirst = false;
|
|
int startingPos = current + 1;
|
|
// if our current type has the split, skip the first time you see it.
|
|
if (type != typeof(Object))
|
|
{
|
|
var props = DefaultTypeMap.GetSettableProps(type);
|
|
var fields = DefaultTypeMap.GetSettableFields(type);
|
|
|
|
foreach (var name in props.Select(p => p.Name).Concat(fields.Select(f => f.Name)))
|
|
{
|
|
if (string.Equals(name, currentSplit, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
skipFirst = true;
|
|
startingPos = current;
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
int pos;
|
|
for (pos = startingPos; pos < reader.FieldCount; pos++)
|
|
{
|
|
// some people like ID some id ... assuming case insensitive splits for now
|
|
if (splitOn == "*")
|
|
{
|
|
break;
|
|
}
|
|
if (string.Equals(reader.GetName(pos), currentSplit, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
if (skipFirst)
|
|
{
|
|
skipFirst = false;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
current = pos;
|
|
return pos;
|
|
};
|
|
|
|
var deserializers = new List<Func<IDataReader, object>>();
|
|
int split = 0;
|
|
bool first = true;
|
|
foreach (var type in types)
|
|
{
|
|
if (type != typeof(DontMap))
|
|
{
|
|
int next = nextSplit(type);
|
|
deserializers.Add(GetDeserializer(type, reader, split, next - split, /* returnNullIfFirstMissing: */ !first));
|
|
first = false;
|
|
split = next;
|
|
}
|
|
}
|
|
|
|
return deserializers.ToArray();
|
|
}
|
|
|
|
private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters)
|
|
{
|
|
CacheInfo info;
|
|
if (!TryGetQueryCache(identity, out info))
|
|
{
|
|
info = new CacheInfo();
|
|
if (identity.parametersType != null)
|
|
{
|
|
if (exampleParameters is IDynamicParameters)
|
|
{
|
|
info.ParamReader = (cmd, obj) => { ((IDynamicParameters)obj).AddParameters(cmd, identity); };
|
|
}
|
|
#if !CSHARP30
|
|
// special-case dictionary && `dynamic`
|
|
else if (exampleParameters is IEnumerable<KeyValuePair<string, object>> && exampleParameters is System.Dynamic.IDynamicMetaObjectProvider)
|
|
{
|
|
info.ParamReader = (cmd, obj) =>
|
|
{
|
|
IDynamicParameters mapped = new DynamicParameters(obj);
|
|
mapped.AddParameters(cmd, identity);
|
|
};
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
var literals = GetLiteralTokens(identity.sql);
|
|
info.ParamReader = CreateParamInfoGenerator(identity, false, true, literals);
|
|
}
|
|
}
|
|
SetQueryCache(identity, info);
|
|
}
|
|
return info;
|
|
}
|
|
|
|
private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
|
|
{
|
|
#if !CSHARP30
|
|
// dynamic is passed in as Object ... by c# design
|
|
if (type == typeof(object)
|
|
|| type == typeof(DapperRow))
|
|
{
|
|
return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing);
|
|
}
|
|
#else
|
|
if (type.IsAssignableFrom(typeof(Dictionary<string, object>)))
|
|
{
|
|
return GetDictionaryDeserializer(reader, startBound, length, returnNullIfFirstMissing);
|
|
}
|
|
#endif
|
|
Type underlyingType = null;
|
|
if (!(typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary ||
|
|
(type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum)))
|
|
{
|
|
return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing);
|
|
}
|
|
return GetStructDeserializer(type, underlyingType ?? type, startBound);
|
|
|
|
}
|
|
|
|
#if !CSHARP30
|
|
private sealed partial class DapperTable
|
|
{
|
|
string[] fieldNames;
|
|
readonly Dictionary<string, int> fieldNameLookup;
|
|
|
|
internal string[] FieldNames { get { return fieldNames; } }
|
|
|
|
public DapperTable(string[] fieldNames)
|
|
{
|
|
if (fieldNames == null) throw new ArgumentNullException("fieldNames");
|
|
this.fieldNames = fieldNames;
|
|
|
|
fieldNameLookup = new Dictionary<string, int>(fieldNames.Length, StringComparer.Ordinal);
|
|
// if there are dups, we want the **first** key to be the "winner" - so iterate backwards
|
|
for (int i = fieldNames.Length - 1; i >= 0; i--)
|
|
{
|
|
string key = fieldNames[i];
|
|
if (key != null) fieldNameLookup[key] = i;
|
|
}
|
|
}
|
|
|
|
internal int IndexOfName(string name)
|
|
{
|
|
int result;
|
|
return (name != null && fieldNameLookup.TryGetValue(name, out result)) ? result : -1;
|
|
}
|
|
internal int AddField(string name)
|
|
{
|
|
if (name == null) throw new ArgumentNullException("name");
|
|
if (fieldNameLookup.ContainsKey(name)) throw new InvalidOperationException("Field already exists: " + name);
|
|
int oldLen = fieldNames.Length;
|
|
Array.Resize(ref fieldNames, oldLen + 1); // yes, this is sub-optimal, but this is not the expected common case
|
|
fieldNames[oldLen] = name;
|
|
fieldNameLookup[name] = oldLen;
|
|
return oldLen;
|
|
}
|
|
|
|
|
|
internal bool FieldExists(string key)
|
|
{
|
|
return key != null && fieldNameLookup.ContainsKey(key);
|
|
}
|
|
|
|
public int FieldCount { get { return fieldNames.Length; } }
|
|
}
|
|
|
|
sealed partial class DapperRowMetaObject : System.Dynamic.DynamicMetaObject
|
|
{
|
|
static readonly MethodInfo getValueMethod = typeof(IDictionary<string, object>).GetProperty("Item").GetGetMethod();
|
|
static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) });
|
|
|
|
public DapperRowMetaObject(
|
|
System.Linq.Expressions.Expression expression,
|
|
System.Dynamic.BindingRestrictions restrictions
|
|
)
|
|
: base(expression, restrictions)
|
|
{
|
|
}
|
|
|
|
public DapperRowMetaObject(
|
|
System.Linq.Expressions.Expression expression,
|
|
System.Dynamic.BindingRestrictions restrictions,
|
|
object value
|
|
)
|
|
: base(expression, restrictions, value)
|
|
{
|
|
}
|
|
|
|
System.Dynamic.DynamicMetaObject CallMethod(
|
|
MethodInfo method,
|
|
System.Linq.Expressions.Expression[] parameters
|
|
)
|
|
{
|
|
var callMethod = new System.Dynamic.DynamicMetaObject(
|
|
System.Linq.Expressions.Expression.Call(
|
|
System.Linq.Expressions.Expression.Convert(Expression, LimitType),
|
|
method,
|
|
parameters),
|
|
System.Dynamic.BindingRestrictions.GetTypeRestriction(Expression, LimitType)
|
|
);
|
|
return callMethod;
|
|
}
|
|
|
|
public override System.Dynamic.DynamicMetaObject BindGetMember(System.Dynamic.GetMemberBinder binder)
|
|
{
|
|
var parameters = new System.Linq.Expressions.Expression[]
|
|
{
|
|
System.Linq.Expressions.Expression.Constant(binder.Name)
|
|
};
|
|
|
|
var callMethod = CallMethod(getValueMethod, parameters);
|
|
|
|
return callMethod;
|
|
}
|
|
|
|
// Needed for Visual basic dynamic support
|
|
public override System.Dynamic.DynamicMetaObject BindInvokeMember(System.Dynamic.InvokeMemberBinder binder, System.Dynamic.DynamicMetaObject[] args)
|
|
{
|
|
var parameters = new System.Linq.Expressions.Expression[]
|
|
{
|
|
System.Linq.Expressions.Expression.Constant(binder.Name)
|
|
};
|
|
|
|
var callMethod = CallMethod(getValueMethod, parameters);
|
|
|
|
return callMethod;
|
|
}
|
|
|
|
public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.SetMemberBinder binder, System.Dynamic.DynamicMetaObject value)
|
|
{
|
|
var parameters = new System.Linq.Expressions.Expression[]
|
|
{
|
|
System.Linq.Expressions.Expression.Constant(binder.Name),
|
|
value.Expression,
|
|
};
|
|
|
|
var callMethod = CallMethod(setValueMethod, parameters);
|
|
|
|
return callMethod;
|
|
}
|
|
}
|
|
|
|
private sealed partial class DapperRow
|
|
: System.Dynamic.IDynamicMetaObjectProvider
|
|
, IDictionary<string, object>
|
|
{
|
|
readonly DapperTable table;
|
|
object[] values;
|
|
|
|
public DapperRow(DapperTable table, object[] values)
|
|
{
|
|
if (table == null) throw new ArgumentNullException("table");
|
|
if (values == null) throw new ArgumentNullException("values");
|
|
this.table = table;
|
|
this.values = values;
|
|
}
|
|
private sealed class DeadValue
|
|
{
|
|
public static readonly DeadValue Default = new DeadValue();
|
|
private DeadValue() { }
|
|
}
|
|
int ICollection<KeyValuePair<string, object>>.Count
|
|
{
|
|
get
|
|
{
|
|
int count = 0;
|
|
for (int i = 0; i < values.Length; i++)
|
|
{
|
|
if (!(values[i] is DeadValue)) count++;
|
|
}
|
|
return count;
|
|
}
|
|
}
|
|
|
|
public bool TryGetValue(string name, out object value)
|
|
{
|
|
var index = table.IndexOfName(name);
|
|
if (index < 0)
|
|
{ // doesn't exist
|
|
value = null;
|
|
return false;
|
|
}
|
|
// exists, **even if** we don't have a value; consider table rows heterogeneous
|
|
value = index < values.Length ? values[index] : null;
|
|
if (value is DeadValue)
|
|
{ // pretend it isn't here
|
|
value = null;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
var sb = new StringBuilder("{DapperRow");
|
|
foreach (var kv in this)
|
|
{
|
|
var value = kv.Value;
|
|
sb.Append(", ").Append(kv.Key);
|
|
if (value != null)
|
|
{
|
|
sb.Append(" = '").Append(kv.Value).Append('\'');
|
|
}
|
|
else
|
|
{
|
|
sb.Append(" = NULL");
|
|
}
|
|
}
|
|
|
|
return sb.Append('}').ToString();
|
|
}
|
|
|
|
System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(
|
|
System.Linq.Expressions.Expression parameter)
|
|
{
|
|
return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this);
|
|
}
|
|
|
|
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
|
{
|
|
var names = table.FieldNames;
|
|
for (var i = 0; i < names.Length; i++)
|
|
{
|
|
object value = i < values.Length ? values[i] : null;
|
|
if (!(value is DeadValue))
|
|
{
|
|
yield return new KeyValuePair<string, object>(names[i], value);
|
|
}
|
|
}
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
#region Implementation of ICollection<KeyValuePair<string,object>>
|
|
|
|
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
|
|
{
|
|
IDictionary<string, object> dic = this;
|
|
dic.Add(item.Key, item.Value);
|
|
}
|
|
|
|
void ICollection<KeyValuePair<string, object>>.Clear()
|
|
{ // removes values for **this row**, but doesn't change the fundamental table
|
|
for (int i = 0; i < values.Length; i++)
|
|
values[i] = DeadValue.Default;
|
|
}
|
|
|
|
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
|
|
{
|
|
object value;
|
|
return TryGetValue(item.Key, out value) && Equals(value, item.Value);
|
|
}
|
|
|
|
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
|
|
{
|
|
foreach (var kv in this)
|
|
{
|
|
array[arrayIndex++] = kv; // if they didn't leave enough space; not our fault
|
|
}
|
|
}
|
|
|
|
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
|
|
{
|
|
IDictionary<string, object> dic = this;
|
|
return dic.Remove(item.Key);
|
|
}
|
|
|
|
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
|
|
{
|
|
get { return false; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Implementation of IDictionary<string,object>
|
|
|
|
bool IDictionary<string, object>.ContainsKey(string key)
|
|
{
|
|
int index = table.IndexOfName(key);
|
|
if (index < 0 || index >= values.Length || values[index] is DeadValue) return false;
|
|
return true;
|
|
}
|
|
|
|
void IDictionary<string, object>.Add(string key, object value)
|
|
{
|
|
SetValue(key, value, true);
|
|
}
|
|
|
|
bool IDictionary<string, object>.Remove(string key)
|
|
{
|
|
int index = table.IndexOfName(key);
|
|
if (index < 0 || index >= values.Length || values[index] is DeadValue) return false;
|
|
values[index] = DeadValue.Default;
|
|
return true;
|
|
}
|
|
|
|
object IDictionary<string, object>.this[string key]
|
|
{
|
|
get { object val; TryGetValue(key, out val); return val; }
|
|
set { SetValue(key, value, false); }
|
|
}
|
|
|
|
public object SetValue(string key, object value)
|
|
{
|
|
return SetValue(key, value, false);
|
|
}
|
|
private object SetValue(string key, object value, bool isAdd)
|
|
{
|
|
if (key == null) throw new ArgumentNullException("key");
|
|
int index = table.IndexOfName(key);
|
|
if (index < 0)
|
|
{
|
|
index = table.AddField(key);
|
|
}
|
|
else if (isAdd && index < values.Length && !(values[index] is DeadValue))
|
|
{
|
|
// then semantically, this value already exists
|
|
throw new ArgumentException("An item with the same key has already been added", "key");
|
|
}
|
|
int oldLength = values.Length;
|
|
if (oldLength <= index)
|
|
{
|
|
// we'll assume they're doing lots of things, and
|
|
// grow it to the full width of the table
|
|
Array.Resize(ref values, table.FieldCount);
|
|
for (int i = oldLength; i < values.Length; i++)
|
|
{
|
|
values[i] = DeadValue.Default;
|
|
}
|
|
}
|
|
return values[index] = value;
|
|
}
|
|
|
|
ICollection<string> IDictionary<string, object>.Keys
|
|
{
|
|
get { return this.Select(kv => kv.Key).ToArray(); }
|
|
}
|
|
|
|
ICollection<object> IDictionary<string, object>.Values
|
|
{
|
|
get { return this.Select(kv => kv.Value).ToArray(); }
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
#endif
|
|
private const string MultiMapSplitExceptionMessage = "When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id";
|
|
#if !CSHARP30
|
|
internal static Func<IDataReader, object> GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)
|
|
{
|
|
var fieldCount = reader.FieldCount;
|
|
if (length == -1)
|
|
{
|
|
length = fieldCount - startBound;
|
|
}
|
|
|
|
if (fieldCount <= startBound)
|
|
{
|
|
throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn");
|
|
}
|
|
|
|
var effectiveFieldCount = Math.Min(fieldCount - startBound, length);
|
|
|
|
DapperTable table = null;
|
|
|
|
return
|
|
r =>
|
|
{
|
|
if (table == null)
|
|
{
|
|
string[] names = new string[effectiveFieldCount];
|
|
for (int i = 0; i < effectiveFieldCount; i++)
|
|
{
|
|
names[i] = r.GetName(i + startBound);
|
|
}
|
|
table = new DapperTable(names);
|
|
}
|
|
|
|
var values = new object[effectiveFieldCount];
|
|
|
|
if (returnNullIfFirstMissing)
|
|
{
|
|
values[0] = r.GetValue(startBound);
|
|
if (values[0] is DBNull)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (startBound == 0)
|
|
{
|
|
r.GetValues(values);
|
|
for (int i = 0; i < values.Length; i++)
|
|
if (values[i] is DBNull) values[i] = null;
|
|
}
|
|
else
|
|
{
|
|
var begin = returnNullIfFirstMissing ? 1 : 0;
|
|
for (var iter = begin; iter < effectiveFieldCount; ++iter)
|
|
{
|
|
object obj = r.GetValue(iter + startBound);
|
|
values[iter] = obj is DBNull ? null : obj;
|
|
}
|
|
}
|
|
return new DapperRow(table, values);
|
|
};
|
|
}
|
|
#else
|
|
internal static Func<IDataReader, object> GetDictionaryDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)
|
|
{
|
|
var fieldCount = reader.FieldCount;
|
|
if (length == -1)
|
|
{
|
|
length = fieldCount - startBound;
|
|
}
|
|
|
|
if (fieldCount <= startBound)
|
|
{
|
|
throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn");
|
|
}
|
|
|
|
return
|
|
r =>
|
|
{
|
|
IDictionary<string, object> row = new Dictionary<string, object>(length);
|
|
for (var i = startBound; i < startBound + length; i++)
|
|
{
|
|
var tmp = r.GetValue(i);
|
|
tmp = tmp == DBNull.Value ? null : tmp;
|
|
row[r.GetName(i)] = tmp;
|
|
if (returnNullIfFirstMissing && i == startBound && tmp == null)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
return row;
|
|
};
|
|
}
|
|
#endif
|
|
/// <summary>
|
|
/// Internal use only
|
|
/// </summary>
|
|
/// <param name="value"></param>
|
|
/// <returns></returns>
|
|
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
|
|
[Obsolete("This method is for internal usage only", false)]
|
|
public static char ReadChar(object value)
|
|
{
|
|
if (value == null || value is DBNull) throw new ArgumentNullException("value");
|
|
string s = value as string;
|
|
if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value");
|
|
return s[0];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal use only
|
|
/// </summary>
|
|
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
|
|
[Obsolete("This method is for internal usage only", false)]
|
|
public static char? ReadNullableChar(object value)
|
|
{
|
|
if (value == null || value is DBNull) return null;
|
|
string s = value as string;
|
|
if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value");
|
|
return s[0];
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Internal use only
|
|
/// </summary>
|
|
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
|
|
[Obsolete("This method is for internal usage only", true)]
|
|
public static IDbDataParameter FindOrAddParameter(IDataParameterCollection parameters, IDbCommand command, string name)
|
|
{
|
|
IDbDataParameter result;
|
|
if (parameters.Contains(name))
|
|
{
|
|
result = (IDbDataParameter)parameters[name];
|
|
}
|
|
else
|
|
{
|
|
result = command.CreateParameter();
|
|
result.ParameterName = name;
|
|
parameters.Add(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal use only
|
|
/// </summary>
|
|
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
|
|
[Obsolete("This method is for internal usage only", false)]
|
|
public static void PackListParameters(IDbCommand command, string namePrefix, object value)
|
|
{
|
|
// initially we tried TVP, however it performs quite poorly.
|
|
// keep in mind SQL support up to 2000 params easily in sp_executesql, needing more is rare
|
|
|
|
var list = value as IEnumerable;
|
|
var count = 0;
|
|
|
|
if (list != null)
|
|
{
|
|
if (FeatureSupport.Get(command.Connection).Arrays)
|
|
{
|
|
var arrayParm = command.CreateParameter();
|
|
arrayParm.Value = list;
|
|
arrayParm.ParameterName = namePrefix;
|
|
command.Parameters.Add(arrayParm);
|
|
}
|
|
else
|
|
{
|
|
bool isString = value is IEnumerable<string>;
|
|
bool isDbString = value is IEnumerable<DbString>;
|
|
foreach (var item in list)
|
|
{
|
|
count++;
|
|
var listParam = command.CreateParameter();
|
|
listParam.ParameterName = namePrefix + count;
|
|
listParam.Value = item ?? DBNull.Value;
|
|
if (isString)
|
|
{
|
|
listParam.Size = 4000;
|
|
if (item != null && ((string)item).Length > 4000)
|
|
{
|
|
listParam.Size = -1;
|
|
}
|
|
}
|
|
if (isDbString && item as DbString != null)
|
|
{
|
|
var str = item as DbString;
|
|
str.AddParameter(command, listParam.ParameterName);
|
|
}
|
|
else
|
|
{
|
|
command.Parameters.Add(listParam);
|
|
}
|
|
}
|
|
|
|
var regexIncludingUnkown = @"([?@:]" + Regex.Escape(namePrefix) + @")(\s*(?i)unknown(?-i))?";
|
|
if (count == 0)
|
|
{
|
|
command.CommandText = Regex.Replace(command.CommandText, regexIncludingUnkown, match =>
|
|
{
|
|
var variableName = match.Groups[1].Value;
|
|
if (match.Groups[2].Success)
|
|
{
|
|
// looks like an optimize hint; leave it alone!
|
|
return match.Value;
|
|
}
|
|
else
|
|
{
|
|
return "(SELECT " + variableName + " WHERE 1 = 0)";
|
|
};
|
|
});
|
|
var dummyParam = command.CreateParameter();
|
|
dummyParam.ParameterName = namePrefix;
|
|
dummyParam.Value = DBNull.Value;
|
|
command.Parameters.Add(dummyParam);
|
|
}
|
|
else
|
|
{
|
|
command.CommandText = Regex.Replace(command.CommandText, regexIncludingUnkown, match =>
|
|
{
|
|
var variableName = match.Groups[1].Value;
|
|
if (match.Groups[2].Success)
|
|
{
|
|
// looks like an optimize hint; expand it
|
|
var suffix = match.Groups[2].Value;
|
|
|
|
var sb = new StringBuilder(variableName).Append(1).Append(suffix);
|
|
for (int i = 2; i <= count; i++)
|
|
{
|
|
sb.Append(',').Append(variableName).Append(i).Append(suffix);
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
else
|
|
{
|
|
var sb = new StringBuilder("(").Append(variableName).Append(1);
|
|
for (int i = 2; i <= count; i++)
|
|
{
|
|
sb.Append(',').Append(variableName).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 | RegexOptions.CultureInvariant));
|
|
}
|
|
|
|
|
|
// look for ? / @ / : *by itself*
|
|
static readonly Regex smellsLikeOleDb = new Regex(@"(?<![a-zA-Z0-9_])[?@:](?![a-zA-Z0-9_])", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled),
|
|
literalTokens = new Regex(@"\{=([a-zA-Z0-9_]+)\}", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled);
|
|
|
|
/// <summary>
|
|
/// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql
|
|
/// </summary>
|
|
internal struct LiteralToken
|
|
{
|
|
private readonly string token, member;
|
|
/// <summary>
|
|
/// The text in the original command that should be replaced
|
|
/// </summary>
|
|
public string Token { get { return token; } }
|
|
|
|
/// <summary>
|
|
/// The name of the member referred to by the token
|
|
/// </summary>
|
|
public string Member { get { return member; } }
|
|
internal LiteralToken(string token, string member)
|
|
{
|
|
this.token = token;
|
|
this.member = member;
|
|
}
|
|
|
|
internal static readonly IList<LiteralToken> None = new LiteralToken[0];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replace all literal tokens with their text form
|
|
/// </summary>
|
|
public static void ReplaceLiterals(this IParameterLookup parameters, IDbCommand command)
|
|
{
|
|
var tokens = GetLiteralTokens(command.CommandText);
|
|
if (tokens.Count != 0) ReplaceLiterals(parameters, command, tokens);
|
|
}
|
|
|
|
internal static readonly MethodInfo format = typeof(SqlMapper).GetMethod("Format", BindingFlags.Public | BindingFlags.Static);
|
|
/// <summary>
|
|
/// Convert numeric values to their string form for SQL literal purposes
|
|
/// </summary>
|
|
[Obsolete("This is intended for internal usage only")]
|
|
public static string Format(object value)
|
|
{
|
|
if (value == null)
|
|
{
|
|
return "null";
|
|
}
|
|
else
|
|
{
|
|
switch (Type.GetTypeCode(value.GetType()))
|
|
{
|
|
case TypeCode.DBNull:
|
|
return "null";
|
|
case TypeCode.Boolean:
|
|
return ((bool)value) ? "1" : "0";
|
|
case TypeCode.Byte:
|
|
return ((byte)value).ToString(CultureInfo.InvariantCulture);
|
|
case TypeCode.SByte:
|
|
return ((sbyte)value).ToString(CultureInfo.InvariantCulture);
|
|
case TypeCode.UInt16:
|
|
return ((ushort)value).ToString(CultureInfo.InvariantCulture);
|
|
case TypeCode.Int16:
|
|
return ((short)value).ToString(CultureInfo.InvariantCulture);
|
|
case TypeCode.UInt32:
|
|
return ((uint)value).ToString(CultureInfo.InvariantCulture);
|
|
case TypeCode.Int32:
|
|
return ((int)value).ToString(CultureInfo.InvariantCulture);
|
|
case TypeCode.UInt64:
|
|
return ((ulong)value).ToString(CultureInfo.InvariantCulture);
|
|
case TypeCode.Int64:
|
|
return ((long)value).ToString(CultureInfo.InvariantCulture);
|
|
case TypeCode.Single:
|
|
return ((float)value).ToString(CultureInfo.InvariantCulture);
|
|
case TypeCode.Double:
|
|
return ((double)value).ToString(CultureInfo.InvariantCulture);
|
|
case TypeCode.Decimal:
|
|
return ((decimal)value).ToString(CultureInfo.InvariantCulture);
|
|
default:
|
|
if(value is IEnumerable && !(value is string))
|
|
{
|
|
var sb = new StringBuilder();
|
|
bool first = true;
|
|
foreach (object subval in (IEnumerable)value)
|
|
{
|
|
sb.Append(first ? '(' : ',').Append(Format(subval));
|
|
first = false;
|
|
}
|
|
if(first)
|
|
{
|
|
return "(select null where 1=0)";
|
|
}
|
|
else
|
|
{
|
|
return sb.Append(')').ToString();
|
|
}
|
|
}
|
|
throw new NotSupportedException(value.GetType().Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand command, IList<LiteralToken> tokens)
|
|
{
|
|
var sql = command.CommandText;
|
|
foreach (var token in tokens)
|
|
{
|
|
object value = parameters[token.Member];
|
|
#pragma warning disable 0618
|
|
string text = Format(value);
|
|
#pragma warning restore 0618
|
|
sql = sql.Replace(token.Token, text);
|
|
}
|
|
command.CommandText = sql;
|
|
}
|
|
|
|
internal static IList<LiteralToken> GetLiteralTokens(string sql)
|
|
{
|
|
if (string.IsNullOrEmpty(sql)) return LiteralToken.None;
|
|
if (!literalTokens.IsMatch(sql)) return LiteralToken.None;
|
|
|
|
var matches = literalTokens.Matches(sql);
|
|
var found = new HashSet<string>(StringComparer.InvariantCulture);
|
|
List<LiteralToken> list = new List<LiteralToken>(matches.Count);
|
|
foreach(Match match in matches)
|
|
{
|
|
string token = match.Value;
|
|
if(found.Add(match.Value))
|
|
{
|
|
list.Add(new LiteralToken(token, match.Groups[1].Value));
|
|
}
|
|
}
|
|
return list.Count == 0 ? LiteralToken.None : list;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal use only
|
|
/// </summary>
|
|
public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused)
|
|
{
|
|
return CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql));
|
|
}
|
|
|
|
internal static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList<LiteralToken> literals)
|
|
{
|
|
Type type = identity.parametersType;
|
|
|
|
bool filterParams = false;
|
|
if (removeUnused && identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text)
|
|
{
|
|
filterParams = !smellsLikeOleDb.IsMatch(identity.sql);
|
|
}
|
|
var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true);
|
|
|
|
var il = dm.GetILGenerator();
|
|
|
|
il.DeclareLocal(type); // 0
|
|
bool haveInt32Arg1 = false;
|
|
il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param]
|
|
il.Emit(OpCodes.Unbox_Any, type); // stack is now [typed-param]
|
|
il.Emit(OpCodes.Stloc_0);// stack is now empty
|
|
|
|
il.Emit(OpCodes.Ldarg_0); // stack is now [command]
|
|
il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty("Parameters").GetGetMethod(), null); // stack is now [parameters]
|
|
|
|
var propsArr = type.GetProperties().Where(p => p.GetIndexParameters().Length == 0).ToArray();
|
|
var ctors = type.GetConstructors();
|
|
ParameterInfo[] ctorParams;
|
|
IEnumerable<PropertyInfo> props = null;
|
|
// try to detect tuple patterns, e.g. anon-types, and use that to choose the order
|
|
// otherwise: alphabetical
|
|
if (ctors.Length == 1 && propsArr.Length == (ctorParams = ctors[0].GetParameters()).Length)
|
|
{
|
|
// check if reflection was kind enough to put everything in the right order for us
|
|
bool ok = true;
|
|
for (int i = 0; i < propsArr.Length; i++)
|
|
{
|
|
if (!string.Equals(propsArr[i].Name, ctorParams[i].Name, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
if(ok)
|
|
{
|
|
// pre-sorted; the reflection gods have smiled upon us
|
|
props = propsArr;
|
|
}
|
|
else { // might still all be accounted for; check the hard way
|
|
var positionByName = new Dictionary<string,int>(StringComparer.InvariantCultureIgnoreCase);
|
|
foreach(var param in ctorParams)
|
|
{
|
|
positionByName[param.Name] = param.Position;
|
|
}
|
|
if (positionByName.Count == propsArr.Length)
|
|
{
|
|
int[] positions = new int[propsArr.Length];
|
|
ok = true;
|
|
for (int i = 0; i < propsArr.Length; i++)
|
|
{
|
|
int pos;
|
|
if (!positionByName.TryGetValue(propsArr[i].Name, out pos))
|
|
{
|
|
ok = false;
|
|
break;
|
|
}
|
|
positions[i] = pos;
|
|
}
|
|
if (ok)
|
|
{
|
|
Array.Sort(positions, propsArr);
|
|
props = propsArr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(props == null) props = propsArr.OrderBy(x => x.Name);
|
|
if (filterParams)
|
|
{
|
|
props = FilterParameters(props, identity.sql);
|
|
}
|
|
|
|
foreach (var prop in props)
|
|
{
|
|
if (typeof(ICustomQueryParameter).IsAssignableFrom(prop.PropertyType))
|
|
{
|
|
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param]
|
|
il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [dbstring]
|
|
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [dbstring] [command]
|
|
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [dbstring] [command] [name]
|
|
il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod("AddParameter"), null); // stack is now [parameters]
|
|
continue;
|
|
}
|
|
DbType dbType = LookupDbType(prop.PropertyType, prop.Name);
|
|
if (dbType == DynamicParameters.EnumerableMultiParameter)
|
|
{
|
|
// this actually represents special handling for list types;
|
|
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command]
|
|
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name]
|
|
il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param]
|
|
il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value]
|
|
if (prop.PropertyType.IsValueType)
|
|
{
|
|
il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value]
|
|
}
|
|
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("PackListParameters"), null); // stack is [parameters]
|
|
continue;
|
|
}
|
|
il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters]
|
|
|
|
il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [parameters] [command]
|
|
|
|
if (checkForDuplicates)
|
|
{
|
|
// need to be a little careful about adding; use a utility method
|
|
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [command] [name]
|
|
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("FindOrAddParameter"), null); // stack is [parameters] [parameter]
|
|
}
|
|
else
|
|
{
|
|
// no risk of duplicates; just blindly add
|
|
il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod("CreateParameter"), null);// stack is now [parameters] [parameters] [parameter]
|
|
|
|
il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
|
|
il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name]
|
|
il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("ParameterName").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
|
|
}
|
|
if (dbType != DbType.Time) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time
|
|
{
|
|
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
|
|
|
|
if(literals.Count != 0 && propsArr != null)
|
|
{
|
|
il.Emit(OpCodes.Ldarg_0); // command
|
|
il.Emit(OpCodes.Ldarg_0); // command, command
|
|
var cmdText = typeof(IDbCommand).GetProperty("CommandText");
|
|
il.EmitCall(OpCodes.Callvirt, cmdText.GetGetMethod(), null); // command, sql
|
|
Dictionary<Type, LocalBuilder> locals = null;
|
|
LocalBuilder local = null;
|
|
foreach (var literal in literals)
|
|
{
|
|
// find the best member, preferring case-sensitive
|
|
PropertyInfo exact = null, fallback = null;
|
|
string huntName = literal.Member;
|
|
for(int i = 0; i < propsArr.Length;i++)
|
|
{
|
|
string thisName = propsArr[i].Name;
|
|
if(string.Equals(thisName, huntName, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
fallback = propsArr[i];
|
|
if(string.Equals(thisName, huntName, StringComparison.InvariantCulture))
|
|
{
|
|
exact = fallback;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
var prop = exact ?? fallback;
|
|
|
|
if(prop != null)
|
|
{
|
|
il.Emit(OpCodes.Ldstr, literal.Token);
|
|
il.Emit(OpCodes.Ldloc_0); // command, sql, typed parameter
|
|
il.EmitCall(OpCodes.Callvirt, prop.GetGetMethod(), null); // command, sql, typed value
|
|
Type propType = prop.PropertyType;
|
|
var typeCode = Type.GetTypeCode(propType);
|
|
switch (typeCode)
|
|
{
|
|
case TypeCode.Boolean:
|
|
case TypeCode.Byte:
|
|
case TypeCode.SByte:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.Int16:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.Int32:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Int64:
|
|
case TypeCode.Single:
|
|
case TypeCode.Double:
|
|
case TypeCode.Decimal:
|
|
// neeed to stloc, ldloca, call
|
|
// re-use existing locals (both the last known, and via a dictionary)
|
|
var convert = GetToString(typeCode);
|
|
if (local == null || local.LocalType != propType)
|
|
{
|
|
if (locals == null)
|
|
{
|
|
locals = new Dictionary<Type, LocalBuilder>();
|
|
local = null;
|
|
}
|
|
else
|
|
{
|
|
if (!locals.TryGetValue(propType, out local)) local = null;
|
|
}
|
|
if (local == null)
|
|
{
|
|
local = il.DeclareLocal(propType);
|
|
locals.Add(propType, local);
|
|
}
|
|
}
|
|
il.Emit(OpCodes.Stloc, local); // command, sql
|
|
il.Emit(OpCodes.Ldloca, local); // command, sql, ref-to-value
|
|
il.EmitCall(OpCodes.Call, InvariantCulture, null); // command, sql, ref-to-value, culture
|
|
il.EmitCall(OpCodes.Call, convert, null); // command, sql, string value
|
|
break;
|
|
default:
|
|
if (propType.IsValueType) il.Emit(OpCodes.Box, propType); // command, sql, object value
|
|
il.EmitCall(OpCodes.Call, format, null); // command, sql, string value
|
|
break;
|
|
|
|
}
|
|
il.EmitCall(OpCodes.Callvirt, StringReplace, null);
|
|
}
|
|
}
|
|
il.EmitCall(OpCodes.Callvirt, cmdText.GetSetMethod(), null); // empty
|
|
}
|
|
|
|
il.Emit(OpCodes.Ret);
|
|
return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>));
|
|
}
|
|
static readonly Dictionary<TypeCode, MethodInfo> toStrings = new[]
|
|
{
|
|
typeof(bool), typeof(sbyte), typeof(byte), typeof(ushort), typeof(short),
|
|
typeof(uint), typeof(int), typeof(ulong), typeof(long), typeof(float), typeof(double), typeof(decimal)
|
|
}.ToDictionary(x => Type.GetTypeCode(x), x => x.GetMethod("ToString", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(IFormatProvider) }, null));
|
|
static MethodInfo GetToString(TypeCode typeCode)
|
|
{
|
|
MethodInfo method;
|
|
return toStrings.TryGetValue(typeCode, out method) ? method : null;
|
|
}
|
|
static readonly MethodInfo StringReplace = typeof(string).GetMethod("Replace", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string), typeof(string) }, null),
|
|
InvariantCulture = typeof(CultureInfo).GetProperty("InvariantCulture", BindingFlags.Public | BindingFlags.Static).GetGetMethod();
|
|
|
|
private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action<IDbCommand, object> paramReader)
|
|
{
|
|
IDbCommand cmd = null;
|
|
bool wasClosed = cnn.State == ConnectionState.Closed;
|
|
try
|
|
{
|
|
cmd = command.SetupCommand(cnn, paramReader);
|
|
if (wasClosed) cnn.Open();
|
|
return cmd.ExecuteNonQuery();
|
|
}
|
|
finally
|
|
{
|
|
if (wasClosed) cnn.Close();
|
|
if (cmd != null) cmd.Dispose();
|
|
}
|
|
}
|
|
|
|
private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command)
|
|
{
|
|
Action<IDbCommand, object> paramReader = GetParameterReader(cnn, ref command);
|
|
|
|
IDbCommand cmd = null;
|
|
bool wasClosed = cnn.State == ConnectionState.Closed;
|
|
try
|
|
{
|
|
cmd = command.SetupCommand(cnn, paramReader);
|
|
if (wasClosed) cnn.Open();
|
|
var reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
|
|
wasClosed = false;
|
|
return reader;
|
|
}
|
|
finally
|
|
{
|
|
if (wasClosed) cnn.Close();
|
|
if (cmd != null) cmd.Dispose();
|
|
}
|
|
}
|
|
|
|
private static Action<IDbCommand, object> GetParameterReader(IDbConnection cnn, ref CommandDefinition command)
|
|
{
|
|
object param = command.Parameters;
|
|
IEnumerable multiExec = (object)param as IEnumerable;
|
|
Identity identity;
|
|
CacheInfo info = null;
|
|
if (multiExec != null && !(multiExec is string))
|
|
{
|
|
throw new NotSupportedException("MultiExec is not supported by ExecuteReader");
|
|
}
|
|
|
|
// nice and simple
|
|
if (param != null)
|
|
{
|
|
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
|
|
info = GetCacheInfo(identity, param);
|
|
}
|
|
var paramReader = info == null ? null : info.ParamReader;
|
|
return paramReader;
|
|
}
|
|
|
|
private static Func<IDataReader, object> GetStructDeserializer(Type type, Type effectiveType, int index)
|
|
{
|
|
// no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!)
|
|
#pragma warning disable 618
|
|
if (type == typeof(char))
|
|
{ // this *does* need special handling, though
|
|
return r => SqlMapper.ReadChar(r.GetValue(index));
|
|
}
|
|
if (type == typeof(char?))
|
|
{
|
|
return r => SqlMapper.ReadNullableChar(r.GetValue(index));
|
|
}
|
|
if (type.FullName == LinqBinary)
|
|
{
|
|
return r => Activator.CreateInstance(type, r.GetValue(index));
|
|
}
|
|
#pragma warning restore 618
|
|
|
|
if (effectiveType.IsEnum)
|
|
{ // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum
|
|
return r =>
|
|
{
|
|
var val = r.GetValue(index);
|
|
return val is DBNull ? null : Enum.ToObject(effectiveType, val);
|
|
};
|
|
}
|
|
return r =>
|
|
{
|
|
var val = r.GetValue(index);
|
|
return val is DBNull ? null : val;
|
|
};
|
|
}
|
|
|
|
static readonly MethodInfo
|
|
enumParse = typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string), typeof(bool) }),
|
|
getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
|
.Where(p => p.GetIndexParameters().Any() && p.GetIndexParameters()[0].ParameterType == typeof(int))
|
|
.Select(p => p.GetGetMethod()).First();
|
|
|
|
/// <summary>
|
|
/// Gets type-map for the given type
|
|
/// </summary>
|
|
/// <returns>Type map implementation, DefaultTypeMap instance if no override present</returns>
|
|
public static ITypeMap GetTypeMap(Type type)
|
|
{
|
|
if (type == null) throw new ArgumentNullException("type");
|
|
var map = (ITypeMap)_typeMaps[type];
|
|
if (map == null)
|
|
{
|
|
lock (_typeMaps)
|
|
{ // double-checked; store this to avoid reflection next time we see this type
|
|
// since multiple queries commonly use the same domain-entity/DTO/view-model type
|
|
map = (ITypeMap)_typeMaps[type];
|
|
if (map == null)
|
|
{
|
|
map = new DefaultTypeMap(type);
|
|
_typeMaps[type] = map;
|
|
}
|
|
}
|
|
}
|
|
return map;
|
|
}
|
|
|
|
// use Hashtable to get free lockless reading
|
|
private static readonly Hashtable _typeMaps = new Hashtable();
|
|
|
|
/// <summary>
|
|
/// Set custom mapping for type deserializers
|
|
/// </summary>
|
|
/// <param name="type">Entity type to override</param>
|
|
/// <param name="map">Mapping rules impementation, null to remove custom map</param>
|
|
public static void SetTypeMap(Type type, ITypeMap map)
|
|
{
|
|
if (type == null)
|
|
throw new ArgumentNullException("type");
|
|
|
|
if (map == null || map is DefaultTypeMap)
|
|
{
|
|
lock (_typeMaps)
|
|
{
|
|
_typeMaps.Remove(type);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lock (_typeMaps)
|
|
{
|
|
_typeMaps[type] = map;
|
|
}
|
|
}
|
|
|
|
PurgeQueryCacheByType(type);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal use only
|
|
/// </summary>
|
|
/// <param name="type"></param>
|
|
/// <param name="reader"></param>
|
|
/// <param name="startBound"></param>
|
|
/// <param name="length"></param>
|
|
/// <param name="returnNullIfFirstMissing"></param>
|
|
/// <returns></returns>
|
|
public static Func<IDataReader, object> GetTypeDeserializer(
|
|
#if CSHARP30
|
|
Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
|
|
#else
|
|
Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false
|
|
#endif
|
|
)
|
|
{
|
|
|
|
var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(IDataReader) }, true);
|
|
var il = dm.GetILGenerator();
|
|
il.DeclareLocal(typeof(int));
|
|
il.DeclareLocal(type);
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
il.Emit(OpCodes.Stloc_0);
|
|
|
|
if (length == -1)
|
|
{
|
|
length = reader.FieldCount - startBound;
|
|
}
|
|
|
|
if (reader.FieldCount <= startBound)
|
|
{
|
|
throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn");
|
|
}
|
|
|
|
var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray();
|
|
|
|
ITypeMap typeMap = GetTypeMap(type);
|
|
|
|
int index = startBound;
|
|
|
|
ConstructorInfo specializedConstructor = null;
|
|
|
|
if (type.IsValueType)
|
|
{
|
|
il.Emit(OpCodes.Ldloca_S, (byte)1);
|
|
il.Emit(OpCodes.Initobj, type);
|
|
}
|
|
else
|
|
{
|
|
var types = new Type[length];
|
|
for (int i = startBound; i < startBound + length; i++)
|
|
{
|
|
types[i - startBound] = reader.GetFieldType(i);
|
|
}
|
|
|
|
var ctor = typeMap.FindConstructor(names, types);
|
|
if (ctor == null)
|
|
{
|
|
string proposedTypes = "(" + string.Join(", ", types.Select((t, i) => t.FullName + " " + names[i]).ToArray()) + ")";
|
|
throw new InvalidOperationException(string.Format("A parameterless default constructor or one matching signature {0} is required for {1} materialization", proposedTypes, type.FullName));
|
|
}
|
|
|
|
if (ctor.GetParameters().Length == 0)
|
|
{
|
|
il.Emit(OpCodes.Newobj, ctor);
|
|
il.Emit(OpCodes.Stloc_1);
|
|
}
|
|
else
|
|
specializedConstructor = ctor;
|
|
}
|
|
|
|
il.BeginExceptionBlock();
|
|
if (type.IsValueType)
|
|
{
|
|
il.Emit(OpCodes.Ldloca_S, (byte)1);// [target]
|
|
}
|
|
else if (specializedConstructor == null)
|
|
{
|
|
il.Emit(OpCodes.Ldloc_1);// [target]
|
|
}
|
|
|
|
var members = (specializedConstructor != null
|
|
? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n))
|
|
: names.Select(n => typeMap.GetMember(n))).ToList();
|
|
|
|
// stack is now [target]
|
|
|
|
bool first = true;
|
|
var allDone = il.DefineLabel();
|
|
int enumDeclareLocal = -1;
|
|
foreach (var item in members)
|
|
{
|
|
if (item != null)
|
|
{
|
|
if (specializedConstructor == null)
|
|
il.Emit(OpCodes.Dup); // stack is now [target][target]
|
|
Label isDbNullLabel = il.DefineLabel();
|
|
Label finishLabel = il.DefineLabel();
|
|
|
|
il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader]
|
|
EmitInt32(il, index); // stack is now [target][target][reader][index]
|
|
il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index]
|
|
il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index]
|
|
il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object]
|
|
|
|
Type memberType = item.MemberType;
|
|
|
|
if (memberType == typeof(char) || memberType == typeof(char?))
|
|
{
|
|
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(
|
|
memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value]
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
|
|
il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null]
|
|
il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object]
|
|
|
|
// unbox nullable enums as the primitive, i.e. byte etc
|
|
|
|
var nullUnderlyingType = Nullable.GetUnderlyingType(memberType);
|
|
var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType;
|
|
|
|
if (unboxType.IsEnum)
|
|
{
|
|
if (enumDeclareLocal == -1)
|
|
{
|
|
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
|
|
}
|
|
|
|
Label isNotString = il.DefineLabel();
|
|
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
|
|
il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null]
|
|
il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null]
|
|
StoreLocal(il, enumDeclareLocal); // stack is now [target][target][value-as-object][string or null]
|
|
il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object]
|
|
|
|
il.Emit(OpCodes.Pop); // stack is now [target][target]
|
|
|
|
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
|
|
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
|
|
il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string]
|
|
il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
|
|
il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
|
|
|
|
il.MarkLabel(isNotString);
|
|
|
|
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
|
|
|
|
if (nullUnderlyingType != null)
|
|
{
|
|
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][enum-value]
|
|
}
|
|
}
|
|
else if (memberType.FullName == LinqBinary)
|
|
{
|
|
il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array]
|
|
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary]
|
|
}
|
|
else
|
|
{
|
|
Type dataType = reader.GetFieldType(index);
|
|
TypeCode dataTypeCode = Type.GetTypeCode(dataType), unboxTypeCode = Type.GetTypeCode(unboxType);
|
|
if (dataType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType))
|
|
{
|
|
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
|
|
}
|
|
else
|
|
{
|
|
// not a direct match; need to tweak the unbox
|
|
MethodInfo op;
|
|
if ((op = GetOperator(dataType, nullUnderlyingType ?? unboxType)) != null)
|
|
{ // this is handy for things like decimal <===> double
|
|
il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]
|
|
il.Emit(OpCodes.Call, op); // stack is now [target][target][typed-value]
|
|
}
|
|
else
|
|
{
|
|
bool handled = true;
|
|
OpCode opCode = default(OpCode);
|
|
if (dataTypeCode == TypeCode.Decimal || unboxTypeCode == TypeCode.Decimal)
|
|
{ // no IL level conversions to/from decimal; I guess we could use the static operators, but
|
|
// this feels an edge-case
|
|
handled = false;
|
|
}
|
|
else
|
|
{
|
|
switch (unboxTypeCode)
|
|
{
|
|
case TypeCode.Byte:
|
|
opCode = OpCodes.Conv_Ovf_I1_Un; break;
|
|
case TypeCode.SByte:
|
|
opCode = OpCodes.Conv_Ovf_I1; break;
|
|
case TypeCode.UInt16:
|
|
opCode = OpCodes.Conv_Ovf_I2_Un; break;
|
|
case TypeCode.Int16:
|
|
opCode = OpCodes.Conv_Ovf_I2; break;
|
|
case TypeCode.UInt32:
|
|
opCode = OpCodes.Conv_Ovf_I4_Un; break;
|
|
case TypeCode.Boolean: // boolean is basically an int, at least at this level
|
|
case TypeCode.Int32:
|
|
opCode = OpCodes.Conv_Ovf_I4; break;
|
|
case TypeCode.UInt64:
|
|
opCode = OpCodes.Conv_Ovf_I8_Un; break;
|
|
case TypeCode.Int64:
|
|
opCode = OpCodes.Conv_Ovf_I8; break;
|
|
case TypeCode.Single:
|
|
opCode = OpCodes.Conv_R4; break;
|
|
case TypeCode.Double:
|
|
opCode = OpCodes.Conv_R8; break;
|
|
default:
|
|
handled = false;
|
|
break;
|
|
}
|
|
}
|
|
if (handled)
|
|
{ // unbox as the data-type, then use IL-level convert
|
|
il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]
|
|
il.Emit(opCode); // stack is now [target][target][typed-value]
|
|
if (unboxTypeCode == TypeCode.Boolean)
|
|
{ // compare to zero; I checked "csc" - this is the trick it uses; nice
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
il.Emit(OpCodes.Ceq);
|
|
il.Emit(OpCodes.Ldc_I4_0);
|
|
il.Emit(OpCodes.Ceq);
|
|
}
|
|
}
|
|
else
|
|
{ // use flexible conversion
|
|
il.Emit(OpCodes.Ldtoken, nullUnderlyingType ?? unboxType); // stack is now [target][target][value][member-type-token]
|
|
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // stack is now [target][target][value][member-type]
|
|
il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value]
|
|
il.Emit(OpCodes.Unbox_Any, nullUnderlyingType ?? unboxType); // stack is now [target][target][typed-value]
|
|
}
|
|
}
|
|
if (nullUnderlyingType != null)
|
|
{
|
|
il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
if (specializedConstructor == null)
|
|
{
|
|
// Store the value in the property/field
|
|
if (item.Property != null)
|
|
{
|
|
if (type.IsValueType)
|
|
{
|
|
il.Emit(OpCodes.Call, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
|
|
}
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
|
|
}
|
|
}
|
|
|
|
il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target]
|
|
|
|
il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]
|
|
if (specializedConstructor != null)
|
|
{
|
|
il.Emit(OpCodes.Pop);
|
|
if (item.MemberType.IsValueType)
|
|
{
|
|
int localIndex = il.DeclareLocal(item.MemberType).LocalIndex;
|
|
LoadLocalAddress(il, localIndex);
|
|
il.Emit(OpCodes.Initobj, item.MemberType);
|
|
LoadLocal(il, localIndex);
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Ldnull);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Pop); // stack is now [target][target]
|
|
il.Emit(OpCodes.Pop); // stack is now [target]
|
|
}
|
|
|
|
if (first && returnNullIfFirstMissing)
|
|
{
|
|
il.Emit(OpCodes.Pop);
|
|
il.Emit(OpCodes.Ldnull); // stack is now [null]
|
|
il.Emit(OpCodes.Stloc_1);
|
|
il.Emit(OpCodes.Br, allDone);
|
|
}
|
|
|
|
il.MarkLabel(finishLabel);
|
|
}
|
|
first = false;
|
|
index += 1;
|
|
}
|
|
if (type.IsValueType)
|
|
{
|
|
il.Emit(OpCodes.Pop);
|
|
}
|
|
else
|
|
{
|
|
if (specializedConstructor != null)
|
|
{
|
|
il.Emit(OpCodes.Newobj, specializedConstructor);
|
|
}
|
|
il.Emit(OpCodes.Stloc_1); // stack is empty
|
|
}
|
|
il.MarkLabel(allDone);
|
|
il.BeginCatchBlock(typeof(Exception)); // stack is Exception
|
|
il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
|
|
il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader
|
|
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null);
|
|
il.EndExceptionBlock();
|
|
|
|
il.Emit(OpCodes.Ldloc_1); // stack is [rval]
|
|
if (type.IsValueType)
|
|
{
|
|
il.Emit(OpCodes.Box, type);
|
|
}
|
|
il.Emit(OpCodes.Ret);
|
|
|
|
return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>));
|
|
}
|
|
static MethodInfo GetOperator(Type from, Type to)
|
|
{
|
|
if (to == null) return null;
|
|
MethodInfo[] fromMethods, toMethods;
|
|
return ResolveOperator(fromMethods = from.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit")
|
|
?? ResolveOperator(toMethods = to.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit")
|
|
?? ResolveOperator(fromMethods, from, to, "op_Explicit")
|
|
?? ResolveOperator(toMethods, from, to, "op_Explicit");
|
|
|
|
}
|
|
static MethodInfo ResolveOperator(MethodInfo[] methods, Type from, Type to, string name)
|
|
{
|
|
for (int i = 0; i < methods.Length; i++)
|
|
{
|
|
if (methods[i].Name != name || methods[i].ReturnType != to) continue;
|
|
var args = methods[i].GetParameters();
|
|
if (args.Length != 1 || args[0].ParameterType != from) continue;
|
|
return methods[i];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static void LoadLocal(ILGenerator il, int index)
|
|
{
|
|
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
|
|
switch (index)
|
|
{
|
|
case 0: il.Emit(OpCodes.Ldloc_0); break;
|
|
case 1: il.Emit(OpCodes.Ldloc_1); break;
|
|
case 2: il.Emit(OpCodes.Ldloc_2); break;
|
|
case 3: il.Emit(OpCodes.Ldloc_3); break;
|
|
default:
|
|
if (index <= 255)
|
|
{
|
|
il.Emit(OpCodes.Ldloc_S, (byte)index);
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Ldloc, (short)index);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
private static void StoreLocal(ILGenerator il, int index)
|
|
{
|
|
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
|
|
switch (index)
|
|
{
|
|
case 0: il.Emit(OpCodes.Stloc_0); break;
|
|
case 1: il.Emit(OpCodes.Stloc_1); break;
|
|
case 2: il.Emit(OpCodes.Stloc_2); break;
|
|
case 3: il.Emit(OpCodes.Stloc_3); break;
|
|
default:
|
|
if (index <= 255)
|
|
{
|
|
il.Emit(OpCodes.Stloc_S, (byte)index);
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Stloc, (short)index);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
private static void LoadLocalAddress(ILGenerator il, int index)
|
|
{
|
|
if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
|
|
|
|
if (index <= 255)
|
|
{
|
|
il.Emit(OpCodes.Ldloca_S, (byte)index);
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Ldloca, (short)index);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Throws a data exception, only used internally
|
|
/// </summary>
|
|
/// <param name="ex"></param>
|
|
/// <param name="index"></param>
|
|
/// <param name="reader"></param>
|
|
public static void ThrowDataException(Exception ex, int index, IDataReader reader)
|
|
{
|
|
Exception toThrow;
|
|
try
|
|
{
|
|
string name = "(n/a)", value = "(n/a)";
|
|
if (reader != null && index >= 0 && index < reader.FieldCount)
|
|
{
|
|
name = reader.GetName(index);
|
|
try
|
|
{
|
|
object val = reader.GetValue(index); // if there throw an exception, then I got one message, but Which column?
|
|
if (val == null || val is DBNull)
|
|
{
|
|
value = "<null>";
|
|
}
|
|
else
|
|
{
|
|
value = Convert.ToString(val) + " - " + Type.GetTypeCode(val.GetType());
|
|
}
|
|
}
|
|
catch (Exception valEx)
|
|
{
|
|
value = valEx.Message;
|
|
}
|
|
}
|
|
toThrow = new DataException(string.Format("Error parsing column {0} ({1}={2})", index, name, value), ex);
|
|
}
|
|
catch
|
|
{ // throw the **original** exception, wrapped as DataException
|
|
toThrow = new DataException(ex.Message, ex);
|
|
}
|
|
throw toThrow;
|
|
}
|
|
private static void EmitInt32(ILGenerator il, int value)
|
|
{
|
|
switch (value)
|
|
{
|
|
case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
|
|
case 0: il.Emit(OpCodes.Ldc_I4_0); break;
|
|
case 1: il.Emit(OpCodes.Ldc_I4_1); break;
|
|
case 2: il.Emit(OpCodes.Ldc_I4_2); break;
|
|
case 3: il.Emit(OpCodes.Ldc_I4_3); break;
|
|
case 4: il.Emit(OpCodes.Ldc_I4_4); break;
|
|
case 5: il.Emit(OpCodes.Ldc_I4_5); break;
|
|
case 6: il.Emit(OpCodes.Ldc_I4_6); break;
|
|
case 7: il.Emit(OpCodes.Ldc_I4_7); break;
|
|
case 8: il.Emit(OpCodes.Ldc_I4_8); break;
|
|
default:
|
|
if (value >= -128 && value <= 127)
|
|
{
|
|
il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
|
|
}
|
|
else
|
|
{
|
|
il.Emit(OpCodes.Ldc_I4, value);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// How should connection strings be compared for equivalence? Defaults to StringComparer.Ordinal.
|
|
/// Providing a custom implementation can be useful for allowing multi-tenancy databases with identical
|
|
/// schema to share startegies. Note that usual equivalence rules apply: any equivalent connection strings
|
|
/// <b>MUST</b> yield the same hash-code.
|
|
/// </summary>
|
|
public static IEqualityComparer<string> ConnectionStringComparer
|
|
{
|
|
get { return connectionStringComparer; }
|
|
set { connectionStringComparer = value ?? StringComparer.Ordinal; }
|
|
}
|
|
private static IEqualityComparer<string> connectionStringComparer = StringComparer.Ordinal;
|
|
|
|
/// <summary>
|
|
/// The grid reader provides interfaces for reading multiple result sets from a Dapper query
|
|
/// </summary>
|
|
public partial class GridReader : IDisposable
|
|
{
|
|
private IDataReader reader;
|
|
private IDbCommand command;
|
|
private Identity identity;
|
|
|
|
internal GridReader(IDbCommand command, IDataReader reader, Identity identity)
|
|
{
|
|
this.command = command;
|
|
this.reader = reader;
|
|
this.identity = identity;
|
|
}
|
|
|
|
#if !CSHARP30
|
|
|
|
/// <summary>
|
|
/// Read the next grid of results, returned as a dynamic object
|
|
/// </summary>
|
|
public IEnumerable<dynamic> Read(bool buffered = true)
|
|
{
|
|
return Read<DapperRow>(buffered);
|
|
}
|
|
#endif
|
|
|
|
#if CSHARP30
|
|
/// <summary>
|
|
/// Read the next grid of results
|
|
/// </summary>
|
|
public IEnumerable<T> Read<T>()
|
|
{
|
|
return Read<T>(true);
|
|
}
|
|
#endif
|
|
/// <summary>
|
|
/// Read the next grid of results
|
|
/// </summary>
|
|
#if CSHARP30
|
|
public IEnumerable<T> Read<T>(bool buffered)
|
|
#else
|
|
public IEnumerable<T> Read<T>(bool buffered = true)
|
|
#endif
|
|
{
|
|
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
|
|
if (consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
|
|
var typedIdentity = identity.ForGrid(typeof(T), gridIndex);
|
|
CacheInfo cache = GetCacheInfo(typedIdentity, null);
|
|
var deserializer = cache.Deserializer;
|
|
|
|
int hash = GetColumnHash(reader);
|
|
if (deserializer.Func == null || deserializer.Hash != hash)
|
|
{
|
|
deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false));
|
|
cache.Deserializer = deserializer;
|
|
}
|
|
consumed = true;
|
|
var result = ReadDeferred<T>(gridIndex, deserializer.Func, typedIdentity);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
|
|
private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Delegate func, string splitOn)
|
|
{
|
|
var identity = this.identity.ForGrid(typeof(TReturn), new Type[] {
|
|
typeof(TFirst),
|
|
typeof(TSecond),
|
|
typeof(TThird),
|
|
typeof(TFourth),
|
|
typeof(TFifth),
|
|
typeof(TSixth),
|
|
typeof(TSeventh)
|
|
}, gridIndex);
|
|
try
|
|
{
|
|
foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default(CommandDefinition), func, splitOn, reader, identity))
|
|
{
|
|
yield return r;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
NextResult();
|
|
}
|
|
}
|
|
|
|
#if CSHARP30
|
|
/// <summary>
|
|
/// Read multiple objects from a single recordset on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn)
|
|
{
|
|
return Read<TFirst, TSecond, TReturn>(func, splitOn, true);
|
|
}
|
|
#endif
|
|
/// <summary>
|
|
/// Read multiple objects from a single recordset on the grid
|
|
/// </summary>
|
|
#if CSHARP30
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn, bool buffered)
|
|
#else
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
#endif
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
|
|
#if CSHARP30
|
|
/// <summary>
|
|
/// Read multiple objects from a single recordset on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn)
|
|
{
|
|
return Read<TFirst, TSecond, TThird, TReturn>(func, splitOn, true);
|
|
}
|
|
#endif
|
|
/// <summary>
|
|
/// Read multiple objects from a single recordset on the grid
|
|
/// </summary>
|
|
#if CSHARP30
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn, bool buffered)
|
|
#else
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
#endif
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
|
|
#if CSHARP30
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn)
|
|
{
|
|
return Read<TFirst, TSecond, TThird, TFourth, TReturn>(func, splitOn, true);
|
|
}
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
#if CSHARP30
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn, bool buffered)
|
|
#else
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
#endif
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
|
|
|
|
|
|
#if !CSHARP30
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
#endif
|
|
|
|
private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity)
|
|
{
|
|
try
|
|
{
|
|
while (index == gridIndex && reader.Read())
|
|
{
|
|
yield return (T)deserializer(reader);
|
|
}
|
|
}
|
|
finally // finally so that First etc progresses things even when multiple rows
|
|
{
|
|
if (index == gridIndex)
|
|
{
|
|
NextResult();
|
|
}
|
|
}
|
|
}
|
|
private int gridIndex, readCount;
|
|
private bool consumed;
|
|
private void NextResult()
|
|
{
|
|
if (reader.NextResult())
|
|
{
|
|
readCount++;
|
|
gridIndex++;
|
|
consumed = false;
|
|
}
|
|
else
|
|
{
|
|
// happy path; close the reader cleanly - no
|
|
// need for "Cancel" etc
|
|
reader.Dispose();
|
|
reader = null;
|
|
|
|
Dispose();
|
|
}
|
|
|
|
}
|
|
/// <summary>
|
|
/// Dispose the grid, closing and disposing both the underlying reader and command.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
if (reader != null)
|
|
{
|
|
if (!reader.IsClosed && command != null) command.Cancel();
|
|
reader.Dispose();
|
|
reader = null;
|
|
}
|
|
if (command != null)
|
|
{
|
|
command.Dispose();
|
|
command = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A bag of parameters that can be passed to the Dapper Query and Execute methods
|
|
/// </summary>
|
|
partial class DynamicParameters : SqlMapper.IDynamicParameters, SqlMapper.IParameterLookup
|
|
{
|
|
internal const DbType EnumerableMultiParameter = (DbType)(-1);
|
|
static Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>();
|
|
|
|
Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>();
|
|
List<object> templates;
|
|
|
|
object SqlMapper.IParameterLookup.this[string member]
|
|
{
|
|
get
|
|
{
|
|
ParamInfo param;
|
|
return parameters.TryGetValue(member, out param) ? param.Value : null;
|
|
}
|
|
}
|
|
|
|
partial class ParamInfo
|
|
{
|
|
public string Name { get; set; }
|
|
public object Value { get; set; }
|
|
public ParameterDirection ParameterDirection { get; set; }
|
|
public DbType? DbType { get; set; }
|
|
public int? Size { get; set; }
|
|
public IDbDataParameter AttachedParam { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// construct a dynamic parameter bag
|
|
/// </summary>
|
|
public DynamicParameters()
|
|
{
|
|
RemoveUnused = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// construct a dynamic parameter bag
|
|
/// </summary>
|
|
/// <param name="template">can be an anonymous type or a DynamicParameters bag</param>
|
|
public DynamicParameters(object template)
|
|
{
|
|
RemoveUnused = true;
|
|
AddDynamicParams(template);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Append a whole object full of params to the dynamic
|
|
/// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic
|
|
/// </summary>
|
|
/// <param name="param"></param>
|
|
public void AddDynamicParams(
|
|
#if CSHARP30
|
|
object param
|
|
#else
|
|
dynamic param
|
|
#endif
|
|
)
|
|
{
|
|
var obj = param as object;
|
|
if (obj != null)
|
|
{
|
|
var subDynamic = obj as DynamicParameters;
|
|
if (subDynamic == null)
|
|
{
|
|
var dictionary = obj as IEnumerable<KeyValuePair<string, object>>;
|
|
if (dictionary == null)
|
|
{
|
|
templates = templates ?? new List<object>();
|
|
templates.Add(obj);
|
|
}
|
|
else
|
|
{
|
|
foreach (var kvp in dictionary)
|
|
{
|
|
#if CSHARP30
|
|
Add(kvp.Key, kvp.Value, null, null, null);
|
|
#else
|
|
Add(kvp.Key, kvp.Value);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (subDynamic.parameters != null)
|
|
{
|
|
foreach (var kvp in subDynamic.parameters)
|
|
{
|
|
parameters.Add(kvp.Key, kvp.Value);
|
|
}
|
|
}
|
|
|
|
if (subDynamic.templates != null)
|
|
{
|
|
templates = templates ?? new List<object>();
|
|
foreach (var t in subDynamic.templates)
|
|
{
|
|
templates.Add(t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a parameter to this dynamic parameter list
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <param name="value"></param>
|
|
/// <param name="dbType"></param>
|
|
/// <param name="direction"></param>
|
|
/// <param name="size"></param>
|
|
public void Add(
|
|
#if CSHARP30
|
|
string name, object value, DbType? dbType, ParameterDirection? direction, int? size
|
|
#else
|
|
string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null
|
|
#endif
|
|
)
|
|
{
|
|
parameters[Clean(name)] = new ParamInfo() { Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType, Size = size };
|
|
}
|
|
|
|
static string Clean(string name)
|
|
{
|
|
if (!string.IsNullOrEmpty(name))
|
|
{
|
|
switch (name[0])
|
|
{
|
|
case '@':
|
|
case ':':
|
|
case '?':
|
|
return name.Substring(1);
|
|
}
|
|
}
|
|
return name;
|
|
}
|
|
|
|
void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
|
|
{
|
|
AddParameters(command, identity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// If true, the command-text is inspected and only values that are clearly used are included on the connection
|
|
/// </summary>
|
|
public bool RemoveUnused { get; set; }
|
|
|
|
/// <summary>
|
|
/// Add all the parameters needed to the command just before it executes
|
|
/// </summary>
|
|
/// <param name="command">The raw command prior to execution</param>
|
|
/// <param name="identity">Information about the query</param>
|
|
protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
|
|
{
|
|
var literals = SqlMapper.GetLiteralTokens(identity.sql);
|
|
if (templates != null)
|
|
{
|
|
foreach (var template in templates)
|
|
{
|
|
var newIdent = identity.ForDynamicParameters(template.GetType());
|
|
Action<IDbCommand, object> appender;
|
|
|
|
lock (paramReaderCache)
|
|
{
|
|
if (!paramReaderCache.TryGetValue(newIdent, out appender))
|
|
{
|
|
appender = SqlMapper.CreateParamInfoGenerator(newIdent, true, RemoveUnused, literals);
|
|
paramReaderCache[newIdent] = appender;
|
|
}
|
|
}
|
|
|
|
appender(command, template);
|
|
}
|
|
}
|
|
|
|
foreach (var param in parameters.Values)
|
|
{
|
|
var dbType = param.DbType;
|
|
var val = param.Value;
|
|
string name = Clean(param.Name);
|
|
var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter;
|
|
|
|
if (dbType == null && val != null && !isCustomQueryParameter) dbType = SqlMapper.LookupDbType(val.GetType(), name);
|
|
|
|
if (dbType == DynamicParameters.EnumerableMultiParameter)
|
|
{
|
|
#pragma warning disable 612, 618
|
|
SqlMapper.PackListParameters(command, name, val);
|
|
#pragma warning restore 612, 618
|
|
}
|
|
else if (isCustomQueryParameter)
|
|
{
|
|
((SqlMapper.ICustomQueryParameter)val).AddParameter(command, name);
|
|
}
|
|
else
|
|
{
|
|
|
|
bool add = !command.Parameters.Contains(name);
|
|
IDbDataParameter p;
|
|
if (add)
|
|
{
|
|
p = command.CreateParameter();
|
|
p.ParameterName = name;
|
|
}
|
|
else
|
|
{
|
|
p = (IDbDataParameter)command.Parameters[name];
|
|
}
|
|
|
|
p.Value = val ?? DBNull.Value;
|
|
p.Direction = param.ParameterDirection;
|
|
var s = val as string;
|
|
if (s != null)
|
|
{
|
|
if (s.Length <= 4000)
|
|
{
|
|
p.Size = 4000;
|
|
}
|
|
}
|
|
if (param.Size != null)
|
|
{
|
|
p.Size = param.Size.Value;
|
|
}
|
|
if (dbType != null && p.DbType != dbType)
|
|
{
|
|
p.DbType = dbType.Value;
|
|
}
|
|
if (add)
|
|
{
|
|
command.Parameters.Add(p);
|
|
}
|
|
param.AttachedParam = p;
|
|
}
|
|
}
|
|
// note: most non-priveleged implementations would use: this.ReplaceLiterals(command);
|
|
if(literals.Count != 0) SqlMapper.ReplaceLiterals(this, command, literals);
|
|
}
|
|
|
|
/// <summary>
|
|
/// All the names of the param in the bag, use Get to yank them out
|
|
/// </summary>
|
|
public IEnumerable<string> ParameterNames
|
|
{
|
|
get
|
|
{
|
|
return parameters.Select(p => p.Key);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Get the value of a parameter
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="name"></param>
|
|
/// <returns>The value, note DBNull.Value is not returned, instead the value is returned as null</returns>
|
|
public T Get<T>(string name)
|
|
{
|
|
var val = parameters[Clean(name)].AttachedParam.Value;
|
|
if (val == DBNull.Value)
|
|
{
|
|
if (default(T) != null)
|
|
{
|
|
throw new ApplicationException("Attempting to cast a DBNull to a non nullable type!");
|
|
}
|
|
return default(T);
|
|
}
|
|
return (T)val;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class represents a SQL string, it can be used if you need to denote your parameter is a Char vs VarChar vs nVarChar vs nChar
|
|
/// </summary>
|
|
sealed partial class DbString : Dapper.SqlMapper.ICustomQueryParameter
|
|
{
|
|
/// <summary>
|
|
/// Create a new DbString
|
|
/// </summary>
|
|
public DbString() { Length = -1; }
|
|
/// <summary>
|
|
/// Ansi vs Unicode
|
|
/// </summary>
|
|
public bool IsAnsi { get; set; }
|
|
/// <summary>
|
|
/// Fixed length
|
|
/// </summary>
|
|
public bool IsFixedLength { get; set; }
|
|
/// <summary>
|
|
/// Length of the string -1 for max
|
|
/// </summary>
|
|
public int Length { get; set; }
|
|
/// <summary>
|
|
/// The value of the string
|
|
/// </summary>
|
|
public string Value { get; set; }
|
|
/// <summary>
|
|
/// Add the parameter to the command... internal use only
|
|
/// </summary>
|
|
/// <param name="command"></param>
|
|
/// <param name="name"></param>
|
|
public void AddParameter(IDbCommand command, string name)
|
|
{
|
|
if (IsFixedLength && Length == -1)
|
|
{
|
|
throw new InvalidOperationException("If specifying IsFixedLength, a Length must also be specified");
|
|
}
|
|
var param = command.CreateParameter();
|
|
param.ParameterName = name;
|
|
param.Value = (object)Value ?? DBNull.Value;
|
|
if (Length == -1 && Value != null && Value.Length <= 4000)
|
|
{
|
|
param.Size = 4000;
|
|
}
|
|
else
|
|
{
|
|
param.Size = Length;
|
|
}
|
|
param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String);
|
|
command.Parameters.Add(param);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles variances in features per DBMS
|
|
/// </summary>
|
|
partial class FeatureSupport
|
|
{
|
|
/// <summary>
|
|
/// Dictionary of supported features index by connection type name
|
|
/// </summary>
|
|
private static readonly Dictionary<string, FeatureSupport> FeatureList = new Dictionary<string, FeatureSupport>(StringComparer.InvariantCultureIgnoreCase) {
|
|
{"sqlserverconnection", new FeatureSupport { Arrays = false}},
|
|
{"npgsqlconnection", new FeatureSupport {Arrays = true}}
|
|
};
|
|
|
|
/// <summary>
|
|
/// Gets the featureset based on the passed connection
|
|
/// </summary>
|
|
public static FeatureSupport Get(IDbConnection connection)
|
|
{
|
|
string name = connection.GetType().Name;
|
|
FeatureSupport features;
|
|
return FeatureList.TryGetValue(name, out features) ? features : FeatureList.Values.First();
|
|
}
|
|
|
|
/// <summary>
|
|
/// True if the db supports array columns e.g. Postgresql
|
|
/// </summary>
|
|
public bool Arrays { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents simple memeber map for one of target parameter or property or field to source DataReader column
|
|
/// </summary>
|
|
sealed partial class SimpleMemberMap : SqlMapper.IMemberMap
|
|
{
|
|
private readonly string _columnName;
|
|
private readonly PropertyInfo _property;
|
|
private readonly FieldInfo _field;
|
|
private readonly ParameterInfo _parameter;
|
|
|
|
/// <summary>
|
|
/// Creates instance for simple property mapping
|
|
/// </summary>
|
|
/// <param name="columnName">DataReader column name</param>
|
|
/// <param name="property">Target property</param>
|
|
public SimpleMemberMap(string columnName, PropertyInfo property)
|
|
{
|
|
if (columnName == null)
|
|
throw new ArgumentNullException("columnName");
|
|
|
|
if (property == null)
|
|
throw new ArgumentNullException("property");
|
|
|
|
_columnName = columnName;
|
|
_property = property;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates instance for simple field mapping
|
|
/// </summary>
|
|
/// <param name="columnName">DataReader column name</param>
|
|
/// <param name="field">Target property</param>
|
|
public SimpleMemberMap(string columnName, FieldInfo field)
|
|
{
|
|
if (columnName == null)
|
|
throw new ArgumentNullException("columnName");
|
|
|
|
if (field == null)
|
|
throw new ArgumentNullException("field");
|
|
|
|
_columnName = columnName;
|
|
_field = field;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates instance for simple constructor parameter mapping
|
|
/// </summary>
|
|
/// <param name="columnName">DataReader column name</param>
|
|
/// <param name="parameter">Target constructor parameter</param>
|
|
public SimpleMemberMap(string columnName, ParameterInfo parameter)
|
|
{
|
|
if (columnName == null)
|
|
throw new ArgumentNullException("columnName");
|
|
|
|
if (parameter == null)
|
|
throw new ArgumentNullException("parameter");
|
|
|
|
_columnName = columnName;
|
|
_parameter = parameter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// DataReader column name
|
|
/// </summary>
|
|
public string ColumnName
|
|
{
|
|
get { return _columnName; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Target member type
|
|
/// </summary>
|
|
public Type MemberType
|
|
{
|
|
get
|
|
{
|
|
if (_field != null)
|
|
return _field.FieldType;
|
|
|
|
if (_property != null)
|
|
return _property.PropertyType;
|
|
|
|
if (_parameter != null)
|
|
return _parameter.ParameterType;
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Target property
|
|
/// </summary>
|
|
public PropertyInfo Property
|
|
{
|
|
get { return _property; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Target field
|
|
/// </summary>
|
|
public FieldInfo Field
|
|
{
|
|
get { return _field; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Target constructor parameter
|
|
/// </summary>
|
|
public ParameterInfo Parameter
|
|
{
|
|
get { return _parameter; }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents default type mapping strategy used by Dapper
|
|
/// </summary>
|
|
sealed partial class DefaultTypeMap : SqlMapper.ITypeMap
|
|
{
|
|
private readonly List<FieldInfo> _fields;
|
|
private readonly List<PropertyInfo> _properties;
|
|
private readonly Type _type;
|
|
|
|
/// <summary>
|
|
/// Creates default type map
|
|
/// </summary>
|
|
/// <param name="type">Entity type</param>
|
|
public DefaultTypeMap(Type type)
|
|
{
|
|
if (type == null)
|
|
throw new ArgumentNullException("type");
|
|
|
|
_fields = GetSettableFields(type);
|
|
_properties = GetSettableProps(type);
|
|
_type = type;
|
|
}
|
|
|
|
internal static MethodInfo GetPropertySetter(PropertyInfo propertyInfo, Type type)
|
|
{
|
|
return propertyInfo.DeclaringType == type ?
|
|
propertyInfo.GetSetMethod(true) :
|
|
propertyInfo.DeclaringType.GetProperty(propertyInfo.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetSetMethod(true);
|
|
}
|
|
|
|
internal static List<PropertyInfo> GetSettableProps(Type t)
|
|
{
|
|
return t
|
|
.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
|
.Where(p => GetPropertySetter(p, t) != null)
|
|
.ToList();
|
|
}
|
|
|
|
internal static List<FieldInfo> GetSettableFields(Type t)
|
|
{
|
|
return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds best constructor
|
|
/// </summary>
|
|
/// <param name="names">DataReader column names</param>
|
|
/// <param name="types">DataReader column types</param>
|
|
/// <returns>Matching constructor or default one</returns>
|
|
public ConstructorInfo FindConstructor(string[] names, Type[] types)
|
|
{
|
|
var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
foreach (ConstructorInfo ctor in constructors.OrderBy(c => c.IsPublic ? 0 : (c.IsPrivate ? 2 : 1)).ThenBy(c => c.GetParameters().Length))
|
|
{
|
|
ParameterInfo[] ctorParameters = ctor.GetParameters();
|
|
if (ctorParameters.Length == 0)
|
|
return ctor;
|
|
|
|
if (ctorParameters.Length != types.Length)
|
|
continue;
|
|
|
|
int i = 0;
|
|
for (; i < ctorParameters.Length; i++)
|
|
{
|
|
if (!String.Equals(ctorParameters[i].Name, names[i], StringComparison.OrdinalIgnoreCase))
|
|
break;
|
|
if (types[i] == typeof(byte[]) && ctorParameters[i].ParameterType.FullName == SqlMapper.LinqBinary)
|
|
continue;
|
|
var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType;
|
|
if (unboxedType != types[i]
|
|
&& !(unboxedType.IsEnum && Enum.GetUnderlyingType(unboxedType) == types[i])
|
|
&& !(unboxedType == typeof(char) && types[i] == typeof(string)))
|
|
break;
|
|
}
|
|
|
|
if (i == ctorParameters.Length)
|
|
return ctor;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets mapping for constructor parameter
|
|
/// </summary>
|
|
/// <param name="constructor">Constructor to resolve</param>
|
|
/// <param name="columnName">DataReader column name</param>
|
|
/// <returns>Mapping implementation</returns>
|
|
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
|
|
{
|
|
var parameters = constructor.GetParameters();
|
|
|
|
return new SimpleMemberMap(columnName, parameters.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets member mapping for column
|
|
/// </summary>
|
|
/// <param name="columnName">DataReader column name</param>
|
|
/// <returns>Mapping implementation</returns>
|
|
public SqlMapper.IMemberMap GetMember(string columnName)
|
|
{
|
|
var property = _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
|
|
?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (property != null)
|
|
return new SimpleMemberMap(columnName, property);
|
|
|
|
var field = _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
|
|
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (field != null)
|
|
return new SimpleMemberMap(columnName, field);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implements custom property mapping by user provided criteria (usually presence of some custom attribute with column to member mapping)
|
|
/// </summary>
|
|
sealed partial class CustomPropertyTypeMap : SqlMapper.ITypeMap
|
|
{
|
|
private readonly Type _type;
|
|
private readonly Func<Type, string, PropertyInfo> _propertySelector;
|
|
|
|
/// <summary>
|
|
/// Creates custom property mapping
|
|
/// </summary>
|
|
/// <param name="type">Target entity type</param>
|
|
/// <param name="propertySelector">Property selector based on target type and DataReader column name</param>
|
|
public CustomPropertyTypeMap(Type type, Func<Type, string, PropertyInfo> propertySelector)
|
|
{
|
|
if (type == null)
|
|
throw new ArgumentNullException("type");
|
|
|
|
if (propertySelector == null)
|
|
throw new ArgumentNullException("propertySelector");
|
|
|
|
_type = type;
|
|
_propertySelector = propertySelector;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Always returns default constructor
|
|
/// </summary>
|
|
/// <param name="names">DataReader column names</param>
|
|
/// <param name="types">DataReader column types</param>
|
|
/// <returns>Default constructor</returns>
|
|
public ConstructorInfo FindConstructor(string[] names, Type[] types)
|
|
{
|
|
return _type.GetConstructor(new Type[0]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Not impelmeneted as far as default constructor used for all cases
|
|
/// </summary>
|
|
/// <param name="constructor"></param>
|
|
/// <param name="columnName"></param>
|
|
/// <returns></returns>
|
|
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns property based on selector strategy
|
|
/// </summary>
|
|
/// <param name="columnName">DataReader column name</param>
|
|
/// <returns>Poperty member map</returns>
|
|
public SqlMapper.IMemberMap GetMember(string columnName)
|
|
{
|
|
var prop = _propertySelector(_type, columnName);
|
|
return prop != null ? new SimpleMemberMap(columnName, prop) : null;
|
|
}
|
|
}
|
|
|
|
// Define DAPPER_MAKE_PRIVATE if you reference Dapper by source
|
|
// and you like to make the Dapper types private (in order to avoid
|
|
// conflicts with other projects that also reference Dapper by source)
|
|
#if !DAPPER_MAKE_PRIVATE
|
|
|
|
public partial class SqlMapper
|
|
{
|
|
}
|
|
|
|
public partial class DynamicParameters
|
|
{
|
|
|
|
}
|
|
|
|
public partial class DbString
|
|
{
|
|
|
|
}
|
|
|
|
public partial class SimpleMemberMap
|
|
{
|
|
|
|
}
|
|
|
|
public partial class DefaultTypeMap
|
|
{
|
|
|
|
}
|
|
|
|
public partial class CustomPropertyTypeMap
|
|
{
|
|
|
|
}
|
|
|
|
public partial class FeatureSupport
|
|
{
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|