This commit is contained in:
ky_sunl
2021-03-16 05:49:42 +00:00
parent 67839640ff
commit 1688c9f8d1
93 changed files with 11945 additions and 166 deletions

View File

@@ -0,0 +1,187 @@
using System;
using System.Data;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
namespace Dapper
{
/// <summary>
/// Represents the key aspects of a sql operation
/// </summary>
public struct CommandDefinition
{
internal static CommandDefinition ForCallback(object parameters)
{
if (parameters is DynamicParameters)
{
return new CommandDefinition(parameters);
}
else
{
return default(CommandDefinition);
}
}
internal void OnCompleted()
{
(Parameters as SqlMapper.IParameterCallbacks)?.OnCompleted();
}
/// <summary>
/// The command (sql or a stored-procedure name) to execute
/// </summary>
public string CommandText { get; }
/// <summary>
/// The parameters associated with the command
/// </summary>
public object Parameters { get; }
/// <summary>
/// The active transaction for the command
/// </summary>
public IDbTransaction Transaction { get; }
/// <summary>
/// The effective timeout for the command
/// </summary>
public int? CommandTimeout { get; }
/// <summary>
/// The type of command that the command-text represents
/// </summary>
public CommandType? CommandType { get; }
/// <summary>
/// Should data be buffered before returning?
/// </summary>
public bool Buffered => (Flags & CommandFlags.Buffered) != 0;
/// <summary>
/// Should the plan for this query be cached?
/// </summary>
internal bool AddToCache => (Flags & CommandFlags.NoCache) == 0;
/// <summary>
/// Additional state flags against this command
/// </summary>
public CommandFlags Flags { get; }
/// <summary>
/// Can async queries be pipelined?
/// </summary>
public bool Pipelined => (Flags & CommandFlags.Pipelined) != 0;
/// <summary>
/// Initialize the command definition
/// </summary>
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
)
{
CommandText = commandText;
Parameters = parameters;
Transaction = transaction;
CommandTimeout = commandTimeout;
CommandType = commandType;
Flags = flags;
#if ASYNC
CancellationToken = cancellationToken;
#endif
}
private CommandDefinition(object parameters) : this()
{
Parameters = parameters;
}
#if ASYNC
/// <summary>
/// For asynchronous operations, the cancellation-token
/// </summary>
public CancellationToken CancellationToken { get; }
#endif
internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object> paramReader)
{
var cmd = cnn.CreateCommand();
var init = GetInit(cmd.GetType());
init?.Invoke(cmd);
if (Transaction != null)
cmd.Transaction = Transaction;
cmd.CommandText = CommandText;
if (CommandTimeout.HasValue)
{
cmd.CommandTimeout = CommandTimeout.Value;
}
else if (SqlMapper.Settings.CommandTimeout.HasValue)
{
cmd.CommandTimeout = SqlMapper.Settings.CommandTimeout.Value;
}
if (CommandType.HasValue)
cmd.CommandType = CommandType.Value;
paramReader?.Invoke(cmd, Parameters);
return cmd;
}
private static SqlMapper.Link<Type, Action<IDbCommand>> commandInitCache;
private static Action<IDbCommand> GetInit(Type commandType)
{
if (commandType == null)
return null; // GIGO
Action<IDbCommand> action;
if (SqlMapper.Link<Type, Action<IDbCommand>>.TryGet(commandInitCache, commandType, out action))
{
return action;
}
var bindByName = GetBasicPropertySetter(commandType, "BindByName", typeof(bool));
var initialLongFetchSize = GetBasicPropertySetter(commandType, "InitialLONGFetchSize", typeof(int));
action = null;
if (bindByName != null || initialLongFetchSize != null)
{
var method = new DynamicMethod(commandType.Name + "_init", null, new Type[] { typeof(IDbCommand) });
var il = method.GetILGenerator();
if (bindByName != null)
{
// .BindByName = true
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldc_I4_1);
il.EmitCall(OpCodes.Callvirt, bindByName, null);
}
if (initialLongFetchSize != null)
{
// .InitialLONGFetchSize = -1
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldc_I4_M1);
il.EmitCall(OpCodes.Callvirt, initialLongFetchSize, null);
}
il.Emit(OpCodes.Ret);
action = (Action<IDbCommand>)method.CreateDelegate(typeof(Action<IDbCommand>));
}
// cache it
SqlMapper.Link<Type, Action<IDbCommand>>.TryAdd(ref commandInitCache, commandType, ref action);
return action;
}
private static MethodInfo GetBasicPropertySetter(Type declaringType, string name, Type expectedType)
{
var prop = declaringType.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
if (prop != null && prop.CanWrite && prop.PropertyType == expectedType && prop.GetIndexParameters().Length == 0)
{
return prop.GetSetMethod();
}
return null;
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
namespace Dapper
{
/// <summary>
/// Additional state flags that control command behaviour
/// </summary>
[Flags]
public enum CommandFlags
{
/// <summary>
/// No additional flags
/// </summary>
None = 0,
/// <summary>
/// Should data be buffered before returning?
/// </summary>
Buffered = 1,
/// <summary>
/// Can async queries be pipelined?
/// </summary>
Pipelined = 2,
/// <summary>
/// Should the plan cache be bypassed?
/// </summary>
NoCache = 4,
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Reflection;
namespace Dapper
{
/// <summary>
/// Implements custom property mapping by user provided criteria (usually presence of some custom attribute with column to member mapping)
/// </summary>
public sealed 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(nameof(type));
if (propertySelector == null)
throw new ArgumentNullException(nameof(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>
/// Always returns null
/// </summary>
/// <returns></returns>
public ConstructorInfo FindExplicitConstructor()
{
return null;
}
/// <summary>
/// Not implemented 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;
}
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Data;
#if !COREFX
namespace Dapper
{
sealed class DataTableHandler : SqlMapper.ITypeHandler
{
public object Parse(Type destinationType, object value)
{
throw new NotImplementedException();
}
public void SetValue(IDbDataParameter parameter, object value)
{
TableValuedParameter.Set(parameter, value as DataTable, null);
}
}
}
#endif

View File

@@ -0,0 +1,75 @@
using System;
using System.Data;
namespace Dapper
{
/// <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>
public sealed class DbString : SqlMapper.ICustomQueryParameter
{
/// <summary>
/// Default value for IsAnsi.
/// </summary>
public static bool IsAnsiDefault { get; set; }
/// <summary>
/// A value to set the default value of strings
/// going through Dapper. Default is 4000, any value larger than this
/// field will not have the default value applied.
/// </summary>
public const int DefaultLength = 4000;
/// <summary>
/// Create a new DbString
/// </summary>
public DbString()
{
Length = -1;
IsAnsi = IsAnsiDefault;
}
/// <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;
#pragma warning disable 0618
param.Value = SqlMapper.SanitizeParameterValue(Value);
#pragma warning restore 0618
if (Length == -1 && Value != null && Value.Length <= DefaultLength)
{
param.Size = DefaultLength;
}
else
{
param.Size = Length;
}
param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String);
command.Parameters.Add(param);
}
}
}

View File

@@ -0,0 +1,203 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Dapper
{
/// <summary>
/// Represents default type mapping strategy used by Dapper
/// </summary>
public sealed class DefaultTypeMap : SqlMapper.ITypeMap
{
private readonly List<FieldInfo> _fields;
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(nameof(type));
_fields = GetSettableFields(type);
Properties = GetSettableProps(type);
_type = type;
}
#if COREFX
static bool IsParameterMatch(ParameterInfo[] x, ParameterInfo[] y)
{
if (ReferenceEquals(x, y)) return true;
if (x == null || y == null) return false;
if (x.Length != y.Length) return false;
for (int i = 0; i < x.Length; i++)
if (x[i].ParameterType != y[i].ParameterType) return false;
return true;
}
#endif
internal static MethodInfo GetPropertySetter(PropertyInfo propertyInfo, Type type)
{
if (propertyInfo.DeclaringType == type) return propertyInfo.GetSetMethod(true);
#if COREFX
return propertyInfo.DeclaringType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Single(x => x.Name == propertyInfo.Name
&& x.PropertyType == propertyInfo.PropertyType
&& IsParameterMatch(x.GetIndexParameters(), propertyInfo.GetIndexParameters())
).GetSetMethod(true);
#else
return propertyInfo.DeclaringType.GetProperty(
propertyInfo.Name,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
Type.DefaultBinder,
propertyInfo.PropertyType,
propertyInfo.GetIndexParameters().Select(p => p.ParameterType).ToArray(),
null).GetSetMethod(true);
#endif
}
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] && !SqlMapper.HasTypeHandler(unboxedType))
&& !(unboxedType.IsEnum() && Enum.GetUnderlyingType(unboxedType) == types[i])
&& !(unboxedType == typeof(char) && types[i] == typeof(string))
&& !(unboxedType.IsEnum() && types[i] == typeof(string)))
{
break;
}
}
if (i == ctorParameters.Length)
return ctor;
}
return null;
}
/// <summary>
/// Returns the constructor, if any, that has the ExplicitConstructorAttribute on it.
/// </summary>
public ConstructorInfo FindExplicitConstructor()
{
var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
#if COREFX
var withAttr = constructors.Where(c => c.CustomAttributes.Any(x => x.AttributeType == typeof(ExplicitConstructorAttribute))).ToList();
#else
var withAttr = constructors.Where(c => c.GetCustomAttributes(typeof(ExplicitConstructorAttribute), true).Length > 0).ToList();
#endif
if (withAttr.Count == 1)
{
return withAttr[0];
}
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 && MatchNamesWithUnderscores)
{
property = Properties.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.Ordinal))
?? Properties.FirstOrDefault(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.OrdinalIgnoreCase));
}
if (property != null)
return new SimpleMemberMap(columnName, property);
// roslyn automatically implemented properties, in particular for get-only properties: <{Name}>k__BackingField;
var backingFieldName = "<" + columnName + ">k__BackingField";
// preference order is:
// exact match over underscre match, exact case over wrong case, backing fields over regular fields, match-inc-underscores over match-exc-underscores
var field = _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase));
if (field == null && MatchNamesWithUnderscores)
{
var effectiveColumnName = columnName.Replace("_", "");
backingFieldName = "<" +effectiveColumnName + ">k__BackingField";
field = _fields.FirstOrDefault(p => string.Equals(p.Name, effectiveColumnName, StringComparison.Ordinal))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, effectiveColumnName, StringComparison.OrdinalIgnoreCase))
?? _fields.FirstOrDefault(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase));
}
if (field != null)
return new SimpleMemberMap(columnName, field);
return null;
}
/// <summary>
/// Should column names like User_Id be allowed to match properties/fields like UserId ?
/// </summary>
public static bool MatchNamesWithUnderscores { get; set; }
/// <summary>
/// The settable properties for this typemap
/// </summary>
public List<PropertyInfo> Properties { get; }
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections;
namespace Dapper
{
partial class DynamicParameters
{
// The type here is used to differentiate the cache by type via generics
// ReSharper disable once UnusedTypeParameter
internal static class CachedOutputSetters<T>
{
// Intentional, abusing generics to get our cache splits
// ReSharper disable once StaticMemberInGenericType
public static readonly Hashtable Cache = new Hashtable();
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Data;
namespace Dapper
{
partial class DynamicParameters
{
sealed 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; }
internal Action<object, DynamicParameters> OutputCallback { get; set; }
internal object OutputTarget { get; set; }
internal bool CameFromTemplate { get; set; }
public byte? Precision { get; set; }
public byte? Scale { get; set; }
}
}
}

View File

@@ -0,0 +1,505 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
#if COREFX
using ApplicationException = System.InvalidOperationException;
#endif
namespace Dapper
{
/// <summary>
/// A bag of parameters that can be passed to the Dapper Query and Execute methods
/// </summary>
public partial class DynamicParameters : SqlMapper.IDynamicParameters, SqlMapper.IParameterLookup, SqlMapper.IParameterCallbacks
{
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;
}
}
/// <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(object param)
{
var obj = param;
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)
{
Add(kvp.Key, kvp.Value, null, null, null);
}
}
}
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>
public void Add(string name, object value, DbType? dbType, ParameterDirection? direction, int? size)
{
parameters[Clean(name)] = new ParamInfo
{
Name = name,
Value = value,
ParameterDirection = direction ?? ParameterDirection.Input,
DbType = dbType,
Size = size
};
}
/// <summary>
/// Add a parameter to this dynamic parameter list
/// </summary>
public void Add(
string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null
)
{
parameters[Clean(name)] = new ParamInfo
{
Name = name,
Value = value,
ParameterDirection = direction ?? ParameterDirection.Input,
DbType = dbType,
Size = size,
Precision = precision,
Scale = scale
};
}
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);
}
// The parameters were added to the command, but not the
// DynamicParameters until now.
foreach (IDbDataParameter param in command.Parameters)
{
// If someone makes a DynamicParameters with a template,
// then explicitly adds a parameter of a matching name,
// it will already exist in 'parameters'.
if (!parameters.ContainsKey(param.ParameterName))
{
parameters.Add(param.ParameterName, new ParamInfo
{
AttachedParam = param,
CameFromTemplate = true,
DbType = param.DbType,
Name = param.ParameterName,
ParameterDirection = param.Direction,
Size = param.Size,
Value = param.Value
});
}
}
// Now that the parameters are added to the command, let's place our output callbacks
var tmp = outputCallbacks;
if (tmp != null)
{
foreach (var generator in tmp)
{
generator();
}
}
}
foreach (var param in parameters.Values)
{
if (param.CameFromTemplate) continue;
var dbType = param.DbType;
var val = param.Value;
string name = Clean(param.Name);
var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter;
SqlMapper.ITypeHandler handler = null;
if (dbType == null && val != null && !isCustomQueryParameter)
{
#pragma warning disable 618
dbType = SqlMapper.LookupDbType(val.GetType(), name, true, out handler);
#pragma warning disable 618
}
if (isCustomQueryParameter)
{
((SqlMapper.ICustomQueryParameter)val).AddParameter(command, name);
}
else if (dbType == EnumerableMultiParameter)
{
#pragma warning disable 612, 618
SqlMapper.PackListParameters(command, name, val);
#pragma warning restore 612, 618
}
else
{
bool add = !command.Parameters.Contains(name);
IDbDataParameter p;
if (add)
{
p = command.CreateParameter();
p.ParameterName = name;
}
else
{
p = (IDbDataParameter)command.Parameters[name];
}
p.Direction = param.ParameterDirection;
if (handler == null)
{
#pragma warning disable 0618
p.Value = SqlMapper.SanitizeParameterValue(val);
#pragma warning restore 0618
if (dbType != null && p.DbType != dbType)
{
p.DbType = dbType.Value;
}
var s = val as string;
if (s?.Length <= DbString.DefaultLength)
{
p.Size = DbString.DefaultLength;
}
if (param.Size != null) p.Size = param.Size.Value;
if (param.Precision != null) p.Precision = param.Precision.Value;
if (param.Scale != null) p.Scale = param.Scale.Value;
}
else
{
if (dbType != null) p.DbType = dbType.Value;
if (param.Size != null) p.Size = param.Size.Value;
if (param.Precision != null) p.Precision = param.Precision.Value;
if (param.Scale != null) p.Scale = param.Scale.Value;
handler.SetValue(p, val ?? DBNull.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 => 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 paramInfo = parameters[Clean(name)];
var attachedParam = paramInfo.AttachedParam;
object val = attachedParam == null ? paramInfo.Value : attachedParam.Value;
if (val == DBNull.Value)
{
if (default(T) != null)
{
throw new ApplicationException("Attempting to cast a DBNull to a non nullable type! Note that out/return parameters will not have updated values until the data stream completes (after the 'foreach' for Query(..., buffered: false), or after the GridReader has been disposed for QueryMultiple)");
}
return default(T);
}
return (T)val;
}
/// <summary>
/// Allows you to automatically populate a target property/field from output parameters. It actually
/// creates an InputOutput parameter, so you can still pass data in.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="target">The object whose property/field you wish to populate.</param>
/// <param name="expression">A MemberExpression targeting a property/field of the target (or descendant thereof.)</param>
/// <param name="dbType"></param>
/// <param name="size">The size to set on the parameter. Defaults to 0, or DbString.DefaultLength in case of strings.</param>
/// <returns>The DynamicParameters instance</returns>
public DynamicParameters Output<T>(T target, Expression<Func<T, object>> expression, DbType? dbType = null, int? size = null)
{
var failMessage = "Expression must be a property/field chain off of a(n) {0} instance";
failMessage = string.Format(failMessage, typeof(T).Name);
Action @throw = () => { throw new InvalidOperationException(failMessage); };
// Is it even a MemberExpression?
var lastMemberAccess = expression.Body as MemberExpression;
if (lastMemberAccess == null ||
(!(lastMemberAccess.Member is PropertyInfo) &&
!(lastMemberAccess.Member is FieldInfo)))
{
if (expression.Body.NodeType == ExpressionType.Convert &&
expression.Body.Type == typeof(object) &&
((UnaryExpression)expression.Body).Operand is MemberExpression)
{
// It's got to be unboxed
lastMemberAccess = (MemberExpression)((UnaryExpression)expression.Body).Operand;
}
else @throw();
}
// Does the chain consist of MemberExpressions leading to a ParameterExpression of type T?
MemberExpression diving = lastMemberAccess;
// Retain a list of member names and the member expressions so we can rebuild the chain.
List<string> names = new List<string>();
List<MemberExpression> chain = new List<MemberExpression>();
do
{
// Insert the names in the right order so expression
// "Post.Author.Name" becomes parameter "PostAuthorName"
names.Insert(0, diving?.Member.Name);
chain.Insert(0, diving);
var constant = diving?.Expression as ParameterExpression;
diving = diving?.Expression as MemberExpression;
if (constant != null &&
constant.Type == typeof(T))
{
break;
}
else if (diving == null ||
(!(diving.Member is PropertyInfo) &&
!(diving.Member is FieldInfo)))
{
@throw();
}
}
while (diving != null);
var dynamicParamName = string.Join(string.Empty, names.ToArray());
// Before we get all emitty...
var lookup = string.Join("|", names.ToArray());
var cache = CachedOutputSetters<T>.Cache;
var setter = (Action<object, DynamicParameters>)cache[lookup];
if (setter != null) goto MAKECALLBACK;
// Come on let's build a method, let's build it, let's build it now!
var dm = new DynamicMethod("ExpressionParam" + Guid.NewGuid().ToString(), null, new[] { typeof(object), GetType() }, true);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); // [object]
il.Emit(OpCodes.Castclass, typeof(T)); // [T]
// Count - 1 to skip the last member access
var i = 0;
for (; i < (chain.Count - 1); i++)
{
var member = chain[0].Member;
if (member is PropertyInfo)
{
var get = ((PropertyInfo)member).GetGetMethod(true);
il.Emit(OpCodes.Callvirt, get); // [Member{i}]
}
else // Else it must be a field!
{
il.Emit(OpCodes.Ldfld, ((FieldInfo)member)); // [Member{i}]
}
}
var paramGetter = GetType().GetMethod("Get", new Type[] { typeof(string) }).MakeGenericMethod(lastMemberAccess.Type);
il.Emit(OpCodes.Ldarg_1); // [target] [DynamicParameters]
il.Emit(OpCodes.Ldstr, dynamicParamName); // [target] [DynamicParameters] [ParamName]
il.Emit(OpCodes.Callvirt, paramGetter); // [target] [value], it's already typed thanks to generic method
// GET READY
var lastMember = lastMemberAccess.Member;
if (lastMember is PropertyInfo)
{
var set = ((PropertyInfo)lastMember).GetSetMethod(true);
il.Emit(OpCodes.Callvirt, set); // SET
}
else
{
il.Emit(OpCodes.Stfld, ((FieldInfo)lastMember)); // SET
}
il.Emit(OpCodes.Ret); // GO
setter = (Action<object, DynamicParameters>)dm.CreateDelegate(typeof(Action<object, DynamicParameters>));
lock (cache)
{
cache[lookup] = setter;
}
// Queue the preparation to be fired off when adding parameters to the DbCommand
MAKECALLBACK:
(outputCallbacks ?? (outputCallbacks = new List<Action>())).Add(() =>
{
// Finally, prep the parameter and attach the callback to it
ParamInfo parameter;
var targetMemberType = lastMemberAccess?.Type;
int sizeToSet = (!size.HasValue && targetMemberType == typeof(string)) ? DbString.DefaultLength : size ?? 0;
if (parameters.TryGetValue(dynamicParamName, out parameter))
{
parameter.ParameterDirection = parameter.AttachedParam.Direction = ParameterDirection.InputOutput;
if (parameter.AttachedParam.Size == 0)
{
parameter.Size = parameter.AttachedParam.Size = sizeToSet;
}
}
else
{
SqlMapper.ITypeHandler handler;
dbType = (!dbType.HasValue)
#pragma warning disable 618
? SqlMapper.LookupDbType(targetMemberType, targetMemberType?.Name, true, out handler)
#pragma warning restore 618
: dbType;
// CameFromTemplate property would not apply here because this new param
// Still needs to be added to the command
Add(dynamicParamName, expression.Compile().Invoke(target), null, ParameterDirection.InputOutput, sizeToSet);
}
parameter = parameters[dynamicParamName];
parameter.OutputCallback = setter;
parameter.OutputTarget = target;
});
return this;
}
private List<Action> outputCallbacks;
void SqlMapper.IParameterCallbacks.OnCompleted()
{
foreach (var param in (from p in parameters select p.Value))
{
param.OutputCallback?.Invoke(param.OutputTarget, this);
}
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
namespace Dapper
{
/// <summary>
/// Tell Dapper to use an explicit constructor, passing nulls or 0s for all parameters
/// </summary>
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false)]
public sealed class ExplicitConstructorAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Data;
namespace Dapper
{
/// <summary>
/// Handles variances in features per DBMS
/// </summary>
class FeatureSupport
{
private static readonly FeatureSupport
Default = new FeatureSupport(false),
Postgres = new FeatureSupport(true);
/// <summary>
/// Gets the feature set based on the passed connection
/// </summary>
public static FeatureSupport Get(IDbConnection connection)
{
string name = connection?.GetType().Name;
if (string.Equals(name, "npgsqlconnection", StringComparison.OrdinalIgnoreCase)) return Postgres;
return Default;
}
private FeatureSupport(bool arrays)
{
Arrays = arrays;
}
/// <summary>
/// True if the db supports array columns e.g. Postgresql
/// </summary>
public bool Arrays { get; }
}
}

View File

@@ -0,0 +1,87 @@
using System;
using System.Reflection;
namespace Dapper
{
/// <summary>
/// Represents simple member map for one of target parameter or property or field to source DataReader column
/// </summary>
sealed class SimpleMemberMap : SqlMapper.IMemberMap
{
/// <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(nameof(columnName));
if (property == null)
throw new ArgumentNullException(nameof(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(nameof(columnName));
if (field == null)
throw new ArgumentNullException(nameof(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(nameof(columnName));
if (parameter == null)
throw new ArgumentNullException(nameof(parameter));
ColumnName = columnName;
Parameter = parameter;
}
/// <summary>
/// DataReader column name
/// </summary>
public string ColumnName { get; }
/// <summary>
/// Target member type
/// </summary>
public Type MemberType => Field?.FieldType ?? Property?.PropertyType ?? Parameter?.ParameterType;
/// <summary>
/// Target property
/// </summary>
public PropertyInfo Property { get; }
/// <summary>
/// Target field
/// </summary>
public FieldInfo Field { get; }
/// <summary>
/// Target constructor parameter
/// </summary>
public ParameterInfo Parameter { get; }
}
}

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Data;
#if !COREFX
namespace Dapper
{
sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler
{
public object Parse(Type destinationType, object value)
{
throw new NotSupportedException();
}
public void SetValue(IDbDataParameter parameter, object value)
{
SqlDataRecordListTVPParameter.Set(parameter, value as IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord>, null);
}
}
}
#endif

View File

@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
#if !COREFX
namespace Dapper
{
/// <summary>
/// Used to pass a IEnumerable&lt;SqlDataRecord&gt; as a SqlDataRecordListTVPParameter
/// </summary>
sealed class SqlDataRecordListTVPParameter : SqlMapper.ICustomQueryParameter
{
private readonly IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord> data;
private readonly string typeName;
/// <summary>
/// Create a new instance of SqlDataRecordListTVPParameter
/// </summary>
public SqlDataRecordListTVPParameter(IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord> data, string typeName)
{
this.data = data;
this.typeName = typeName;
}
static readonly Action<System.Data.SqlClient.SqlParameter, string> setTypeName;
static SqlDataRecordListTVPParameter()
{
var prop = typeof(System.Data.SqlClient.SqlParameter).GetProperty(nameof(System.Data.SqlClient.SqlParameter.TypeName), BindingFlags.Instance | BindingFlags.Public);
if (prop != null && prop.PropertyType == typeof(string) && prop.CanWrite)
{
setTypeName = (Action<System.Data.SqlClient.SqlParameter, string>)
Delegate.CreateDelegate(typeof(Action<System.Data.SqlClient.SqlParameter, string>), prop.GetSetMethod());
}
}
void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name)
{
var param = command.CreateParameter();
param.ParameterName = name;
Set(param, data, typeName);
command.Parameters.Add(param);
}
internal static void Set(IDbDataParameter parameter, IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord> data, string typeName)
{
parameter.Value = (object)data ?? DBNull.Value;
var sqlParam = parameter as System.Data.SqlClient.SqlParameter;
if (sqlParam != null)
{
sqlParam.SqlDbType = SqlDbType.Structured;
sqlParam.TypeName = typeName;
}
}
}
}
#endif

View File

@@ -0,0 +1,907 @@
#if ASYNC
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Dapper
{
public static partial class SqlMapper
{
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static Task<IEnumerable<dynamic>> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryAsync<dynamic>(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static Task<IEnumerable<dynamic>> QueryAsync(this IDbConnection cnn, CommandDefinition command)
{
return QueryAsync<dynamic>(cnn, typeof(DapperRow), command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static Task<dynamic> QueryFirstAsync(this IDbConnection cnn, CommandDefinition command)
{
return QueryRowAsync<dynamic>(cnn, Row.First, typeof(DapperRow), command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static Task<dynamic> QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command)
{
return QueryRowAsync<dynamic>(cnn, Row.FirstOrDefault, typeof(DapperRow), command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static Task<dynamic> QuerySingleAsync(this IDbConnection cnn, CommandDefinition command)
{
return QueryRowAsync<dynamic>(cnn, Row.Single, typeof(DapperRow), command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static Task<dynamic> QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command)
{
return QueryRowAsync<dynamic>(cnn, Row.SingleOrDefault, typeof(DapperRow), command);
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryAsync<T>(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<T> QueryFirstAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryRowAsync<T>(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<T> QueryFirstOrDefaultAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryRowAsync<T>(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<T> QuerySingleAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryRowAsync<T>(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<T> QuerySingleOrDefaultAsync<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return QueryRowAsync<T>(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<IEnumerable<object>> QueryAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return QueryAsync<object>(cnn, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QueryFirstAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return QueryRowAsync<object>(cnn, Row.First, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return QueryRowAsync<object>(cnn, Row.FirstOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QuerySingleAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return QueryRowAsync<object>(cnn, Row.Single, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return QueryRowAsync<object>(cnn, Row.SingleOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken)));
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, CommandDefinition command)
{
return QueryAsync<T>(cnn, typeof(T), command);
}
/// <summary>
/// Execute a query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<IEnumerable<object>> QueryAsync(this IDbConnection cnn, Type type, CommandDefinition command)
{
return QueryAsync<object>(cnn, type, command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QueryFirstAsync(this IDbConnection cnn, Type type, CommandDefinition command)
{
return QueryRowAsync<object>(cnn, Row.First, type, command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command)
{
return QueryRowAsync<object>(cnn, Row.FirstOrDefault, type, command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QuerySingleAsync(this IDbConnection cnn, Type type, CommandDefinition command)
{
return QueryRowAsync<object>(cnn, Row.Single, type, command);
}
/// <summary>
/// Execute a single-row query asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<object> QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command)
{
return QueryRowAsync<object>(cnn, Row.SingleOrDefault, type, command);
}
private static Task<DbDataReader> ExecuteReaderWithFlagsFallbackAsync(DbCommand cmd, bool wasClosed, CommandBehavior behavior, CancellationToken cancellationToken)
{
var task = cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken);
if (task.Status == TaskStatus.Faulted && DisableCommandBehaviorOptimizations(behavior, task.Exception.InnerException))
{ // we can retry; this time it will have different flags
task = cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken);
}
return task;
}
private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null);
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
var cancel = command.CancellationToken;
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
{
DbDataReader reader = null;
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(cancel).ConfigureAwait(false);
reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false);
var tuple = info.Deserializer;
int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash)
{
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
if (command.AddToCache) SetQueryCache(identity, info);
}
var func = tuple.Func;
if (command.Buffered)
{
List<T> buffer = new List<T>();
var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
while (await reader.ReadAsync(cancel).ConfigureAwait(false))
{
object val = func(reader);
if (val == null || val is T)
{
buffer.Add((T) val);
}
else
{
buffer.Add((T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture));
}
}
while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { }
command.OnCompleted();
return buffer;
}
else
{
// can't use ReadAsync / cancellation; but this will have to do
wasClosed = false; // don't close if handing back an open reader; rely on the command-behavior
var deferred = ExecuteReaderSync<T>(reader, func, command.Parameters);
reader = null; // to prevent it being disposed before the caller gets to see it
return deferred;
}
}
finally
{
using (reader) { } // dispose if non-null
if (wasClosed) cnn.Close();
}
}
}
private static async Task<T> QueryRowAsync<T>(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null);
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
var cancel = command.CancellationToken;
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
{
DbDataReader reader = null;
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(cancel).ConfigureAwait(false);
reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, (row & Row.Single) != 0
? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition
: CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow, cancel).ConfigureAwait(false);
T result = default(T);
if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0)
{
var tuple = info.Deserializer;
int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash)
{
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false));
if (command.AddToCache) SetQueryCache(identity, info);
}
var func = tuple.Func;
object val = func(reader);
if (val == null || val is T)
{
result = (T)val;
}
else
{
var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
}
if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row);
while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { }
}
else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one
{
ThrowZeroRows(row);
}
while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { }
return result;
}
finally
{
using (reader) { } // dispose if non-null
if (wasClosed) cnn.Close();
}
}
}
/// <summary>
/// Execute a command asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<int> ExecuteAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return ExecuteAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken)));
}
/// <summary>
/// Execute a command asynchronously using .NET 4.5 Task.
/// </summary>
public static Task<int> ExecuteAsync(this IDbConnection cnn, CommandDefinition command)
{
object param = command.Parameters;
IEnumerable multiExec = GetMultiExec(param);
if (multiExec != null)
{
return ExecuteMultiImplAsync(cnn, command, multiExec);
}
else
{
return ExecuteImplAsync(cnn, command, param);
}
}
private struct AsyncExecState
{
public readonly DbCommand Command;
public readonly Task<int> Task;
public AsyncExecState(DbCommand command, Task<int> task)
{
Command = command;
Task = task;
}
}
private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandDefinition command, IEnumerable multiExec)
{
bool isFirst = true;
int total = 0;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
CacheInfo info = null;
string masterSql = null;
if ((command.Flags & CommandFlags.Pipelined) != 0)
{
const int MAX_PENDING = 100;
var pending = new Queue<AsyncExecState>(MAX_PENDING);
DbCommand cmd = null;
try
{
foreach (var obj in multiExec)
{
if (isFirst)
{
isFirst = false;
cmd = (DbCommand)command.SetupCommand(cnn, null);
masterSql = cmd.CommandText;
var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
info = GetCacheInfo(identity, obj, command.AddToCache);
} else if(pending.Count >= MAX_PENDING)
{
var recycled = pending.Dequeue();
total += await recycled.Task.ConfigureAwait(false);
cmd = recycled.Command;
cmd.CommandText = masterSql; // because we do magic replaces on "in" etc
cmd.Parameters.Clear(); // current code is Add-tastic
}
else
{
cmd = (DbCommand)command.SetupCommand(cnn, null);
}
info.ParamReader(cmd, obj);
var task = cmd.ExecuteNonQueryAsync(command.CancellationToken);
pending.Enqueue(new AsyncExecState(cmd, task));
cmd = null; // note the using in the finally: this avoids a double-dispose
}
while (pending.Count != 0)
{
var pair = pending.Dequeue();
using (pair.Command) { } // dispose commands
total += await pair.Task.ConfigureAwait(false);
}
} finally
{
// this only has interesting work to do if there are failures
using (cmd) { } // dispose commands
while (pending.Count != 0)
{ // dispose tasks even in failure
using (pending.Dequeue().Command) { } // dispose commands
}
}
}
else
{
using (var cmd = (DbCommand)command.SetupCommand(cnn, null))
{
foreach (var obj in multiExec)
{
if (isFirst)
{
masterSql = cmd.CommandText;
isFirst = false;
var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
info = GetCacheInfo(identity, obj, command.AddToCache);
}
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 += await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false);
}
}
}
command.OnCompleted();
}
finally
{
if (wasClosed) cnn.Close();
}
return total;
}
private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object param)
{
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType(), null);
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
{
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
var result = await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false);
command.OnCompleted();
return result;
}
finally
{
if (wasClosed) cnn.Close();
}
}
}
/// <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 Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return MultiMapAsync<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn,
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <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="splitOn">The field we should split and read the second object from (default: id)</param>
/// <param name="command">The command to execute</param>
/// <param name="map"></param>
/// <returns></returns>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TReturn> map, string splitOn = "Id")
{
return MultiMapAsync<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
}
/// <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 Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return MultiMapAsync<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn,
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <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="splitOn">The field we should split and read the second object from (default: id)</param>
/// <param name="command">The command to execute</param>
/// <param name="map"></param>
/// <returns></returns>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TThird, TReturn> map, string splitOn = "Id")
{
return MultiMapAsync<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
}
/// <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 Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn,
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <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="splitOn">The field we should split and read the second object from (default: id)</param>
/// <param name="command">The command to execute</param>
/// <param name="map"></param>
/// <returns></returns>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, string splitOn = "Id")
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
}
/// <summary>
/// Perform a multi mapping query with 5 input parameters
/// </summary>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn,
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <summary>
/// Perform a multi mapping query with 5 input parameters
/// </summary>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, string splitOn = "Id")
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn, command, map, splitOn);
}
/// <summary>
/// Perform a multi mapping query with 6 input parameters
/// </summary>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(cnn,
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <summary>
/// Perform a multi mapping query with 6 input parameters
/// </summary>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> map, string splitOn = "Id")
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(cnn, command, map, splitOn);
}
/// <summary>
/// Perform a multi mapping query with 7 input parameters
/// </summary>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn,
new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn);
}
/// <summary>
/// Perform a multi mapping query with 7 input parameters
/// </summary>
public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, string splitOn = "Id")
{
return MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, command, map, splitOn);
}
private static async Task<IEnumerable<TReturn>> MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false))
{
if (!command.Buffered) wasClosed = false; // handing back open reader; rely on command-behavior
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true);
return command.Buffered ? results.ToList() : results;
}
} finally
{
if (wasClosed) cnn.Close();
}
}
/// <summary>
/// Perform a multi mapping query with arbitrary input parameters
/// </summary>
/// <typeparam name="TReturn">The return type</typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="types">array of types in the recordset</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 Task<IEnumerable<TReturn>> QueryAsync<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken));
return MultiMapAsync<TReturn>(cnn, command, types, map, splitOn);
}
private static async Task<IEnumerable<TReturn>> MultiMapAsync<TReturn>(this IDbConnection cnn, CommandDefinition command, Type[] types, Func<object[], TReturn> map, string splitOn)
{
if (types.Length < 1)
{
throw new ArgumentException("you must provide at least one type to deserialize");
}
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types);
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
try {
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false)) {
var results = MultiMapImpl<TReturn>(null, default(CommandDefinition), types, map, splitOn, reader, identity, true);
return command.Buffered ? results.ToList() : results;
}
}
finally {
if (wasClosed) cnn.Close();
}
}
private static IEnumerable<T> ExecuteReaderSync<T>(IDataReader reader, Func<IDataReader, object> func, object parameters)
{
using (reader)
{
while (reader.Read())
{
yield return (T)func(reader);
}
while (reader.NextResult()) { }
(parameters as IParameterCallbacks)?.OnCompleted();
}
}
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static Task<GridReader> QueryMultipleAsync(
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 QueryMultipleAsync(cnn, command);
}
/// <summary>
/// Execute a command that returns multiple result sets, and access each in turn
/// </summary>
public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command)
{
object param = command.Parameters;
Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType(), null);
CacheInfo info = GetCacheInfo(identity, param, command.AddToCache);
DbCommand cmd = null;
IDataReader reader = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader);
reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false);
var result = new GridReader(cmd, reader, identity, command.Parameters as DynamicParameters, command.AddToCache, command.CancellationToken);
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();
}
cmd?.Dispose();
if (wasClosed) cnn.Close();
throw;
}
}
/// <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="T:DataSet"/>.
/// </remarks>
/// <example>
/// <code>
/// <![CDATA[
/// DataTable table = new DataTable("MyTable");
/// using (var reader = ExecuteReader(cnn, sql, param))
/// {
/// table.Load(reader);
/// }
/// ]]>
/// </code>
/// </example>
public static Task<IDataReader> ExecuteReaderAsync(
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 ExecuteReaderImplAsync(cnn, 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="T:DataSet"/>.
/// </remarks>
public static Task<IDataReader> ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteReaderImplAsync(cnn, command);
}
private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn, CommandDefinition command)
{
Action<IDbCommand, object> paramReader = GetParameterReader(cnn, ref command);
DbCommand cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
try
{
cmd = (DbCommand)command.SetupCommand(cnn, paramReader);
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.Default, command.CancellationToken).ConfigureAwait(false);
wasClosed = false;
return reader;
}
finally
{
if (wasClosed) cnn.Close();
cmd?.Dispose();
}
}
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static Task<object> ExecuteScalarAsync(
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 ExecuteScalarImplAsync<object>(cnn, command);
}
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static Task<T> ExecuteScalarAsync<T>(
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 ExecuteScalarImplAsync<T>(cnn, command);
}
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static Task<object> ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteScalarImplAsync<object>(cnn, command);
}
/// <summary>
/// Execute parameterized SQL that selects a single value
/// </summary>
/// <returns>The first cell selected</returns>
public static Task<T> ExecuteScalarAsync<T>(this IDbConnection cnn, CommandDefinition command)
{
return ExecuteScalarImplAsync<T>(cnn, command);
}
private static async Task<T> ExecuteScalarImplAsync<T>(IDbConnection cnn, CommandDefinition command)
{
Action<IDbCommand, object> paramReader = null;
object param = command.Parameters;
if (param != null)
{
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader;
}
DbCommand cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
object result;
try
{
cmd = (DbCommand)command.SetupCommand(cnn, paramReader);
if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false);
result = await cmd.ExecuteScalarAsync(command.CancellationToken).ConfigureAwait(false);
command.OnCompleted();
}
finally
{
if (wasClosed) cnn.Close();
cmd?.Dispose();
}
return Parse<T>(result);
}
}
}
#endif

View File

@@ -0,0 +1,19 @@
using System;
using System.Data;
using System.Threading;
namespace Dapper
{
partial class SqlMapper
{
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); }
}
}
}

View File

@@ -0,0 +1,214 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Dapper
{
partial class SqlMapper
{
private sealed 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(nameof(table));
if (values == null) throw new ArgumentNullException(nameof(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 = GetStringBuilder().Append("{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('}').__ToStringRecycle();
}
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 => 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(nameof(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", nameof(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
}
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Dapper
{
partial class SqlMapper
{
sealed 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;
}
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
namespace Dapper
{
partial class SqlMapper
{
private sealed class DapperTable
{
string[] fieldNames;
readonly Dictionary<string, int> fieldNameLookup;
internal string[] FieldNames => fieldNames;
public DapperTable(string[] fieldNames)
{
if (fieldNames == null) throw new ArgumentNullException(nameof(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(nameof(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) => key != null && fieldNameLookup.ContainsKey(key);
public int FieldCount => fieldNames.Length;
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Data;
namespace Dapper
{
partial class SqlMapper
{
struct DeserializerState
{
public readonly int Hash;
public readonly Func<IDataReader, object> Func;
public DeserializerState(int hash, Func<IDataReader, object> func)
{
Hash = hash;
Func = func;
}
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Dapper
{
partial class SqlMapper
{
/// <summary>
/// Dummy type for excluding from multi-map
/// </summary>
class DontMap { }
}
}

View File

@@ -0,0 +1,254 @@
#if ASYNC
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Dapper
{
partial class SqlMapper
{
partial class GridReader
{
CancellationToken cancel;
internal GridReader(IDbCommand command, IDataReader reader, Identity identity, DynamicParameters dynamicParams, bool addToCache, CancellationToken cancel)
: this(command, reader, identity, dynamicParams, addToCache)
{
this.cancel = cancel;
}
/// <summary>
/// Read the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public Task<IEnumerable<dynamic>> ReadAsync(bool buffered = true)
{
return ReadAsyncImpl<dynamic>(typeof(DapperRow), buffered);
}
/// <summary>
/// Read an individual row of the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public Task<dynamic> ReadFirstAsync()
{
return ReadRowAsyncImpl<dynamic>(typeof(DapperRow), Row.First);
}
/// <summary>
/// Read an individual row of the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public Task<dynamic> ReadFirstOrDefaultAsync()
{
return ReadRowAsyncImpl<dynamic>(typeof(DapperRow), Row.FirstOrDefault);
}
/// <summary>
/// Read an individual row of the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public Task<dynamic> ReadSingleAsync()
{
return ReadRowAsyncImpl<dynamic>(typeof(DapperRow), Row.Single);
}
/// <summary>
/// Read an individual row of the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public Task<dynamic> ReadSingleOrDefaultAsync()
{
return ReadRowAsyncImpl<dynamic>(typeof(DapperRow), Row.SingleOrDefault);
}
/// <summary>
/// Read the next grid of results
/// </summary>
public Task<IEnumerable<object>> ReadAsync(Type type, bool buffered = true)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadAsyncImpl<object>(type, buffered);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<object> ReadFirstAsync(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRowAsyncImpl<object>(type, Row.First);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<object> ReadFirstOrDefaultAsync(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRowAsyncImpl<object>(type, Row.FirstOrDefault);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<object> ReadSingleAsync(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRowAsyncImpl<object>(type, Row.Single);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<object> ReadSingleOrDefaultAsync(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRowAsyncImpl<object>(type, Row.SingleOrDefault);
}
/// <summary>
/// Read the next grid of results
/// </summary>
public Task<IEnumerable<T>> ReadAsync<T>(bool buffered = true)
{
return ReadAsyncImpl<T>(typeof(T), buffered);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<T> ReadFirstAsync<T>()
{
return ReadRowAsyncImpl<T>(typeof(T), Row.First);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<T> ReadFirstOrDefaultAsync<T>()
{
return ReadRowAsyncImpl<T>(typeof(T), Row.FirstOrDefault);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<T> ReadSingleAsync<T>()
{
return ReadRowAsyncImpl<T>(typeof(T), Row.Single);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public Task<T> ReadSingleOrDefaultAsync<T>()
{
return ReadRowAsyncImpl<T>(typeof(T), Row.SingleOrDefault);
}
private async Task NextResultAsync()
{
if (await ((DbDataReader)reader).NextResultAsync(cancel).ConfigureAwait(false))
{
readCount++;
gridIndex++;
IsConsumed = false;
}
else
{
// happy path; close the reader cleanly - no
// need for "Cancel" etc
reader.Dispose();
reader = null;
callbacks?.OnCompleted();
Dispose();
}
}
private Task<IEnumerable<T>> ReadAsyncImpl<T>(Type type, bool buffered)
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);
var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false));
cache.Deserializer = deserializer;
}
IsConsumed = true;
if (buffered && reader is DbDataReader)
{
return ReadBufferedAsync<T>(gridIndex, deserializer.Func, typedIdentity);
}
else
{
var result = ReadDeferred<T>(gridIndex, deserializer.Func, typedIdentity, type);
if (buffered) result = result.ToList(); // for the "not a DbDataReader" scenario
return Task.FromResult(result);
}
}
private Task<T> ReadRowAsyncImpl<T>(Type type, Row row)
{
var dbReader = reader as DbDataReader;
if (dbReader != null) return ReadRowAsyncImplViaDbReader<T>(dbReader, type, row);
// no async API available; use non-async and fake it
return Task.FromResult<T>(ReadRow<T>(type, row));
}
private async Task<T> ReadRowAsyncImplViaDbReader<T>(DbDataReader reader, Type type, Row row)
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
IsConsumed = true;
T result = default(T);
if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0)
{
var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);
var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false));
cache.Deserializer = deserializer;
}
result = (T)deserializer.Func(reader);
if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row);
while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { }
}
else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one
{
ThrowZeroRows(row);
}
await NextResultAsync().ConfigureAwait(false);
return result;
}
private async Task<IEnumerable<T>> ReadBufferedAsync<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity)
{
try
{
var reader = (DbDataReader)this.reader;
List<T> buffer = new List<T>();
while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false))
{
buffer.Add((T)deserializer(reader));
}
return buffer;
}
finally // finally so that First etc progresses things even when multiple rows
{
if (index == gridIndex)
{
await NextResultAsync().ConfigureAwait(false);
}
}
}
}
}
}
#endif

View File

@@ -0,0 +1,381 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Globalization;
namespace Dapper
{
partial class SqlMapper
{
/// <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 Identity identity;
private bool addToCache;
internal GridReader(IDbCommand command, IDataReader reader, Identity identity, IParameterCallbacks callbacks, bool addToCache)
{
Command = command;
this.reader = reader;
this.identity = identity;
this.callbacks = callbacks;
this.addToCache = addToCache;
}
/// <summary>
/// Read the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public IEnumerable<dynamic> Read(bool buffered = true)
{
return ReadImpl<dynamic>(typeof(DapperRow), buffered);
}
/// <summary>
/// Read an individual row of the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public dynamic ReadFirst()
{
return ReadRow<dynamic>(typeof(DapperRow), Row.First);
}
/// <summary>
/// Read an individual row of the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public dynamic ReadFirstOrDefault()
{
return ReadRow<dynamic>(typeof(DapperRow), Row.FirstOrDefault);
}
/// <summary>
/// Read an individual row of the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public dynamic ReadSingle()
{
return ReadRow<dynamic>(typeof(DapperRow), Row.Single);
}
/// <summary>
/// Read an individual row of the next grid of results, returned as a dynamic object
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public dynamic ReadSingleOrDefault()
{
return ReadRow<dynamic>(typeof(DapperRow), Row.SingleOrDefault);
}
/// <summary>
/// Read the next grid of results
/// </summary>
public IEnumerable<T> Read<T>(bool buffered = true)
{
return ReadImpl<T>(typeof(T), buffered);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public T ReadFirst<T>()
{
return ReadRow<T>(typeof(T), Row.First);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public T ReadFirstOrDefault<T>()
{
return ReadRow<T>(typeof(T), Row.FirstOrDefault);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public T ReadSingle<T>()
{
return ReadRow<T>(typeof(T), Row.Single);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public T ReadSingleOrDefault<T>()
{
return ReadRow<T>(typeof(T), Row.SingleOrDefault);
}
/// <summary>
/// Read the next grid of results
/// </summary>
public IEnumerable<object> Read(Type type, bool buffered = true)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadImpl<object>(type, buffered);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public object ReadFirst(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRow<object>(type, Row.First);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public object ReadFirstOrDefault(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRow<object>(type, Row.FirstOrDefault);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public object ReadSingle(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRow<object>(type, Row.Single);
}
/// <summary>
/// Read an individual row of the next grid of results
/// </summary>
public object ReadSingleOrDefault(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
return ReadRow<object>(type, Row.SingleOrDefault);
}
private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);
var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false));
cache.Deserializer = deserializer;
}
IsConsumed = true;
var result = ReadDeferred<T>(gridIndex, deserializer.Func, typedIdentity, type);
return buffered ? result.ToList() : result;
}
private T ReadRow<T>(Type type, Row row)
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
IsConsumed = true;
T result = default(T);
if(reader.Read() && reader.FieldCount != 0)
{
var typedIdentity = identity.ForGrid(type, gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);
var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false));
cache.Deserializer = deserializer;
}
object val = deserializer.Func(reader);
if(val == null || val is T)
{
result = (T)val;
} else {
var convertToType = Nullable.GetUnderlyingType(type) ?? type;
result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
}
if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row);
while (reader.Read()) { }
}
else if((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one
{
ThrowZeroRows(row);
}
NextResult();
return 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 MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default(CommandDefinition), func, splitOn, reader, identity, false))
{
yield return r;
}
}
finally
{
NextResult();
}
}
private IEnumerable<TReturn> MultiReadInternal<TReturn>(Type[] types, Func<object[], TReturn> map, string splitOn)
{
var identity = this.identity.ForGrid(typeof(TReturn), types, gridIndex);
try
{
foreach (var r in MultiMapImpl<TReturn>(null, default(CommandDefinition), types, map, splitOn, reader, identity, false))
{
yield return r;
}
}
finally
{
NextResult();
}
}
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn = "id", bool buffered = true)
{
var result = MultiReadInternal<TFirst, TSecond, DontMap, DontMap, DontMap, 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, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn = "id", bool buffered = true)
{
var result = MultiReadInternal<TFirst, TSecond, TThird, DontMap, DontMap, 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, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id", bool buffered = true)
{
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, 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, 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;
}
/// <summary>
/// Read multiple objects from a single record set on the grid
/// </summary>
public IEnumerable<TReturn> Read<TReturn>(Type[] types, Func<object[], TReturn> map, string splitOn = "id", bool buffered = true)
{
var result = MultiReadInternal<TReturn>(types, map, splitOn);
return buffered ? result.ToList() : result;
}
private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity, Type effectiveType)
{
try
{
var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
while (index == gridIndex && reader.Read())
{
object val = deserializer(reader);
if (val == null || val is T) {
yield return (T)val;
} else {
yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
}
}
}
finally // finally so that First etc progresses things even when multiple rows
{
if (index == gridIndex)
{
NextResult();
}
}
}
private int gridIndex, readCount;
private IParameterCallbacks callbacks;
/// <summary>
/// Has the underlying reader been consumed?
/// </summary>
public bool IsConsumed { get; private set; }
/// <summary>
/// The command associated with the reader
/// </summary>
public IDbCommand Command { get; set; }
private void NextResult()
{
if (reader.NextResult())
{
readCount++;
gridIndex++;
IsConsumed = false;
}
else
{
// happy path; close the reader cleanly - no
// need for "Cancel" etc
reader.Dispose();
reader = null;
callbacks?.OnCompleted();
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?.Cancel();
reader.Dispose();
reader = null;
}
if (Command != null)
{
Command.Dispose();
Command = null;
}
}
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Data;
namespace Dapper
{
partial class SqlMapper
{
/// <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);
}
}
}

View File

@@ -0,0 +1,137 @@
using System;
using System.Collections.Generic;
using System.Data;
namespace Dapper
{
partial class SqlMapper
{
/// <summary>
/// Parses a data reader to a sequence of data of the supplied type. Used for deserializing a reader without a connection, etc.
/// </summary>
public static IEnumerable<T> Parse<T>(this IDataReader reader)
{
if(reader.Read())
{
var deser = GetDeserializer(typeof(T), reader, 0, -1, false);
do
{
yield return (T)deser(reader);
} while (reader.Read());
}
}
/// <summary>
/// Parses a data reader to a sequence of data of the supplied type (as object). Used for deserializing a reader without a connection, etc.
/// </summary>
public static IEnumerable<object> Parse(this IDataReader reader, Type type)
{
if (reader.Read())
{
var deser = GetDeserializer(type, reader, 0, -1, false);
do
{
yield return deser(reader);
} while (reader.Read());
}
}
/// <summary>
/// Parses a data reader to a sequence of dynamic. Used for deserializing a reader without a connection, etc.
/// </summary>
public static IEnumerable<dynamic> Parse(this IDataReader reader)
{
if (reader.Read())
{
var deser = GetDapperRowDeserializer(reader, 0, -1, false);
do
{
yield return deser(reader);
} while (reader.Read());
}
}
/// <summary>
/// Gets the row parser for a specific row on a data reader. This allows for type switching every row based on, for example, a TypeId column.
/// You could return a collection of the base type but have each more specific.
/// </summary>
/// <param name="reader">The data reader to get the parser for the current row from</param>
/// <param name="type">The type to get the parser for</param>
/// <param name="startIndex">The start column index of the object (default 0)</param>
/// <param name="length">The length of columns to read (default -1 = all fields following startIndex)</param>
/// <param name="returnNullIfFirstMissing">Return null if we can't find the first column? (default false)</param>
/// <returns>A parser for this specific object from this row.</returns>
public static Func<IDataReader, object> GetRowParser(this IDataReader reader, Type type,
int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false)
{
return GetDeserializer(type, reader, startIndex, length, returnNullIfFirstMissing);
}
/// <summary>
/// Gets the row parser for a specific row on a data reader. This allows for type switching every row based on, for example, a TypeId column.
/// You could return a collection of the base type but have each more specific.
/// </summary>
/// <param name="reader">The data reader to get the parser for the current row from</param>
/// <param name="concreteType">The type to get the parser for</param>
/// <param name="startIndex">The start column index of the object (default 0)</param>
/// <param name="length">The length of columns to read (default -1 = all fields following startIndex)</param>
/// <param name="returnNullIfFirstMissing">Return null if we can't find the first column? (default false)</param>
/// <returns>A parser for this specific object from this row.</returns>
/// <example>
/// var result = new List&lt;BaseType&gt;();
/// using (var reader = connection.ExecuteReader(@"
/// select 'abc' as Name, 1 as Type, 3.0 as Value
/// union all
/// select 'def' as Name, 2 as Type, 4.0 as Value"))
/// {
/// if (reader.Read())
/// {
/// var toFoo = reader.GetRowParser&lt;BaseType&gt;(typeof(Foo));
/// var toBar = reader.GetRowParser&lt;BaseType&gt;(typeof(Bar));
/// var col = reader.GetOrdinal("Type");
/// do
/// {
/// switch (reader.GetInt32(col))
/// {
/// case 1:
/// result.Add(toFoo(reader));
/// break;
/// case 2:
/// result.Add(toBar(reader));
/// break;
/// }
/// } while (reader.Read());
/// }
/// }
///
/// abstract class BaseType
/// {
/// public abstract int Type { get; }
/// }
/// class Foo : BaseType
/// {
/// public string Name { get; set; }
/// public override int Type =&gt; 1;
/// }
/// class Bar : BaseType
/// {
/// public float Value { get; set; }
/// public override int Type =&gt; 2;
/// }
/// </example>
public static Func<IDataReader, T> GetRowParser<T>(this IDataReader reader, Type concreteType = null,
int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false)
{
if (concreteType == null) concreteType = typeof(T);
var func = GetDeserializer(concreteType, reader, startIndex, length, returnNullIfFirstMissing);
if (concreteType.IsValueType())
{
return _ => (T)func(_);
}
else
{
return (Func<IDataReader, T>)(Delegate)func;
}
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Data;
namespace Dapper
{
partial class SqlMapper
{
/// <summary>
/// Implement this interface to pass an arbitrary db specific set of parameters to Dapper
/// </summary>
public 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);
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Reflection;
namespace Dapper
{
partial class SqlMapper
{
/// <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; }
}
}
}

View File

@@ -0,0 +1,16 @@
namespace Dapper
{
partial class SqlMapper
{
/// <summary>
/// Extends IDynamicParameters with facilities for executing callbacks after commands have completed
/// </summary>
public interface IParameterCallbacks : IDynamicParameters
{
/// <summary>
/// Invoked when the command has executed
/// </summary>
void OnCompleted();
}
}
}

View File

@@ -0,0 +1,16 @@
namespace Dapper
{
partial class SqlMapper
{
/// <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; }
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Data;
namespace Dapper
{
partial class SqlMapper
{
/// <summary>
/// Implement this interface to perform custom type-based parameter handling and value parsing
/// </summary>
public interface ITypeHandler
{
/// <summary>
/// Assign the value of a parameter before a command executes
/// </summary>
/// <param name="parameter">The parameter to configure</param>
/// <param name="value">Parameter value</param>
void SetValue(IDbDataParameter parameter, object value);
/// <summary>
/// Parse a database value back to a typed value
/// </summary>
/// <param name="value">The value from the database</param>
/// <param name="destinationType">The type to parse to</param>
/// <returns>The typed value</returns>
object Parse(Type destinationType, object value);
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Reflection;
namespace Dapper
{
partial class SqlMapper
{
/// <summary>
/// Implement this interface to change default mapping of reader columns to type members
/// </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>
/// Returns a constructor which should *always* be used.
///
/// Parameters will be default values, nulls for reference types and zero'd for value types.
///
/// Use this class to force object creation away from parameterless constructors you don't control.
/// </summary>
ConstructorInfo FindExplicitConstructor();
/// <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);
}
}
}

View File

@@ -0,0 +1,122 @@
using System;
using System.Data;
namespace Dapper
{
partial class SqlMapper
{
/// <summary>
/// Identity of a cached query in Dapper, used for extensibility
/// </summary>
public 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?.GetHashCode() ?? 0);
hashCode = hashCode * 23 + (type?.GetHashCode() ?? 0);
if (otherTypes != null)
{
foreach (var t in otherTypes)
{
hashCode = hashCode * 23 + (t?.GetHashCode() ?? 0);
}
}
hashCode = hashCode * 23 + (connectionString == null ? 0 : connectionStringComparer.GetHashCode(connectionString));
hashCode = hashCode * 23 + (parametersType?.GetHashCode() ?? 0);
}
}
/// <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 &&
connectionStringComparer.Equals(connectionString, other.connectionString) &&
parametersType == other.parametersType;
}
}
}
}

View File

@@ -0,0 +1,57 @@
using System.Threading;
namespace Dapper
{
partial class SqlMapper
{
/// <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 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; }
public TValue Value { get; }
public Link<TKey, TValue> Tail { get; }
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
namespace Dapper
{
partial class SqlMapper
{
/// <summary>
/// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql
/// </summary>
internal struct LiteralToken
{
/// <summary>
/// The text in the original command that should be replaced
/// </summary>
public string Token { get; }
/// <summary>
/// The name of the member referred to by the token
/// </summary>
public string Member { get; }
internal LiteralToken(string token, string member)
{
Token = token;
Member = member;
}
internal static readonly IList<LiteralToken> None = new LiteralToken[0];
}
}
}

View File

@@ -0,0 +1,54 @@
namespace Dapper
{
partial class SqlMapper
{
/// <summary>
/// Permits specifying certain SqlMapper values globally.
/// </summary>
public static class Settings
{
static Settings()
{
SetDefaults();
}
/// <summary>
/// Resets all Settings to their default values
/// </summary>
public static void SetDefaults()
{
CommandTimeout = null;
ApplyNullValues = false;
}
/// <summary>
/// Specifies the default Command Timeout for all Queries
/// </summary>
public static int? CommandTimeout { get; set; }
/// <summary>
/// Indicates whether nulls in data are silently ignored (default) vs actively applied and assigned to members
/// </summary>
public static bool ApplyNullValues { get; set; }
/// <summary>
/// Should list expansions be padded with null-valued parameters, to prevent query-plan saturation? For example,
/// an 'in @foo' expansion with 7, 8 or 9 values will be sent as a list of 10 values, with 3, 2 or 1 of them null.
/// The padding size is relative to the size of the list; "next 10" under 150, "next 50" under 500,
/// "next 100" under 1500, etc.
/// </summary>
/// <remarks>
/// Caution: this should be treated with care if your DB provider (or the specific configuration) allows for null
/// equality (aka "ansi nulls off"), as this may change the intent of your query; as such, this is disabled by
/// default and must be enabled.
/// </remarks>
public static bool PadListExpansions { get; set; }
/// <summary>
/// If set (non-negative), when performing in-list expansions of integer types ("where id in @ids", etc), switch to a string_split based
/// operation if there are more than this many elements. Note that this feautre requires SQL Server 2016 / compatibility level 130 (or above).
/// </summary>
public static int InListStringSplitCount { get; set; } = -1;
}
}
}

View File

@@ -0,0 +1,160 @@
using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace Dapper
{
partial class SqlMapper
{
private class TypeDeserializerCache
{
private TypeDeserializerCache(Type type)
{
this.type = type;
}
static readonly Hashtable byType = new Hashtable();
private readonly Type type;
internal static void Purge(Type type)
{
lock (byType)
{
byType.Remove(type);
}
}
internal static void Purge()
{
lock (byType)
{
byType.Clear();
}
}
internal static Func<IDataReader, object> GetReader(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
var found = (TypeDeserializerCache)byType[type];
if (found == null)
{
lock (byType)
{
found = (TypeDeserializerCache)byType[type];
if (found == null)
{
byType[type] = found = new TypeDeserializerCache(type);
}
}
}
return found.GetReader(reader, startBound, length, returnNullIfFirstMissing);
}
private Dictionary<DeserializerKey, Func<IDataReader, object>> readers = new Dictionary<DeserializerKey, Func<IDataReader, object>>();
struct DeserializerKey : IEquatable<DeserializerKey>
{
private readonly int startBound, length;
private readonly bool returnNullIfFirstMissing;
private readonly IDataReader reader;
private readonly string[] names;
private readonly Type[] types;
private readonly int hashCode;
public DeserializerKey(int hashCode, int startBound, int length, bool returnNullIfFirstMissing, IDataReader reader, bool copyDown)
{
this.hashCode = hashCode;
this.startBound = startBound;
this.length = length;
this.returnNullIfFirstMissing = returnNullIfFirstMissing;
if (copyDown)
{
this.reader = null;
names = new string[length];
types = new Type[length];
int index = startBound;
for (int i = 0; i < length; i++)
{
names[i] = reader.GetName(index);
types[i] = reader.GetFieldType(index++);
}
}
else
{
this.reader = reader;
names = null;
types = null;
}
}
public override int GetHashCode()
{
return hashCode;
}
public override string ToString()
{ // only used in the debugger
if (names != null)
{
return string.Join(", ", names);
}
if (reader != null)
{
var sb = new StringBuilder();
int index = startBound;
for (int i = 0; i < length; i++)
{
if (i != 0) sb.Append(", ");
sb.Append(reader.GetName(index++));
}
return sb.ToString();
}
return base.ToString();
}
public override bool Equals(object obj)
{
return obj is DeserializerKey && Equals((DeserializerKey)obj);
}
public bool Equals(DeserializerKey other)
{
if (this.hashCode != other.hashCode
|| this.startBound != other.startBound
|| this.length != other.length
|| this.returnNullIfFirstMissing != other.returnNullIfFirstMissing)
{
return false; // clearly different
}
for (int i = 0; i < length; i++)
{
if ((this.names?[i] ?? this.reader?.GetName(startBound + i)) != (other.names?[i] ?? other.reader?.GetName(startBound + i))
||
(this.types?[i] ?? this.reader?.GetFieldType(startBound + i)) != (other.types?[i] ?? other.reader?.GetFieldType(startBound + i))
)
{
return false; // different column name or type
}
}
return true;
}
}
private Func<IDataReader, object> GetReader(IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
if (length < 0) length = reader.FieldCount - startBound;
int hash = GetColumnHash(reader, startBound, length);
if (returnNullIfFirstMissing) hash *= -27;
// get a cheap key first: false means don't copy the values down
var key = new DeserializerKey(hash, startBound, length, returnNullIfFirstMissing, reader, false);
Func<IDataReader, object> deser;
lock (readers)
{
if (readers.TryGetValue(key, out deser)) return deser;
}
deser = GetTypeDeserializerImpl(type, reader, startBound, length, returnNullIfFirstMissing);
// get a more expensive key: true means copy the values down so it can be used as a key later
key = new DeserializerKey(hash, startBound, length, returnNullIfFirstMissing, reader, true);
lock (readers)
{
return readers[key] = deser;
}
}
}
}
}

View File

@@ -0,0 +1,78 @@
using System;
using System.Data;
namespace Dapper
{
partial class SqlMapper
{
/// <summary>
/// Base-class for simple type-handlers
/// </summary>
public abstract class TypeHandler<T> : ITypeHandler
{
/// <summary>
/// Assign the value of a parameter before a command executes
/// </summary>
/// <param name="parameter">The parameter to configure</param>
/// <param name="value">Parameter value</param>
public abstract void SetValue(IDbDataParameter parameter, T value);
/// <summary>
/// Parse a database value back to a typed value
/// </summary>
/// <param name="value">The value from the database</param>
/// <returns>The typed value</returns>
public abstract T Parse(object value);
void ITypeHandler.SetValue(IDbDataParameter parameter, object value)
{
if (value is DBNull)
{
parameter.Value = value;
}
else
{
SetValue(parameter, (T)value);
}
}
object ITypeHandler.Parse(Type destinationType, object value)
{
return Parse(value);
}
}
/// <summary>
/// Base-class for simple type-handlers that are based around strings
/// </summary>
public abstract class StringTypeHandler<T> : TypeHandler<T>
{
/// <summary>
/// Parse a string into the expected type (the string will never be null)
/// </summary>
protected abstract T Parse(string xml);
/// <summary>
/// Format an instace into a string (the instance will never be null)
/// </summary>
protected abstract string Format(T xml);
/// <summary>
/// Assign the value of a parameter before a command executes
/// </summary>
/// <param name="parameter">The parameter to configure</param>
/// <param name="value">Parameter value</param>
public override void SetValue(IDbDataParameter parameter, T value)
{
parameter.Value = value == null ? (object)DBNull.Value : Format(value);
}
/// <summary>
/// Parse a database value back to a typed value
/// </summary>
/// <param name="value">The value from the database</param>
/// <returns>The typed value</returns>
public override T Parse(object value)
{
if (value == null || value is DBNull) return default(T);
return Parse((string)value);
}
}
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.ComponentModel;
using System.Data;
namespace Dapper
{
partial class SqlMapper
{
/// <summary>
/// Not intended for direct usage
/// </summary>
[Obsolete(ObsoleteInternalUsageOnly, false)]
#if !COREFX
[Browsable(false)]
#endif
[EditorBrowsable(EditorBrowsableState.Never)]
public static class TypeHandlerCache<T>
{
/// <summary>
/// Not intended for direct usage
/// </summary>
[Obsolete(ObsoleteInternalUsageOnly, true)]
public static T Parse(object value)
{
return (T)handler.Parse(typeof(T), value);
}
/// <summary>
/// Not intended for direct usage
/// </summary>
[Obsolete(ObsoleteInternalUsageOnly, true)]
public static void SetValue(IDbDataParameter parameter, object value)
{
handler.SetValue(parameter, value);
}
internal static void SetHandler(ITypeHandler handler)
{
#pragma warning disable 618
TypeHandlerCache<T>.handler = handler;
#pragma warning restore 618
}
private static ITypeHandler handler;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
using System;
using System.Data;
using System.Reflection;
#if !COREFX
namespace Dapper
{
/// <summary>
/// Used to pass a DataTable as a TableValuedParameter
/// </summary>
sealed class TableValuedParameter : SqlMapper.ICustomQueryParameter
{
private readonly DataTable table;
private readonly string typeName;
/// <summary>
/// Create a new instance of TableValuedParameter
/// </summary>
public TableValuedParameter(DataTable table) : this(table, null) { }
/// <summary>
/// Create a new instance of TableValuedParameter
/// </summary>
public TableValuedParameter(DataTable table, string typeName)
{
this.table = table;
this.typeName = typeName;
}
static readonly Action<System.Data.SqlClient.SqlParameter, string> setTypeName;
static TableValuedParameter()
{
var prop = typeof(System.Data.SqlClient.SqlParameter).GetProperty("TypeName", BindingFlags.Instance | BindingFlags.Public);
if (prop != null && prop.PropertyType == typeof(string) && prop.CanWrite)
{
setTypeName = (Action<System.Data.SqlClient.SqlParameter, string>)
Delegate.CreateDelegate(typeof(Action<System.Data.SqlClient.SqlParameter, string>), prop.GetSetMethod());
}
}
void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name)
{
var param = command.CreateParameter();
param.ParameterName = name;
Set(param, table, typeName);
command.Parameters.Add(param);
}
internal static void Set(IDbDataParameter parameter, DataTable table, string typeName)
{
#pragma warning disable 0618
parameter.Value = SqlMapper.SanitizeParameterValue(table);
#pragma warning restore 0618
if (string.IsNullOrEmpty(typeName) && table != null)
{
typeName = table.GetTypeName();
}
if (!string.IsNullOrEmpty(typeName))
{
var sqlParam = parameter as System.Data.SqlClient.SqlParameter;
if (sqlParam != null)
{
setTypeName?.Invoke(sqlParam, typeName);
sqlParam.SqlDbType = SqlDbType.Structured;
}
}
}
}
}
#endif

View File

@@ -0,0 +1,106 @@
using System;
using System.Reflection;
using System.Collections.Generic;
namespace Dapper
{
internal static class TypeExtensions
{
public static string Name(this Type type)
{
#if COREFX
return type.GetTypeInfo().Name;
#else
return type.Name;
#endif
}
public static bool IsValueType(this Type type)
{
#if COREFX
return type.GetTypeInfo().IsValueType;
#else
return type.IsValueType;
#endif
}
public static bool IsEnum(this Type type)
{
#if COREFX
return type.GetTypeInfo().IsEnum;
#else
return type.IsEnum;
#endif
}
public static bool IsGenericType(this Type type)
{
#if COREFX
return type.GetTypeInfo().IsGenericType;
#else
return type.IsGenericType;
#endif
}
public static bool IsInterface(this Type type)
{
#if COREFX
return type.GetTypeInfo().IsInterface;
#else
return type.IsInterface;
#endif
}
#if COREFX
public static IEnumerable<Attribute> GetCustomAttributes(this Type type, bool inherit)
{
return type.GetTypeInfo().GetCustomAttributes(inherit);
}
public static TypeCode GetTypeCode(Type type)
{
if (type == null) return TypeCode.Empty;
TypeCode result;
if (typeCodeLookup.TryGetValue(type, out result)) return result;
if (type.IsEnum())
{
type = Enum.GetUnderlyingType(type);
if (typeCodeLookup.TryGetValue(type, out result)) return result;
}
return TypeCode.Object;
}
static readonly Dictionary<Type, TypeCode> typeCodeLookup = new Dictionary<Type, TypeCode>
{
{typeof(bool), TypeCode.Boolean },
{typeof(byte), TypeCode.Byte },
{typeof(char), TypeCode.Char},
{typeof(DateTime), TypeCode.DateTime},
{typeof(decimal), TypeCode.Decimal},
{typeof(double), TypeCode.Double },
{typeof(short), TypeCode.Int16 },
{typeof(int), TypeCode.Int32 },
{typeof(long), TypeCode.Int64 },
{typeof(object), TypeCode.Object},
{typeof(sbyte), TypeCode.SByte },
{typeof(float), TypeCode.Single },
{typeof(string), TypeCode.String },
{typeof(ushort), TypeCode.UInt16 },
{typeof(uint), TypeCode.UInt32 },
{typeof(ulong), TypeCode.UInt64 },
};
#else
public static TypeCode GetTypeCode(Type type)
{
return Type.GetTypeCode(type);
}
#endif
public static MethodInfo GetPublicInstanceMethod(this Type type, string name, Type[] types)
{
#if COREFX
var method = type.GetMethod(name, types);
return (method != null && method.IsPublic && !method.IsStatic) ? method : null;
#else
return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public, null, types, null);
#endif
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Data;
namespace Dapper
{
partial class SqlMapper
{
#if !COREFX
/// <summary>
/// A type handler for data-types that are supported by the underlying provider, but which need
/// a well-known UdtTypeName to be specified
/// </summary>
public class UdtTypeHandler : ITypeHandler
{
private readonly string udtTypeName;
/// <summary>
/// Creates a new instance of UdtTypeHandler with the specified UdtTypeName
/// </summary>
public UdtTypeHandler(string udtTypeName)
{
if (string.IsNullOrEmpty(udtTypeName)) throw new ArgumentException("Cannot be null or empty", udtTypeName);
this.udtTypeName = udtTypeName;
}
object ITypeHandler.Parse(Type destinationType, object value)
{
return value is DBNull ? null : value;
}
void ITypeHandler.SetValue(IDbDataParameter parameter, object value)
{
#pragma warning disable 0618
parameter.Value = SanitizeParameterValue(value);
#pragma warning restore 0618
if (parameter is System.Data.SqlClient.SqlParameter && !(value is DBNull))
{
((System.Data.SqlClient.SqlParameter)parameter).SqlDbType = SqlDbType.Udt;
((System.Data.SqlClient.SqlParameter)parameter).UdtTypeName = udtTypeName;
}
}
}
#endif
}
}

View File

@@ -0,0 +1,20 @@
using System.Data;
namespace Dapper
{
/// <summary>
/// Describes a reader that controls the lifetime of both a command and a reader,
/// exposing the downstream command/reader as properties.
/// </summary>
public interface IWrappedDataReader : IDataReader
{
/// <summary>
/// Obtain the underlying reader
/// </summary>
IDataReader Reader { get; }
/// <summary>
/// Obtain the underlying command
/// </summary>
IDbCommand Command { get; }
}
}

View File

@@ -0,0 +1,186 @@
using System;
using System.Data;
namespace Dapper
{
internal class WrappedReader : IDataReader, IWrappedDataReader
{
private IDataReader reader;
private IDbCommand cmd;
public IDataReader Reader
{
get
{
var tmp = reader;
if (tmp == null) throw new ObjectDisposedException(GetType().Name);
return tmp;
}
}
IDbCommand IWrappedDataReader.Command
{
get
{
var tmp = cmd;
if (tmp == null) throw new ObjectDisposedException(GetType().Name);
return tmp;
}
}
public WrappedReader(IDbCommand cmd, IDataReader reader)
{
this.cmd = cmd;
this.reader = reader;
}
void IDataReader.Close()
{
reader?.Close();
}
int IDataReader.Depth => Reader.Depth;
DataTable IDataReader.GetSchemaTable()
{
return Reader.GetSchemaTable();
}
bool IDataReader.IsClosed => reader?.IsClosed ?? true;
bool IDataReader.NextResult()
{
return Reader.NextResult();
}
bool IDataReader.Read()
{
return Reader.Read();
}
int IDataReader.RecordsAffected => Reader.RecordsAffected;
void IDisposable.Dispose()
{
reader?.Close();
reader?.Dispose();
reader = null;
cmd?.Dispose();
cmd = null;
}
int IDataRecord.FieldCount => Reader.FieldCount;
bool IDataRecord.GetBoolean(int i)
{
return Reader.GetBoolean(i);
}
byte IDataRecord.GetByte(int i)
{
return Reader.GetByte(i);
}
long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
{
return Reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
}
char IDataRecord.GetChar(int i)
{
return Reader.GetChar(i);
}
long IDataRecord.GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
{
return Reader.GetChars(i, fieldoffset, buffer, bufferoffset, length);
}
IDataReader IDataRecord.GetData(int i)
{
return Reader.GetData(i);
}
string IDataRecord.GetDataTypeName(int i)
{
return Reader.GetDataTypeName(i);
}
DateTime IDataRecord.GetDateTime(int i)
{
return Reader.GetDateTime(i);
}
decimal IDataRecord.GetDecimal(int i)
{
return Reader.GetDecimal(i);
}
double IDataRecord.GetDouble(int i)
{
return Reader.GetDouble(i);
}
Type IDataRecord.GetFieldType(int i)
{
return Reader.GetFieldType(i);
}
float IDataRecord.GetFloat(int i)
{
return Reader.GetFloat(i);
}
Guid IDataRecord.GetGuid(int i)
{
return Reader.GetGuid(i);
}
short IDataRecord.GetInt16(int i)
{
return Reader.GetInt16(i);
}
int IDataRecord.GetInt32(int i)
{
return Reader.GetInt32(i);
}
long IDataRecord.GetInt64(int i)
{
return Reader.GetInt64(i);
}
string IDataRecord.GetName(int i)
{
return Reader.GetName(i);
}
int IDataRecord.GetOrdinal(string name)
{
return Reader.GetOrdinal(name);
}
string IDataRecord.GetString(int i)
{
return Reader.GetString(i);
}
object IDataRecord.GetValue(int i)
{
return Reader.GetValue(i);
}
int IDataRecord.GetValues(object[] values)
{
return Reader.GetValues(values);
}
bool IDataRecord.IsDBNull(int i)
{
return Reader.IsDBNull(i);
}
object IDataRecord.this[string name] => Reader[name];
object IDataRecord.this[int i] => Reader[i];
}
}

View File

@@ -0,0 +1,35 @@
using System.Data;
using System.Xml;
using System.Xml.Linq;
namespace Dapper
{
internal abstract class XmlTypeHandler<T> : SqlMapper.StringTypeHandler<T>
{
public override void SetValue(IDbDataParameter parameter, T value)
{
base.SetValue(parameter, value);
parameter.DbType = DbType.Xml;
}
}
internal sealed class XmlDocumentHandler : XmlTypeHandler<XmlDocument>
{
protected override XmlDocument Parse(string xml)
{
var doc = new XmlDocument();
doc.LoadXml(xml);
return doc;
}
protected override string Format(XmlDocument xml) => xml.OuterXml;
}
internal sealed class XDocumentHandler : XmlTypeHandler<XDocument>
{
protected override XDocument Parse(string xml) => XDocument.Parse(xml);
protected override string Format(XDocument xml) => xml.ToString();
}
internal sealed class XElementHandler : XmlTypeHandler<XElement>
{
protected override XElement Parse(string xml) => XElement.Parse(xml);
protected override string Format(XElement xml) => xml.ToString();
}
}

View File

@@ -0,0 +1,79 @@
{
"packOptions": {
"summary": "A high performance Micro-ORM",
"tags": [ "orm", "sql", "micro-orm" ],
"owners": [ "marc.gravell", "nick.craver" ],
"releaseNotes": "http://stackexchange.github.io/dapper-dot-net/",
"projectUrl": "https://github.com/StackExchange/dapper-dot-net",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0",
"repository": {
"type": "git",
"url": "https://github.com/StackExchange/dapper-dot-net"
}
},
"version": "1.50.2-*",
"authors": [ "Sam Saffron", "Marc Gravell", "Nick Craver" ],
"description": "A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..",
"title": "Dapper dot net",
"copyright": "2016 Stack Exchange, Inc.",
"dependencies": {
},
"buildOptions": {
"xmlDoc": true,
"warningsAsErrors": true
},
"frameworks": {
"net40": {
"frameworkAssemblies": {
"System.Data": "4.0.0.0",
"System.Xml": "4.0.0.0",
"System.Xml.Linq": "4.0.0.0"
}
},
"net45": {
"buildOptions": {
"define": [ "ASYNC" ]
},
"frameworkAssemblies": {
"System.Data": "4.0.0.0",
"System.Xml": "4.0.0.0",
"System.Xml.Linq": "4.0.0.0"
}
},
"net451": {
"buildOptions": {
"define": [ "ASYNC" ]
},
"frameworkAssemblies": {
"System.Data": "4.0.0.0",
"System.Xml": "4.0.0.0",
"System.Xml.Linq": "4.0.0.0"
}
},
"netstandard1.3": {
"buildOptions": {
"define": [ "ASYNC", "COREFX" ]
},
"dependencies": {
"System.Collections": "4.0.11",
"System.Collections.Concurrent": "4.0.12",
"System.Collections.NonGeneric": "4.0.1",
"System.Data.SqlClient": "4.1.0",
"System.Dynamic.Runtime": "4.0.11",
"System.Linq": "4.1.0",
"System.Reflection": "4.1.0",
"System.Reflection.Emit": "4.0.1",
"System.Reflection.Emit.Lightweight": "4.0.1",
"System.Reflection.Extensions": "4.0.1",
"System.Reflection.TypeExtensions": "4.1.0",
"System.Runtime": "4.1.0",
"System.Runtime.Extensions": "4.1.0",
"System.Runtime.InteropServices": "4.1.0",
"System.Text.RegularExpressions": "4.1.0",
"System.Threading": "4.0.11",
"System.Xml.XDocument": "4.0.11",
"System.Xml.XmlDocument": "4.0.1"
}
}
}
}

View File

@@ -0,0 +1,254 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using DapperExtensions.Sql;
using DapperExtensions.Mapper;
namespace DapperExtensions
{
public static class DapperExtensions
{
private readonly static object _lock = new object();
private static Func<IDapperExtensionsConfiguration, IDapperImplementor> _instanceFactory;
private static IDapperImplementor _instance;
private static IDapperExtensionsConfiguration _configuration;
/// <summary>
/// Gets or sets the default class mapper to use when generating class maps. If not specified, AutoClassMapper<T> is used.
/// DapperExtensions.Configure(Type, IList<Assembly>, ISqlDialect) can be used instead to set all values at once
/// </summary>
public static Type DefaultMapper
{
get
{
return _configuration.DefaultMapper;
}
set
{
Configure(value, _configuration.MappingAssemblies, _configuration.Dialect);
}
}
/// <summary>
/// Gets or sets the type of sql to be generated.
/// DapperExtensions.Configure(Type, IList<Assembly>, ISqlDialect) can be used instead to set all values at once
/// </summary>
public static ISqlDialect SqlDialect
{
get
{
return _configuration.Dialect;
}
set
{
Configure(_configuration.DefaultMapper, _configuration.MappingAssemblies, value);
}
}
/// <summary>
/// Get or sets the Dapper Extensions Implementation Factory.
/// </summary>
public static Func<IDapperExtensionsConfiguration, IDapperImplementor> InstanceFactory
{
get
{
if (_instanceFactory == null)
{
_instanceFactory = config => new DapperImplementor(new SqlGeneratorImpl(config));
}
return _instanceFactory;
}
set
{
_instanceFactory = value;
Configure(_configuration.DefaultMapper, _configuration.MappingAssemblies, _configuration.Dialect);
}
}
/// <summary>
/// Gets the Dapper Extensions Implementation
/// </summary>
private static IDapperImplementor Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = InstanceFactory(_configuration);
}
}
}
return _instance;
}
}
static DapperExtensions()
{
Configure(typeof(AutoClassMapper<>), new List<Assembly>(), new MySqlDialect()); // 设置默认数据库类型
}
/// <summary>
/// Add other assemblies that Dapper Extensions will search if a mapping is not found in the same assembly of the POCO.
/// </summary>
/// <param name="assemblies"></param>
public static void SetMappingAssemblies(IList<Assembly> assemblies)
{
Configure(_configuration.DefaultMapper, assemblies, _configuration.Dialect);
}
/// <summary>
/// Configure DapperExtensions extension methods.
/// </summary>
/// <param name="defaultMapper"></param>
/// <param name="mappingAssemblies"></param>
/// <param name="sqlDialect"></param>
public static void Configure(IDapperExtensionsConfiguration configuration)
{
_instance = null;
_configuration = configuration;
}
/// <summary>
/// Configure DapperExtensions extension methods.
/// </summary>
/// <param name="defaultMapper"></param>
/// <param name="mappingAssemblies"></param>
/// <param name="sqlDialect"></param>
public static void Configure(Type defaultMapper, IList<Assembly> mappingAssemblies, ISqlDialect sqlDialect)
{
Configure(new DapperExtensionsConfiguration(defaultMapper, mappingAssemblies, sqlDialect));
}
/// <summary>
/// Executes a query for the specified id, returning the data typed as per T
/// </summary>
public static T Get<T>(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
var result = Instance.Get<T>(connection, id, transaction, commandTimeout);
return (T)result;
}
/// <summary>
/// Executes an insert query for the specified entity.
/// </summary>
public static void Insert<T>(this IDbConnection connection, IEnumerable<T> entities, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
Instance.Insert<T>(connection, entities, transaction, commandTimeout);
}
/// <summary>
/// Executes an insert query for the specified entity, returning the primary key.
/// If the entity has a single key, just the value is returned.
/// If the entity has a composite key, an IDictionary&lt;string, object&gt; is returned with the key values.
/// The key value for the entity will also be updated if the KeyType is a Guid or Identity.
/// </summary>
public static dynamic Insert<T>(this IDbConnection connection, T entity, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
return Instance.Insert<T>(connection, entity, transaction, commandTimeout);
}
/// <summary>
/// Executes an update query for the specified entity.
/// </summary>
public static bool Update<T>(this IDbConnection connection, T entity, IDbTransaction transaction = null, int? commandTimeout = null, bool ignoreAllKeyProperties = false) where T : class
{
return Instance.Update<T>(connection, entity, transaction, commandTimeout, ignoreAllKeyProperties);
}
/// <summary>
/// Executes a delete query for the specified entity.
/// </summary>
public static bool Delete<T>(this IDbConnection connection, T entity, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
return Instance.Delete<T>(connection, entity, transaction, commandTimeout);
}
/// <summary>
/// Executes a delete query using the specified predicate.
/// </summary>
public static bool Delete<T>(this IDbConnection connection, object predicate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
return Instance.Delete<T>(connection, predicate, transaction, commandTimeout);
}
/// <summary>
/// Executes a select query using the specified predicate, returning an IEnumerable data typed as per T.
/// </summary>
public static IEnumerable<T> GetList<T>(this IDbConnection connection, object predicate = null, IList<ISort> sort = null, IDbTransaction transaction = null, int? commandTimeout = null, bool buffered = false) where T : class
{
return Instance.GetList<T>(connection, predicate, sort, transaction, commandTimeout, buffered);
}
/// <summary>
/// Executes a select query using the specified predicate, returning an IEnumerable data typed as per T.
/// Data returned is dependent upon the specified page and resultsPerPage.
/// </summary>
public static IEnumerable<T> GetPage<T>(this IDbConnection connection, object predicate, IList<ISort> sort, int page, int resultsPerPage, IDbTransaction transaction = null, int? commandTimeout = null, bool buffered = false) where T : class
{
return Instance.GetPage<T>(connection, predicate, sort, page, resultsPerPage, transaction, commandTimeout, buffered);
}
/// <summary>
/// Executes a select query using the specified predicate, returning an IEnumerable data typed as per T.
/// Data returned is dependent upon the specified firstResult and maxResults.
/// </summary>
public static IEnumerable<T> GetSet<T>(this IDbConnection connection, object predicate, IList<ISort> sort, int firstResult, int maxResults, IDbTransaction transaction = null, int? commandTimeout = null, bool buffered = false) where T : class
{
return Instance.GetSet<T>(connection, predicate, sort, firstResult, maxResults, transaction, commandTimeout, buffered);
}
/// <summary>
/// Executes a query using the specified predicate, returning an integer that represents the number of rows that match the query.
/// </summary>
public static int Count<T>(this IDbConnection connection, object predicate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
{
return Instance.Count<T>(connection, predicate, transaction, commandTimeout);
}
/// <summary>
/// Executes a select query for multiple objects, returning IMultipleResultReader for each predicate.
/// </summary>
public static IMultipleResultReader GetMultiple(this IDbConnection connection, GetMultiplePredicate predicate, IDbTransaction transaction = null, int? commandTimeout = null)
{
return Instance.GetMultiple(connection, predicate, transaction, commandTimeout);
}
/// <summary>
/// Gets the appropriate mapper for the specified type T.
/// If the mapper for the type is not yet created, a new mapper is generated from the mapper type specifed by DefaultMapper.
/// </summary>
public static IClassMapper GetMap<T>() where T : class
{
return Instance.SqlGenerator.Configuration.GetMap<T>();
}
/// <summary>
/// Clears the ClassMappers for each type.
/// </summary>
public static void ClearCache()
{
Instance.SqlGenerator.Configuration.ClearCache();
}
/// <summary>
/// Generates a COMB Guid which solves the fragmented index issue.
/// See: http://davybrion.com/blog/2009/05/using-the-guidcomb-identifier-strategy
/// </summary>
public static Guid GetNextGuid()
{
return Instance.SqlGenerator.Configuration.GetNextGuid();
}
}
}

View File

@@ -0,0 +1,119 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using DapperExtensions.Mapper;
using DapperExtensions.Sql;
namespace DapperExtensions
{
public interface IDapperExtensionsConfiguration
{
Type DefaultMapper { get; }
IList<Assembly> MappingAssemblies { get; }
ISqlDialect Dialect { get; }
IClassMapper GetMap(Type entityType);
IClassMapper GetMap<T>() where T : class;
void ClearCache();
Guid GetNextGuid();
}
public class DapperExtensionsConfiguration : IDapperExtensionsConfiguration
{
private readonly ConcurrentDictionary<Type, IClassMapper> _classMaps = new ConcurrentDictionary<Type, IClassMapper>();
public DapperExtensionsConfiguration()
: this(typeof(AutoClassMapper<>), new List<Assembly>(), new SqlServerDialect())
{
}
public DapperExtensionsConfiguration(Type defaultMapper, IList<Assembly> mappingAssemblies, ISqlDialect sqlDialect)
{
DefaultMapper = defaultMapper;
MappingAssemblies = mappingAssemblies ?? new List<Assembly>();
Dialect = sqlDialect;
}
public Type DefaultMapper { get; private set; }
public IList<Assembly> MappingAssemblies { get; private set; }
public ISqlDialect Dialect { get; private set; }
public IClassMapper GetMap(Type entityType)
{
IClassMapper map;
if (!_classMaps.TryGetValue(entityType, out map))
{
Type mapType = GetMapType(entityType);
if (mapType == null)
{
mapType = DefaultMapper.MakeGenericType(entityType);
}
map = Activator.CreateInstance(mapType) as IClassMapper;
_classMaps[entityType] = map;
}
return map;
}
public IClassMapper GetMap<T>() where T : class
{
return GetMap(typeof (T));
}
public void ClearCache()
{
_classMaps.Clear();
}
public Guid GetNextGuid()
{
byte[] b = Guid.NewGuid().ToByteArray();
DateTime dateTime = new DateTime(1900, 1, 1);
DateTime now = DateTime.Now;
TimeSpan timeSpan = new TimeSpan(now.Ticks - dateTime.Ticks);
TimeSpan timeOfDay = now.TimeOfDay;
byte[] bytes1 = BitConverter.GetBytes(timeSpan.Days);
byte[] bytes2 = BitConverter.GetBytes((long)(timeOfDay.TotalMilliseconds / 3.333333));
Array.Reverse(bytes1);
Array.Reverse(bytes2);
Array.Copy(bytes1, bytes1.Length - 2, b, b.Length - 6, 2);
Array.Copy(bytes2, bytes2.Length - 4, b, b.Length - 4, 4);
return new Guid(b);
}
protected virtual Type GetMapType(Type entityType)
{
Func<Assembly, Type> getType = a =>
{
Type[] types = a.GetTypes();
return (from type in types
let interfaceType = type.GetInterface(typeof(IClassMapper<>).FullName)
where
interfaceType != null &&
interfaceType.GetGenericArguments()[0] == entityType
select type).SingleOrDefault();
};
Type result = getType(entityType.Assembly);
if (result != null)
{
return result;
}
foreach (var mappingAssembly in MappingAssemblies)
{
result = getType(mappingAssembly);
if (result != null)
{
return result;
}
}
return getType(entityType.Assembly);
}
}
}

View File

@@ -0,0 +1,478 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Text;
using Dapper;
using DapperExtensions.Mapper;
using DapperExtensions.Sql;
namespace DapperExtensions
{
public interface IDapperImplementor
{
ISqlGenerator SqlGenerator { get; }
T Get<T>(IDbConnection connection, dynamic id, IDbTransaction transaction, int? commandTimeout) where T : class;
void Insert<T>(IDbConnection connection, IEnumerable<T> entities, IDbTransaction transaction, int? commandTimeout) where T : class;
dynamic Insert<T>(IDbConnection connection, T entity, IDbTransaction transaction, int? commandTimeout) where T : class;
bool Update<T>(IDbConnection connection, T entity, IDbTransaction transaction, int? commandTimeout, bool ignoreAllKeyProperties) where T : class;
bool Delete<T>(IDbConnection connection, T entity, IDbTransaction transaction, int? commandTimeout) where T : class;
bool Delete<T>(IDbConnection connection, object predicate, IDbTransaction transaction, int? commandTimeout) where T : class;
IEnumerable<T> GetList<T>(IDbConnection connection, object predicate, IList<ISort> sort, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class;
IEnumerable<T> GetPage<T>(IDbConnection connection, object predicate, IList<ISort> sort, int page, int resultsPerPage, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class;
IEnumerable<T> GetSet<T>(IDbConnection connection, object predicate, IList<ISort> sort, int firstResult, int maxResults, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class;
int Count<T>(IDbConnection connection, object predicate, IDbTransaction transaction, int? commandTimeout) where T : class;
IMultipleResultReader GetMultiple(IDbConnection connection, GetMultiplePredicate predicate, IDbTransaction transaction, int? commandTimeout);
}
public class DapperImplementor : IDapperImplementor
{
public DapperImplementor(ISqlGenerator sqlGenerator)
{
SqlGenerator = sqlGenerator;
}
public ISqlGenerator SqlGenerator { get; private set; }
public T Get<T>(IDbConnection connection, dynamic id, IDbTransaction transaction, int? commandTimeout) where T : class
{
IClassMapper classMap = SqlGenerator.Configuration.GetMap<T>();
IPredicate predicate = GetIdPredicate(classMap, id);
T result = GetList<T>(connection, classMap, predicate, null, transaction, commandTimeout, true).SingleOrDefault();
return result;
}
public void Insert<T>(IDbConnection connection, IEnumerable<T> entities, IDbTransaction transaction, int? commandTimeout) where T : class
{
IEnumerable<PropertyInfo> properties = null;
IClassMapper classMap = SqlGenerator.Configuration.GetMap<T>();
var notKeyProperties = classMap.Properties.Where(p => p.KeyType != KeyType.NotAKey);
var triggerIdentityColumn = classMap.Properties.SingleOrDefault(p => p.KeyType == KeyType.TriggerIdentity);
var parameters = new List<DynamicParameters>();
if (triggerIdentityColumn != null)
{
properties = typeof (T).GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.Name != triggerIdentityColumn.PropertyInfo.Name);
}
foreach (var e in entities)
{
foreach (var column in notKeyProperties)
{
if (column.KeyType == KeyType.Guid && (Guid)column.PropertyInfo.GetValue(e, null) == Guid.Empty)
{
Guid comb = SqlGenerator.Configuration.GetNextGuid();
column.PropertyInfo.SetValue(e, comb, null);
}
}
if (triggerIdentityColumn != null)
{
var dynamicParameters = new DynamicParameters();
foreach (var prop in properties)
{
dynamicParameters.Add(prop.Name, prop.GetValue(e, null));
}
// defaultValue need for identify type of parameter
var defaultValue = typeof(T).GetProperty(triggerIdentityColumn.PropertyInfo.Name).GetValue(e, null);
dynamicParameters.Add("IdOutParam", direction: ParameterDirection.Output, value: defaultValue);
parameters.Add(dynamicParameters);
}
}
string sql = SqlGenerator.Insert(classMap);
if (triggerIdentityColumn == null)
{
connection.Execute(sql, entities, transaction, commandTimeout, CommandType.Text);
}
else
{
connection.Execute(sql, parameters, transaction, commandTimeout, CommandType.Text);
}
}
public dynamic Insert<T>(IDbConnection connection, T entity, IDbTransaction transaction, int? commandTimeout) where T : class
{
IClassMapper classMap = SqlGenerator.Configuration.GetMap<T>();
List<IPropertyMap> nonIdentityKeyProperties = classMap.Properties.Where(p => p.KeyType == KeyType.Guid || p.KeyType == KeyType.Assigned).ToList();
var identityColumn = classMap.Properties.SingleOrDefault(p => p.KeyType == KeyType.Identity);
var triggerIdentityColumn = classMap.Properties.SingleOrDefault(p => p.KeyType == KeyType.TriggerIdentity);
foreach (var column in nonIdentityKeyProperties)
{
if (column.KeyType == KeyType.Guid && (Guid)column.PropertyInfo.GetValue(entity, null) == Guid.Empty)
{
Guid comb = SqlGenerator.Configuration.GetNextGuid();
column.PropertyInfo.SetValue(entity, comb, null);
}
}
IDictionary<string, object> keyValues = new ExpandoObject();
string sql = SqlGenerator.Insert(classMap);
if (identityColumn != null)
{
IEnumerable<long> result;
if (SqlGenerator.SupportsMultipleStatements())
{
sql += SqlGenerator.Configuration.Dialect.BatchSeperator + SqlGenerator.IdentitySql(classMap);
result = connection.Query<long>(sql, entity, transaction, false, commandTimeout, CommandType.Text);
}
else
{
connection.Execute(sql, entity, transaction, commandTimeout, CommandType.Text);
sql = SqlGenerator.IdentitySql(classMap);
result = connection.Query<long>(sql, entity, transaction, false, commandTimeout, CommandType.Text);
}
// We are only interested in the first identity, but we are iterating over all resulting items (if any).
// This makes sure that ADO.NET drivers (like MySql) won't actively terminate the query.
bool hasResult = false;
int identityInt = 0;
foreach (var identityValue in result)
{
if (hasResult)
{
continue;
}
identityInt = Convert.ToInt32(identityValue);
hasResult = true;
}
if (!hasResult)
{
throw new InvalidOperationException("The source sequence is empty.");
}
keyValues.Add(identityColumn.Name, identityInt);
identityColumn.PropertyInfo.SetValue(entity, identityInt, null);
}
else if (triggerIdentityColumn != null)
{
var dynamicParameters = new DynamicParameters();
foreach (var prop in entity.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.Name != triggerIdentityColumn.PropertyInfo.Name))
{
dynamicParameters.Add(prop.Name, prop.GetValue(entity, null));
}
// defaultValue need for identify type of parameter
var defaultValue = entity.GetType().GetProperty(triggerIdentityColumn.PropertyInfo.Name).GetValue(entity, null);
dynamicParameters.Add("IdOutParam", direction: ParameterDirection.Output, value: defaultValue);
connection.Execute(sql, dynamicParameters, transaction, commandTimeout, CommandType.Text);
var value = dynamicParameters.Get<object>(SqlGenerator.Configuration.Dialect.ParameterPrefix + "IdOutParam");
keyValues.Add(triggerIdentityColumn.Name, value);
triggerIdentityColumn.PropertyInfo.SetValue(entity, value, null);
}
else
{
connection.Execute(sql, entity, transaction, commandTimeout, CommandType.Text);
}
foreach (var column in nonIdentityKeyProperties)
{
keyValues.Add(column.Name, column.PropertyInfo.GetValue(entity, null));
}
if (keyValues.Count == 1)
{
return keyValues.First().Value;
}
return keyValues;
}
public bool Update<T>(IDbConnection connection, T entity, IDbTransaction transaction, int? commandTimeout, bool ignoreAllKeyProperties = false) where T : class
{
IClassMapper classMap = SqlGenerator.Configuration.GetMap<T>();
IPredicate predicate = GetKeyPredicate<T>(classMap, entity);
Dictionary<string, object> parameters = new Dictionary<string, object>();
string sql = SqlGenerator.Update(classMap, predicate, parameters, ignoreAllKeyProperties);
DynamicParameters dynamicParameters = new DynamicParameters();
var columns = ignoreAllKeyProperties
? classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly) && p.KeyType == KeyType.NotAKey)
: classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly || p.KeyType == KeyType.Identity || p.KeyType == KeyType.Assigned));
foreach (var property in ReflectionHelper.GetObjectValues(entity).Where(property => columns.Any(c => c.Name == property.Key)))
{
dynamicParameters.Add(property.Key, property.Value);
}
foreach (var parameter in parameters)
{
dynamicParameters.Add(parameter.Key, parameter.Value);
}
return connection.Execute(sql, dynamicParameters, transaction, commandTimeout, CommandType.Text) > 0;
}
public bool Delete<T>(IDbConnection connection, T entity, IDbTransaction transaction, int? commandTimeout) where T : class
{
IClassMapper classMap = SqlGenerator.Configuration.GetMap<T>();
IPredicate predicate = GetKeyPredicate<T>(classMap, entity);
return Delete<T>(connection, classMap, predicate, transaction, commandTimeout);
}
public bool Delete<T>(IDbConnection connection, object predicate, IDbTransaction transaction, int? commandTimeout) where T : class
{
IClassMapper classMap = SqlGenerator.Configuration.GetMap<T>();
IPredicate wherePredicate = GetPredicate(classMap, predicate);
return Delete<T>(connection, classMap, wherePredicate, transaction, commandTimeout);
}
public IEnumerable<T> GetList<T>(IDbConnection connection, object predicate, IList<ISort> sort, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class
{
IClassMapper classMap = SqlGenerator.Configuration.GetMap<T>();
IPredicate wherePredicate = GetPredicate(classMap, predicate);
return GetList<T>(connection, classMap, wherePredicate, sort, transaction, commandTimeout, buffered);
}
public IEnumerable<T> GetPage<T>(IDbConnection connection, object predicate, IList<ISort> sort, int page, int resultsPerPage, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class
{
IClassMapper classMap = SqlGenerator.Configuration.GetMap<T>();
IPredicate wherePredicate = GetPredicate(classMap, predicate);
return GetPage<T>(connection, classMap, wherePredicate, sort, page, resultsPerPage, transaction, commandTimeout, buffered);
}
public IEnumerable<T> GetSet<T>(IDbConnection connection, object predicate, IList<ISort> sort, int firstResult, int maxResults, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class
{
IClassMapper classMap = SqlGenerator.Configuration.GetMap<T>();
IPredicate wherePredicate = GetPredicate(classMap, predicate);
return GetSet<T>(connection, classMap, wherePredicate, sort, firstResult, maxResults, transaction, commandTimeout, buffered);
}
public int Count<T>(IDbConnection connection, object predicate, IDbTransaction transaction, int? commandTimeout) where T : class
{
IClassMapper classMap = SqlGenerator.Configuration.GetMap<T>();
IPredicate wherePredicate = GetPredicate(classMap, predicate);
Dictionary<string, object> parameters = new Dictionary<string, object>();
string sql = SqlGenerator.Count(classMap, wherePredicate, parameters);
DynamicParameters dynamicParameters = new DynamicParameters();
foreach (var parameter in parameters)
{
dynamicParameters.Add(parameter.Key, parameter.Value);
}
return (int)connection.Query(sql, dynamicParameters, transaction, false, commandTimeout, CommandType.Text).Single().Total;
}
public IMultipleResultReader GetMultiple(IDbConnection connection, GetMultiplePredicate predicate, IDbTransaction transaction, int? commandTimeout)
{
if (SqlGenerator.SupportsMultipleStatements())
{
return GetMultipleByBatch(connection, predicate, transaction, commandTimeout);
}
return GetMultipleBySequence(connection, predicate, transaction, commandTimeout);
}
protected IEnumerable<T> GetList<T>(IDbConnection connection, IClassMapper classMap, IPredicate predicate, IList<ISort> sort, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
string sql = SqlGenerator.Select(classMap, predicate, sort, parameters);
DynamicParameters dynamicParameters = new DynamicParameters();
foreach (var parameter in parameters)
{
dynamicParameters.Add(parameter.Key, parameter.Value);
}
return connection.Query<T>(sql, dynamicParameters, transaction, buffered, commandTimeout, CommandType.Text);
}
protected IEnumerable<T> GetPage<T>(IDbConnection connection, IClassMapper classMap, IPredicate predicate, IList<ISort> sort, int page, int resultsPerPage, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
string sql = SqlGenerator.SelectPaged(classMap, predicate, sort, page, resultsPerPage, parameters);
DynamicParameters dynamicParameters = new DynamicParameters();
foreach (var parameter in parameters)
{
dynamicParameters.Add(parameter.Key, parameter.Value);
}
return connection.Query<T>(sql, dynamicParameters, transaction, buffered, commandTimeout, CommandType.Text);
}
protected IEnumerable<T> GetSet<T>(IDbConnection connection, IClassMapper classMap, IPredicate predicate, IList<ISort> sort, int firstResult, int maxResults, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
string sql = SqlGenerator.SelectSet(classMap, predicate, sort, firstResult, maxResults, parameters);
DynamicParameters dynamicParameters = new DynamicParameters();
foreach (var parameter in parameters)
{
dynamicParameters.Add(parameter.Key, parameter.Value);
}
return connection.Query<T>(sql, dynamicParameters, transaction, buffered, commandTimeout, CommandType.Text);
}
protected bool Delete<T>(IDbConnection connection, IClassMapper classMap, IPredicate predicate, IDbTransaction transaction, int? commandTimeout) where T : class
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
string sql = SqlGenerator.Delete(classMap, predicate, parameters);
DynamicParameters dynamicParameters = new DynamicParameters();
foreach (var parameter in parameters)
{
dynamicParameters.Add(parameter.Key, parameter.Value);
}
return connection.Execute(sql, dynamicParameters, transaction, commandTimeout, CommandType.Text) > 0;
}
protected IPredicate GetPredicate(IClassMapper classMap, object predicate)
{
IPredicate wherePredicate = predicate as IPredicate;
if (wherePredicate == null && predicate != null)
{
wherePredicate = GetEntityPredicate(classMap, predicate);
}
return wherePredicate;
}
protected IPredicate GetIdPredicate(IClassMapper classMap, object id)
{
bool isSimpleType = ReflectionHelper.IsSimpleType(id.GetType());
var keys = classMap.Properties.Where(p => p.KeyType != KeyType.NotAKey);
IDictionary<string, object> paramValues = null;
IList<IPredicate> predicates = new List<IPredicate>();
if (!isSimpleType)
{
paramValues = ReflectionHelper.GetObjectValues(id);
}
foreach (var key in keys)
{
object value = id;
if (!isSimpleType)
{
value = paramValues[key.Name];
}
Type predicateType = typeof(FieldPredicate<>).MakeGenericType(classMap.EntityType);
IFieldPredicate fieldPredicate = Activator.CreateInstance(predicateType) as IFieldPredicate;
fieldPredicate.Not = false;
fieldPredicate.Operator = Operator.Eq;
fieldPredicate.PropertyName = key.Name;
fieldPredicate.Value = value;
predicates.Add(fieldPredicate);
}
return predicates.Count == 1
? predicates[0]
: new PredicateGroup
{
Operator = GroupOperator.And,
Predicates = predicates
};
}
protected IPredicate GetKeyPredicate<T>(IClassMapper classMap, T entity) where T : class
{
var whereFields = classMap.Properties.Where(p => p.KeyType != KeyType.NotAKey);
if (!whereFields.Any())
{
throw new ArgumentException("At least one Key column must be defined.");
}
IList<IPredicate> predicates = (from field in whereFields
select new FieldPredicate<T>
{
Not = false,
Operator = Operator.Eq,
PropertyName = field.Name,
Value = field.PropertyInfo.GetValue(entity, null)
}).Cast<IPredicate>().ToList();
return predicates.Count == 1
? predicates[0]
: new PredicateGroup
{
Operator = GroupOperator.And,
Predicates = predicates
};
}
protected IPredicate GetEntityPredicate(IClassMapper classMap, object entity)
{
Type predicateType = typeof(FieldPredicate<>).MakeGenericType(classMap.EntityType);
IList<IPredicate> predicates = new List<IPredicate>();
foreach (var kvp in ReflectionHelper.GetObjectValues(entity))
{
IFieldPredicate fieldPredicate = Activator.CreateInstance(predicateType) as IFieldPredicate;
fieldPredicate.Not = false;
fieldPredicate.Operator = Operator.Eq;
fieldPredicate.PropertyName = kvp.Key;
fieldPredicate.Value = kvp.Value;
predicates.Add(fieldPredicate);
}
return predicates.Count == 1
? predicates[0]
: new PredicateGroup
{
Operator = GroupOperator.And,
Predicates = predicates
};
}
protected GridReaderResultReader GetMultipleByBatch(IDbConnection connection, GetMultiplePredicate predicate, IDbTransaction transaction, int? commandTimeout)
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
StringBuilder sql = new StringBuilder();
foreach (var item in predicate.Items)
{
IClassMapper classMap = SqlGenerator.Configuration.GetMap(item.Type);
IPredicate itemPredicate = item.Value as IPredicate;
if (itemPredicate == null && item.Value != null)
{
itemPredicate = GetPredicate(classMap, item.Value);
}
sql.AppendLine(SqlGenerator.Select(classMap, itemPredicate, item.Sort, parameters) + SqlGenerator.Configuration.Dialect.BatchSeperator);
}
DynamicParameters dynamicParameters = new DynamicParameters();
foreach (var parameter in parameters)
{
dynamicParameters.Add(parameter.Key, parameter.Value);
}
SqlMapper.GridReader grid = connection.QueryMultiple(sql.ToString(), dynamicParameters, transaction, commandTimeout, CommandType.Text);
return new GridReaderResultReader(grid);
}
protected SequenceReaderResultReader GetMultipleBySequence(IDbConnection connection, GetMultiplePredicate predicate, IDbTransaction transaction, int? commandTimeout)
{
IList<SqlMapper.GridReader> items = new List<SqlMapper.GridReader>();
foreach (var item in predicate.Items)
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
IClassMapper classMap = SqlGenerator.Configuration.GetMap(item.Type);
IPredicate itemPredicate = item.Value as IPredicate;
if (itemPredicate == null && item.Value != null)
{
itemPredicate = GetPredicate(classMap, item.Value);
}
string sql = SqlGenerator.Select(classMap, itemPredicate, item.Sort, parameters);
DynamicParameters dynamicParameters = new DynamicParameters();
foreach (var parameter in parameters)
{
dynamicParameters.Add(parameter.Key, parameter.Value);
}
SqlMapper.GridReader queryResult = connection.QueryMultiple(sql, dynamicParameters, transaction, commandTimeout, CommandType.Text);
items.Add(queryResult);
}
return new SequenceReaderResultReader(items);
}
}
}

View File

@@ -0,0 +1,268 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using DapperExtensions.Mapper;
using DapperExtensions.Sql;
namespace DapperExtensions
{
public interface IDatabase : IDisposable
{
bool HasActiveTransaction { get; }
IDbConnection Connection { get; }
void BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted);
void Commit();
void Rollback();
void RunInTransaction(Action action);
T RunInTransaction<T>(Func<T> func);
T Get<T>(dynamic id, IDbTransaction transaction, int? commandTimeout = null) where T : class;
T Get<T>(dynamic id, int? commandTimeout = null) where T : class;
void Insert<T>(IEnumerable<T> entities, IDbTransaction transaction, int? commandTimeout = null) where T : class;
void Insert<T>(IEnumerable<T> entities, int? commandTimeout = null) where T : class;
dynamic Insert<T>(T entity, IDbTransaction transaction, int? commandTimeout = null) where T : class;
dynamic Insert<T>(T entity, int? commandTimeout = null) where T : class;
bool Update<T>(T entity, IDbTransaction transaction, int? commandTimeout = null, bool ignoreAllKeyProperties = false) where T : class;
bool Update<T>(T entity, int? commandTimeout = null, bool ignoreAllKeyProperties = false) where T : class;
bool Delete<T>(T entity, IDbTransaction transaction, int? commandTimeout = null) where T : class;
bool Delete<T>(T entity, int? commandTimeout = null) where T : class;
bool Delete<T>(object predicate, IDbTransaction transaction, int? commandTimeout = null) where T : class;
bool Delete<T>(object predicate, int? commandTimeout = null) where T : class;
IEnumerable<T> GetList<T>(object predicate, IList<ISort> sort, IDbTransaction transaction, int? commandTimeout = null, bool buffered = true) where T : class;
IEnumerable<T> GetList<T>(object predicate = null, IList<ISort> sort = null, int? commandTimeout = null, bool buffered = true) where T : class;
IEnumerable<T> GetPage<T>(object predicate, IList<ISort> sort, int page, int resultsPerPage, IDbTransaction transaction, int? commandTimeout = null, bool buffered = true) where T : class;
IEnumerable<T> GetPage<T>(object predicate, IList<ISort> sort, int page, int resultsPerPage, int? commandTimeout = null, bool buffered = true) where T : class;
IEnumerable<T> GetSet<T>(object predicate, IList<ISort> sort, int firstResult, int maxResults, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class;
IEnumerable<T> GetSet<T>(object predicate, IList<ISort> sort, int firstResult, int maxResults, int? commandTimeout, bool buffered) where T : class;
int Count<T>(object predicate, IDbTransaction transaction, int? commandTimeout = null) where T : class;
int Count<T>(object predicate, int? commandTimeout = null) where T : class;
IMultipleResultReader GetMultiple(GetMultiplePredicate predicate, IDbTransaction transaction, int? commandTimeout = null);
IMultipleResultReader GetMultiple(GetMultiplePredicate predicate, int? commandTimeout = null);
void ClearCache();
Guid GetNextGuid();
IClassMapper GetMap<T>() where T : class;
}
public class Database : IDatabase
{
private readonly IDapperImplementor _dapper;
private IDbTransaction _transaction;
public Database(IDbConnection connection, ISqlGenerator sqlGenerator)
{
_dapper = new DapperImplementor(sqlGenerator);
Connection = connection;
if (Connection.State != ConnectionState.Open)
{
Connection.Open();
}
}
public bool HasActiveTransaction
{
get
{
return _transaction != null;
}
}
public IDbConnection Connection { get; private set; }
public void Dispose()
{
if (Connection.State != ConnectionState.Closed)
{
if (_transaction != null)
{
_transaction.Rollback();
}
Connection.Close();
}
}
public void BeginTransaction(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
{
_transaction = Connection.BeginTransaction(isolationLevel);
}
public void Commit()
{
_transaction.Commit();
_transaction = null;
}
public void Rollback()
{
_transaction.Rollback();
_transaction = null;
}
public void RunInTransaction(Action action)
{
BeginTransaction();
try
{
action();
Commit();
}
catch (Exception ex)
{
if (HasActiveTransaction)
{
Rollback();
}
throw ex;
}
}
public T RunInTransaction<T>(Func<T> func)
{
BeginTransaction();
try
{
T result = func();
Commit();
return result;
}
catch (Exception ex)
{
if (HasActiveTransaction)
{
Rollback();
}
throw ex;
}
}
public T Get<T>(dynamic id, IDbTransaction transaction, int? commandTimeout) where T : class
{
return (T)_dapper.Get<T>(Connection, id, transaction, commandTimeout);
}
public T Get<T>(dynamic id, int? commandTimeout) where T : class
{
return (T)_dapper.Get<T>(Connection, id, _transaction, commandTimeout);
}
public void Insert<T>(IEnumerable<T> entities, IDbTransaction transaction, int? commandTimeout) where T : class
{
_dapper.Insert<T>(Connection, entities, transaction, commandTimeout);
}
public void Insert<T>(IEnumerable<T> entities, int? commandTimeout) where T : class
{
_dapper.Insert<T>(Connection, entities, _transaction, commandTimeout);
}
public dynamic Insert<T>(T entity, IDbTransaction transaction, int? commandTimeout) where T : class
{
return _dapper.Insert<T>(Connection, entity, transaction, commandTimeout);
}
public dynamic Insert<T>(T entity, int? commandTimeout) where T : class
{
return _dapper.Insert<T>(Connection, entity, _transaction, commandTimeout);
}
public bool Update<T>(T entity, IDbTransaction transaction, int? commandTimeout, bool ignoreAllKeyProperties) where T : class
{
return _dapper.Update<T>(Connection, entity, transaction, commandTimeout, ignoreAllKeyProperties);
}
public bool Update<T>(T entity, int? commandTimeout, bool ignoreAllKeyProperties) where T : class
{
return _dapper.Update<T>(Connection, entity, _transaction, commandTimeout, ignoreAllKeyProperties);
}
public bool Delete<T>(T entity, IDbTransaction transaction, int? commandTimeout) where T : class
{
return _dapper.Delete(Connection, entity, transaction, commandTimeout);
}
public bool Delete<T>(T entity, int? commandTimeout) where T : class
{
return _dapper.Delete(Connection, entity, _transaction, commandTimeout);
}
public bool Delete<T>(object predicate, IDbTransaction transaction, int? commandTimeout) where T : class
{
return _dapper.Delete<T>(Connection, predicate, transaction, commandTimeout);
}
public bool Delete<T>(object predicate, int? commandTimeout) where T : class
{
return _dapper.Delete<T>(Connection, predicate, _transaction, commandTimeout);
}
public IEnumerable<T> GetList<T>(object predicate, IList<ISort> sort, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class
{
return _dapper.GetList<T>(Connection, predicate, sort, transaction, commandTimeout, buffered);
}
public IEnumerable<T> GetList<T>(object predicate, IList<ISort> sort, int? commandTimeout, bool buffered) where T : class
{
return _dapper.GetList<T>(Connection, predicate, sort, _transaction, commandTimeout, buffered);
}
public IEnumerable<T> GetPage<T>(object predicate, IList<ISort> sort, int page, int resultsPerPage, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class
{
return _dapper.GetPage<T>(Connection, predicate, sort, page, resultsPerPage, transaction, commandTimeout, buffered);
}
public IEnumerable<T> GetPage<T>(object predicate, IList<ISort> sort, int page, int resultsPerPage, int? commandTimeout, bool buffered) where T : class
{
return _dapper.GetPage<T>(Connection, predicate, sort, page, resultsPerPage, _transaction, commandTimeout, buffered);
}
public IEnumerable<T> GetSet<T>(object predicate, IList<ISort> sort, int firstResult, int maxResults, IDbTransaction transaction, int? commandTimeout, bool buffered) where T : class
{
return _dapper.GetSet<T>(Connection, predicate, sort, firstResult, maxResults, transaction, commandTimeout, buffered);
}
public IEnumerable<T> GetSet<T>(object predicate, IList<ISort> sort, int firstResult, int maxResults, int? commandTimeout, bool buffered) where T : class
{
return _dapper.GetSet<T>(Connection, predicate, sort, firstResult, maxResults, _transaction, commandTimeout, buffered);
}
public int Count<T>(object predicate, IDbTransaction transaction, int? commandTimeout) where T : class
{
return _dapper.Count<T>(Connection, predicate, transaction, commandTimeout);
}
public int Count<T>(object predicate, int? commandTimeout) where T : class
{
return _dapper.Count<T>(Connection, predicate, _transaction, commandTimeout);
}
public IMultipleResultReader GetMultiple(GetMultiplePredicate predicate, IDbTransaction transaction, int? commandTimeout)
{
return _dapper.GetMultiple(Connection, predicate, transaction, commandTimeout);
}
public IMultipleResultReader GetMultiple(GetMultiplePredicate predicate, int? commandTimeout)
{
return _dapper.GetMultiple(Connection, predicate, _transaction, commandTimeout);
}
public void ClearCache()
{
_dapper.SqlGenerator.Configuration.ClearCache();
}
public Guid GetNextGuid()
{
return _dapper.SqlGenerator.Configuration.GetNextGuid();
}
public IClassMapper GetMap<T>() where T : class
{
return _dapper.SqlGenerator.Configuration.GetMap<T>();
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DapperExtensions
{
public class GetMultiplePredicate
{
private readonly List<GetMultiplePredicateItem> _items;
public GetMultiplePredicate()
{
_items = new List<GetMultiplePredicateItem>();
}
public IEnumerable<GetMultiplePredicateItem> Items
{
get { return _items.AsReadOnly(); }
}
public void Add<T>(IPredicate predicate, IList<ISort> sort = null) where T : class
{
_items.Add(new GetMultiplePredicateItem
{
Value = predicate,
Type = typeof(T),
Sort = sort
});
}
public void Add<T>(object id) where T : class
{
_items.Add(new GetMultiplePredicateItem
{
Value = id,
Type = typeof (T)
});
}
public class GetMultiplePredicateItem
{
public object Value { get; set; }
public Type Type { get; set; }
public IList<ISort> Sort { get; set; }
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Dapper;
namespace DapperExtensions
{
public interface IMultipleResultReader
{
IEnumerable<T> Read<T>();
}
public class GridReaderResultReader : IMultipleResultReader
{
private readonly SqlMapper.GridReader _reader;
public GridReaderResultReader(SqlMapper.GridReader reader)
{
_reader = reader;
}
public IEnumerable<T> Read<T>()
{
return _reader.Read<T>();
}
}
public class SequenceReaderResultReader : IMultipleResultReader
{
private readonly Queue<SqlMapper.GridReader> _items;
public SequenceReaderResultReader(IEnumerable<SqlMapper.GridReader> items)
{
_items = new Queue<SqlMapper.GridReader>(items);
}
public IEnumerable<T> Read<T>()
{
SqlMapper.GridReader reader = _items.Dequeue();
return reader.Read<T>();
}
}
}

View File

@@ -0,0 +1,20 @@
using System.Text;
using System.Linq;
using System.Collections.Generic;
using System;
namespace DapperExtensions.Mapper
{
/// <summary>
/// Automatically maps an entity to a table using a combination of reflection and naming conventions for keys.
/// </summary>
public class AutoClassMapper<T> : ClassMapper<T> where T : class
{
public AutoClassMapper()
{
Type type = typeof(T);
Table(type.Name);
AutoMap();
}
}
}

View File

@@ -0,0 +1,171 @@
using System.Numerics;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Renci.SshNet.Common;
namespace DapperExtensions.Mapper
{
public interface IClassMapper
{
string SchemaName { get; }
string TableName { get; }
IList<IPropertyMap> Properties { get; }
Type EntityType { get; }
}
public interface IClassMapper<T> : IClassMapper where T : class
{
}
/// <summary>
/// Maps an entity to a table through a collection of property maps.
/// </summary>
public class ClassMapper<T> : IClassMapper<T> where T : class
{
/// <summary>
/// Gets or sets the schema to use when referring to the corresponding table name in the database.
/// </summary>
public string SchemaName { get; protected set; }
/// <summary>
/// Gets or sets the table to use in the database.
/// </summary>
public string TableName { get; protected set; }
/// <summary>
/// A collection of properties that will map to columns in the database table.
/// </summary>
public IList<IPropertyMap> Properties { get; private set; }
public Type EntityType
{
get { return typeof(T); }
}
public ClassMapper()
{
PropertyTypeKeyTypeMapping = new Dictionary<Type, KeyType>
{
{ typeof(byte), KeyType.Identity }, { typeof(byte?), KeyType.Identity },
{ typeof(sbyte), KeyType.Identity }, { typeof(sbyte?), KeyType.Identity },
{ typeof(short), KeyType.Identity }, { typeof(short?), KeyType.Identity },
{ typeof(ushort), KeyType.Identity }, { typeof(ushort?), KeyType.Identity },
{ typeof(int), KeyType.Identity }, { typeof(int?), KeyType.Identity },
{ typeof(uint), KeyType.Identity}, { typeof(uint?), KeyType.Identity },
{ typeof(long), KeyType.Identity }, { typeof(long?), KeyType.Identity },
{ typeof(ulong), KeyType.Identity }, { typeof(ulong?), KeyType.Identity },
{ typeof(BigInteger), KeyType.Identity }, { typeof(BigInteger?), KeyType.Identity },
{ typeof(Guid), KeyType.Guid }, { typeof(Guid?), KeyType.Guid },
};
Properties = new List<IPropertyMap>();
Table(typeof(T).Name);
}
protected Dictionary<Type, KeyType> PropertyTypeKeyTypeMapping { get; private set; }
public virtual void Schema(string schemaName)
{
SchemaName = schemaName;
}
public virtual void Table(string tableName)
{
TableName = tableName;
}
protected virtual void AutoMap()
{
AutoMap(null);
}
protected virtual void AutoMap(Func<Type, PropertyInfo, bool> canMap)
{
Type type = typeof(T);
bool hasDefinedKey = Properties.Any(p => p.KeyType != KeyType.NotAKey);
PropertyMap keyMap = null;
foreach (var propertyInfo in type.GetProperties())
{
if (Properties.Any(p => p.Name.Equals(propertyInfo.Name, StringComparison.InvariantCultureIgnoreCase)))
{
continue;
}
if ((canMap != null && !canMap(type, propertyInfo)))
{
continue;
}
PropertyMap map = Map(propertyInfo);
if (!hasDefinedKey)
{
if (string.Equals(map.PropertyInfo.Name, "id", StringComparison.InvariantCultureIgnoreCase))
{
keyMap = map;
}
if (keyMap == null && map.PropertyInfo.Name.EndsWith("id", true, CultureInfo.InvariantCulture))
{
keyMap = map;
}
}
}
if (keyMap != null)
{
keyMap.Key(PropertyTypeKeyTypeMapping.ContainsKey(keyMap.PropertyInfo.PropertyType)
? PropertyTypeKeyTypeMapping[keyMap.PropertyInfo.PropertyType]
: KeyType.Assigned);
}
}
/// <summary>
/// Fluently, maps an entity property to a column
/// </summary>
protected PropertyMap Map(Expression<Func<T, object>> expression)
{
PropertyInfo propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
return Map(propertyInfo);
}
/// <summary>
/// Fluently, maps an entity property to a column
/// </summary>
protected PropertyMap Map(PropertyInfo propertyInfo)
{
PropertyMap result = new PropertyMap(propertyInfo);
this.GuardForDuplicatePropertyMap(result);
Properties.Add(result);
return result;
}
/// <summary>
/// Removes a propertymap entry
/// </summary>
/// <param name="expression"></param>
protected void UnMap(Expression<Func<T, object>> expression)
{
var propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
var mapping = this.Properties.Where(w => w.Name == propertyInfo.Name).SingleOrDefault();
if (mapping == null)
{
throw new ApplicationException("Unable to UnMap because mapping does not exist.");
}
this.Properties.Remove(mapping);
}
private void GuardForDuplicatePropertyMap(PropertyMap result)
{
if (Properties.Any(p => p.Name.Equals(result.Name)))
{
throw new ArgumentException(string.Format("Duplicate mapping for property {0} detected.",result.Name));
}
}
}
}

View File

@@ -0,0 +1,67 @@
using System.Text;
using System.Linq;
using System.Collections.Generic;
using System;
using System.Text.RegularExpressions;
namespace DapperExtensions.Mapper
{
/// <summary>
/// Automatically maps an entity to a table using a combination of reflection and naming conventions for keys.
/// Identical to AutoClassMapper, but attempts to pluralize table names automatically.
/// Example: Person entity maps to People table
/// </summary>
public class PluralizedAutoClassMapper<T> : AutoClassMapper<T> where T : class
{
public override void Table(string tableName)
{
base.Table(Formatting.Pluralize(tableName));
}
// Adapted from: http://mattgrande.wordpress.com/2009/10/28/pluralization-helper-for-c/
public static class Formatting
{
private static readonly IList<string> Unpluralizables = new List<string> { "equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "deer" };
private static readonly IDictionary<string, string> Pluralizations = new Dictionary<string, string>
{
// Start with the rarest cases, and move to the most common
{ "person", "people" },
{ "ox", "oxen" },
{ "child", "children" },
{ "foot", "feet" },
{ "tooth", "teeth" },
{ "goose", "geese" },
// And now the more standard rules.
{ "(.*)fe?$", "$1ves" }, // ie, wolf, wife
{ "(.*)man$", "$1men" },
{ "(.+[aeiou]y)$", "$1s" },
{ "(.+[^aeiou])y$", "$1ies" },
{ "(.+z)$", "$1zes" },
{ "([m|l])ouse$", "$1ice" },
{ "(.+)(e|i)x$", @"$1ices"}, // ie, Matrix, Index
{ "(octop|vir)us$", "$1i"},
{ "(.+(s|x|sh|ch))$", @"$1es"},
{ "(.+)", @"$1s" }
};
public static string Pluralize(string singular)
{
if (Unpluralizables.Contains(singular))
return singular;
var plural = string.Empty;
foreach (var pluralization in Pluralizations)
{
if (Regex.IsMatch(singular, pluralization.Key))
{
plural = Regex.Replace(singular, pluralization.Key, pluralization.Value);
break;
}
}
return plural;
}
}
}
}

View File

@@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace DapperExtensions.Mapper
{
/// <summary>
/// Maps an entity property to its corresponding column in the database.
/// </summary>
public interface IPropertyMap
{
string Name { get; }
string ColumnName { get; }
bool Ignored { get; }
bool IsReadOnly { get; }
KeyType KeyType { get; }
PropertyInfo PropertyInfo { get; }
}
/// <summary>
/// Maps an entity property to its corresponding column in the database.
/// </summary>
public class PropertyMap : IPropertyMap
{
public PropertyMap(PropertyInfo propertyInfo)
{
PropertyInfo = propertyInfo;
ColumnName = PropertyInfo.Name;
}
/// <summary>
/// Gets the name of the property by using the specified propertyInfo.
/// </summary>
public string Name
{
get { return PropertyInfo.Name; }
}
/// <summary>
/// Gets the column name for the current property.
/// </summary>
public string ColumnName { get; private set; }
/// <summary>
/// Gets the key type for the current property.
/// </summary>
public KeyType KeyType { get; private set; }
/// <summary>
/// Gets the ignore status of the current property. If ignored, the current property will not be included in queries.
/// </summary>
public bool Ignored { get; private set; }
/// <summary>
/// Gets the read-only status of the current property. If read-only, the current property will not be included in INSERT and UPDATE queries.
/// </summary>
public bool IsReadOnly { get; private set; }
/// <summary>
/// Gets the property info for the current property.
/// </summary>
public PropertyInfo PropertyInfo { get; private set; }
/// <summary>
/// Fluently sets the column name for the property.
/// </summary>
/// <param name="columnName">The column name as it exists in the database.</param>
public PropertyMap Column(string columnName)
{
ColumnName = columnName;
return this;
}
/// <summary>
/// Fluently sets the key type of the property.
/// </summary>
/// <param name="columnName">The column name as it exists in the database.</param>
public PropertyMap Key(KeyType keyType)
{
if (Ignored)
{
throw new ArgumentException(string.Format("'{0}' is ignored and cannot be made a key field. ", Name));
}
if (IsReadOnly)
{
throw new ArgumentException(string.Format("'{0}' is readonly and cannot be made a key field. ", Name));
}
KeyType = keyType;
return this;
}
/// <summary>
/// Fluently sets the ignore status of the property.
/// </summary>
public PropertyMap Ignore()
{
if (KeyType != KeyType.NotAKey)
{
throw new ArgumentException(string.Format("'{0}' is a key field and cannot be ignored.", Name));
}
Ignored = true;
return this;
}
/// <summary>
/// Fluently sets the read-only status of the property.
/// </summary>
public PropertyMap ReadOnly()
{
if (KeyType != KeyType.NotAKey)
{
throw new ArgumentException(string.Format("'{0}' is a key field and cannot be marked readonly.", Name));
}
IsReadOnly = true;
return this;
}
}
/// <summary>
/// Used by ClassMapper to determine which entity property represents the key.
/// </summary>
public enum KeyType
{
/// <summary>
/// The property is not a key and is not automatically managed.
/// </summary>
NotAKey,
/// <summary>
/// The property is an integery-based identity generated from the database.
/// </summary>
Identity,
/// <summary>
/// The property is an identity generated by the database trigger.
/// </summary>
TriggerIdentity,
/// <summary>
/// The property is a Guid identity which is automatically managed.
/// </summary>
Guid,
/// <summary>
/// The property is a key that is not automatically managed.
/// </summary>
Assigned
}
}

View File

@@ -0,0 +1,393 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using DapperExtensions.Mapper;
using DapperExtensions.Sql;
namespace DapperExtensions
{
public static class Predicates
{
/// <summary>
/// Factory method that creates a new IFieldPredicate predicate: [FieldName] [Operator] [Value].
/// Example: WHERE FirstName = 'Foo'
/// </summary>
/// <typeparam name="T">The type of the entity.</typeparam>
/// <param name="expression">An expression that returns the left operand [FieldName].</param>
/// <param name="op">The comparison operator.</param>
/// <param name="value">The value for the predicate.</param>
/// <param name="not">Effectively inverts the comparison operator. Example: WHERE FirstName &lt;&gt; 'Foo'.</param>
/// <returns>An instance of IFieldPredicate.</returns>
public static IFieldPredicate Field<T>(Expression<Func<T, object>> expression, Operator op, object value, bool not = false) where T : class
{
PropertyInfo propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
return new FieldPredicate<T>
{
PropertyName = propertyInfo.Name,
Operator = op,
Value = value,
Not = not
};
}
/// <summary>
/// Factory method that creates a new IPropertyPredicate predicate: [FieldName1] [Operator] [FieldName2]
/// Example: WHERE FirstName = LastName
/// </summary>
/// <typeparam name="T">The type of the entity for the left operand.</typeparam>
/// <typeparam name="T2">The type of the entity for the right operand.</typeparam>
/// <param name="expression">An expression that returns the left operand [FieldName1].</param>
/// <param name="op">The comparison operator.</param>
/// <param name="expression2">An expression that returns the right operand [FieldName2].</param>
/// <param name="not">Effectively inverts the comparison operator. Example: WHERE FirstName &lt;&gt; LastName </param>
/// <returns>An instance of IPropertyPredicate.</returns>
public static IPropertyPredicate Property<T, T2>(Expression<Func<T, object>> expression, Operator op, Expression<Func<T2, object>> expression2, bool not = false)
where T : class
where T2 : class
{
PropertyInfo propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
PropertyInfo propertyInfo2 = ReflectionHelper.GetProperty(expression2) as PropertyInfo;
return new PropertyPredicate<T, T2>
{
PropertyName = propertyInfo.Name,
PropertyName2 = propertyInfo2.Name,
Operator = op,
Not = not
};
}
/// <summary>
/// Factory method that creates a new IPredicateGroup predicate.
/// Predicate groups can be joined together with other predicate groups.
/// </summary>
/// <param name="op">The grouping operator to use when joining the predicates (AND / OR).</param>
/// <param name="predicate">A list of predicates to group.</param>
/// <returns>An instance of IPredicateGroup.</returns>
public static IPredicateGroup Group(GroupOperator op, params IPredicate[] predicate)
{
return new PredicateGroup
{
Operator = op,
Predicates = predicate
};
}
/// <summary>
/// Factory method that creates a new IExistsPredicate predicate.
/// </summary>
public static IExistsPredicate Exists<TSub>(IPredicate predicate, bool not = false)
where TSub : class
{
return new ExistsPredicate<TSub>
{
Not = not,
Predicate = predicate
};
}
/// <summary>
/// Factory method that creates a new IBetweenPredicate predicate.
/// </summary>
public static IBetweenPredicate Between<T>(Expression<Func<T, object>> expression, BetweenValues values, bool not = false)
where T : class
{
PropertyInfo propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
return new BetweenPredicate<T>
{
Not = not,
PropertyName = propertyInfo.Name,
Value = values
};
}
/// <summary>
/// Factory method that creates a new Sort which controls how the results will be sorted.
/// </summary>
public static ISort Sort<T>(Expression<Func<T, object>> expression, bool ascending = true)
{
PropertyInfo propertyInfo = ReflectionHelper.GetProperty(expression) as PropertyInfo;
return new Sort
{
PropertyName = propertyInfo.Name,
Ascending = ascending
};
}
}
public interface IPredicate
{
string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters);
}
public interface IBasePredicate : IPredicate
{
string PropertyName { get; set; }
}
public abstract class BasePredicate : IBasePredicate
{
public abstract string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters);
public string PropertyName { get; set; }
protected virtual string GetColumnName(Type entityType, ISqlGenerator sqlGenerator, string propertyName)
{
IClassMapper map = sqlGenerator.Configuration.GetMap(entityType);
if (map == null)
{
throw new NullReferenceException(string.Format("Map was not found for {0}", entityType));
}
IPropertyMap propertyMap = map.Properties.SingleOrDefault(p => p.Name == propertyName);
if (propertyMap == null)
{
throw new NullReferenceException(string.Format("{0} was not found for {1}", propertyName, entityType));
}
return sqlGenerator.GetColumnName(map, propertyMap, false);
}
}
public interface IComparePredicate : IBasePredicate
{
Operator Operator { get; set; }
bool Not { get; set; }
}
public abstract class ComparePredicate : BasePredicate
{
public Operator Operator { get; set; }
public bool Not { get; set; }
public virtual string GetOperatorString()
{
switch (Operator)
{
case Operator.Gt:
return Not ? "<=" : ">";
case Operator.Ge:
return Not ? "<" : ">=";
case Operator.Lt:
return Not ? ">=" : "<";
case Operator.Le:
return Not ? ">" : "<=";
case Operator.Like:
return Not ? "NOT LIKE" : "LIKE";
default:
return Not ? "<>" : "=";
}
}
}
public interface IFieldPredicate : IComparePredicate
{
object Value { get; set; }
}
public class FieldPredicate<T> : ComparePredicate, IFieldPredicate
where T : class
{
public object Value { get; set; }
public override string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters)
{
string columnName = GetColumnName(typeof(T), sqlGenerator, PropertyName);
if (Value == null)
{
return string.Format("({0} IS {1}NULL)", columnName, Not ? "NOT " : string.Empty);
}
if (Value is IEnumerable && !(Value is string))
{
if (Operator != Operator.Eq)
{
throw new ArgumentException("Operator must be set to Eq for Enumerable types");
}
List<string> @params = new List<string>();
foreach (var value in (IEnumerable)Value)
{
string valueParameterName = parameters.SetParameterName(this.PropertyName, value, sqlGenerator.Configuration.Dialect.ParameterPrefix);
@params.Add(valueParameterName);
}
string paramStrings = @params.Aggregate(new StringBuilder(), (sb, s) => sb.Append((sb.Length != 0 ? ", " : string.Empty) + s), sb => sb.ToString());
return string.Format("({0} {1}IN ({2}))", columnName, Not ? "NOT " : string.Empty, paramStrings);
}
string parameterName = parameters.SetParameterName(this.PropertyName, this.Value, sqlGenerator.Configuration.Dialect.ParameterPrefix);
return string.Format("({0} {1} {2})", columnName, GetOperatorString(), parameterName);
}
}
public interface IPropertyPredicate : IComparePredicate
{
string PropertyName2 { get; set; }
}
public class PropertyPredicate<T, T2> : ComparePredicate, IPropertyPredicate
where T : class
where T2 : class
{
public string PropertyName2 { get; set; }
public override string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters)
{
string columnName = GetColumnName(typeof(T), sqlGenerator, PropertyName);
string columnName2 = GetColumnName(typeof(T2), sqlGenerator, PropertyName2);
return string.Format("({0} {1} {2})", columnName, GetOperatorString(), columnName2);
}
}
public struct BetweenValues
{
public object Value1 { get; set; }
public object Value2 { get; set; }
}
public interface IBetweenPredicate : IPredicate
{
string PropertyName { get; set; }
BetweenValues Value { get; set; }
bool Not { get; set; }
}
public class BetweenPredicate<T> : BasePredicate, IBetweenPredicate
where T : class
{
public override string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters)
{
string columnName = GetColumnName(typeof(T), sqlGenerator, PropertyName);
string propertyName1 = parameters.SetParameterName(this.PropertyName, this.Value.Value1, sqlGenerator.Configuration.Dialect.ParameterPrefix);
string propertyName2 = parameters.SetParameterName(this.PropertyName, this.Value.Value2, sqlGenerator.Configuration.Dialect.ParameterPrefix);
return string.Format("({0} {1}BETWEEN {2} AND {3})", columnName, Not ? "NOT " : string.Empty, propertyName1, propertyName2);
}
public BetweenValues Value { get; set; }
public bool Not { get; set; }
}
/// <summary>
/// Comparison operator for predicates.
/// </summary>
public enum Operator
{
/// <summary>
/// Equal to
/// </summary>
Eq,
/// <summary>
/// Greater than
/// </summary>
Gt,
/// <summary>
/// Greater than or equal to
/// </summary>
Ge,
/// <summary>
/// Less than
/// </summary>
Lt,
/// <summary>
/// Less than or equal to
/// </summary>
Le,
/// <summary>
/// Like (You can use % in the value to do wilcard searching)
/// </summary>
Like
}
public interface IPredicateGroup : IPredicate
{
GroupOperator Operator { get; set; }
IList<IPredicate> Predicates { get; set; }
}
/// <summary>
/// Groups IPredicates together using the specified group operator.
/// </summary>
public class PredicateGroup : IPredicateGroup
{
public GroupOperator Operator { get; set; }
public IList<IPredicate> Predicates { get; set; }
public string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters)
{
string seperator = Operator == GroupOperator.And ? " AND " : " OR ";
return "(" + Predicates.Aggregate(new StringBuilder(),
(sb, p) => (sb.Length == 0 ? sb : sb.Append(seperator)).Append(p.GetSql(sqlGenerator, parameters)),
sb =>
{
var s = sb.ToString();
if (s.Length == 0) return sqlGenerator.Configuration.Dialect.EmptyExpression;
return s;
}
) + ")";
}
}
public interface IExistsPredicate : IPredicate
{
IPredicate Predicate { get; set; }
bool Not { get; set; }
}
public class ExistsPredicate<TSub> : IExistsPredicate
where TSub : class
{
public IPredicate Predicate { get; set; }
public bool Not { get; set; }
public string GetSql(ISqlGenerator sqlGenerator, IDictionary<string, object> parameters)
{
IClassMapper mapSub = GetClassMapper(typeof(TSub), sqlGenerator.Configuration);
string sql = string.Format("({0}EXISTS (SELECT 1 FROM {1} WHERE {2}))",
Not ? "NOT " : string.Empty,
sqlGenerator.GetTableName(mapSub),
Predicate.GetSql(sqlGenerator, parameters));
return sql;
}
protected virtual IClassMapper GetClassMapper(Type type, IDapperExtensionsConfiguration configuration)
{
IClassMapper map = configuration.GetMap(type);
if (map == null)
{
throw new NullReferenceException(string.Format("Map was not found for {0}", type));
}
return map;
}
}
public interface ISort
{
string PropertyName { get; set; }
bool Ascending { get; set; }
}
public class Sort : ISort
{
public string PropertyName { get; set; }
public bool Ascending { get; set; }
}
/// <summary>
/// Operator to use when joining predicates in a PredicateGroup.
/// </summary>
public enum GroupOperator
{
And,
Or
}
}

View File

@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
namespace DapperExtensions
{
public static class ReflectionHelper
{
private static List<Type> _simpleTypes = new List<Type>
{
typeof(byte),
typeof(sbyte),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal),
typeof(bool),
typeof(string),
typeof(char),
typeof(Guid),
typeof(DateTime),
typeof(DateTimeOffset),
typeof(byte[])
};
public static MemberInfo GetProperty(LambdaExpression lambda)
{
Expression expr = lambda;
for (; ; )
{
switch (expr.NodeType)
{
case ExpressionType.Lambda:
expr = ((LambdaExpression)expr).Body;
break;
case ExpressionType.Convert:
expr = ((UnaryExpression)expr).Operand;
break;
case ExpressionType.MemberAccess:
MemberExpression memberExpression = (MemberExpression)expr;
MemberInfo mi = memberExpression.Member;
return mi;
default:
return null;
}
}
}
public static IDictionary<string, object> GetObjectValues(object obj)
{
IDictionary<string, object> result = new Dictionary<string, object>();
if (obj == null)
{
return result;
}
foreach (var propertyInfo in obj.GetType().GetProperties())
{
string name = propertyInfo.Name;
object value = propertyInfo.GetValue(obj, null);
result[name] = value;
}
return result;
}
public static string AppendStrings(this IEnumerable<string> list, string seperator = ", ")
{
return list.Aggregate(
new StringBuilder(),
(sb, s) => (sb.Length == 0 ? sb : sb.Append(seperator)).Append(s),
sb => sb.ToString());
}
public static bool IsSimpleType(Type type)
{
Type actualType = type;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
actualType = type.GetGenericArguments()[0];
}
return _simpleTypes.Contains(actualType);
}
public static string GetParameterName(this IDictionary<string, object> parameters, string parameterName, char parameterPrefix)
{
return string.Format("{0}{1}_{2}", parameterPrefix, parameterName, parameters.Count);
}
public static string SetParameterName(this IDictionary<string, object> parameters, string parameterName, object value, char parameterPrefix)
{
string name = parameters.GetParameterName(parameterName, parameterPrefix);
parameters.Add(name, value);
return name;
}
}
}

View File

@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DapperExtensions.Sql
{
public class DB2Dialect : SqlDialectBase
{
public override string GetIdentitySql(string tableName)
{
return "SELECT CAST(IDENTITY_VAL_LOCAL() AS BIGINT) AS \"ID\" FROM SYSIBM.SYSDUMMY1";
}
public override string GetPagingSql(string sql, int page, int resultsPerPage, IDictionary<string, object> parameters)
{
var startValue = ((page - 1) * resultsPerPage) + 1;
var endValue = (page * resultsPerPage);
return GetSetSql(sql, startValue, endValue, parameters);
}
public override string GetSetSql(string sql, int firstResult, int maxResults, IDictionary<string, object> parameters)
{
if (string.IsNullOrEmpty(sql))
{
throw new ArgumentNullException("SQL");
}
if (parameters == null)
{
throw new ArgumentNullException("Parameters");
}
int selectIndex = GetSelectEnd(sql) + 1;
string orderByClause = GetOrderByClause(sql);
if (orderByClause == null)
{
orderByClause = "ORDER BY CURRENT_TIMESTAMP";
}
string projectedColumns = GetColumnNames(sql).Aggregate(new StringBuilder(), (sb, s) => (sb.Length == 0 ? sb : sb.Append(", ")).Append(GetColumnName("_TEMP", s, null)), sb => sb.ToString());
string newSql = sql
.Replace(" " + orderByClause, string.Empty)
.Insert(selectIndex, string.Format("ROW_NUMBER() OVER(ORDER BY {0}) AS {1}, ", orderByClause.Substring(9), GetColumnName(null, "_ROW_NUMBER", null)));
string result = string.Format("SELECT {0} FROM ({1}) AS \"_TEMP\" WHERE {2} BETWEEN @_pageStartRow AND @_pageEndRow",
projectedColumns.Trim(), newSql, GetColumnName("_TEMP", "_ROW_NUMBER", null));
parameters.Add("@_pageStartRow", firstResult);
parameters.Add("@_pageEndRow", maxResults);
return result;
}
protected string GetOrderByClause(string sql)
{
int orderByIndex = sql.LastIndexOf(" ORDER BY ", StringComparison.InvariantCultureIgnoreCase);
if (orderByIndex == -1)
{
return null;
}
string result = sql.Substring(orderByIndex).Trim();
int whereIndex = result.IndexOf(" WHERE ", StringComparison.InvariantCultureIgnoreCase);
if (whereIndex == -1)
{
return result;
}
return result.Substring(0, whereIndex).Trim();
}
protected int GetFromStart(string sql)
{
int selectCount = 0;
string[] words = sql.Split(' ');
int fromIndex = 0;
foreach (var word in words)
{
if (word.Equals("SELECT", StringComparison.InvariantCultureIgnoreCase))
{
selectCount++;
}
if (word.Equals("FROM", StringComparison.InvariantCultureIgnoreCase))
{
selectCount--;
if (selectCount == 0)
{
break;
}
}
fromIndex += word.Length + 1;
}
return fromIndex;
}
protected virtual int GetSelectEnd(string sql)
{
if (sql.StartsWith("SELECT DISTINCT", StringComparison.InvariantCultureIgnoreCase))
{
return 15;
}
if (sql.StartsWith("SELECT", StringComparison.InvariantCultureIgnoreCase))
{
return 6;
}
throw new ArgumentException("SQL must be a SELECT statement.", "sql");
}
protected virtual IList<string> GetColumnNames(string sql)
{
int start = GetSelectEnd(sql);
int stop = GetFromStart(sql);
string[] columnSql = sql.Substring(start, stop - start).Split(',');
List<string> result = new List<string>();
foreach (string c in columnSql)
{
int index = c.IndexOf(" AS ", StringComparison.InvariantCultureIgnoreCase);
if (index > 0)
{
result.Add(c.Substring(index + 4).Trim());
continue;
}
string[] colParts = c.Split('.');
result.Add(colParts[colParts.Length - 1].Trim());
}
return result;
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace DapperExtensions.Sql
{
public class MySqlDialect : SqlDialectBase
{
public override char OpenQuote
{
get { return '`'; }
}
public override char CloseQuote
{
get { return '`'; }
}
public override string GetIdentitySql(string tableName)
{
return "SELECT CONVERT(LAST_INSERT_ID(), SIGNED INTEGER) AS ID";
}
public override string GetPagingSql(string sql, int page, int resultsPerPage, IDictionary<string, object> parameters)
{
int startValue = page * resultsPerPage;
return GetSetSql(sql, startValue, resultsPerPage, parameters);
}
public override string GetSetSql(string sql, int firstResult, int maxResults, IDictionary<string, object> parameters)
{
string result = string.Format("{0} LIMIT @firstResult, @maxResults", sql);
parameters.Add("@firstResult", firstResult);
parameters.Add("@maxResults", maxResults);
return result;
}
}
}

View File

@@ -0,0 +1,80 @@
using System.Collections.Generic;
using System.Text;
namespace DapperExtensions.Sql
{
public class OracleDialect : SqlDialectBase
{
public OracleDialect() { }
public override string GetIdentitySql(string tableName)
{
throw new System.NotImplementedException("Oracle does not support get last inserted identity.");
}
public override bool SupportsMultipleStatements
{
get { return false; }
}
//from Simple.Data.Oracle implementation https://github.com/flq/Simple.Data.Oracle/blob/master/Simple.Data.Oracle/OraclePager.cs
public override string GetPagingSql(string sql, int page, int resultsPerPage, IDictionary<string, object> parameters)
{
var toSkip = page * resultsPerPage;
var topLimit = (page + 1) * resultsPerPage;
var sb = new StringBuilder();
sb.AppendLine("SELECT * FROM (");
sb.AppendLine("SELECT \"_ss_dapper_1_\".*, ROWNUM RNUM FROM (");
sb.Append(sql);
sb.AppendLine(") \"_ss_dapper_1_\"");
sb.AppendLine("WHERE ROWNUM <= :topLimit) \"_ss_dapper_2_\" ");
sb.AppendLine("WHERE \"_ss_dapper_2_\".RNUM > :toSkip");
parameters.Add(":topLimit", topLimit);
parameters.Add(":toSkip", toSkip);
return sb.ToString();
}
public override string GetSetSql(string sql, int firstResult, int maxResults, IDictionary<string, object> parameters)
{
var sb = new StringBuilder();
sb.AppendLine("SELECT * FROM (");
sb.AppendLine("SELECT \"_ss_dapper_1_\".*, ROWNUM RNUM FROM (");
sb.Append(sql);
sb.AppendLine(") \"_ss_dapper_1_\"");
sb.AppendLine("WHERE ROWNUM <= :topLimit) \"_ss_dapper_2_\" ");
sb.AppendLine("WHERE \"_ss_dapper_2_\".RNUM > :toSkip");
parameters.Add(":topLimit", maxResults + firstResult);
parameters.Add(":toSkip", firstResult);
return sb.ToString();
}
public override string QuoteString(string value)
{
if (value != null && value[0]=='`')
{
return string.Format("{0}{1}{2}", OpenQuote, value.Substring(1, value.Length - 2), CloseQuote);
}
return value.ToUpper();
}
public override char ParameterPrefix
{
get { return ':'; }
}
public override char OpenQuote
{
get { return '"'; }
}
public override char CloseQuote
{
get { return '"'; }
}
}
}

View File

@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DapperExtensions.Sql
{
public class PostgreSqlDialect : SqlDialectBase
{
public override string GetIdentitySql(string tableName)
{
return "SELECT LASTVAL() AS Id";
}
public override string GetPagingSql(string sql, int page, int resultsPerPage, IDictionary<string, object> parameters)
{
int startValue = page * resultsPerPage;
return GetSetSql(sql, startValue, resultsPerPage, parameters);
}
public override string GetSetSql(string sql, int pageNumber, int maxResults, IDictionary<string, object> parameters)
{
string result = string.Format("{0} LIMIT @maxResults OFFSET @pageStartRowNbr", sql);
parameters.Add("@maxResults", maxResults);
parameters.Add("@pageStartRowNbr", pageNumber * maxResults);
return result;
}
public override string GetColumnName(string prefix, string columnName, string alias)
{
return base.GetColumnName(null, columnName, alias).ToLower();
}
public override string GetTableName(string schemaName, string tableName, string alias)
{
return base.GetTableName(schemaName, tableName, alias).ToLower();
}
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DapperExtensions.Sql
{
public class SqlCeDialect : SqlDialectBase
{
public override char OpenQuote
{
get { return '['; }
}
public override char CloseQuote
{
get { return ']'; }
}
public override bool SupportsMultipleStatements
{
get { return false; }
}
public override string GetTableName(string schemaName, string tableName, string alias)
{
if (string.IsNullOrWhiteSpace(tableName))
{
throw new ArgumentNullException("TableName");
}
StringBuilder result = new StringBuilder();
result.Append(OpenQuote);
if (!string.IsNullOrWhiteSpace(schemaName))
{
result.AppendFormat("{0}_", schemaName);
}
result.AppendFormat("{0}{1}", tableName, CloseQuote);
if (!string.IsNullOrWhiteSpace(alias))
{
result.AppendFormat(" AS {0}{1}{2}", OpenQuote, alias, CloseQuote);
}
return result.ToString();
}
public override string GetIdentitySql(string tableName)
{
return "SELECT CAST(@@IDENTITY AS BIGINT) AS [Id]";
}
public override string GetPagingSql(string sql, int page, int resultsPerPage, IDictionary<string, object> parameters)
{
int startValue = (page * resultsPerPage);
return GetSetSql(sql, startValue, resultsPerPage, parameters);
}
public override string GetSetSql(string sql, int firstResult, int maxResults, IDictionary<string, object> parameters)
{
string result = string.Format("{0} OFFSET @firstResult ROWS FETCH NEXT @maxResults ROWS ONLY", sql);
parameters.Add("@firstResult", firstResult);
parameters.Add("@maxResults", maxResults);
return result;
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DapperExtensions.Sql
{
public interface ISqlDialect
{
char OpenQuote { get; }
char CloseQuote { get; }
string BatchSeperator { get; }
bool SupportsMultipleStatements { get; }
char ParameterPrefix { get; }
string EmptyExpression { get; }
string GetTableName(string schemaName, string tableName, string alias);
string GetColumnName(string prefix, string columnName, string alias);
string GetIdentitySql(string tableName);
string GetPagingSql(string sql, int page, int resultsPerPage, IDictionary<string, object> parameters);
string GetSetSql(string sql, int firstResult, int maxResults, IDictionary<string, object> parameters);
bool IsQuoted(string value);
string QuoteString(string value);
}
public abstract class SqlDialectBase : ISqlDialect
{
public virtual char OpenQuote
{
get { return '"'; }
}
public virtual char CloseQuote
{
get { return '"'; }
}
public virtual string BatchSeperator
{
get { return ";" + Environment.NewLine; }
}
public virtual bool SupportsMultipleStatements
{
get { return true; }
}
public virtual char ParameterPrefix
{
get
{
return '@';
}
}
public string EmptyExpression
{
get
{
return "1=1";
}
}
public virtual string GetTableName(string schemaName, string tableName, string alias)
{
if (string.IsNullOrWhiteSpace(tableName))
{
throw new ArgumentNullException("TableName", "tableName cannot be null or empty.");
}
StringBuilder result = new StringBuilder();
if (!string.IsNullOrWhiteSpace(schemaName))
{
result.AppendFormat(QuoteString(schemaName) + ".");
}
result.AppendFormat(QuoteString(tableName));
if (!string.IsNullOrWhiteSpace(alias))
{
result.AppendFormat(" AS {0}", QuoteString(alias));
}
return result.ToString();
}
public virtual string GetColumnName(string prefix, string columnName, string alias)
{
if (string.IsNullOrWhiteSpace(columnName))
{
throw new ArgumentNullException("ColumnName", "columnName cannot be null or empty.");
}
StringBuilder result = new StringBuilder();
if (!string.IsNullOrWhiteSpace(prefix))
{
result.AppendFormat(QuoteString(prefix) + ".");
}
result.AppendFormat(QuoteString(columnName));
if (!string.IsNullOrWhiteSpace(alias))
{
result.AppendFormat(" AS {0}", QuoteString(alias));
}
return result.ToString();
}
public abstract string GetIdentitySql(string tableName);
public abstract string GetPagingSql(string sql, int page, int resultsPerPage, IDictionary<string, object> parameters);
public abstract string GetSetSql(string sql, int firstResult, int maxResults, IDictionary<string, object> parameters);
public virtual bool IsQuoted(string value)
{
if (value.Trim()[0] == OpenQuote)
{
return value.Trim().Last() == CloseQuote;
}
return false;
}
public virtual string QuoteString(string value)
{
if (IsQuoted(value) || value == "*")
{
return value;
}
return string.Format("{0}{1}{2}", OpenQuote, value.Trim(), CloseQuote);
}
public virtual string UnQuoteString(string value)
{
return IsQuoted(value) ? value.Substring(1, value.Length - 2) : value;
}
}
}

View File

@@ -0,0 +1,264 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DapperExtensions.Mapper;
namespace DapperExtensions.Sql
{
public interface ISqlGenerator
{
IDapperExtensionsConfiguration Configuration { get; }
string Select(IClassMapper classMap, IPredicate predicate, IList<ISort> sort, IDictionary<string, object> parameters);
string SelectPaged(IClassMapper classMap, IPredicate predicate, IList<ISort> sort, int page, int resultsPerPage, IDictionary<string, object> parameters);
string SelectSet(IClassMapper classMap, IPredicate predicate, IList<ISort> sort, int firstResult, int maxResults, IDictionary<string, object> parameters);
string Count(IClassMapper classMap, IPredicate predicate, IDictionary<string, object> parameters);
string Insert(IClassMapper classMap);
string Update(IClassMapper classMap, IPredicate predicate, IDictionary<string, object> parameters, bool ignoreAllKeyProperties);
string Delete(IClassMapper classMap, IPredicate predicate, IDictionary<string, object> parameters);
string IdentitySql(IClassMapper classMap);
string GetTableName(IClassMapper map);
string GetColumnName(IClassMapper map, IPropertyMap property, bool includeAlias);
string GetColumnName(IClassMapper map, string propertyName, bool includeAlias);
bool SupportsMultipleStatements();
}
public class SqlGeneratorImpl : ISqlGenerator
{
public SqlGeneratorImpl(IDapperExtensionsConfiguration configuration)
{
Configuration = configuration;
}
public IDapperExtensionsConfiguration Configuration { get; private set; }
public virtual string Select(IClassMapper classMap, IPredicate predicate, IList<ISort> sort, IDictionary<string, object> parameters)
{
if (parameters == null)
{
throw new ArgumentNullException("Parameters");
}
StringBuilder sql = new StringBuilder(string.Format("SELECT {0} FROM {1}",
BuildSelectColumns(classMap),
GetTableName(classMap)));
if (predicate != null)
{
sql.Append(" WHERE ")
.Append(predicate.GetSql(this, parameters));
}
if (sort != null && sort.Any())
{
sql.Append(" ORDER BY ")
.Append(sort.Select(s => GetColumnName(classMap, s.PropertyName, false) + (s.Ascending ? " ASC" : " DESC")).AppendStrings());
}
return sql.ToString();
}
public virtual string SelectPaged(IClassMapper classMap, IPredicate predicate, IList<ISort> sort, int page, int resultsPerPage, IDictionary<string, object> parameters)
{
if (sort == null || !sort.Any())
{
throw new ArgumentNullException("Sort", "Sort cannot be null or empty.");
}
if (parameters == null)
{
throw new ArgumentNullException("Parameters");
}
StringBuilder innerSql = new StringBuilder(string.Format("SELECT {0} FROM {1}",
BuildSelectColumns(classMap),
GetTableName(classMap)));
if (predicate != null)
{
innerSql.Append(" WHERE ")
.Append(predicate.GetSql(this, parameters));
}
string orderBy = sort.Select(s => GetColumnName(classMap, s.PropertyName, false) + (s.Ascending ? " ASC" : " DESC")).AppendStrings();
innerSql.Append(" ORDER BY " + orderBy);
string sql = Configuration.Dialect.GetPagingSql(innerSql.ToString(), page, resultsPerPage, parameters);
return sql;
}
public virtual string SelectSet(IClassMapper classMap, IPredicate predicate, IList<ISort> sort, int firstResult, int maxResults, IDictionary<string, object> parameters)
{
if (sort == null || !sort.Any())
{
throw new ArgumentNullException("Sort", "Sort cannot be null or empty.");
}
if (parameters == null)
{
throw new ArgumentNullException("Parameters");
}
StringBuilder innerSql = new StringBuilder(string.Format("SELECT {0} FROM {1}",
BuildSelectColumns(classMap),
GetTableName(classMap)));
if (predicate != null)
{
innerSql.Append(" WHERE ")
.Append(predicate.GetSql(this, parameters));
}
string orderBy = sort.Select(s => GetColumnName(classMap, s.PropertyName, false) + (s.Ascending ? " ASC" : " DESC")).AppendStrings();
innerSql.Append(" ORDER BY " + orderBy);
string sql = Configuration.Dialect.GetSetSql(innerSql.ToString(), firstResult, maxResults, parameters);
return sql;
}
public virtual string Count(IClassMapper classMap, IPredicate predicate, IDictionary<string, object> parameters)
{
if (parameters == null)
{
throw new ArgumentNullException("Parameters");
}
StringBuilder sql = new StringBuilder(string.Format("SELECT COUNT(*) AS {0}Total{1} FROM {2}",
Configuration.Dialect.OpenQuote,
Configuration.Dialect.CloseQuote,
GetTableName(classMap)));
if (predicate != null)
{
sql.Append(" WHERE ")
.Append(predicate.GetSql(this, parameters));
}
return sql.ToString();
}
public virtual string Insert(IClassMapper classMap)
{
var columns = classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly || p.KeyType == KeyType.Identity || p.KeyType == KeyType.TriggerIdentity));
if (!columns.Any())
{
throw new ArgumentException("No columns were mapped.");
}
var columnNames = columns.Select(p => GetColumnName(classMap, p, false));
var parameters = columns.Select(p => Configuration.Dialect.ParameterPrefix + p.Name);
string sql = string.Format("INSERT INTO {0} ({1}) VALUES ({2})",
GetTableName(classMap),
columnNames.AppendStrings(),
parameters.AppendStrings());
var triggerIdentityColumn = classMap.Properties.Where(p => p.KeyType == KeyType.TriggerIdentity).ToList();
if (triggerIdentityColumn.Count > 0)
{
if (triggerIdentityColumn.Count > 1)
throw new ArgumentException("TriggerIdentity generator cannot be used with multi-column keys");
sql += string.Format(" RETURNING {0} INTO {1}IdOutParam", triggerIdentityColumn.Select(p => GetColumnName(classMap, p, false)).First(), Configuration.Dialect.ParameterPrefix);
}
return sql;
}
public virtual string Update(IClassMapper classMap, IPredicate predicate, IDictionary<string, object> parameters, bool ignoreAllKeyProperties)
{
if (predicate == null)
{
throw new ArgumentNullException("Predicate");
}
if (parameters == null)
{
throw new ArgumentNullException("Parameters");
}
var columns = ignoreAllKeyProperties
? classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly) && p.KeyType == KeyType.NotAKey)
: classMap.Properties.Where(p => !(p.Ignored || p.IsReadOnly || p.KeyType == KeyType.Identity || p.KeyType == KeyType.Assigned));
if (!columns.Any())
{
throw new ArgumentException("No columns were mapped.");
}
var setSql =
columns.Select(
p =>
string.Format(
"{0} = {1}{2}", GetColumnName(classMap, p, false), Configuration.Dialect.ParameterPrefix, p.Name));
return string.Format("UPDATE {0} SET {1} WHERE {2}",
GetTableName(classMap),
setSql.AppendStrings(),
predicate.GetSql(this, parameters));
}
public virtual string Delete(IClassMapper classMap, IPredicate predicate, IDictionary<string, object> parameters)
{
if (predicate == null)
{
throw new ArgumentNullException("Predicate");
}
if (parameters == null)
{
throw new ArgumentNullException("Parameters");
}
StringBuilder sql = new StringBuilder(string.Format("DELETE FROM {0}", GetTableName(classMap)));
sql.Append(" WHERE ").Append(predicate.GetSql(this, parameters));
return sql.ToString();
}
public virtual string IdentitySql(IClassMapper classMap)
{
return Configuration.Dialect.GetIdentitySql(GetTableName(classMap));
}
public virtual string GetTableName(IClassMapper map)
{
return Configuration.Dialect.GetTableName(map.SchemaName, map.TableName, null);
}
public virtual string GetColumnName(IClassMapper map, IPropertyMap property, bool includeAlias)
{
string alias = null;
if (property.ColumnName != property.Name && includeAlias)
{
alias = property.Name;
}
return Configuration.Dialect.GetColumnName(GetTableName(map), property.ColumnName, alias);
}
public virtual string GetColumnName(IClassMapper map, string propertyName, bool includeAlias)
{
IPropertyMap propertyMap = map.Properties.SingleOrDefault(p => p.Name.Equals(propertyName, StringComparison.InvariantCultureIgnoreCase));
if (propertyMap == null)
{
throw new ArgumentException(string.Format("Could not find '{0}' in Mapping.", propertyName));
}
return GetColumnName(map, propertyMap, includeAlias);
}
public virtual bool SupportsMultipleStatements()
{
return Configuration.Dialect.SupportsMultipleStatements;
}
public virtual string BuildSelectColumns(IClassMapper classMap)
{
var columns = classMap.Properties
.Where(p => !p.Ignored)
.Select(p => GetColumnName(classMap, p, true));
return columns.AppendStrings();
}
}
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DapperExtensions.Sql
{
public class SqlServerDialect : SqlDialectBase
{
public override char OpenQuote
{
get { return '['; }
}
public override char CloseQuote
{
get { return ']'; }
}
public override string GetIdentitySql(string tableName)
{
return string.Format("SELECT CAST(SCOPE_IDENTITY() AS BIGINT) AS [Id]");
}
public override string GetPagingSql(string sql, int page, int resultsPerPage, IDictionary<string, object> parameters)
{
int startValue = (page * resultsPerPage) + 1;
return GetSetSql(sql, startValue, resultsPerPage, parameters);
}
public override string GetSetSql(string sql, int firstResult, int maxResults, IDictionary<string, object> parameters)
{
if (string.IsNullOrEmpty(sql))
{
throw new ArgumentNullException("SQL");
}
if (parameters == null)
{
throw new ArgumentNullException("Parameters");
}
int selectIndex = GetSelectEnd(sql) + 1;
string orderByClause = GetOrderByClause(sql);
if (orderByClause == null)
{
orderByClause = "ORDER BY CURRENT_TIMESTAMP";
}
string projectedColumns = GetColumnNames(sql).Aggregate(new StringBuilder(), (sb, s) => (sb.Length == 0 ? sb : sb.Append(", ")).Append(GetColumnName("_proj", s, null)), sb => sb.ToString());
string newSql = sql
.Replace(" " + orderByClause, string.Empty)
.Insert(selectIndex, string.Format("ROW_NUMBER() OVER(ORDER BY {0}) AS {1}, ", orderByClause.Substring(9), GetColumnName(null, "_row_number", null)));
string result = string.Format("SELECT TOP({0}) {1} FROM ({2}) [_proj] WHERE {3} >= @_pageStartRow ORDER BY {3}",
maxResults, projectedColumns.Trim(), newSql, GetColumnName("_proj", "_row_number", null));
parameters.Add("@_pageStartRow", firstResult);
return result;
}
protected string GetOrderByClause(string sql)
{
int orderByIndex = sql.LastIndexOf(" ORDER BY ", StringComparison.InvariantCultureIgnoreCase);
if (orderByIndex == -1)
{
return null;
}
string result = sql.Substring(orderByIndex).Trim();
int whereIndex = result.IndexOf(" WHERE ", StringComparison.InvariantCultureIgnoreCase);
if (whereIndex == -1)
{
return result;
}
return result.Substring(0, whereIndex).Trim();
}
protected int GetFromStart(string sql)
{
int selectCount = 0;
string[] words = sql.Split(' ');
int fromIndex = 0;
foreach (var word in words)
{
if (word.Equals("SELECT", StringComparison.InvariantCultureIgnoreCase))
{
selectCount++;
}
if (word.Equals("FROM", StringComparison.InvariantCultureIgnoreCase))
{
selectCount--;
if (selectCount == 0)
{
break;
}
}
fromIndex += word.Length + 1;
}
return fromIndex;
}
protected virtual int GetSelectEnd(string sql)
{
if (sql.StartsWith("SELECT DISTINCT", StringComparison.InvariantCultureIgnoreCase))
{
return 15;
}
if (sql.StartsWith("SELECT", StringComparison.InvariantCultureIgnoreCase))
{
return 6;
}
throw new ArgumentException("SQL must be a SELECT statement.", "sql");
}
protected virtual IList<string> GetColumnNames(string sql)
{
int start = GetSelectEnd(sql);
int stop = GetFromStart(sql);
string[] columnSql = sql.Substring(start, stop - start).Split(',');
List<string> result = new List<string>();
foreach (string c in columnSql)
{
int index = c.IndexOf(" AS ", StringComparison.InvariantCultureIgnoreCase);
if (index > 0)
{
result.Add(c.Substring(index + 4).Trim());
continue;
}
string[] colParts = c.Split('.');
result.Add(colParts[colParts.Length - 1].Trim());
}
return result;
}
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DapperExtensions.Sql
{
public class SqliteDialect : SqlDialectBase
{
public override string GetIdentitySql(string tableName)
{
return "SELECT LAST_INSERT_ROWID() AS [Id]";
}
public override string GetPagingSql(string sql, int page, int resultsPerPage, IDictionary<string, object> parameters)
{
int startValue = page * resultsPerPage;
return GetSetSql(sql, startValue, resultsPerPage, parameters);
}
public override string GetSetSql(string sql, int firstResult, int maxResults, IDictionary<string, object> parameters)
{
if (string.IsNullOrEmpty(sql))
{
throw new ArgumentNullException("SQL");
}
if (parameters == null)
{
throw new ArgumentNullException("Parameters");
}
var result = string.Format("{0} LIMIT @Offset, @Count", sql);
parameters.Add("@Offset", firstResult);
parameters.Add("@Count", maxResults);
return result;
}
public override string GetColumnName(string prefix, string columnName, string alias)
{
if (string.IsNullOrWhiteSpace(columnName))
{
throw new ArgumentNullException(columnName, "columnName cannot be null or empty.");
}
var result = new StringBuilder();
result.AppendFormat(columnName);
if (!string.IsNullOrWhiteSpace(alias))
{
result.AppendFormat(" AS {0}", QuoteString(alias));
}
return result.ToString();
}
}
}

View File

@@ -0,0 +1,82 @@
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ewide.Core.Data
{
public class DapperHelper : IDisposable
{
private readonly static string _connString = System.Configuration.ConfigurationManager.ConnectionStrings["MySqlConnection"].ToString();
public IDbConnection Sql = null;
public DapperHelper()
{
Sql = new MySqlConnection(_connString);
if (Sql.State == ConnectionState.Closed)
{
Sql.Open();
}
}
void IDisposable.Dispose()
{
if (Sql.State == ConnectionState.Open)
{
Sql.Close();
}
Sql.Dispose();
}
}
public class DapperTransactionHelper : IDisposable
{
private readonly static string _connString = System.Configuration.ConfigurationManager.ConnectionStrings["MySqlConnection"].ToString();
public IDbConnection Sql = null;
private IDbTransaction _trans = null;
public DapperTransactionHelper()
{
Sql = new MySqlConnection(_connString);
if (Sql.State == ConnectionState.Closed)
{
Sql.Open();
}
_trans = Sql.BeginTransaction();
}
private void Close()
{
if (Sql.State == ConnectionState.Open)
{
Sql.Close();
}
}
void IDisposable.Dispose()
{
_trans.Dispose();
this.Close();
Sql.Dispose();
}
public void Complete()
{
_trans.Commit();
this.Close();
}
public void RollBack()
{
_trans.Rollback();
this.Close();
}
}
}

View File

@@ -0,0 +1,167 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B5B46BAD-81E3-4DF0-83EF-75148236F7CE}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Ewide.Core.Data</RootNamespace>
<AssemblyName>Ewide.Core.Data</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="BouncyCastle.Crypto, Version=1.8.5.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
<HintPath>..\packages\BouncyCastle.1.8.5\lib\BouncyCastle.Crypto.dll</HintPath>
</Reference>
<Reference Include="Google.Protobuf, Version=3.11.4.0, Culture=neutral, PublicKeyToken=a7d26565bac4d604, processorArchitecture=MSIL">
<HintPath>..\packages\Google.Protobuf.3.11.4\lib\net45\Google.Protobuf.dll</HintPath>
</Reference>
<Reference Include="K4os.Compression.LZ4, Version=1.1.11.0, Culture=neutral, PublicKeyToken=2186fa9121ef231d, processorArchitecture=MSIL">
<HintPath>..\packages\K4os.Compression.LZ4.1.1.11\lib\net45\K4os.Compression.LZ4.dll</HintPath>
</Reference>
<Reference Include="K4os.Compression.LZ4.Streams, Version=1.1.11.0, Culture=neutral, PublicKeyToken=2186fa9121ef231d, processorArchitecture=MSIL">
<HintPath>..\packages\K4os.Compression.LZ4.Streams.1.1.11\lib\net45\K4os.Compression.LZ4.Streams.dll</HintPath>
</Reference>
<Reference Include="K4os.Hash.xxHash, Version=1.0.6.0, Culture=neutral, PublicKeyToken=32cd54395057cec3, processorArchitecture=MSIL">
<HintPath>..\packages\K4os.Hash.xxHash.1.0.6\lib\net45\K4os.Hash.xxHash.dll</HintPath>
</Reference>
<Reference Include="MySql.Data, Version=8.0.23.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL">
<HintPath>..\packages\MySql.Data.8.0.23\lib\net452\MySql.Data.dll</HintPath>
</Reference>
<Reference Include="Renci.SshNet, Version=2020.0.0.0, Culture=neutral, PublicKeyToken=1cee9f8bde3db106, processorArchitecture=MSIL">
<HintPath>..\packages\SSH.NET.2020.0.0\lib\net40\Renci.SshNet.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.5.1\lib\netstandard1.1\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.Management" />
<Reference Include="System.Memory, Version=4.0.1.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.5.3\lib\netstandard1.1\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="Ubiety.Dns.Core, Version=2.2.1.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL">
<HintPath>..\packages\MySql.Data.8.0.23\lib\net452\Ubiety.Dns.Core.dll</HintPath>
</Reference>
<Reference Include="Zstandard.Net, Version=1.1.7.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL">
<HintPath>..\packages\MySql.Data.8.0.23\lib\net452\Zstandard.Net.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="DapperExtensions\DapperExtensions.cs" />
<Compile Include="DapperExtensions\DapperExtensionsConfiguration.cs" />
<Compile Include="DapperExtensions\DapperImplementor.cs" />
<Compile Include="DapperExtensions\Database.cs" />
<Compile Include="DapperExtensions\GetMultiplePredicate.cs" />
<Compile Include="DapperExtensions\GetMultipleResult.cs" />
<Compile Include="DapperExtensions\Mapper\AutoClassMapper.cs" />
<Compile Include="DapperExtensions\Mapper\ClassMapper.cs" />
<Compile Include="DapperExtensions\Mapper\PluralizedAutoClassMapper.cs" />
<Compile Include="DapperExtensions\Mapper\PropertyMap.cs" />
<Compile Include="DapperExtensions\Predicates.cs" />
<Compile Include="DapperExtensions\ReflectionHelper.cs" />
<Compile Include="DapperExtensions\Sql\DB2Dialect.cs" />
<Compile Include="DapperExtensions\Sql\MySqlDialect.cs" />
<Compile Include="DapperExtensions\Sql\OracleDialect.cs" />
<Compile Include="DapperExtensions\Sql\PostgreSqlDialect.cs" />
<Compile Include="DapperExtensions\Sql\SqlCeDialect.cs" />
<Compile Include="DapperExtensions\Sql\SqlDialectBase.cs" />
<Compile Include="DapperExtensions\Sql\SqlGenerator.cs" />
<Compile Include="DapperExtensions\Sql\SqliteDialect.cs" />
<Compile Include="DapperExtensions\Sql\SqlServerDialect.cs" />
<Compile Include="DapperHelper.cs" />
<Compile Include="Dapper\CommandDefinition.cs" />
<Compile Include="Dapper\CommandFlags.cs" />
<Compile Include="Dapper\CustomPropertyTypeMap.cs" />
<Compile Include="Dapper\DataTableHandler.cs" />
<Compile Include="Dapper\DbString.cs" />
<Compile Include="Dapper\DefaultTypeMap.cs" />
<Compile Include="Dapper\DynamicParameters.CachedOutputSetters.cs" />
<Compile Include="Dapper\DynamicParameters.cs" />
<Compile Include="Dapper\DynamicParameters.ParamInfo.cs" />
<Compile Include="Dapper\ExplicitConstructorAttribute.cs" />
<Compile Include="Dapper\FeatureSupport.cs" />
<Compile Include="Dapper\SimpleMemberMap.cs" />
<Compile Include="Dapper\SqlDataRecordHandler.cs" />
<Compile Include="Dapper\SqlDataRecordListTVPParameter.cs" />
<Compile Include="Dapper\SqlMapper.Async.cs" />
<Compile Include="Dapper\SqlMapper.CacheInfo.cs" />
<Compile Include="Dapper\SqlMapper.cs" />
<Compile Include="Dapper\SqlMapper.DapperRow.cs" />
<Compile Include="Dapper\SqlMapper.DapperRowMetaObject.cs" />
<Compile Include="Dapper\SqlMapper.DapperTable.cs" />
<Compile Include="Dapper\SqlMapper.DeserializerState.cs" />
<Compile Include="Dapper\SqlMapper.DontMap.cs" />
<Compile Include="Dapper\SqlMapper.GridReader.Async.cs" />
<Compile Include="Dapper\SqlMapper.GridReader.cs" />
<Compile Include="Dapper\SqlMapper.ICustomQueryParameter.cs" />
<Compile Include="Dapper\SqlMapper.IDataReader.cs" />
<Compile Include="Dapper\SqlMapper.Identity.cs" />
<Compile Include="Dapper\SqlMapper.IDynamicParameters.cs" />
<Compile Include="Dapper\SqlMapper.IMemberMap.cs" />
<Compile Include="Dapper\SqlMapper.IParameterCallbacks.cs" />
<Compile Include="Dapper\SqlMapper.IParameterLookup.cs" />
<Compile Include="Dapper\SqlMapper.ITypeHandler.cs" />
<Compile Include="Dapper\SqlMapper.ITypeMap.cs" />
<Compile Include="Dapper\SqlMapper.Link.cs" />
<Compile Include="Dapper\SqlMapper.LiteralToken.cs" />
<Compile Include="Dapper\SqlMapper.Settings.cs" />
<Compile Include="Dapper\SqlMapper.TypeDeserializerCache.cs" />
<Compile Include="Dapper\SqlMapper.TypeHandler.cs" />
<Compile Include="Dapper\SqlMapper.TypeHandlerCache.cs" />
<Compile Include="Dapper\TableValuedParameter.cs" />
<Compile Include="Dapper\TypeExtensions.cs" />
<Compile Include="Dapper\UdtTypeHandler.cs" />
<Compile Include="Dapper\WrappedDataReader.cs" />
<Compile Include="Dapper\WrappedReader.cs" />
<Compile Include="Dapper\XmlHandlers.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="Dapper\project.json" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ewide.Core.Model\Ewide.Core.Model.csproj">
<Project>{31c3ca3d-14a1-453a-866d-76d4c74a9bdc}</Project>
<Name>Ewide.Core.Model</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("Ewide.Core.Data")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Ewide.Core.Data")]
[assembly: AssemblyCopyright("Copyright © 2021")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
[assembly: Guid("b5b46bad-81e3-4df0-83ef-75148236f7ce")]
// 程序集的版本信息由下列四个值组成:
//
// 主版本
// 次版本
// 生成号
// 修订号
//
// 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.1.1" newVersion="4.0.1.1" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BouncyCastle" version="1.8.5" targetFramework="net452" />
<package id="Google.Protobuf" version="3.11.4" targetFramework="net452" />
<package id="K4os.Compression.LZ4" version="1.1.11" targetFramework="net452" />
<package id="K4os.Compression.LZ4.Streams" version="1.1.11" targetFramework="net452" />
<package id="K4os.Hash.xxHash" version="1.0.6" targetFramework="net452" />
<package id="MySql.Data" version="8.0.23" targetFramework="net452" />
<package id="SSH.NET" version="2020.0.0" targetFramework="net452" />
<package id="System.Buffers" version="4.5.1" targetFramework="net452" />
<package id="System.Memory" version="4.5.3" targetFramework="net452" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net452" />
</packages>