This commit is contained in:
187
Api/Ewide.Core/Ewide.Core.Data/Dapper/CommandDefinition.cs
Normal file
187
Api/Ewide.Core/Ewide.Core.Data/Dapper/CommandDefinition.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
30
Api/Ewide.Core/Ewide.Core.Data/Dapper/CommandFlags.cs
Normal file
30
Api/Ewide.Core/Ewide.Core.Data/Dapper/CommandFlags.cs
Normal 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,
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Api/Ewide.Core/Ewide.Core.Data/Dapper/DataTableHandler.cs
Normal file
19
Api/Ewide.Core/Ewide.Core.Data/Dapper/DataTableHandler.cs
Normal 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
|
||||
75
Api/Ewide.Core/Ewide.Core.Data/Dapper/DbString.cs
Normal file
75
Api/Ewide.Core/Ewide.Core.Data/Dapper/DbString.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
203
Api/Ewide.Core/Ewide.Core.Data/Dapper/DefaultTypeMap.cs
Normal file
203
Api/Ewide.Core/Ewide.Core.Data/Dapper/DefaultTypeMap.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
505
Api/Ewide.Core/Ewide.Core.Data/Dapper/DynamicParameters.cs
Normal file
505
Api/Ewide.Core/Ewide.Core.Data/Dapper/DynamicParameters.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
33
Api/Ewide.Core/Ewide.Core.Data/Dapper/FeatureSupport.cs
Normal file
33
Api/Ewide.Core/Ewide.Core.Data/Dapper/FeatureSupport.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
87
Api/Ewide.Core/Ewide.Core.Data/Dapper/SimpleMemberMap.cs
Normal file
87
Api/Ewide.Core/Ewide.Core.Data/Dapper/SimpleMemberMap.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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<SqlDataRecord> 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
|
||||
907
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.Async.cs
Normal file
907
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.Async.cs
Normal 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<string,object></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<string,object></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<string,object></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<string,object></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<string,object></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<string,object></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
|
||||
19
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.CacheInfo.cs
Normal file
19
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.CacheInfo.cs
Normal 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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
214
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.DapperRow.cs
Normal file
214
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.DapperRow.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.DontMap.cs
Normal file
10
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.DontMap.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Dapper
|
||||
{
|
||||
partial class SqlMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Dummy type for excluding from multi-map
|
||||
/// </summary>
|
||||
class DontMap { }
|
||||
}
|
||||
}
|
||||
@@ -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<string,object></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<string,object></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<string,object></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<string,object></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<string,object></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
|
||||
381
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.GridReader.cs
Normal file
381
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.GridReader.cs
Normal 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<string,object></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<string,object></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<string,object></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<string,object></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<string,object></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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
137
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.IDataReader.cs
Normal file
137
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.IDataReader.cs
Normal 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<BaseType>();
|
||||
/// 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<BaseType>(typeof(Foo));
|
||||
/// var toBar = reader.GetRowParser<BaseType>(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 => 1;
|
||||
/// }
|
||||
/// class Bar : BaseType
|
||||
/// {
|
||||
/// public float Value { get; set; }
|
||||
/// public override int Type => 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.ITypeMap.cs
Normal file
46
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.ITypeMap.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
122
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.Identity.cs
Normal file
122
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.Identity.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.Link.cs
Normal file
57
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.Link.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.Settings.cs
Normal file
54
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.Settings.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3543
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.cs
Normal file
3543
Api/Ewide.Core/Ewide.Core.Data/Dapper/SqlMapper.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
106
Api/Ewide.Core/Ewide.Core.Data/Dapper/TypeExtensions.cs
Normal file
106
Api/Ewide.Core/Ewide.Core.Data/Dapper/TypeExtensions.cs
Normal 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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
43
Api/Ewide.Core/Ewide.Core.Data/Dapper/UdtTypeHandler.cs
Normal file
43
Api/Ewide.Core/Ewide.Core.Data/Dapper/UdtTypeHandler.cs
Normal 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
|
||||
}
|
||||
}
|
||||
20
Api/Ewide.Core/Ewide.Core.Data/Dapper/WrappedDataReader.cs
Normal file
20
Api/Ewide.Core/Ewide.Core.Data/Dapper/WrappedDataReader.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
186
Api/Ewide.Core/Ewide.Core.Data/Dapper/WrappedReader.cs
Normal file
186
Api/Ewide.Core/Ewide.Core.Data/Dapper/WrappedReader.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
35
Api/Ewide.Core/Ewide.Core.Data/Dapper/XmlHandlers.cs
Normal file
35
Api/Ewide.Core/Ewide.Core.Data/Dapper/XmlHandlers.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
79
Api/Ewide.Core/Ewide.Core.Data/Dapper/project.json
Normal file
79
Api/Ewide.Core/Ewide.Core.Data/Dapper/project.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<string, object> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
268
Api/Ewide.Core/Ewide.Core.Data/DapperExtensions/Database.cs
Normal file
268
Api/Ewide.Core/Ewide.Core.Data/DapperExtensions/Database.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
393
Api/Ewide.Core/Ewide.Core.Data/DapperExtensions/Predicates.cs
Normal file
393
Api/Ewide.Core/Ewide.Core.Data/DapperExtensions/Predicates.cs
Normal 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 <> '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 <> 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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 '"'; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Api/Ewide.Core/Ewide.Core.Data/DapperHelper.cs
Normal file
82
Api/Ewide.Core/Ewide.Core.Data/DapperHelper.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
167
Api/Ewide.Core/Ewide.Core.Data/Ewide.Core.Data.csproj
Normal file
167
Api/Ewide.Core/Ewide.Core.Data/Ewide.Core.Data.csproj
Normal 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>
|
||||
36
Api/Ewide.Core/Ewide.Core.Data/Properties/AssemblyInfo.cs
Normal file
36
Api/Ewide.Core/Ewide.Core.Data/Properties/AssemblyInfo.cs
Normal 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")]
|
||||
15
Api/Ewide.Core/Ewide.Core.Data/app.config
Normal file
15
Api/Ewide.Core/Ewide.Core.Data/app.config
Normal 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>
|
||||
13
Api/Ewide.Core/Ewide.Core.Data/packages.config
Normal file
13
Api/Ewide.Core/Ewide.Core.Data/packages.config
Normal 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>
|
||||
Reference in New Issue
Block a user