/*
License: http://www.apache.org/licenses/LICENSE-2.0
Home page: https://github.com/StackExchange/dapper-dot-net
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
#if NETCOREAPP
using DataException = System.InvalidOperationException;
#endif
namespace Dapper
{
///
/// Dapper, a light weight object mapper for ADO.NET
///
public static partial class SqlMapper
{
static int GetColumnHash(IDataReader reader, int startBound = 0, int length = -1)
{
unchecked
{
int max = length < 0 ? reader.FieldCount : startBound + length;
int hash = (-37 * startBound) + max;
for (int i = startBound; i < max; i++)
{
object tmp = reader.GetName(i);
hash = -79 * ((hash * 31) + (tmp?.GetHashCode() ?? 0)) + (reader.GetFieldType(i)?.GetHashCode() ?? 0);
}
return hash;
}
}
///
/// Called if the query cache is purged via PurgeQueryCache
///
public static event EventHandler QueryCachePurged;
private static void OnQueryCachePurged()
{
var handler = QueryCachePurged;
handler?.Invoke(null, EventArgs.Empty);
}
static readonly System.Collections.Concurrent.ConcurrentDictionary _queryCache = new System.Collections.Concurrent.ConcurrentDictionary();
private static void SetQueryCache(Identity key, CacheInfo value)
{
if (Interlocked.Increment(ref collect) == COLLECT_PER_ITEMS)
{
CollectCacheGarbage();
}
_queryCache[key] = value;
}
private static void CollectCacheGarbage()
{
try
{
foreach (var pair in _queryCache)
{
if (pair.Value.GetHitCount() <= COLLECT_HIT_COUNT_MIN)
{
CacheInfo cache;
_queryCache.TryRemove(pair.Key, out cache);
}
}
}
finally
{
Interlocked.Exchange(ref collect, 0);
}
}
private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0;
private static int collect;
private static bool TryGetQueryCache(Identity key, out CacheInfo value)
{
if (_queryCache.TryGetValue(key, out value))
{
value.RecordHit();
return true;
}
value = null;
return false;
}
///
/// Purge the query cache
///
public static void PurgeQueryCache()
{
_queryCache.Clear();
TypeDeserializerCache.Purge();
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);
}
TypeDeserializerCache.Purge(type);
}
///
/// Return a count of all the cached queries by dapper
///
///
public static int GetCachedSQLCount()
{
return _queryCache.Count;
}
///
/// Return a list of all the queries cached by dapper
///
///
///
public static IEnumerable> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue)
{
var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount()));
if (ignoreHitCountAbove < int.MaxValue) data = data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove);
return data;
}
///
/// Deep diagnostics only: find any hash collisions in the cache
///
///
public static IEnumerable> GetHashCollissions()
{
var counts = new Dictionary();
foreach (var key in _queryCache.Keys)
{
int count;
if (!counts.TryGetValue(key.hashCode, out count))
{
counts.Add(key.hashCode, 1);
}
else
{
counts[key.hashCode] = count + 1;
}
}
return from pair in counts
where pair.Value > 1
select Tuple.Create(pair.Key, pair.Value);
}
static Dictionary typeMap;
static SqlMapper()
{
typeMap = new Dictionary
{
[typeof(byte)] = DbType.Byte,
[typeof(sbyte)] = DbType.SByte,
[typeof(short)] = DbType.Int16,
[typeof(ushort)] = DbType.UInt16,
[typeof(int)] = DbType.Int32,
[typeof(uint)] = DbType.UInt32,
[typeof(long)] = DbType.Int64,
[typeof(ulong)] = DbType.UInt64,
[typeof(float)] = DbType.Single,
[typeof(double)] = DbType.Double,
[typeof(decimal)] = DbType.Decimal,
[typeof(bool)] = DbType.Boolean,
[typeof(string)] = DbType.String,
[typeof(char)] = DbType.StringFixedLength,
[typeof(Guid)] = DbType.Guid,
[typeof(DateTime)] = DbType.DateTime,
[typeof(DateTimeOffset)] = DbType.DateTimeOffset,
[typeof(TimeSpan)] = DbType.Time,
[typeof(byte[])] = DbType.Binary,
[typeof(byte?)] = DbType.Byte,
[typeof(sbyte?)] = DbType.SByte,
[typeof(short?)] = DbType.Int16,
[typeof(ushort?)] = DbType.UInt16,
[typeof(int?)] = DbType.Int32,
[typeof(uint?)] = DbType.UInt32,
[typeof(long?)] = DbType.Int64,
[typeof(ulong?)] = DbType.UInt64,
[typeof(float?)] = DbType.Single,
[typeof(double?)] = DbType.Double,
[typeof(decimal?)] = DbType.Decimal,
[typeof(bool?)] = DbType.Boolean,
[typeof(char?)] = DbType.StringFixedLength,
[typeof(Guid?)] = DbType.Guid,
[typeof(DateTime?)] = DbType.DateTime,
[typeof(DateTimeOffset?)] = DbType.DateTimeOffset,
[typeof(TimeSpan?)] = DbType.Time,
[typeof(object)] = DbType.Object
};
ResetTypeHandlers(false);
}
///
/// Clear the registered type handlers
///
public static void ResetTypeHandlers()
{
ResetTypeHandlers(true);
}
private static void ResetTypeHandlers(bool clone)
{
typeHandlers = new Dictionary();
#if !NETCOREAPP
AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), clone);
try // see https://github.com/StackExchange/dapper-dot-net/issues/424
{
AddSqlDataRecordsTypeHandler(clone);
}
catch { }
#endif
AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone);
AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone);
AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone);
allowedCommandBehaviors = DefaultAllowedCommandBehaviors;
}
#if !NETCOREAPP
[MethodImpl(MethodImplOptions.NoInlining)]
private static void AddSqlDataRecordsTypeHandler(bool clone)
{
AddTypeHandlerImpl(typeof(IEnumerable), new SqlDataRecordHandler(), clone);
}
#endif
///
/// Configure the specified type to be mapped to a given db-type
///
public static void AddTypeMap(Type type, DbType dbType)
{
// use clone, mutate, replace to avoid threading issues
var snapshot = typeMap;
DbType oldValue;
if (snapshot.TryGetValue(type, out oldValue) && oldValue == dbType) return; // nothing to do
var newCopy = new Dictionary(snapshot) { [type] = dbType };
typeMap = newCopy;
}
///
/// Configure the specified type to be processed by a custom handler
///
public static void AddTypeHandler(Type type, ITypeHandler handler)
{
AddTypeHandlerImpl(type, handler, true);
}
internal static bool HasTypeHandler(Type type)
{
return typeHandlers.ContainsKey(type);
}
///
/// Configure the specified type to be processed by a custom handler
///
public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clone)
{
if (type == null) throw new ArgumentNullException(nameof(type));
Type secondary = null;
if(type.IsValueType())
{
var underlying = Nullable.GetUnderlyingType(type);
if(underlying == null)
{
secondary = typeof(Nullable<>).MakeGenericType(type); // the Nullable
// type is already the T
}
else
{
secondary = type; // the Nullable
type = underlying; // the T
}
}
var snapshot = typeHandlers;
ITypeHandler oldValue;
if (snapshot.TryGetValue(type, out oldValue) && handler == oldValue) return; // nothing to do
var newCopy = clone ? new Dictionary(snapshot) : snapshot;
#pragma warning disable 618
typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { handler });
if(secondary != null)
{
typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { handler });
}
#pragma warning restore 618
if (handler == null)
{
newCopy.Remove(type);
if (secondary != null) newCopy.Remove(secondary);
}
else
{
newCopy[type] = handler;
if(secondary != null) newCopy[secondary] = handler;
}
typeHandlers = newCopy;
}
///
/// Configure the specified type to be processed by a custom handler
///
public static void AddTypeHandler(TypeHandler handler)
{
AddTypeHandlerImpl(typeof(T), handler, true);
}
private static Dictionary typeHandlers;
internal const string LinqBinary = "System.Data.Linq.Binary";
private const string ObsoleteInternalUsageOnly = "This method is for internal use only";
///
/// Get the DbType that maps to a given value
///
[Obsolete(ObsoleteInternalUsageOnly, false)]
#if !NETCOREAPP
[Browsable(false)]
#endif
[EditorBrowsable(EditorBrowsableState.Never)]
public static DbType GetDbType(object value)
{
if (value == null || value is DBNull) return DbType.Object;
ITypeHandler handler;
return LookupDbType(value.GetType(), "n/a", false, out handler);
}
///
/// OBSOLETE: For internal usage only. Lookup the DbType and handler for a given Type and member
///
[Obsolete(ObsoleteInternalUsageOnly, false)]
#if !NETCOREAPP
[Browsable(false)]
#endif
[EditorBrowsable(EditorBrowsableState.Never)]
public static DbType LookupDbType(Type type, string name, bool demand, out ITypeHandler handler)
{
DbType dbType;
handler = null;
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 (typeHandlers.TryGetValue(type, out handler))
{
return DbType.Object;
}
if (typeof(IEnumerable).IsAssignableFrom(type))
{
return DynamicParameters.EnumerableMultiParameter;
}
#if !NETCOREAPP
switch (type.FullName)
{
case "Microsoft.SqlServer.Types.SqlGeography":
AddTypeHandler(type, handler = new UdtTypeHandler("geography"));
return DbType.Object;
case "Microsoft.SqlServer.Types.SqlGeometry":
AddTypeHandler(type, handler = new UdtTypeHandler("geometry"));
return DbType.Object;
case "Microsoft.SqlServer.Types.SqlHierarchyId":
AddTypeHandler(type, handler = new UdtTypeHandler("hierarchyid"));
return DbType.Object;
}
#endif
if(demand)
throw new NotSupportedException($"The member {name} of type {type.FullName} cannot be used as a parameter value");
return DbType.Object;
}
///
/// Obtains the data as a list; if it is *already* a list, the original object is returned without
/// any duplication; otherwise, ToList() is invoked.
///
public static List AsList(this IEnumerable source)
{
return (source == null || source is List) ? (List)source : source.ToList();
}
///
/// Execute parameterized SQL
///
/// Number of rows affected
public static int Execute(
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteImpl(cnn, ref command);
}
///
/// Execute parameterized SQL
///
/// Number of rows affected
public static int Execute(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteImpl(cnn, ref command);
}
///
/// Execute parameterized SQL that selects a single value
///
/// The first cell selected
public static object ExecuteScalar(
this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
)
{
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
return ExecuteScalarImpl