diff --git a/src/ServiceStack.OrmLite/Dapper/CommandDefinition.cs b/src/ServiceStack.OrmLite/Dapper/CommandDefinition.cs index 8998d96d9..25217dd1c 100644 --- a/src/ServiceStack.OrmLite/Dapper/CommandDefinition.cs +++ b/src/ServiceStack.OrmLite/Dapper/CommandDefinition.cs @@ -76,11 +76,16 @@ internal void OnCompleted() /// /// Initialize the command definition /// + /// The text for this command. + /// The parameters for this command. + /// The transaction for this command to participate in. + /// The timeout (in seconds) for this command. + /// The for this command. + /// The behavior flags for this command. + /// The cancellation token for this command. 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; @@ -89,9 +94,7 @@ internal void OnCompleted() CommandTimeout = commandTimeout; CommandType = commandType; Flags = flags; -#if ASYNC CancellationToken = cancellationToken; -#endif } private CommandDefinition(object parameters) : this() @@ -99,13 +102,10 @@ private CommandDefinition(object parameters) : this() Parameters = parameters; } -#if ASYNC - /// /// For asynchronous operations, the cancellation-token /// public CancellationToken CancellationToken { get; } -#endif internal IDbCommand SetupCommand(IDbConnection cnn, Action paramReader) { @@ -135,8 +135,7 @@ private static Action GetInit(Type commandType) { if (commandType == null) return null; // GIGO - Action action; - if (SqlMapper.Link>.TryGet(commandInitCache, commandType, out action)) + if (SqlMapper.Link>.TryGet(commandInitCache, commandType, out Action action)) { return action; } @@ -176,12 +175,11 @@ private static Action GetInit(Type commandType) 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) + if (prop?.CanWrite == true && prop.PropertyType == expectedType && prop.GetIndexParameters().Length == 0) { return prop.GetSetMethod(); } return null; } } - } diff --git a/src/ServiceStack.OrmLite/Dapper/CommandFlags.cs b/src/ServiceStack.OrmLite/Dapper/CommandFlags.cs index cc5c08020..12141516b 100644 --- a/src/ServiceStack.OrmLite/Dapper/CommandFlags.cs +++ b/src/ServiceStack.OrmLite/Dapper/CommandFlags.cs @@ -2,7 +2,6 @@ namespace ServiceStack.OrmLite.Dapper { - /// /// Additional state flags that control command behaviour /// @@ -26,5 +25,4 @@ public enum CommandFlags /// NoCache = 4, } - } diff --git a/src/ServiceStack.OrmLite/Dapper/CustomPropertyTypeMap.cs b/src/ServiceStack.OrmLite/Dapper/CustomPropertyTypeMap.cs index 5f97aa30b..3baf6c2a6 100644 --- a/src/ServiceStack.OrmLite/Dapper/CustomPropertyTypeMap.cs +++ b/src/ServiceStack.OrmLite/Dapper/CustomPropertyTypeMap.cs @@ -3,7 +3,6 @@ namespace ServiceStack.OrmLite.Dapper { - /// /// Implements custom property mapping by user provided criteria (usually presence of some custom attribute with column to member mapping) /// @@ -19,14 +18,8 @@ public sealed class CustomPropertyTypeMap : SqlMapper.ITypeMap /// Property selector based on target type and DataReader column name public CustomPropertyTypeMap(Type type, Func propertySelector) { - if (type == null) - throw new ArgumentNullException(nameof(type)); - - if (propertySelector == null) - throw new ArgumentNullException(nameof(propertySelector)); - - _type = type; - _propertySelector = propertySelector; + _type = type ?? throw new ArgumentNullException(nameof(type)); + _propertySelector = propertySelector ?? throw new ArgumentNullException(nameof(propertySelector)); } /// @@ -35,19 +28,14 @@ public CustomPropertyTypeMap(Type type, Func propert /// DataReader column names /// DataReader column types /// Default constructor - public ConstructorInfo FindConstructor(string[] names, Type[] types) - { - return _type.GetConstructor(new Type[0]); - } + public ConstructorInfo FindConstructor(string[] names, Type[] types) => + _type.GetConstructor(new Type[0]); /// /// Always returns null /// /// - public ConstructorInfo FindExplicitConstructor() - { - return null; - } + public ConstructorInfo FindExplicitConstructor() => null; /// /// Not implemented as far as default constructor used for all cases diff --git a/src/ServiceStack.OrmLite/Dapper/DataTableHandler.cs b/src/ServiceStack.OrmLite/Dapper/DataTableHandler.cs index 8bc6a9b06..a0bc5ca2b 100644 --- a/src/ServiceStack.OrmLite/Dapper/DataTableHandler.cs +++ b/src/ServiceStack.OrmLite/Dapper/DataTableHandler.cs @@ -1,9 +1,9 @@ using System; using System.Data; -#if !NETSTANDARD2_0 +#if !NETSTANDARD1_3 namespace ServiceStack.OrmLite.Dapper { - sealed class DataTableHandler : SqlMapper.ITypeHandler + internal sealed class DataTableHandler : SqlMapper.ITypeHandler { public object Parse(Type destinationType, object value) { diff --git a/src/ServiceStack.OrmLite/Dapper/DbString.cs b/src/ServiceStack.OrmLite/Dapper/DbString.cs index 8061fcaca..5db82d624 100644 --- a/src/ServiceStack.OrmLite/Dapper/DbString.cs +++ b/src/ServiceStack.OrmLite/Dapper/DbString.cs @@ -55,8 +55,17 @@ public void AddParameter(IDbCommand command, string name) { throw new InvalidOperationException("If specifying IsFixedLength, a Length must also be specified"); } - var param = command.CreateParameter(); - param.ParameterName = name; + bool add = !command.Parameters.Contains(name); + IDbDataParameter param; + if (add) + { + param = command.CreateParameter(); + param.ParameterName = name; + } + else + { + param = (IDbDataParameter)command.Parameters[name]; + } #pragma warning disable 0618 param.Value = SqlMapper.SanitizeParameterValue(Value); #pragma warning restore 0618 @@ -69,7 +78,10 @@ public void AddParameter(IDbCommand command, string name) param.Size = Length; } param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String); - command.Parameters.Add(param); + if (add) + { + command.Parameters.Add(param); + } } } } diff --git a/src/ServiceStack.OrmLite/Dapper/DefaultTypeMap.cs b/src/ServiceStack.OrmLite/Dapper/DefaultTypeMap.cs index f2a36d3b3..53b68b6cb 100644 --- a/src/ServiceStack.OrmLite/Dapper/DefaultTypeMap.cs +++ b/src/ServiceStack.OrmLite/Dapper/DefaultTypeMap.cs @@ -26,8 +26,8 @@ public DefaultTypeMap(Type type) Properties = GetSettableProps(type); _type = type; } -#if NETSTANDARD2_0 - static bool IsParameterMatch(ParameterInfo[] x, ParameterInfo[] y) +#if NETSTANDARD1_3 + private static bool IsParameterMatch(ParameterInfo[] x, ParameterInfo[] y) { if (ReferenceEquals(x, y)) return true; if (x == null || y == null) return false; @@ -40,7 +40,7 @@ static bool IsParameterMatch(ParameterInfo[] x, ParameterInfo[] y) internal static MethodInfo GetPropertySetter(PropertyInfo propertyInfo, Type type) { if (propertyInfo.DeclaringType == type) return propertyInfo.GetSetMethod(true); -#if NETSTANDARD2_0 +#if NETSTANDARD1_3 return propertyInfo.DeclaringType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Single(x => x.Name == propertyInfo.Name && x.PropertyType == propertyInfo.PropertyType @@ -91,15 +91,15 @@ public ConstructorInfo FindConstructor(string[] names, Type[] types) int i = 0; for (; i < ctorParameters.Length; i++) { - if (!String.Equals(ctorParameters[i].Name, names[i], StringComparison.OrdinalIgnoreCase)) + 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.IsEnum() && Enum.GetUnderlyingType(unboxedType) == types[i]) && !(unboxedType == typeof(char) && types[i] == typeof(string)) - && !(unboxedType.IsEnum && types[i] == typeof(string))) + && !(unboxedType.IsEnum() && types[i] == typeof(string))) { break; } @@ -118,7 +118,7 @@ public ConstructorInfo FindConstructor(string[] names, Type[] types) public ConstructorInfo FindExplicitConstructor() { var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); -#if NETSTANDARD2_0 +#if NETSTANDARD1_3 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(); @@ -152,13 +152,13 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, /// Mapping implementation 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)); + var property = Properties.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) + ?? Properties.Find(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)); + property = Properties.Find(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.Ordinal)) + ?? Properties.Find(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.OrdinalIgnoreCase)); } if (property != null) @@ -169,20 +169,20 @@ public SqlMapper.IMemberMap GetMember(string columnName) // 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)); + var field = _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) + ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) + ?? _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)) + ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); if (field == null && MatchNamesWithUnderscores) { var effectiveColumnName = columnName.Replace("_", ""); - backingFieldName = "<" +effectiveColumnName + ">k__BackingField"; + 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)); + field = _fields.Find(p => string.Equals(p.Name, effectiveColumnName, StringComparison.Ordinal)) + ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) + ?? _fields.Find(p => string.Equals(p.Name, effectiveColumnName, StringComparison.OrdinalIgnoreCase)) + ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); } if (field != null) diff --git a/src/ServiceStack.OrmLite/Dapper/DynamicParameters.CachedOutputSetters.cs b/src/ServiceStack.OrmLite/Dapper/DynamicParameters.CachedOutputSetters.cs index 46eb7d1ea..ce9117007 100644 --- a/src/ServiceStack.OrmLite/Dapper/DynamicParameters.CachedOutputSetters.cs +++ b/src/ServiceStack.OrmLite/Dapper/DynamicParameters.CachedOutputSetters.cs @@ -2,7 +2,7 @@ namespace ServiceStack.OrmLite.Dapper { - partial class DynamicParameters + public partial class DynamicParameters { // The type here is used to differentiate the cache by type via generics // ReSharper disable once UnusedTypeParameter diff --git a/src/ServiceStack.OrmLite/Dapper/DynamicParameters.ParamInfo.cs b/src/ServiceStack.OrmLite/Dapper/DynamicParameters.ParamInfo.cs index 18bf007f4..4e9d41f61 100644 --- a/src/ServiceStack.OrmLite/Dapper/DynamicParameters.ParamInfo.cs +++ b/src/ServiceStack.OrmLite/Dapper/DynamicParameters.ParamInfo.cs @@ -3,9 +3,9 @@ namespace ServiceStack.OrmLite.Dapper { - partial class DynamicParameters + public partial class DynamicParameters { - sealed class ParamInfo + private sealed class ParamInfo { public string Name { get; set; } public object Value { get; set; } diff --git a/src/ServiceStack.OrmLite/Dapper/DynamicParameters.cs b/src/ServiceStack.OrmLite/Dapper/DynamicParameters.cs index 6494f931d..7aed219eb 100644 --- a/src/ServiceStack.OrmLite/Dapper/DynamicParameters.cs +++ b/src/ServiceStack.OrmLite/Dapper/DynamicParameters.cs @@ -6,33 +6,24 @@ using System.Reflection; using System.Reflection.Emit; -#if NETSTANDARD2_0 +#if NETSTANDARD1_3 using ApplicationException = System.InvalidOperationException; #endif namespace ServiceStack.OrmLite.Dapper { - /// /// A bag of parameters that can be passed to the Dapper Query and Execute methods /// public partial class DynamicParameters : SqlMapper.IDynamicParameters, SqlMapper.IParameterLookup, SqlMapper.IParameterCallbacks { internal const DbType EnumerableMultiParameter = (DbType)(-1); - static Dictionary> paramReaderCache = new Dictionary>(); - - Dictionary parameters = new Dictionary(); - List templates; - - object SqlMapper.IParameterLookup.this[string member] - { - get - { - ParamInfo param; - return parameters.TryGetValue(member, out param) ? param.Value : null; - } - } + private static readonly Dictionary> paramReaderCache = new Dictionary>(); + private readonly Dictionary parameters = new Dictionary(); + private List templates; + object SqlMapper.IParameterLookup.this[string name] => + parameters.TryGetValue(name, out ParamInfo param) ? param.Value : null; /// /// construct a dynamic parameter bag @@ -102,8 +93,13 @@ public void AddDynamicParams(object param) } /// - /// Add a parameter to this dynamic parameter list + /// Add a parameter to this dynamic parameter list. /// + /// The name of the parameter. + /// The value of the parameter. + /// The type of the parameter. + /// The in or out direction of the parameter. + /// The size of the parameter. public void Add(string name, object value, DbType? dbType, ParameterDirection? direction, int? size) { parameters[Clean(name)] = new ParamInfo @@ -117,11 +113,16 @@ public void Add(string name, object value, DbType? dbType, ParameterDirection? d } /// - /// Add a parameter to this dynamic parameter list + /// Add a parameter to this dynamic parameter list. /// - public void Add( - string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null -) + /// The name of the parameter. + /// The value of the parameter. + /// The type of the parameter. + /// The in or out direction of the parameter. + /// The size of the parameter. + /// The precision of the parameter. + /// The scale of the parameter. + 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 { @@ -135,7 +136,7 @@ public void Add(string name, object value, DbType? dbType, ParameterDirection? d }; } - static string Clean(string name) + private static string Clean(string name) { if (!string.IsNullOrEmpty(name)) { @@ -249,7 +250,6 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) } else { - bool add = !command.Parameters.Contains(name); IDbDataParameter p; if (add) @@ -307,7 +307,6 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) /// public IEnumerable ParameterNames => parameters.Select(p => p.Key); - /// /// Get the value of a parameter /// @@ -344,23 +343,26 @@ public DynamicParameters Output(T target, Expression> express { 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); }; + 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 (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) + 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(); + else + { + @throw(); + } } // Does the chain consist of MemberExpressions leading to a ParameterExpression of type T? @@ -379,21 +381,20 @@ public DynamicParameters Output(T target, Expression> express var constant = diving?.Expression as ParameterExpression; diving = diving?.Expression as MemberExpression; - if (constant != null && - constant.Type == typeof(T)) + if (constant != null && constant.Type == typeof(T)) { break; } - else if (diving == null || - (!(diving.Member is PropertyInfo) && - !(diving.Member is FieldInfo))) + else if (diving == null + || (!(diving.Member is PropertyInfo) + && !(diving.Member is FieldInfo))) { @throw(); } } while (diving != null); - var dynamicParamName = string.Join(string.Empty, names.ToArray()); + var dynamicParamName = string.Concat(names.ToArray()); // Before we get all emitty... var lookup = string.Join("|", names.ToArray()); @@ -422,7 +423,7 @@ public DynamicParameters Output(T target, Expression> express } else // Else it must be a field! { - il.Emit(OpCodes.Ldfld, ((FieldInfo)member)); // [Member{i}] + il.Emit(OpCodes.Ldfld, (FieldInfo)member); // [Member{i}] } } @@ -441,7 +442,7 @@ public DynamicParameters Output(T target, Expression> express } else { - il.Emit(OpCodes.Stfld, ((FieldInfo)lastMember)); // SET + il.Emit(OpCodes.Stfld, (FieldInfo)lastMember); // SET } il.Emit(OpCodes.Ret); // GO @@ -457,11 +458,10 @@ public DynamicParameters Output(T target, Expression> express (outputCallbacks ?? (outputCallbacks = new List())).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)) + if (parameters.TryGetValue(dynamicParamName, out ParamInfo parameter)) { parameter.ParameterDirection = parameter.AttachedParam.Direction = ParameterDirection.InputOutput; @@ -472,10 +472,9 @@ public DynamicParameters Output(T target, Expression> express } else { - SqlMapper.ITypeHandler handler; dbType = (!dbType.HasValue) #pragma warning disable 618 - ? SqlMapper.LookupDbType(targetMemberType, targetMemberType?.Name, true, out handler) + ? SqlMapper.LookupDbType(targetMemberType, targetMemberType?.Name, true, out SqlMapper.ITypeHandler handler) #pragma warning restore 618 : dbType; @@ -493,10 +492,10 @@ public DynamicParameters Output(T target, Expression> express } private List outputCallbacks; - + void SqlMapper.IParameterCallbacks.OnCompleted() { - foreach (var param in (from p in parameters select p.Value)) + foreach (var param in from p in parameters select p.Value) { param.OutputCallback?.Invoke(param.OutputTarget, this); } diff --git a/src/ServiceStack.OrmLite/Dapper/FeatureSupport.cs b/src/ServiceStack.OrmLite/Dapper/FeatureSupport.cs index 5fc314e34..356c30ba5 100644 --- a/src/ServiceStack.OrmLite/Dapper/FeatureSupport.cs +++ b/src/ServiceStack.OrmLite/Dapper/FeatureSupport.cs @@ -6,7 +6,7 @@ namespace ServiceStack.OrmLite.Dapper /// /// Handles variances in features per DBMS /// - class FeatureSupport + internal class FeatureSupport { private static readonly FeatureSupport Default = new FeatureSupport(false), @@ -15,16 +15,19 @@ private static readonly FeatureSupport /// /// Gets the feature set based on the passed connection /// + /// The connection to get supported features for. 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; } + /// /// True if the db supports array columns e.g. Postgresql /// diff --git a/src/ServiceStack.OrmLite/Dapper/SimpleMemberMap.cs b/src/ServiceStack.OrmLite/Dapper/SimpleMemberMap.cs index 8974d554c..ca12e933e 100644 --- a/src/ServiceStack.OrmLite/Dapper/SimpleMemberMap.cs +++ b/src/ServiceStack.OrmLite/Dapper/SimpleMemberMap.cs @@ -6,7 +6,7 @@ namespace ServiceStack.OrmLite.Dapper /// /// Represents simple member map for one of target parameter or property or field to source DataReader column /// - sealed class SimpleMemberMap : SqlMapper.IMemberMap + internal sealed class SimpleMemberMap : SqlMapper.IMemberMap { /// /// Creates instance for simple property mapping @@ -15,14 +15,8 @@ sealed class SimpleMemberMap : SqlMapper.IMemberMap /// Target property 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; + ColumnName = columnName ?? throw new ArgumentNullException(nameof(columnName)); + Property = property ?? throw new ArgumentNullException(nameof(property)); } /// @@ -32,14 +26,8 @@ public SimpleMemberMap(string columnName, PropertyInfo property) /// Target property 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; + ColumnName = columnName ?? throw new ArgumentNullException(nameof(columnName)); + Field = field ?? throw new ArgumentNullException(nameof(field)); } /// @@ -49,14 +37,8 @@ public SimpleMemberMap(string columnName, FieldInfo field) /// Target constructor parameter 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; + ColumnName = columnName ?? throw new ArgumentNullException(nameof(columnName)); + Parameter = parameter ?? throw new ArgumentNullException(nameof(parameter)); } /// diff --git a/src/ServiceStack.OrmLite/Dapper/SqlDataRecordHandler.cs b/src/ServiceStack.OrmLite/Dapper/SqlDataRecordHandler.cs index 826277a11..782f6a1d8 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlDataRecordHandler.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlDataRecordHandler.cs @@ -2,10 +2,9 @@ using System.Collections.Generic; using System.Data; -#if !NETSTANDARD2_0 namespace ServiceStack.OrmLite.Dapper { - sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler + internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler { public object Parse(Type destinationType, object value) { @@ -18,4 +17,3 @@ public void SetValue(IDbDataParameter parameter, object value) } } } -#endif \ No newline at end of file diff --git a/src/ServiceStack.OrmLite/Dapper/SqlDataRecordListTVPParameter.cs b/src/ServiceStack.OrmLite/Dapper/SqlDataRecordListTVPParameter.cs index ec95b5d0d..20e9096b6 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlDataRecordListTVPParameter.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlDataRecordListTVPParameter.cs @@ -1,35 +1,27 @@ using System; using System.Collections.Generic; using System.Data; -using System.Reflection; -#if !NETSTANDARD2_0 + namespace ServiceStack.OrmLite.Dapper { /// /// Used to pass a IEnumerable<SqlDataRecord> as a SqlDataRecordListTVPParameter /// - sealed class SqlDataRecordListTVPParameter : SqlMapper.ICustomQueryParameter + internal sealed class SqlDataRecordListTVPParameter : SqlMapper.ICustomQueryParameter { private readonly IEnumerable data; private readonly string typeName; /// - /// Create a new instance of SqlDataRecordListTVPParameter + /// Create a new instance of . /// + /// The data records to convert into TVPs. + /// The parameter type name. public SqlDataRecordListTVPParameter(IEnumerable data, string typeName) { this.data = data; this.typeName = typeName; } - static readonly Action 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) - Delegate.CreateDelegate(typeof(Action), prop.GetSetMethod()); - } - } + void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name) { var param = command.CreateParameter(); @@ -37,11 +29,11 @@ void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string nam Set(param, data, typeName); command.Parameters.Add(param); } + internal static void Set(IDbDataParameter parameter, IEnumerable data, string typeName) { parameter.Value = (object)data ?? DBNull.Value; - var sqlParam = parameter as System.Data.SqlClient.SqlParameter; - if (sqlParam != null) + if (parameter is System.Data.SqlClient.SqlParameter sqlParam) { sqlParam.SqlDbType = SqlDbType.Structured; sqlParam.TypeName = typeName; @@ -49,4 +41,3 @@ internal static void Set(IDbDataParameter parameter, IEnumerable /// Execute a query asynchronously using .NET 4.5 Task. /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) - { - return QueryAsync(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); - } + public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryAsync(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); /// /// Execute a query asynchronously using .NET 4.5 Task. /// + /// The connection to query on. + /// The command used to query on this connection. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command) - { - return QueryAsync(cnn, typeof(DapperRow), command); - } + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command) => + QueryAsync(cnn, typeof(DapperRow), command); /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// + /// The connection to query on. + /// The command used to query on this connection. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static Task QueryFirstAsync(this IDbConnection cnn, CommandDefinition command) - { - return QueryRowAsync(cnn, Row.First, typeof(DapperRow), command); - } + public static Task QueryFirstAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.First, typeof(DapperRow), command); + /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// + /// The connection to query on. + /// The command used to query on this connection. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) - { - return QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), command); - } + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), command); + /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// + /// The connection to query on. + /// The command used to query on this connection. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static Task QuerySingleAsync(this IDbConnection cnn, CommandDefinition command) - { - return QueryRowAsync(cnn, Row.Single, typeof(DapperRow), command); - } + public static Task QuerySingleAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.Single, typeof(DapperRow), command); + /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// + /// The connection to query on. + /// The command used to query on this connection. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) - { - return QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), command); - } + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), command); /// /// Execute a query asynchronously using .NET 4.5 Task. /// - public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) - { - return QueryAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); - } + /// The type of results to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). + /// + public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// - public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) - { - return QueryRowAsync(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); - } + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// - public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) - { - return QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); - } + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// - public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) - { - return QueryRowAsync(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); - } + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// - public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) - { - return QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); - } + /// The type to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.First, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.Single, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); /// /// Execute a query asynchronously using .NET 4.5 Task. /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. public static Task> 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)); @@ -113,6 +207,14 @@ public static Task> QueryAsync(this IDbConnection cnn, Type /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. public static Task 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)); @@ -121,6 +223,14 @@ public static Task QueryFirstAsync(this IDbConnection cnn, Type type, st /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. public static Task 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)); @@ -129,6 +239,14 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. public static Task 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)); @@ -137,6 +255,14 @@ public static Task QuerySingleAsync(this IDbConnection cnn, Type type, s /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. public static Task 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)); @@ -146,57 +272,107 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Typ /// /// Execute a query asynchronously using .NET 4.5 Task. /// - public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command) - { - return QueryAsync(cnn, typeof(T), command); - } + /// The type to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). + /// + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command) => + QueryAsync(cnn, typeof(T), command); /// /// Execute a query asynchronously using .NET 4.5 Task. /// - public static Task> QueryAsync(this IDbConnection cnn, Type type, CommandDefinition command) - { - return QueryAsync(cnn, type, command); - } + /// The connection to query on. + /// The type to return. + /// The command used to query on this connection. + public static Task> QueryAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryAsync(cnn, type, command); + /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// - public static Task QueryFirstAsync(this IDbConnection cnn, Type type, CommandDefinition command) - { - return QueryRowAsync(cnn, Row.First, type, command); - } + /// The connection to query on. + /// The type to return. + /// The command used to query on this connection. + public static Task QueryFirstAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryRowAsync(cnn, Row.First, type, command); + /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// - public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) - { - return QueryRowAsync(cnn, Row.FirstOrDefault, type, command); - } + /// The type to return. + /// The connection to query on. + /// The command used to query on this connection. + public static Task QueryFirstAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.First, typeof(T), command); + /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// - public static Task QuerySingleAsync(this IDbConnection cnn, Type type, CommandDefinition command) - { - return QueryRowAsync(cnn, Row.Single, type, command); - } + /// The connection to query on. + /// The type to return. + /// The command used to query on this connection. + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryRowAsync(cnn, Row.FirstOrDefault, type, command); + /// /// Execute a single-row query asynchronously using .NET 4.5 Task. /// - public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) - { - return QueryRowAsync(cnn, Row.SingleOrDefault, type, command); - } + /// The type to return. + /// The connection to query on. + /// The command used to query on this connection. + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), command); + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The command used to query on this connection. + public static Task QuerySingleAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryRowAsync(cnn, Row.Single, type, command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The type to return. + /// The connection to query on. + /// The command used to query on this connection. + public static Task QuerySingleAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.Single, typeof(T), command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The connection to query on. + /// The type to return. + /// The command used to query on this connection. + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryRowAsync(cnn, Row.SingleOrDefault, type, command); + + /// + /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// + /// The type to return. + /// The connection to query on. + /// The command used to query on this connection. + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), command); private static Task 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)) + if (task.Status == TaskStatus.Faulted && Settings.DisableCommandBehaviorOptimizations(behavior, task.Exception.InnerException)) { // we can retry; this time it will have different flags - task = cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken); + return cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken); } return task; } + private static async Task> QueryAsync(this IDbConnection cnn, Type effectiveType, CommandDefinition command) { object param = command.Parameters; @@ -216,6 +392,8 @@ private static async Task> QueryAsync(this IDbConnection cnn, int hash = GetColumnHash(reader); if (tuple.Func == null || tuple.Hash != hash) { + if (reader.FieldCount == 0) + return Enumerable.Empty(); tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); if (command.AddToCache) SetQueryCache(identity, info); } @@ -224,21 +402,21 @@ private static async Task> QueryAsync(this IDbConnection cnn, if (command.Buffered) { - List buffer = new List(); + var buffer = new List(); 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); + buffer.Add((T)val); } else { buffer.Add((T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture)); } } - while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { } + while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ } command.OnCompleted(); return buffer; } @@ -253,12 +431,12 @@ private static async Task> QueryAsync(this IDbConnection cnn, } finally { - using (reader) { } // dispose if non-null + using (reader) { /* dispose if non-null */ } if (wasClosed) cnn.Close(); - } } } + private static async Task QueryRowAsync(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) { object param = command.Parameters; @@ -300,18 +478,18 @@ private static async Task QueryRowAsync(this IDbConnection cnn, Row row, T 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)) { } + while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore rows after the first */ } } else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { ThrowZeroRows(row); } - while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { } + while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore result sets after the first */ } return result; } finally { - using (reader) { } // dispose if non-null + using (reader) { /* dispose if non-null */ } if (wasClosed) cnn.Close(); } } @@ -320,14 +498,22 @@ private static async Task QueryRowAsync(this IDbConnection cnn, Row row, T /// /// Execute a command asynchronously using .NET 4.5 Task. /// - public static Task 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))); - } + /// The connection to query on. + /// The SQL to execute for this query. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The number of rows affected. + public static Task ExecuteAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + ExecuteAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); /// /// Execute a command asynchronously using .NET 4.5 Task. /// + /// The connection to execute on. + /// The command to execute on this connection. + /// The number of rows affected. public static Task ExecuteAsync(this IDbConnection cnn, CommandDefinition command) { object param = command.Parameters; @@ -352,6 +538,7 @@ public AsyncExecState(DbCommand command, Task task) Task = task; } } + private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandDefinition command, IEnumerable multiExec) { bool isFirst = true; @@ -379,7 +566,8 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD 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) + } + else if (pending.Count >= MAX_PENDING) { var recycled = pending.Dequeue(); total += await recycled.Task.ConfigureAwait(false); @@ -400,16 +588,17 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD while (pending.Count != 0) { var pair = pending.Dequeue(); - using (pair.Command) { } // dispose commands + using (pair.Command) { /* dispose commands */ } total += await pair.Task.ConfigureAwait(false); } - } finally + } + finally { // this only has interesting work to do if there are failures - using (cmd) { } // dispose commands + using (cmd) { /* dispose commands */ } while (pending.Count != 0) { // dispose tasks even in failure - using (pending.Dequeue().Command) { } // dispose commands + using (pending.Dequeue().Command) { /* dispose commands */ } } } } @@ -445,6 +634,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD } return total; } + private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object param) { var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType(), null); @@ -467,175 +657,250 @@ private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefini } /// - /// Maps a query to objects + /// Perform a asynchronous multi-mapping query with 2 input types. + /// This returns a single type, combined from the raw types via . /// - /// The first type in the recordset - /// The second type in the recordset - /// The return type - /// - /// - /// - /// - /// - /// - /// The field we should split and read the second object from (default: id) - /// Number of seconds before command execution timeout + /// The first type in the recordset. + /// The second type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? - /// - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) - { - return MultiMapAsync(cnn, + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); - } /// - /// Maps a query to objects + /// Perform a asynchronous multi-mapping query with 2 input types. + /// This returns a single type, combined from the raw types via . /// - /// The first type in the recordset - /// The second type in the recordset - /// The return type - /// - /// The field we should split and read the second object from (default: id) - /// The command to execute - /// - /// - public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") - { - return MultiMapAsync(cnn, command, map, splitOn); - } + /// The first type in the recordset. + /// The second type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); /// - /// Maps a query to objects - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// The Field we should split and read the second object from (default: id) - /// Number of seconds before command execution timeout - /// - /// - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) - { - return MultiMapAsync(cnn, + /// Perform a asynchronous multi-mapping query with 3 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); - } /// - /// Maps a query to objects + /// Perform a asynchronous multi-mapping query with 3 input types. + /// This returns a single type, combined from the raw types via . /// - /// - /// - /// - /// - /// - /// The field we should split and read the second object from (default: id) - /// The command to execute - /// - /// - public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") - { - return MultiMapAsync(cnn, command, map, splitOn); - } + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); /// - /// Perform a multi mapping query with 4 input parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) - { - return MultiMapAsync(cnn, + /// Perform a asynchronous multi-mapping query with 4 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); - } /// - /// Perform a multi mapping query with 4 input parameters + /// Perform a asynchronous multi-mapping query with 4 input types. + /// This returns a single type, combined from the raw types via . /// - /// - /// - /// - /// - /// - /// - /// The field we should split and read the second object from (default: id) - /// The command to execute - /// - /// - public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") - { - return MultiMapAsync(cnn, command, map, splitOn); - } + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); /// - /// Perform a multi mapping query with 5 input parameters + /// Perform a asynchronous multi-mapping query with 5 input types. + /// This returns a single type, combined from the raw types via . /// - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) - { - return MultiMapAsync(cnn, + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); - } /// - /// Perform a multi mapping query with 5 input parameters + /// Perform a asynchronous multi-mapping query with 5 input types. + /// This returns a single type, combined from the raw types via . /// - public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") - { - return MultiMapAsync(cnn, command, map, splitOn); - } + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); /// - /// Perform a multi mapping query with 6 input parameters + /// Perform a asynchronous multi-mapping query with 6 input types. + /// This returns a single type, combined from the raw types via . /// - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) - { - return MultiMapAsync(cnn, + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); - } /// - /// Perform a multi mapping query with 6 input parameters + /// Perform a asynchronous multi-mapping query with 6 input types. + /// This returns a single type, combined from the raw types via . /// - public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") - { - return MultiMapAsync(cnn, command, map, splitOn); - } + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); /// - /// Perform a multi mapping query with 7 input parameters + /// Perform a asynchronous multi-mapping query with 7 input types. + /// This returns a single type, combined from the raw types via . /// - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) - { - return MultiMapAsync(cnn, + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The seventh type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); - } /// - /// Perform a multi mapping query with 7 input parameters + /// Perform an asynchronous multi-mapping query with 7 input types. + /// This returns a single type, combined from the raw types via . /// - public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") - { - return MultiMapAsync(cnn, command, map, splitOn); - } + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The seventh type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The field we should split and read the second object from (default: "Id"). + /// The command to execute. + /// The function to map row types to the return type. + /// An enumerable of . + public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => + MultiMapAsync(cnn, command, map, splitOn); private static async Task> MultiMapAsync(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn) { @@ -653,31 +918,33 @@ private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefini var results = MultiMapImpl(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true); return command.Buffered ? results.ToList() : results; } - } finally + } + finally { if (wasClosed) cnn.Close(); } } /// - /// Perform a multi mapping query with arbitrary input parameters + /// Perform a asynchronous multi-mapping query with an arbitrary number of input types. + /// This returns a single type, combined from the raw types via . /// - /// The return type - /// - /// - /// array of types in the recordset - /// - /// - /// - /// - /// The Field we should split and read the second object from (default: id) - /// Number of seconds before command execution timeout + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// Array of types in the recordset. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? - /// + /// An enumerable of . public static Task> QueryAsync(this IDbConnection cnn, string sql, Type[] types, Func 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(cnn, command, types, map, splitOn); + return MultiMapAsync(cnn, command, types, map, splitOn); } private static async Task> MultiMapAsync(this IDbConnection cnn, CommandDefinition command, Type[] types, Func map, string splitOn) @@ -691,15 +958,18 @@ private static async Task> MultiMapAsync(this IDbC 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 { + 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(null, default(CommandDefinition), types, map, splitOn, reader, identity, true); + using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false)) + { + var results = MultiMapImpl(null, default(CommandDefinition), types, map, splitOn, reader, identity, true); return command.Buffered ? results.ToList() : results; } } - finally { + finally + { if (wasClosed) cnn.Close(); } } @@ -712,29 +982,32 @@ private static IEnumerable ExecuteReaderSync(IDataReader reader, Func - /// Execute a command that returns multiple result sets, and access each in turn + /// Execute a command that returns multiple result sets, and access each in turn. /// - public static Task 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); - } + /// The connection to query on. + /// The SQL to execute for this query. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + public static Task QueryMultipleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryMultipleAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); /// - /// Execute a command that returns multiple result sets, and access each in turn + /// Execute a command that returns multiple result sets, and access each in turn. /// + /// The connection to query on. + /// The command to execute for this query. public static async Task QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command) { object param = command.Parameters; - Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType(), null); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); DbCommand cmd = null; @@ -758,10 +1031,12 @@ public static async Task QueryMultipleAsync(this IDbConnection cnn, if (reader != null) { if (!reader.IsClosed) - try - { cmd.Cancel(); } + { + try { cmd.Cancel(); } catch - { /* don't spoil the existing exception */ } + { /* don't spoil the existing exception */ + } + } reader.Dispose(); } cmd?.Dispose(); @@ -770,10 +1045,15 @@ public static async Task QueryMultipleAsync(this IDbConnection cnn, } } - /// - /// Execute parameterized SQL and return an + /// Execute parameterized SQL and return an . /// + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a @@ -790,28 +1070,37 @@ public static async Task QueryMultipleAsync(this IDbConnection cnn, /// ]]> /// /// - public static Task 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); - } + public static Task ExecuteReaderAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + ExecuteReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default); /// - /// Execute parameterized SQL and return an + /// Execute parameterized SQL and return an . /// + /// The connection to execute on. + /// The command to execute. /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a /// or . /// - public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command) - { - return ExecuteReaderImplAsync(cnn, command); - } + public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command) => + ExecuteReaderImplAsync(cnn, command, CommandBehavior.Default); - private static async Task ExecuteReaderImplAsync(IDbConnection cnn, CommandDefinition command) + /// + /// Execute parameterized SQL and return an . + /// + /// The connection to execute on. + /// The command to execute. + /// The flags for this reader. + /// An that can be used to iterate over the results of the SQL query. + /// + /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a + /// or . + /// + public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) => + ExecuteReaderImplAsync(cnn, command, commandBehavior); + + private static async Task ExecuteReaderImplAsync(IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) { Action paramReader = GetParameterReader(cnn, ref command); @@ -821,7 +1110,7 @@ private static async Task ExecuteReaderImplAsync(IDbConnection cnn, { 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); + var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, commandBehavior, command.CancellationToken).ConfigureAwait(false); wasClosed = false; return reader; } @@ -832,48 +1121,51 @@ private static async Task ExecuteReaderImplAsync(IDbConnection cnn, } } - /// - /// Execute parameterized SQL that selects a single value + /// Execute parameterized SQL that selects a single value. /// - /// The first cell selected - public static Task 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(cnn, command); - } + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The first cell returned, as . + public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + ExecuteScalarImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); /// - /// Execute parameterized SQL that selects a single value + /// Execute parameterized SQL that selects a single value. /// - /// The first cell selected - public static Task 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(cnn, command); - } + /// The type to return. + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The first cell returned, as . + public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + ExecuteScalarImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); /// - /// Execute parameterized SQL that selects a single value + /// Execute parameterized SQL that selects a single value. /// - /// The first cell selected - public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) - { - return ExecuteScalarImplAsync(cnn, command); - } + /// The connection to execute on. + /// The command to execute. + /// The first cell selected as . + public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) => + ExecuteScalarImplAsync(cnn, command); /// /// Execute parameterized SQL that selects a single value /// - /// The first cell selected - public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) - { - return ExecuteScalarImplAsync(cnn, command); - } + /// The type to return. + /// The connection to execute on. + /// The command to execute. + /// The first cell selected as . + public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) => + ExecuteScalarImplAsync(cnn, command); private static async Task ExecuteScalarImplAsync(IDbConnection cnn, CommandDefinition command) { @@ -904,4 +1196,3 @@ private static async Task ExecuteScalarImplAsync(IDbConnection cnn, Comman } } } -#endif \ No newline at end of file diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.CacheInfo.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.CacheInfo.cs index 483090456..1b28556e9 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.CacheInfo.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.CacheInfo.cs @@ -4,9 +4,9 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { - class CacheInfo + private class CacheInfo { public DeserializerState Deserializer { get; set; } public Func[] OtherDeserializers { get; set; } diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperRow.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperRow.cs index 757d25b2f..49e172106 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperRow.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperRow.cs @@ -5,27 +5,28 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { private sealed class DapperRow : System.Dynamic.IDynamicMetaObjectProvider , IDictionary + , IReadOnlyDictionary { - readonly DapperTable table; - object[] values; + private readonly DapperTable table; + private 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; + this.table = table ?? throw new ArgumentNullException(nameof(table)); + this.values = values ?? throw new ArgumentNullException(nameof(values)); } + private sealed class DeadValue { public static readonly DeadValue Default = new DeadValue(); - private DeadValue() { } + private DeadValue() { /* hiding constructor */ } } + int ICollection>.Count { get @@ -39,9 +40,9 @@ private sealed class DeadValue } } - public bool TryGetValue(string name, out object value) + public bool TryGetValue(string key, out object value) { - var index = table.IndexOfName(name); + var index = table.IndexOfName(key); if (index < 0) { // doesn't exist value = null; @@ -117,8 +118,7 @@ IEnumerator IEnumerable.GetEnumerator() bool ICollection>.Contains(KeyValuePair item) { - object value; - return TryGetValue(item.Key, out value) && Equals(value, item.Value); + return TryGetValue(item.Key, out object value) && Equals(value, item.Value); } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) @@ -162,7 +162,7 @@ IEnumerator IEnumerable.GetEnumerator() object IDictionary.this[string key] { - get { object val; TryGetValue(key, out val); return val; } + get { TryGetValue(key, out object val); return val; } set { SetValue(key, value, false); } } @@ -209,6 +209,41 @@ private object SetValue(string key, object value, bool isAdd) } #endregion + + + #region Implementation of IReadOnlyDictionary + + + int IReadOnlyCollection>.Count + { + get + { + return values.Count(t => !(t is DeadValue)); + } + } + + bool IReadOnlyDictionary.ContainsKey(string key) + { + int index = table.IndexOfName(key); + return index >= 0 && index < values.Length && !(values[index] is DeadValue); + } + + object IReadOnlyDictionary.this[string key] + { + get { TryGetValue(key, out object val); return val; } + } + + IEnumerable IReadOnlyDictionary.Keys + { + get { return this.Select(kv => kv.Key); } + } + + IEnumerable IReadOnlyDictionary.Values + { + get { return this.Select(kv => kv.Value); } + } + + #endregion } } } diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperRowMetaObject.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperRowMetaObject.cs index 499555663..7203fae13 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperRowMetaObject.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperRowMetaObject.cs @@ -3,12 +3,12 @@ using System.Reflection; namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { - sealed class DapperRowMetaObject : System.Dynamic.DynamicMetaObject + private sealed class DapperRowMetaObject : System.Dynamic.DynamicMetaObject { - static readonly MethodInfo getValueMethod = typeof(IDictionary).GetProperty("Item").GetGetMethod(); - static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) }); + private static readonly MethodInfo getValueMethod = typeof(IDictionary).GetProperty("Item").GetGetMethod(); + private static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) }); public DapperRowMetaObject( System.Linq.Expressions.Expression expression, @@ -27,7 +27,7 @@ object value { } - System.Dynamic.DynamicMetaObject CallMethod( + private System.Dynamic.DynamicMetaObject CallMethod( MethodInfo method, System.Linq.Expressions.Expression[] parameters ) diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperTable.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperTable.cs index a48765c59..4754784d7 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperTable.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.DapperTable.cs @@ -3,19 +3,18 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { private sealed class DapperTable { - string[] fieldNames; - readonly Dictionary fieldNameLookup; + private string[] fieldNames; + private readonly Dictionary fieldNameLookup; internal string[] FieldNames => fieldNames; public DapperTable(string[] fieldNames) { - if (fieldNames == null) throw new ArgumentNullException(nameof(fieldNames)); - this.fieldNames = fieldNames; + this.fieldNames = fieldNames ?? throw new ArgumentNullException(nameof(fieldNames)); fieldNameLookup = new Dictionary(fieldNames.Length, StringComparer.Ordinal); // if there are dups, we want the **first** key to be the "winner" - so iterate backwards @@ -28,9 +27,9 @@ public DapperTable(string[] fieldNames) internal int IndexOfName(string name) { - int result; - return (name != null && fieldNameLookup.TryGetValue(name, out result)) ? result : -1; + return (name != null && fieldNameLookup.TryGetValue(name, out int result)) ? result : -1; } + internal int AddField(string name) { if (name == null) throw new ArgumentNullException(nameof(name)); @@ -41,7 +40,7 @@ internal int AddField(string name) fieldNameLookup[name] = oldLen; return oldLen; } - + internal bool FieldExists(string key) => key != null && fieldNameLookup.ContainsKey(key); public int FieldCount => fieldNames.Length; diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.DeserializerState.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.DeserializerState.cs index a7fbb89fc..e302030f2 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.DeserializerState.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.DeserializerState.cs @@ -3,9 +3,9 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { - struct DeserializerState + private struct DeserializerState { public readonly int Hash; public readonly Func Func; diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.DontMap.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.DontMap.cs index 39c0aa16e..96e9f2e17 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.DontMap.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.DontMap.cs @@ -1,10 +1,10 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Dummy type for excluding from multi-map /// - class DontMap { } + private class DontMap { /* hiding constructor */ } } } diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.GridReader.Async.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.GridReader.Async.cs index d83cb6736..4b83b2d00 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.GridReader.Async.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.GridReader.Async.cs @@ -1,5 +1,4 @@ -#if ASYNC -using System; +using System; using System.Collections.Generic; using System.Data; using System.Data.Common; @@ -9,11 +8,11 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { - partial class GridReader + public partial class GridReader { - CancellationToken cancel; + private readonly CancellationToken cancel; internal GridReader(IDbCommand command, IDataReader reader, Identity identity, DynamicParameters dynamicParams, bool addToCache, CancellationToken cancel) : this(command, reader, identity, dynamicParams, addToCache) { @@ -24,47 +23,39 @@ internal GridReader(IDbCommand command, IDataReader reader, Identity identity, D /// Read the next grid of results, returned as a dynamic object /// /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public Task> ReadAsync(bool buffered = true) - { - return ReadAsyncImpl(typeof(DapperRow), buffered); - } + /// Whether to buffer the results. + public Task> ReadAsync(bool buffered = true) => ReadAsyncImpl(typeof(DapperRow), buffered); /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public Task ReadFirstAsync() - { - return ReadRowAsyncImpl(typeof(DapperRow), Row.First); - } + public Task ReadFirstAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.First); + /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public Task ReadFirstOrDefaultAsync() - { - return ReadRowAsyncImpl(typeof(DapperRow), Row.FirstOrDefault); - } + public Task ReadFirstOrDefaultAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.FirstOrDefault); + /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public Task ReadSingleAsync() - { - return ReadRowAsyncImpl(typeof(DapperRow), Row.Single); - } + public Task ReadSingleAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.Single); + /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public Task ReadSingleOrDefaultAsync() - { - return ReadRowAsyncImpl(typeof(DapperRow), Row.SingleOrDefault); - } + public Task ReadSingleOrDefaultAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.SingleOrDefault); /// /// Read the next grid of results /// + /// The type to read. + /// Whether to buffer the results. + /// is null. public Task> ReadAsync(Type type, bool buffered = true) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -74,30 +65,41 @@ public Task> ReadAsync(Type type, bool buffered = true) /// /// Read an individual row of the next grid of results /// + /// The type to read. + /// is null. public Task ReadFirstAsync(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.First); } + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// + /// The type to read. + /// is null. public Task ReadFirstOrDefaultAsync(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.FirstOrDefault); } + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// + /// The type to read. + /// is null. public Task ReadSingleAsync(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.Single); } + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// + /// The type to read. + /// is null. public Task ReadSingleOrDefaultAsync(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -105,41 +107,35 @@ public Task ReadSingleOrDefaultAsync(Type type) } /// - /// Read the next grid of results + /// Read the next grid of results. /// - public Task> ReadAsync(bool buffered = true) - { - return ReadAsyncImpl(typeof(T), buffered); - } + /// The type to read. + /// Whether the results should be buffered in memory. + public Task> ReadAsync(bool buffered = true) => ReadAsyncImpl(typeof(T), buffered); /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// - public Task ReadFirstAsync() - { - return ReadRowAsyncImpl(typeof(T), Row.First); - } + /// The type to read. + public Task ReadFirstAsync() => ReadRowAsyncImpl(typeof(T), Row.First); + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// - public Task ReadFirstOrDefaultAsync() - { - return ReadRowAsyncImpl(typeof(T), Row.FirstOrDefault); - } + /// The type to read. + public Task ReadFirstOrDefaultAsync() => ReadRowAsyncImpl(typeof(T), Row.FirstOrDefault); + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// - public Task ReadSingleAsync() - { - return ReadRowAsyncImpl(typeof(T), Row.Single); - } + /// The type to read. + public Task ReadSingleAsync() => ReadRowAsyncImpl(typeof(T), Row.Single); + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// - public Task ReadSingleOrDefaultAsync() - { - return ReadRowAsyncImpl(typeof(T), Row.SingleOrDefault); - } + /// The type to read. + public Task ReadSingleOrDefaultAsync() => ReadRowAsyncImpl(typeof(T), Row.SingleOrDefault); private async Task NextResultAsync() { @@ -177,11 +173,11 @@ private Task> ReadAsyncImpl(Type type, bool buffered) IsConsumed = true; if (buffered && reader is DbDataReader) { - return ReadBufferedAsync(gridIndex, deserializer.Func, typedIdentity); + return ReadBufferedAsync(gridIndex, deserializer.Func); } else { - var result = ReadDeferred(gridIndex, deserializer.Func, typedIdentity, type); + var result = ReadDeferred(gridIndex, deserializer.Func, type); if (buffered) result = result.ToList(); // for the "not a DbDataReader" scenario return Task.FromResult(result); } @@ -189,11 +185,10 @@ private Task> ReadAsyncImpl(Type type, bool buffered) private Task ReadRowAsyncImpl(Type type, Row row) { - var dbReader = reader as DbDataReader; - if (dbReader != null) return ReadRowAsyncImplViaDbReader(dbReader, type, row); + if (reader is DbDataReader dbReader) return ReadRowAsyncImplViaDbReader(dbReader, type, row); // no async API available; use non-async and fake it - return Task.FromResult(ReadRow(type, row)); + return Task.FromResult(ReadRow(type, row)); } private async Task ReadRowAsyncImplViaDbReader(DbDataReader reader, Type type, Row row) @@ -217,7 +212,7 @@ private async Task ReadRowAsyncImplViaDbReader(DbDataReader reader, Type t } 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)) { } + while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent rows */ } } else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { @@ -227,12 +222,12 @@ private async Task ReadRowAsyncImplViaDbReader(DbDataReader reader, Type t return result; } - private async Task> ReadBufferedAsync(int index, Func deserializer, Identity typedIdentity) + private async Task> ReadBufferedAsync(int index, Func deserializer) { try { var reader = (DbDataReader)this.reader; - List buffer = new List(); + var buffer = new List(); while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false)) { buffer.Add((T)deserializer(reader)); @@ -248,7 +243,5 @@ private async Task> ReadBufferedAsync(int index, Func /// The grid reader provides interfaces for reading multiple result sets from a Dapper query @@ -13,8 +13,8 @@ partial class SqlMapper public partial class GridReader : IDisposable { private IDataReader reader; - private Identity identity; - private bool addToCache; + private readonly Identity identity; + private readonly bool addToCache; internal GridReader(IDbCommand command, IDataReader reader, Identity identity, IParameterCallbacks callbacks, bool addToCache) { @@ -26,87 +26,73 @@ internal GridReader(IDbCommand command, IDataReader reader, Identity identity, I } /// - /// Read the next grid of results, returned as a dynamic object + /// Read the next grid of results, returned as a dynamic object. /// + /// Whether the results should be buffered in memory. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public IEnumerable Read(bool buffered = true) - { - return ReadImpl(typeof(DapperRow), buffered); - } + public IEnumerable Read(bool buffered = true) => ReadImpl(typeof(DapperRow), buffered); /// - /// Read an individual row of the next grid of results, returned as a dynamic object + /// Read an individual row of the next grid of results, returned as a dynamic object. /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public dynamic ReadFirst() - { - return ReadRow(typeof(DapperRow), Row.First); - } + public dynamic ReadFirst() => ReadRow(typeof(DapperRow), Row.First); + /// - /// Read an individual row of the next grid of results, returned as a dynamic object + /// Read an individual row of the next grid of results, returned as a dynamic object. /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public dynamic ReadFirstOrDefault() - { - return ReadRow(typeof(DapperRow), Row.FirstOrDefault); - } + public dynamic ReadFirstOrDefault() => ReadRow(typeof(DapperRow), Row.FirstOrDefault); + /// - /// Read an individual row of the next grid of results, returned as a dynamic object + /// Read an individual row of the next grid of results, returned as a dynamic object. /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public dynamic ReadSingle() - { - return ReadRow(typeof(DapperRow), Row.Single); - } + public dynamic ReadSingle() => ReadRow(typeof(DapperRow), Row.Single); + /// - /// Read an individual row of the next grid of results, returned as a dynamic object + /// Read an individual row of the next grid of results, returned as a dynamic object. /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public dynamic ReadSingleOrDefault() - { - return ReadRow(typeof(DapperRow), Row.SingleOrDefault); - } + public dynamic ReadSingleOrDefault() => ReadRow(typeof(DapperRow), Row.SingleOrDefault); /// - /// Read the next grid of results + /// Read the next grid of results. /// - public IEnumerable Read(bool buffered = true) - { - return ReadImpl(typeof(T), buffered); - } + /// The type to read. + /// Whether the results should be buffered in memory. + public IEnumerable Read(bool buffered = true) => ReadImpl(typeof(T), buffered); /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// - public T ReadFirst() - { - return ReadRow(typeof(T), Row.First); - } + /// The type to read. + public T ReadFirst() => ReadRow(typeof(T), Row.First); + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// - public T ReadFirstOrDefault() - { - return ReadRow(typeof(T), Row.FirstOrDefault); - } + /// The type to read. + public T ReadFirstOrDefault() => ReadRow(typeof(T), Row.FirstOrDefault); + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// - public T ReadSingle() - { - return ReadRow(typeof(T), Row.Single); - } + /// The type to read. + public T ReadSingle() => ReadRow(typeof(T), Row.Single); + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// - public T ReadSingleOrDefault() - { - return ReadRow(typeof(T), Row.SingleOrDefault); - } + /// The type to read. + public T ReadSingleOrDefault() => ReadRow(typeof(T), Row.SingleOrDefault); /// - /// Read the next grid of results + /// Read the next grid of results. /// + /// The type to read. + /// Whether to buffer the results. + /// is null. public IEnumerable Read(Type type, bool buffered = true) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -114,32 +100,43 @@ public IEnumerable Read(Type type, bool buffered = true) } /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// + /// The type to read. + /// is null. public object ReadFirst(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.First); } + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// + /// The type to read. + /// is null. public object ReadFirstOrDefault(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.FirstOrDefault); } + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// + /// The type to read. + /// is null. public object ReadSingle(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.Single); } + /// - /// Read an individual row of the next grid of results + /// Read an individual row of the next grid of results. /// + /// The type to read. + /// is null. public object ReadSingleOrDefault(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -161,7 +158,7 @@ private IEnumerable ReadImpl(Type type, bool buffered) cache.Deserializer = deserializer; } IsConsumed = true; - var result = ReadDeferred(gridIndex, deserializer.Func, typedIdentity, type); + var result = ReadDeferred(gridIndex, deserializer.Func, type); return buffered ? result.ToList() : result; } @@ -172,7 +169,7 @@ private T ReadRow(Type type, Row row) IsConsumed = true; T result = default(T); - if(reader.Read() && reader.FieldCount != 0) + if (reader.Read() && reader.FieldCount != 0) { var typedIdentity = identity.ForGrid(type, gridIndex); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); @@ -185,17 +182,19 @@ private T ReadRow(Type type, Row row) cache.Deserializer = deserializer; } object val = deserializer.Func(reader); - if(val == null || val is T) + if (val == null || val is T) { result = (T)val; - } else { + } + 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()) { } + while (reader.Read()) { /* ignore subsequent rows */ } } - else if((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one + else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { ThrowZeroRows(row); } @@ -203,7 +202,6 @@ private T ReadRow(Type type, Row row) return result; } - private IEnumerable MultiReadInternal(Delegate func, string splitOn) { var identity = this.identity.ForGrid(typeof(TReturn), new Type[] { @@ -215,6 +213,9 @@ private T ReadRow(Type type, Row row) typeof(TSixth), typeof(TSeventh) }, gridIndex); + + IsConsumed = true; + try { foreach (var r in MultiMapImpl(null, default(CommandDefinition), func, splitOn, reader, identity, false)) @@ -245,8 +246,14 @@ private IEnumerable MultiReadInternal(Type[] types, Func - /// Read multiple objects from a single record set on the grid + /// Read multiple objects from a single record set on the grid. /// + /// The first type in the record set. + /// The second type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); @@ -254,8 +261,15 @@ private IEnumerable MultiReadInternal(Type[] types, Func - /// Read multiple objects from a single record set on the grid + /// Read multiple objects from a single record set on the grid. /// + /// The first type in the record set. + /// The second type in the record set. + /// The third type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); @@ -265,6 +279,14 @@ private IEnumerable MultiReadInternal(Type[] types, Func /// Read multiple objects from a single record set on the grid /// + /// The first type in the record set. + /// The second type in the record set. + /// The third type in the record set. + /// The fourth type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); @@ -274,22 +296,54 @@ private IEnumerable MultiReadInternal(Type[] types, Func /// Read multiple objects from a single record set on the grid /// + /// The first type in the record set. + /// The second type in the record set. + /// The third type in the record set. + /// The fourth type in the record set. + /// The fifth type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } + /// /// Read multiple objects from a single record set on the grid /// + /// The first type in the record set. + /// The second type in the record set. + /// The third type in the record set. + /// The fourth type in the record set. + /// The fifth type in the record set. + /// The sixth type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } + /// /// Read multiple objects from a single record set on the grid /// + /// The first type in the record set. + /// The second type in the record set. + /// The third type in the record set. + /// The fourth type in the record set. + /// The fifth type in the record set. + /// The sixth type in the record set. + /// The seventh type in the record set. + /// The type to return from the record set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); @@ -299,13 +353,18 @@ private IEnumerable MultiReadInternal(Type[] types, Func /// Read multiple objects from a single record set on the grid /// + /// The type to return from the record set. + /// The types to read from the result set. + /// The mapping function from the read types to the return type. + /// The field(s) we should split and read the second object from (defaults to "id") + /// Whether to buffer results in memory. public IEnumerable Read(Type[] types, Func map, string splitOn = "id", bool buffered = true) { - var result = MultiReadInternal(types, map, splitOn); + var result = MultiReadInternal(types, map, splitOn); return buffered ? result.ToList() : result; } - private IEnumerable ReadDeferred(int index, Func deserializer, Identity typedIdentity, Type effectiveType) + private IEnumerable ReadDeferred(int index, Func deserializer, Type effectiveType) { try { @@ -313,9 +372,12 @@ private IEnumerable ReadDeferred(int index, Func dese while (index == gridIndex && reader.Read()) { object val = deserializer(reader); - if (val == null || val is T) { + if (val == null || val is T) + { yield return (T)val; - } else { + } + else + { yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); } } @@ -328,8 +390,9 @@ private IEnumerable ReadDeferred(int index, Func dese } } } + private int gridIndex, readCount; - private IParameterCallbacks callbacks; + private readonly IParameterCallbacks callbacks; /// /// Has the underlying reader been consumed? @@ -359,6 +422,7 @@ private void NextResult() Dispose(); } } + /// /// Dispose the grid, closing and disposing both the underlying reader and command. /// diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.ICustomQueryParameter.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.ICustomQueryParameter.cs index c2b857007..54b9f905c 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.ICustomQueryParameter.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.ICustomQueryParameter.cs @@ -2,7 +2,7 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Implement this interface to pass an arbitrary db specific parameter to Dapper diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.IDataReader.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.IDataReader.cs index a15b24a10..0d9583ddd 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.IDataReader.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.IDataReader.cs @@ -4,14 +4,16 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Parses a data reader to a sequence of data of the supplied type. Used for deserializing a reader without a connection, etc. /// + /// The type to parse from the . + /// The data reader to parse results from. public static IEnumerable Parse(this IDataReader reader) { - if(reader.Read()) + if (reader.Read()) { var deser = GetDeserializer(typeof(T), reader, 0, -1, false); do @@ -24,6 +26,8 @@ public static IEnumerable Parse(this IDataReader reader) /// /// Parses a data reader to a sequence of data of the supplied type (as object). Used for deserializing a reader without a connection, etc. /// + /// The data reader to parse results from. + /// The type to parse from the . public static IEnumerable Parse(this IDataReader reader, Type type) { if (reader.Read()) @@ -39,6 +43,7 @@ public static IEnumerable Parse(this IDataReader reader, Type type) /// /// Parses a data reader to a sequence of dynamic. Used for deserializing a reader without a connection, etc. /// + /// The data reader to parse results from. public static IEnumerable Parse(this IDataReader reader) { if (reader.Read()) @@ -50,7 +55,7 @@ public static IEnumerable Parse(this IDataReader reader) } while (reader.Read()); } } - + /// /// 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. @@ -71,11 +76,12 @@ public static IEnumerable Parse(this IDataReader reader) /// 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. /// - /// The data reader to get the parser for the current row from - /// The type to get the parser for - /// The start column index of the object (default 0) - /// The length of columns to read (default -1 = all fields following startIndex) - /// Return null if we can't find the first column? (default false) + /// The type of results to return. + /// The data reader to get the parser for the current row from. + /// The type to get the parser for. + /// The start column index of the object (default: 0). + /// The length of columns to read (default: -1 = all fields following startIndex). + /// Return null if we can't find the first column? (default: false). /// A parser for this specific object from this row. /// /// var result = new List<BaseType>(); @@ -122,9 +128,9 @@ public static IEnumerable Parse(this IDataReader reader) public static Func GetRowParser(this IDataReader reader, Type concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { - if (concreteType == null) concreteType = typeof(T); + concreteType = concreteType ?? typeof(T); var func = GetDeserializer(concreteType, reader, startIndex, length, returnNullIfFirstMissing); - if (concreteType.IsValueType) + if (concreteType.IsValueType()) { return _ => (T)func(_); } diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.IDynamicParameters.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.IDynamicParameters.cs index 71dcbb71d..16b6de122 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.IDynamicParameters.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.IDynamicParameters.cs @@ -2,7 +2,7 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Implement this interface to pass an arbitrary db specific set of parameters to Dapper diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.IMemberMap.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.IMemberMap.cs index 45618be7a..97c28975f 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.IMemberMap.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.IMemberMap.cs @@ -3,7 +3,7 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Implements this interface to provide custom member mapping diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.IParameterCallbacks.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.IParameterCallbacks.cs index a43574485..25102298b 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.IParameterCallbacks.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.IParameterCallbacks.cs @@ -1,6 +1,6 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Extends IDynamicParameters with facilities for executing callbacks after commands have completed diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.IParameterLookup.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.IParameterLookup.cs index 3c2c0fcb9..af7505265 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.IParameterLookup.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.IParameterLookup.cs @@ -1,6 +1,6 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Extends IDynamicParameters providing by-name lookup of parameter values @@ -10,6 +10,7 @@ public interface IParameterLookup : IDynamicParameters /// /// Get the value of the specified parameter (return null if not found) /// + /// The name of the parameter to get. object this[string name] { get; } } } diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.ITypeHandler.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.ITypeHandler.cs index 7d4d5dd9c..aabaf0bfc 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.ITypeHandler.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.ITypeHandler.cs @@ -3,7 +3,7 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Implement this interface to perform custom type-based parameter handling and value parsing diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.ITypeMap.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.ITypeMap.cs index b1e46b98e..64e501cb7 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.ITypeMap.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.ITypeMap.cs @@ -3,7 +3,7 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Implement this interface to change default mapping of reader columns to type members diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.Identity.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.Identity.cs index 684e4d978..e9dc31967 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.Identity.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.Identity.cs @@ -3,35 +3,30 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// - /// Identity of a cached query in Dapper, used for extensibility + /// Identity of a cached query in Dapper, used for extensibility. /// public class Identity : IEquatable { - internal Identity ForGrid(Type primaryType, int gridIndex) - { - return new Identity(sql, commandType, connectionString, primaryType, parametersType, null, gridIndex); - } + internal Identity ForGrid(Type primaryType, int gridIndex) => + new Identity(sql, commandType, connectionString, primaryType, parametersType, null, gridIndex); + + internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) => + new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); - internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) - { - return new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); - } /// - /// Create an identity for use with DynamicParameters, internal use only + /// Create an identity for use with DynamicParameters, internal use only. /// - /// + /// The parameters type to create an for. /// - public Identity ForDynamicParameters(Type type) - { - return new Identity(sql, commandType, connectionString, this.type, type, null, -1); - } + public Identity ForDynamicParameters(Type type) => + 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) - { } + : this(sql, commandType, connection.ConnectionString, type, parametersType, otherTypes, 0) { /* base call */ } + private Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, Type[] otherTypes, int gridIndex) { this.sql = sql; @@ -43,79 +38,83 @@ private Identity(string sql, CommandType? commandType, string connectionString, 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); + 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) + (t?.GetHashCode() ?? 0); } } - hashCode = hashCode * 23 + (connectionString == null ? 0 : connectionStringComparer.GetHashCode(connectionString)); - hashCode = hashCode * 23 + (parametersType?.GetHashCode() ?? 0); + hashCode = (hashCode * 23) + (connectionString == null ? 0 : connectionStringComparer.GetHashCode(connectionString)); + hashCode = (hashCode * 23) + (parametersType?.GetHashCode() ?? 0); } } /// - /// + /// Whether this equals another. /// - /// - /// - public override bool Equals(object obj) - { - return Equals(obj as Identity); - } + /// The other to compare to. + public override bool Equals(object obj) => Equals(obj as Identity); + /// - /// The sql + /// The raw SQL command. /// public readonly string sql; + /// - /// The command type + /// The SQL command type. /// public readonly CommandType? commandType; /// - /// + /// The hash code of this Identity. + /// + public readonly int hashCode; + + /// + /// The grid index (position in the reader) of this Identity. /// - public readonly int hashCode, gridIndex; + public readonly int gridIndex; + /// - /// + /// This of this Identity. /// public readonly Type type; + /// - /// + /// The connection string for this Identity. /// public readonly string connectionString; + /// - /// + /// The type of the parameters object for this Identity. /// public readonly Type parametersType; + /// - /// + /// Gets the hash code for this identity. /// /// - public override int GetHashCode() - { - return hashCode; - } + public override int GetHashCode() => hashCode; + /// /// Compare 2 Identity objects /// - /// - /// + /// The other object to compare. + /// Whether the two are equal 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; + return other != null + && gridIndex == other.gridIndex + && type == other.type + && sql == other.sql + && commandType == other.commandType + && connectionStringComparer.Equals(connectionString, other.connectionString) + && parametersType == other.parametersType; } } } diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.Link.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.Link.cs index a909fed61..9192aa3a4 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.Link.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.Link.cs @@ -2,13 +2,15 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// 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. /// + /// The type to cache. + /// The value type of the cache. internal class Link where TKey : class { public static bool TryGet(Link link, TKey key, out TValue value) @@ -25,14 +27,14 @@ public static bool TryGet(Link link, TKey key, out TValue value) value = default(TValue); return false; } + public static bool TryAdd(ref Link 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)) + if (TryGet(snapshot, key, out TValue found)) { // existing match; report the existing value instead value = found; return false; @@ -43,12 +45,14 @@ public static bool TryAdd(ref Link head, TKey key, ref TValue valu } while (tryAgain); return true; } + private Link(TKey key, TValue value, Link tail) { Key = key; Value = value; Tail = tail; } + public TKey Key { get; } public TValue Value { get; } public Link Tail { get; } diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.LiteralToken.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.LiteralToken.cs index 482b7cff4..fedd9dcde 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.LiteralToken.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.LiteralToken.cs @@ -2,7 +2,7 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.Settings.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.Settings.cs index 10dd749f0..0d61b7e06 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.Settings.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.Settings.cs @@ -1,12 +1,57 @@ -namespace ServiceStack.OrmLite.Dapper +using System; +using System.Data; + +namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Permits specifying certain SqlMapper values globally. /// public static class Settings { + // disable single result by default; prevents errors AFTER the select being detected properly + private const CommandBehavior DefaultAllowedCommandBehaviors = ~CommandBehavior.SingleResult; + internal static CommandBehavior AllowedCommandBehaviors { get; private set; } = DefaultAllowedCommandBehaviors; + private static void SetAllowedCommandBehaviors(CommandBehavior behavior, bool enabled) + { + if (enabled) AllowedCommandBehaviors |= behavior; + else AllowedCommandBehaviors &= ~behavior; + } + /// + /// Gets or sets whether Dapper should use the CommandBehavior.SingleResult optimization + /// + /// Note that a consequence of enabling this option is that errors that happen after the first select may not be reported + public static bool UseSingleResultOptimization + { + get { return (AllowedCommandBehaviors & CommandBehavior.SingleResult) != 0; } + set { SetAllowedCommandBehaviors(CommandBehavior.SingleResult, value); } + } + /// + /// Gets or sets whether Dapper should use the CommandBehavior.SingleRow optimization + /// + /// Note that on some DB providers this optimization can have adverse performance impact + public static bool UseSingleRowOptimization + { + get { return (AllowedCommandBehaviors & CommandBehavior.SingleRow) != 0; } + set { SetAllowedCommandBehaviors(CommandBehavior.SingleRow, value); } + } + + internal static bool DisableCommandBehaviorOptimizations(CommandBehavior behavior, Exception ex) + { + if (AllowedCommandBehaviors == DefaultAllowedCommandBehaviors + && (behavior & (CommandBehavior.SingleResult | CommandBehavior.SingleRow)) != 0) + { + if (ex.Message.Contains(nameof(CommandBehavior.SingleResult)) + || ex.Message.Contains(nameof(CommandBehavior.SingleRow))) + { // some providers just just allow these, so: try again without them and stop issuing them + SetAllowedCommandBehaviors(CommandBehavior.SingleResult | CommandBehavior.SingleRow, false); + return true; + } + } + return false; + } + static Settings() { SetDefaults(); @@ -31,7 +76,6 @@ public static void SetDefaults() /// public static bool ApplyNullValues { get; set; } - /// /// 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. diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeDeserializerCache.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeDeserializerCache.cs index fe7b47e7f..70285a54d 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeDeserializerCache.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeDeserializerCache.cs @@ -6,16 +6,16 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { - private class TypeDeserializerCache { private TypeDeserializerCache(Type type) { this.type = type; } - static readonly Hashtable byType = new Hashtable(); + + private static readonly Hashtable byType = new Hashtable(); private readonly Type type; internal static void Purge(Type type) { @@ -24,6 +24,7 @@ internal static void Purge(Type type) byType.Remove(type); } } + internal static void Purge() { lock (byType) @@ -48,8 +49,10 @@ internal static void Purge() } return found.GetReader(reader, startBound, length, returnNullIfFirstMissing); } - private Dictionary> readers = new Dictionary>(); - struct DeserializerKey : IEquatable + + private readonly Dictionary> readers = new Dictionary>(); + + private struct DeserializerKey : IEquatable { private readonly int startBound, length; private readonly bool returnNullIfFirstMissing; @@ -85,10 +88,8 @@ public DeserializerKey(int hashCode, int startBound, int length, bool returnNull } } - public override int GetHashCode() - { - return hashCode; - } + public override int GetHashCode() => hashCode; + public override string ToString() { // only used in the debugger if (names != null) @@ -108,24 +109,26 @@ public override string 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) + if (hashCode != other.hashCode + || startBound != other.startBound + || length != other.length + || 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)) + if ((names?[i] ?? 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)) + (types?[i] ?? reader?.GetFieldType(startBound + i)) != (other.types?[i] ?? other.reader?.GetFieldType(startBound + i)) ) { return false; // different column name or type @@ -134,6 +137,7 @@ public bool Equals(DeserializerKey other) return true; } } + private Func GetReader(IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { if (length < 0) length = reader.FieldCount - startBound; @@ -155,6 +159,5 @@ public bool Equals(DeserializerKey other) } } } - } } diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeHandler.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeHandler.cs index 9d54f4fd4..3f8d57af5 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeHandler.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeHandler.cs @@ -3,11 +3,12 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Base-class for simple type-handlers /// + /// This this handler is for. public abstract class TypeHandler : ITypeHandler { /// @@ -41,19 +42,25 @@ object ITypeHandler.Parse(Type destinationType, object value) return Parse(value); } } + /// /// Base-class for simple type-handlers that are based around strings /// + /// This this handler is for. public abstract class StringTypeHandler : TypeHandler { /// /// Parse a string into the expected type (the string will never be null) /// + /// The string to parse. protected abstract T Parse(string xml); + /// /// Format an instace into a string (the instance will never be null) /// + /// The string to format. protected abstract string Format(T xml); + /// /// Assign the value of a parameter before a command executes /// @@ -63,6 +70,7 @@ public override void SetValue(IDbDataParameter parameter, T value) { parameter.Value = value == null ? (object)DBNull.Value : Format(value); } + /// /// Parse a database value back to a typed value /// diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeHandlerCache.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeHandlerCache.cs index a99d83da1..2952b50e4 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeHandlerCache.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.TypeHandlerCache.cs @@ -4,35 +4,33 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { /// /// Not intended for direct usage /// + /// The type to have a cache for. [Obsolete(ObsoleteInternalUsageOnly, false)] -#if !COREFX +#if !NETSTANDARD1_3 [Browsable(false)] #endif [EditorBrowsable(EditorBrowsableState.Never)] public static class TypeHandlerCache { /// - /// Not intended for direct usage + /// Not intended for direct usage. /// + /// The object to parse. [Obsolete(ObsoleteInternalUsageOnly, true)] - public static T Parse(object value) - { - return (T)handler.Parse(typeof(T), value); - } + public static T Parse(object value) => (T)handler.Parse(typeof(T), value); /// - /// Not intended for direct usage + /// Not intended for direct usage. /// + /// The parameter to set a value for. + /// The value to set. [Obsolete(ObsoleteInternalUsageOnly, true)] - public static void SetValue(IDbDataParameter parameter, object value) - { - handler.SetValue(parameter, value); - } + public static void SetValue(IDbDataParameter parameter, object value) => handler.SetValue(parameter, value); internal static void SetHandler(ITypeHandler handler) { diff --git a/src/ServiceStack.OrmLite/Dapper/SqlMapper.cs b/src/ServiceStack.OrmLite/Dapper/SqlMapper.cs index 2ede424d2..b2a61febe 100644 --- a/src/ServiceStack.OrmLite/Dapper/SqlMapper.cs +++ b/src/ServiceStack.OrmLite/Dapper/SqlMapper.cs @@ -1,4 +1,4 @@ -/* +/* License: http://www.apache.org/licenses/LICENSE-2.0 Home page: https://github.com/StackExchange/dapper-dot-net */ @@ -17,36 +17,38 @@ using System.Text.RegularExpressions; using System.Threading; using System.Xml; -//using System.Xml.Linq; +using System.Xml.Linq; -#if NETSTANDARD2_0 +#if NETSTANDARD1_3 using DataException = System.InvalidOperationException; #endif namespace ServiceStack.OrmLite.Dapper { - /// /// Dapper, a light weight object mapper for ADO.NET /// public static partial class SqlMapper { - static int GetColumnHash(IDataReader reader, int startBound = 0, int length = -1) + private class PropertyInfoByNameComparer : IComparer + { + public int Compare(PropertyInfo x, PropertyInfo y) => string.CompareOrdinal(x.Name, y.Name); + } + private static int GetColumnHash(IDataReader reader, int startBound = 0, int length = -1) { unchecked { int max = length < 0 ? reader.FieldCount : startBound + length; int hash = (-37 * startBound) + max; for (int i = startBound; i < max; i++) - { + { object tmp = reader.GetName(i); - hash = -79 * ((hash * 31) + (tmp?.GetHashCode() ?? 0)) + (reader.GetFieldType(i)?.GetHashCode() ?? 0); + hash = (-79 * ((hash * 31) + (tmp?.GetHashCode() ?? 0))) + (reader.GetFieldType(i)?.GetHashCode() ?? 0); } return hash; } } - /// /// Called if the query cache is purged via PurgeQueryCache /// @@ -57,7 +59,7 @@ private static void OnQueryCachePurged() handler?.Invoke(null, EventArgs.Empty); } - static readonly System.Collections.Concurrent.ConcurrentDictionary _queryCache = new System.Collections.Concurrent.ConcurrentDictionary(); + private static readonly System.Collections.Concurrent.ConcurrentDictionary _queryCache = new System.Collections.Concurrent.ConcurrentDictionary(); private static void SetQueryCache(Identity key, CacheInfo value) { if (Interlocked.Increment(ref collect) == COLLECT_PER_ITEMS) @@ -75,8 +77,7 @@ private static void CollectCacheGarbage() { if (pair.Value.GetHitCount() <= COLLECT_HIT_COUNT_MIN) { - CacheInfo cache; - _queryCache.TryRemove(pair.Key, out cache); + _queryCache.TryRemove(pair.Key, out CacheInfo cache); } } } @@ -114,15 +115,14 @@ private static void PurgeQueryCacheByType(Type type) { foreach (var entry in _queryCache) { - CacheInfo cache; if (entry.Key.type == type) - _queryCache.TryRemove(entry.Key, out cache); + _queryCache.TryRemove(entry.Key, out CacheInfo cache); } TypeDeserializerCache.Purge(type); } /// - /// Return a count of all the cached queries by dapper + /// Return a count of all the cached queries by Dapper /// /// public static int GetCachedSQLCount() @@ -131,15 +131,16 @@ public static int GetCachedSQLCount() } /// - /// Return a list of all the queries cached by dapper + /// Return a list of all the queries cached by Dapper /// /// /// public static IEnumerable> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue) { var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount())); - if (ignoreHitCountAbove < int.MaxValue) data = data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove); - return data; + return (ignoreHitCountAbove < int.MaxValue) + ? data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove) + : data; } /// @@ -151,8 +152,7 @@ public static int GetCachedSQLCount() var counts = new Dictionary(); foreach (var key in _queryCache.Keys) { - int count; - if (!counts.TryGetValue(key.hashCode, out count)) + if (!counts.TryGetValue(key.hashCode, out int count)) { counts.Add(key.hashCode, 1); } @@ -164,129 +164,138 @@ public static int GetCachedSQLCount() return from pair in counts where pair.Value > 1 select Tuple.Create(pair.Key, pair.Value); - } - - static Dictionary typeMap; + private static Dictionary typeMap; static SqlMapper() { typeMap = new Dictionary - { - [typeof(byte)] = DbType.Byte, - [typeof(sbyte)] = DbType.SByte, - [typeof(short)] = DbType.Int16, - [typeof(ushort)] = DbType.UInt16, - [typeof(int)] = DbType.Int32, - [typeof(uint)] = DbType.UInt32, - [typeof(long)] = DbType.Int64, - [typeof(ulong)] = DbType.UInt64, - [typeof(float)] = DbType.Single, - [typeof(double)] = DbType.Double, - [typeof(decimal)] = DbType.Decimal, - [typeof(bool)] = DbType.Boolean, - [typeof(string)] = DbType.String, - [typeof(char)] = DbType.StringFixedLength, - [typeof(Guid)] = DbType.Guid, - [typeof(DateTime)] = DbType.DateTime, - [typeof(DateTimeOffset)] = DbType.DateTimeOffset, - [typeof(TimeSpan)] = DbType.Time, - [typeof(byte[])] = DbType.Binary, - [typeof(byte?)] = DbType.Byte, - [typeof(sbyte?)] = DbType.SByte, - [typeof(short?)] = DbType.Int16, - [typeof(ushort?)] = DbType.UInt16, - [typeof(int?)] = DbType.Int32, - [typeof(uint?)] = DbType.UInt32, - [typeof(long?)] = DbType.Int64, - [typeof(ulong?)] = DbType.UInt64, - [typeof(float?)] = DbType.Single, - [typeof(double?)] = DbType.Double, - [typeof(decimal?)] = DbType.Decimal, - [typeof(bool?)] = DbType.Boolean, - [typeof(char?)] = DbType.StringFixedLength, - [typeof(Guid?)] = DbType.Guid, - [typeof(DateTime?)] = DbType.DateTime, - [typeof(DateTimeOffset?)] = DbType.DateTimeOffset, - [typeof(TimeSpan?)] = DbType.Time, - [typeof(object)] = DbType.Object - }; + { + [typeof(byte)] = DbType.Byte, + [typeof(sbyte)] = DbType.SByte, + [typeof(short)] = DbType.Int16, + [typeof(ushort)] = DbType.UInt16, + [typeof(int)] = DbType.Int32, + [typeof(uint)] = DbType.UInt32, + [typeof(long)] = DbType.Int64, + [typeof(ulong)] = DbType.UInt64, + [typeof(float)] = DbType.Single, + [typeof(double)] = DbType.Double, + [typeof(decimal)] = DbType.Decimal, + [typeof(bool)] = DbType.Boolean, + [typeof(string)] = DbType.String, + [typeof(char)] = DbType.StringFixedLength, + [typeof(Guid)] = DbType.Guid, + [typeof(DateTime)] = DbType.DateTime, + [typeof(DateTimeOffset)] = DbType.DateTimeOffset, + [typeof(TimeSpan)] = DbType.Time, + [typeof(byte[])] = DbType.Binary, + [typeof(byte?)] = DbType.Byte, + [typeof(sbyte?)] = DbType.SByte, + [typeof(short?)] = DbType.Int16, + [typeof(ushort?)] = DbType.UInt16, + [typeof(int?)] = DbType.Int32, + [typeof(uint?)] = DbType.UInt32, + [typeof(long?)] = DbType.Int64, + [typeof(ulong?)] = DbType.UInt64, + [typeof(float?)] = DbType.Single, + [typeof(double?)] = DbType.Double, + [typeof(decimal?)] = DbType.Decimal, + [typeof(bool?)] = DbType.Boolean, + [typeof(char?)] = DbType.StringFixedLength, + [typeof(Guid?)] = DbType.Guid, + [typeof(DateTime?)] = DbType.DateTime, + [typeof(DateTimeOffset?)] = DbType.DateTimeOffset, + [typeof(TimeSpan?)] = DbType.Time, + [typeof(object)] = DbType.Object + }; ResetTypeHandlers(false); } /// - /// Clear the registered type handlers + /// Clear the registered type handlers. /// - public static void ResetTypeHandlers() - { - ResetTypeHandlers(true); - } + public static void ResetTypeHandlers() => ResetTypeHandlers(true); + private static void ResetTypeHandlers(bool clone) { typeHandlers = new Dictionary(); -#if !NETSTANDARD2_0 +#if !NETSTANDARD1_3 AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), clone); - try // see https://github.com/StackExchange/dapper-dot-net/issues/424 +#endif + try { AddSqlDataRecordsTypeHandler(clone); } - catch { } -#endif + catch { /* https://github.com/StackExchange/dapper-dot-net/issues/424 */ } AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone); - //AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone); - //AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone); - - allowedCommandBehaviors = DefaultAllowedCommandBehaviors; + AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone); + AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone); } -#if !NETSTANDARD2_0 + [MethodImpl(MethodImplOptions.NoInlining)] private static void AddSqlDataRecordsTypeHandler(bool clone) { AddTypeHandlerImpl(typeof(IEnumerable), new SqlDataRecordHandler(), clone); } -#endif /// - /// Configure the specified type to be mapped to a given db-type + /// Configure the specified type to be mapped to a given db-type. /// + /// The type to map from. + /// The database type to map to. public static void AddTypeMap(Type type, DbType dbType) { // use clone, mutate, replace to avoid threading issues var snapshot = typeMap; - DbType oldValue; - if (snapshot.TryGetValue(type, out oldValue) && oldValue == dbType) return; // nothing to do + if (snapshot.TryGetValue(type, out DbType oldValue) && oldValue == dbType) return; // nothing to do - var newCopy = new Dictionary(snapshot) { [type] = dbType }; - typeMap = newCopy; + typeMap = new Dictionary(snapshot) { [type] = dbType }; } /// - /// Configure the specified type to be processed by a custom handler + /// Removes the specified type from the Type/DbType mapping table. /// - public static void AddTypeHandler(Type type, ITypeHandler handler) + /// The type to remove from the current map. + public static void RemoveTypeMap(Type type) { - AddTypeHandlerImpl(type, handler, true); - } + // use clone, mutate, replace to avoid threading issues + var snapshot = typeMap; - internal static bool HasTypeHandler(Type type) - { - return typeHandlers.ContainsKey(type); + if (!snapshot.ContainsKey(type)) return; // nothing to do + + var newCopy = new Dictionary(snapshot); + newCopy.Remove(type); + + typeMap = newCopy; } /// - /// Configure the specified type to be processed by a custom handler + /// Configure the specified type to be processed by a custom handler. + /// + /// The type to handle. + /// The handler to process the . + public static void AddTypeHandler(Type type, ITypeHandler handler) => AddTypeHandlerImpl(type, handler, true); + + internal static bool HasTypeHandler(Type type) => typeHandlers.ContainsKey(type); + + /// + /// Configure the specified type to be processed by a custom handler. /// + /// The type to handle. + /// The handler to process the . + /// Whether to clone the current type handler map. public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clone) { if (type == null) throw new ArgumentNullException(nameof(type)); Type secondary = null; - if(type.IsValueType) + if (type.IsValueType()) { var underlying = Nullable.GetUnderlyingType(type); - if(underlying == null) + if (underlying == null) { secondary = typeof(Nullable<>).MakeGenericType(type); // the Nullable // type is already the T @@ -299,14 +308,13 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clon } var snapshot = typeHandlers; - ITypeHandler oldValue; - if (snapshot.TryGetValue(type, out oldValue) && handler == oldValue) return; // nothing to do + if (snapshot.TryGetValue(type, out ITypeHandler oldValue) && handler == oldValue) return; // nothing to do var newCopy = clone ? new Dictionary(snapshot) : snapshot; #pragma warning disable 618 typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { handler }); - if(secondary != null) + if (secondary != null) { typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { handler }); } @@ -319,18 +327,17 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clon else { newCopy[type] = handler; - if(secondary != null) newCopy[secondary] = handler; + if (secondary != null) newCopy[secondary] = handler; } typeHandlers = newCopy; } /// - /// Configure the specified type to be processed by a custom handler + /// Configure the specified type to be processed by a custom handler. /// - public static void AddTypeHandler(TypeHandler handler) - { - AddTypeHandlerImpl(typeof(T), handler, true); - } + /// The type to handle. + /// The handler for the type . + public static void AddTypeHandler(TypeHandler handler) => AddTypeHandlerImpl(typeof(T), handler, true); private static Dictionary typeHandlers; @@ -339,10 +346,11 @@ public static void AddTypeHandler(TypeHandler handler) private const string ObsoleteInternalUsageOnly = "This method is for internal use only"; /// - /// Get the DbType that maps to a given value + /// Get the DbType that maps to a given value. /// + /// The object to get a corresponding database type for. [Obsolete(ObsoleteInternalUsageOnly, false)] -#if !NETSTANDARD2_0 +#if !NETSTANDARD1_3 [Browsable(false)] #endif [EditorBrowsable(EditorBrowsableState.Never)] @@ -350,30 +358,31 @@ public static DbType GetDbType(object value) { if (value == null || value is DBNull) return DbType.Object; - ITypeHandler handler; - return LookupDbType(value.GetType(), "n/a", false, out handler); - + return LookupDbType(value.GetType(), "n/a", false, out ITypeHandler handler); } /// /// OBSOLETE: For internal usage only. Lookup the DbType and handler for a given Type and member /// + /// The type to lookup. + /// The name (for error messages). + /// Whether to demand a value (throw if missing). + /// The handler for . [Obsolete(ObsoleteInternalUsageOnly, false)] -#if !NETSTANDARD2_0 +#if !NETSTANDARD1_3 [Browsable(false)] #endif [EditorBrowsable(EditorBrowsableState.Never)] public static DbType LookupDbType(Type type, string name, bool demand, out ITypeHandler handler) { - DbType dbType; handler = null; var nullUnderlyingType = Nullable.GetUnderlyingType(type); if (nullUnderlyingType != null) type = nullUnderlyingType; - if (type.IsEnum && !typeMap.ContainsKey(type)) + if (type.IsEnum() && !typeMap.ContainsKey(type)) { type = Enum.GetUnderlyingType(type); } - if (typeMap.TryGetValue(type, out dbType)) + if (typeMap.TryGetValue(type, out DbType dbType)) { return dbType; } @@ -390,7 +399,7 @@ public static DbType LookupDbType(Type type, string name, bool demand, out IType return DynamicParameters.EnumerableMultiParameter; } -#if !NETSTANDARD2_0 +#if !NETSTANDARD1_3 && !NETSTANDARD2_0 switch (type.FullName) { case "Microsoft.SqlServer.Types.SqlGeography": @@ -404,93 +413,103 @@ public static DbType LookupDbType(Type type, string name, bool demand, out IType return DbType.Object; } #endif - if(demand) + if (demand) throw new NotSupportedException($"The member {name} of type {type.FullName} cannot be used as a parameter value"); return DbType.Object; - } - - /// /// Obtains the data as a list; if it is *already* a list, the original object is returned without /// any duplication; otherwise, ToList() is invoked. /// - public static List AsList(this IEnumerable source) - { - return (source == null || source is List) ? (List)source : source.ToList(); - } + /// The type of element in the list. + /// The enumerable to return as a list. + public static List AsList(this IEnumerable source) => + (source == null || source is List) ? (List)source : source.ToList(); /// - /// Execute parameterized SQL + /// Execute parameterized SQL. /// - /// Number of rows affected - public static int Execute( - this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + /// The connection to query on. + /// The SQL to execute for this query. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The number of rows affected. + public static int Execute(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteImpl(cnn, ref command); } + /// - /// Execute parameterized SQL + /// Execute parameterized SQL. /// - /// Number of rows affected - public static int Execute(this IDbConnection cnn, CommandDefinition command) - { - return ExecuteImpl(cnn, ref command); - } - + /// The connection to execute on. + /// The command to execute on this connection. + /// The number of rows affected. + public static int Execute(this IDbConnection cnn, CommandDefinition command) => ExecuteImpl(cnn, ref command); /// - /// Execute parameterized SQL that selects a single value + /// Execute parameterized SQL that selects a single value. /// - /// The first cell selected - public static object ExecuteScalar( - this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The first cell selected as . + public static object ExecuteScalar(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteScalarImpl(cnn, ref command); } /// - /// Execute parameterized SQL that selects a single value + /// Execute parameterized SQL that selects a single value. /// - /// The first cell selected - public static T ExecuteScalar( - this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + /// The type to return. + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// The first cell returned, as . + public static T ExecuteScalar(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteScalarImpl(cnn, ref command); } /// - /// Execute parameterized SQL that selects a single value + /// Execute parameterized SQL that selects a single value. /// - /// The first cell selected - public static object ExecuteScalar(this IDbConnection cnn, CommandDefinition command) - { - return ExecuteScalarImpl(cnn, ref command); - } + /// The connection to execute on. + /// The command to execute. + /// The first cell selected as . + public static object ExecuteScalar(this IDbConnection cnn, CommandDefinition command) => + ExecuteScalarImpl(cnn, ref command); /// - /// Execute parameterized SQL that selects a single value + /// Execute parameterized SQL that selects a single value. /// - /// The first cell selected - public static T ExecuteScalar(this IDbConnection cnn, CommandDefinition command) - { - return ExecuteScalarImpl(cnn, ref command); - } + /// The type to return. + /// The connection to execute on. + /// The command to execute. + /// The first cell selected as . + public static T ExecuteScalar(this IDbConnection cnn, CommandDefinition command) => + ExecuteScalarImpl(cnn, ref command); private static IEnumerable GetMultiExec(object param) { - return (param is IEnumerable && - !(param is string || - param is IEnumerable> || - param is IDynamicParameters) - ) ? (IEnumerable) param : null; + return (param is IEnumerable + && !(param is string + || param is IEnumerable> + || param is IDynamicParameters) + ) ? (IEnumerable)param : null; } private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition command) @@ -501,13 +520,11 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com CacheInfo info = null; if (multiExec != null) { -#if ASYNC - if((command.Flags & CommandFlags.Pipelined) != 0) + if ((command.Flags & CommandFlags.Pipelined) != 0) { // this includes all the code for concurrent/overlapped query return ExecuteMultiImplAsync(cnn, command, multiExec).Result; } -#endif bool isFirst = true; int total = 0; bool wasClosed = cnn.State == ConnectionState.Closed; @@ -536,7 +553,8 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com } } command.OnCompleted(); - } finally + } + finally { if (wasClosed) cnn.Close(); } @@ -553,8 +571,14 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com } /// - /// Execute parameterized SQL and return an + /// Execute parameterized SQL and return an . /// + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a @@ -571,19 +595,18 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com /// ]]> /// /// - public static IDataReader ExecuteReader( - this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null -) + public static IDataReader ExecuteReader(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); - IDbCommand dbcmd; - var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out dbcmd); + var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd); return new WrappedReader(dbcmd, reader); } /// - /// Execute parameterized SQL and return an + /// Execute parameterized SQL and return an . /// + /// The connection to execute on. + /// The command to execute. /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a @@ -591,13 +614,16 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com /// public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command) { - IDbCommand dbcmd; - var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out dbcmd); + var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd); return new WrappedReader(dbcmd, reader); } + /// - /// Execute parameterized SQL and return an + /// Execute parameterized SQL and return an . /// + /// The connection to execute on. + /// The command to execute. + /// The flags for this reader. /// An that can be used to iterate over the results of the SQL query. /// /// This is typically used when the results of a query are not processed by Dapper, for example, used to fill a @@ -605,62 +631,92 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio /// public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) { - IDbCommand dbcmd; - var reader = ExecuteReaderImpl(cnn, ref command, commandBehavior, out dbcmd); + var reader = ExecuteReaderImpl(cnn, ref command, commandBehavior, out IDbCommand dbcmd); return new WrappedReader(dbcmd, reader); } /// - /// Return a sequence of dynamic objects with properties matching the columns + /// Return a sequence of dynamic objects with properties matching the columns. /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// Whether to buffer the results in memory. + /// The command timeout (in seconds). + /// The type of command to execute. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) - { - return Query(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType); - } + public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) => + Query(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType); /// - /// Return a dynamic object with properties matching the columns + /// Return a dynamic object with properties matching the columns. /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static dynamic QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) - { - return QueryFirst(cnn, sql, param as object, transaction, commandTimeout, commandType); - } - /// - /// Return a dynamic object with properties matching the columns - /// - /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) - { - return QueryFirstOrDefault(cnn, sql, param as object, transaction, commandTimeout, commandType); - } + public static dynamic QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryFirst(cnn, sql, param as object, transaction, commandTimeout, commandType); + /// - /// Return a dynamic object with properties matching the columns + /// Return a dynamic object with properties matching the columns. /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static dynamic QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) - { - return QuerySingle(cnn, sql, param as object, transaction, commandTimeout, commandType); - } + public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryFirstOrDefault(cnn, sql, param as object, transaction, commandTimeout, commandType); + /// - /// Return a dynamic object with properties matching the columns + /// Return a dynamic object with properties matching the columns. /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) - { - return QuerySingleOrDefault(cnn, sql, param as object, transaction, commandTimeout, commandType); - } + public static dynamic QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QuerySingle(cnn, sql, param as object, transaction, commandTimeout, commandType); /// - /// Executes a query, returning the data typed as per T + /// Return a dynamic object with properties matching the columns. /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QuerySingleOrDefault(cnn, sql, param as object, transaction, commandTimeout, commandType); + + /// + /// Executes a query, returning the data typed as . + /// + /// The type of results to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// Whether to buffer results in memory. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static IEnumerable Query( - this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null - ) + public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var data = QueryImpl(cnn, command, typeof(T)); @@ -668,134 +724,205 @@ public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, o } /// - /// Executes a single-row query, returning the data typed as per T + /// Executes a single-row query, returning the data typed as . /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static T QueryFirst( - this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + public static T QueryFirst(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.None); return QueryRowImpl(cnn, Row.First, ref command, typeof(T)); } + /// - /// Executes a single-row query, returning the data typed as per T + /// Executes a single-row query, returning the data typed as . /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static T QueryFirstOrDefault( - this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + public static T QueryFirstOrDefault(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.None); return QueryRowImpl(cnn, Row.FirstOrDefault, ref command, typeof(T)); } + /// - /// Executes a single-row query, returning the data typed as per T + /// Executes a single-row query, returning the data typed as . /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static T QuerySingle( - this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + public static T QuerySingle(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.None); return QueryRowImpl(cnn, Row.Single, ref command, typeof(T)); } + /// - /// Executes a single-row query, returning the data typed as per T + /// Executes a single-row query, returning the data typed as . /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The type of result to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static T QuerySingleOrDefault( - this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + public static T QuerySingleOrDefault(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.None); return QueryRowImpl(cnn, Row.SingleOrDefault, ref command, typeof(T)); } /// - /// Executes a single-row query, returning the data typed as per the Type suggested + /// Executes a single-row query, returning the data typed as . /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// Whether to buffer results in memory. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + /// + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static IEnumerable Query( - this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null - ) + public static IEnumerable Query(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var data = QueryImpl(cnn, command, type); return command.Buffered ? data.ToList() : data; } + /// - /// Executes a single-row query, returning the data typed as per the Type suggested + /// Executes a single-row query, returning the data typed as . /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + /// + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static object QueryFirst( - this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + public static object QueryFirst(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)); - var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.First, ref command, type); } + /// - /// Executes a single-row query, returning the data typed as per the Type suggested + /// Executes a single-row query, returning the data typed as . /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + /// + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static object QueryFirstOrDefault( - this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + public static object QueryFirstOrDefault(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)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.FirstOrDefault, ref command, type); } + /// - /// Executes a single-row query, returning the data typed as per the Type suggested + /// Executes a single-row query, returning the data typed as . /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + /// + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static object QuerySingle( - this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + public static object QuerySingle(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)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.Single, ref command, type); } + /// - /// Executes a single-row query, returning the data typed as per the Type suggested + /// Executes a single-row query, returning the data typed as . /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The connection to query on. + /// The type to return. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// is null. + /// + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static object QuerySingleOrDefault( - this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + public static object QuerySingleOrDefault(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)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.SingleOrDefault, ref command, type); } + /// - /// Executes a query, returning the data typed as per T + /// Executes a query, returning the data typed as . /// - /// the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The type of results to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static IEnumerable Query(this IDbConnection cnn, CommandDefinition command) @@ -805,73 +932,84 @@ public static IEnumerable Query(this IDbConnection cnn, CommandDefinition } /// - /// Executes a query, returning the data typed as per T + /// Executes a query, returning the data typed as . /// - /// the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object - /// A single instance or null of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The type of results to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A single instance or null of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static T QueryFirst(this IDbConnection cnn, CommandDefinition command) - { - return QueryRowImpl(cnn, Row.First, ref command, typeof(T)); - } + public static T QueryFirst(this IDbConnection cnn, CommandDefinition command) => + QueryRowImpl(cnn, Row.First, ref command, typeof(T)); + /// - /// Executes a query, returning the data typed as per T + /// Executes a query, returning the data typed as . /// - /// the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object - /// A single or null instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The type of results to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A single or null instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static T QueryFirstOrDefault(this IDbConnection cnn, CommandDefinition command) - { - return QueryRowImpl(cnn, Row.FirstOrDefault, ref command, typeof(T)); - } + public static T QueryFirstOrDefault(this IDbConnection cnn, CommandDefinition command) => + QueryRowImpl(cnn, Row.FirstOrDefault, ref command, typeof(T)); + /// - /// Executes a query, returning the data typed as per T + /// Executes a query, returning the data typed as . /// - /// the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object - /// A single instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The type of results to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A single instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static T QuerySingle(this IDbConnection cnn, CommandDefinition command) - { - return QueryRowImpl(cnn, Row.Single, ref command, typeof(T)); - } + public static T QuerySingle(this IDbConnection cnn, CommandDefinition command) => + QueryRowImpl(cnn, Row.Single, ref command, typeof(T)); + /// - /// Executes a query, returning the data typed as per T + /// Executes a query, returning the data typed as . /// - /// the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object - /// A single instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// The type of results to return. + /// The connection to query on. + /// The command used to query on this connection. + /// + /// A single instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static T QuerySingleOrDefault(this IDbConnection cnn, CommandDefinition command) - { - return QueryRowImpl(cnn, Row.SingleOrDefault, ref command, typeof(T)); - } - + public static T QuerySingleOrDefault(this IDbConnection cnn, CommandDefinition command) => + QueryRowImpl(cnn, Row.SingleOrDefault, ref command, typeof(T)); /// - /// Execute a command that returns multiple result sets, and access each in turn + /// Execute a command that returns multiple result sets, and access each in turn. /// - public static GridReader QueryMultiple( - this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null - ) + /// The connection to query on. + /// The SQL to execute for this query. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + public static GridReader QueryMultiple(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 QueryMultipleImpl(cnn, ref command); } + /// - /// Execute a command that returns multiple result sets, and access each in turn + /// Execute a command that returns multiple result sets, and access each in turn. /// - public static GridReader QueryMultiple(this IDbConnection cnn, CommandDefinition command) - { - return QueryMultipleImpl(cnn, ref command); - } + /// The connection to query on. + /// The command to execute for this query. + public static GridReader QueryMultiple(this IDbConnection cnn, CommandDefinition command) => + QueryMultipleImpl(cnn, ref command); private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandDefinition command) { object param = command.Parameters; - Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType(), null); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand cmd = null; @@ -895,8 +1033,11 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD { if (reader != null) { - if (!reader.IsClosed) try { cmd?.Cancel(); } + if (!reader.IsClosed) + { + try { cmd?.Cancel(); } catch { /* don't spoil the existing exception */ } + } reader.Dispose(); } cmd?.Dispose(); @@ -904,6 +1045,7 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD throw; } } + private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool wasClosed, CommandBehavior behavior) { try @@ -912,7 +1054,7 @@ private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool w } catch (ArgumentException ex) { // thanks, Sqlite! - if (DisableCommandBehaviorOptimizations(behavior, ex)) + if (Settings.DisableCommandBehaviorOptimizations(behavior, ex)) { // we can retry; this time it will have different flags return cmd.ExecuteReader(GetBehavior(wasClosed, behavior)); @@ -920,6 +1062,7 @@ private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool w throw; } } + private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefinition command, Type effectiveType) { object param = command.Parameters; @@ -947,7 +1090,7 @@ private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefini if (reader.FieldCount == 0) //https://code.google.com/p/dapper-dot-net/issues/detail?id=57 yield break; tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); - if(command.AddToCache) SetQueryCache(identity, info); + if (command.AddToCache) SetQueryCache(identity, info); } var func = tuple.Func; @@ -955,13 +1098,16 @@ private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefini while (reader.Read()) { object val = func(reader); - if (val == null || val is T) { + if (val == null || val is T) + { yield return (T)val; - } else { + } + else + { yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); } } - while (reader.NextResult()) { } + while (reader.NextResult()) { /* ignore subsequent result sets */ } // happy path; close the reader cleanly - no // need for "Cancel" etc reader.Dispose(); @@ -973,8 +1119,11 @@ private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefini { if (reader != null) { - if (!reader.IsClosed) try { cmd.Cancel(); } + if (!reader.IsClosed) + { + try { cmd.Cancel(); } catch { /* don't spoil the existing exception */ } + } reader.Dispose(); } if (wasClosed) cnn.Close(); @@ -986,12 +1135,13 @@ private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefini internal enum Row { First = 0, - FirstOrDefault = 1, // &FirstOrDefault != 0: allow zero rows + FirstOrDefault = 1, // & FirstOrDefault != 0: allow zero rows Single = 2, // & Single != 0: demand at least one row SingleOrDefault = 3 } - static readonly int[] ErrTwoRows = new int[2], ErrZeroRows = new int[0]; - static void ThrowMultipleRows(Row row) + + private static readonly int[] ErrTwoRows = new int[2], ErrZeroRows = new int[0]; + private static void ThrowMultipleRows(Row row) { switch (row) { // get the standard exception from the runtime @@ -1000,7 +1150,8 @@ static void ThrowMultipleRows(Row row) default: throw new InvalidOperationException(); } } - static void ThrowZeroRows(Row row) + + private static void ThrowZeroRows(Row row) { switch (row) { // get the standard exception from the runtime @@ -1009,6 +1160,7 @@ static void ThrowZeroRows(Row row) default: throw new InvalidOperationException(); } } + private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefinition command, Type effectiveType) { object param = command.Parameters; @@ -1055,13 +1207,13 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); } if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row); - while (reader.Read()) { } + while (reader.Read()) { /* ignore subsequent rows */ } } else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { ThrowZeroRows(row); } - while (reader.NextResult()) { } + while (reader.NextResult()) { /* ignore subsequent result sets */ } // happy path; close the reader cleanly - no // need for "Cancel" etc reader.Dispose(); @@ -1074,8 +1226,11 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti { if (reader != null) { - if (!reader.IsClosed) try { cmd.Cancel(); } + if (!reader.IsClosed) + { + try { cmd.Cancel(); } catch { /* don't spoil the existing exception */ } + } reader.Dispose(); } if (wasClosed) cnn.Close(); @@ -1084,180 +1239,164 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti } /// - /// Maps a query to objects + /// Perform a multi-mapping query with 2 input types. + /// This returns a single type, combined from the raw types via . /// - /// The first type in the record set - /// The second type in the record set - /// The return type - /// - /// - /// - /// - /// - /// - /// The Field we should split and read the second object from (default: id) - /// Number of seconds before command execution timeout + /// The first type in the recordset. + /// The second type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? - /// - public static IEnumerable Query( - this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null - ) - { - return MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); - } - - /// - /// Maps a query to objects - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// The Field we should split and read the second object from (default: id) - /// Number of seconds before command execution timeout - /// - /// - public static IEnumerable Query( - this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null - ) - { - return MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); - } - - /// - /// Perform a multi mapping query with 4 input parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static IEnumerable Query( - this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null - ) - { - return MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); - } - - /// - /// Perform a multi mapping query with 5 input parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static IEnumerable Query( - this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null -) - { - return MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); - } - - /// - /// Perform a multi mapping query with 6 input parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static IEnumerable Query( - this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null -) - { - return MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); - } - - - /// - /// Perform a multi mapping query with 7 input parameters - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) - { - return MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); - } - - /// - /// Perform a multi mapping query with arbitrary input parameters - /// - /// The return type - /// - /// - /// array of types in the record set - /// - /// - /// - /// - /// The Field we should split and read the second object from (default: id) - /// Number of seconds before command execution timeout + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with 3 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? - /// + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with 4 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with 5 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with 6 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with 7 input types. + /// This returns a single type, combined from the raw types via . + /// + /// The first type in the recordset. + /// The second type in the recordset. + /// The third type in the recordset. + /// The fourth type in the recordset. + /// The fifth type in the recordset. + /// The sixth type in the recordset. + /// The seventh type in the recordset. + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); + + /// + /// Perform a multi-mapping query with an arbitrary number of input types. + /// This returns a single type, combined from the raw types via . + /// + /// The combined type to return. + /// The connection to query on. + /// The SQL to execute for this query. + /// Array of types in the recordset. + /// The function to map row types to the return type. + /// The parameters to use for this query. + /// The transaction to use for this query. + /// Whether to buffer the results in memory. + /// The field we should split and read the second object from (default: "Id"). + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + /// An enumerable of . public static IEnumerable Query(this IDbConnection cnn, string sql, Type[] types, Func 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); - var results = MultiMapImpl(cnn, command, types, map, splitOn, null, null, true); + var results = MultiMapImpl(cnn, command, types, map, splitOn, null, null, true); return buffered ? results.ToList() : results; } - static IEnumerable MultiMap( + private static IEnumerable MultiMap( this IDbConnection cnn, string sql, Delegate map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); @@ -1265,7 +1404,7 @@ public static IEnumerable Query(this IDbConnection cnn, string return buffered ? results.ToList() : results; } - static IEnumerable MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity, bool finalize) + private static IEnumerable MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity, bool finalize) { object param = command.Parameters; identity = 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) }); @@ -1274,7 +1413,7 @@ public static IEnumerable Query(this IDbConnection cnn, string IDbCommand ownedCommand = null; IDataReader ownedReader = null; - bool wasClosed = cnn != null && cnn.State == ConnectionState.Closed; + bool wasClosed = cnn?.State == ConnectionState.Closed; try { if (reader == null) @@ -1284,16 +1423,16 @@ public static IEnumerable Query(this IDbConnection cnn, string ownedReader = ExecuteReaderWithFlagsFallback(ownedCommand, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); reader = ownedReader; } - DeserializerState deserializer = default(DeserializerState); + var deserializer = default(DeserializerState); Func[] otherDeserializers; int hash = GetColumnHash(reader); if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash) { - var deserializers = GenerateDeserializers(new [] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }, splitOn, reader); + var deserializers = GenerateDeserializers(new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }, splitOn, reader); deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray(); - if(command.AddToCache) SetQueryCache(identity, cinfo); + if (command.AddToCache) SetQueryCache(identity, cinfo); } Func mapIt = GenerateMapper(deserializer.Func, otherDeserializers, map); @@ -1304,9 +1443,9 @@ public static IEnumerable Query(this IDbConnection cnn, string { yield return mapIt(reader); } - if(finalize) + if (finalize) { - while (reader.NextResult()) { } + while (reader.NextResult()) { /* ignore remaining result sets */ } command.OnCompleted(); } } @@ -1324,27 +1463,13 @@ public static IEnumerable Query(this IDbConnection cnn, string } } } - const CommandBehavior DefaultAllowedCommandBehaviors = ~((CommandBehavior)0); - static CommandBehavior allowedCommandBehaviors = DefaultAllowedCommandBehaviors; - private static bool DisableCommandBehaviorOptimizations(CommandBehavior behavior, Exception ex) - { - if(allowedCommandBehaviors == DefaultAllowedCommandBehaviors - && (behavior & (CommandBehavior.SingleResult | CommandBehavior.SingleRow)) != 0) - { - if (ex.Message.Contains(nameof(CommandBehavior.SingleResult)) - || ex.Message.Contains(nameof(CommandBehavior.SingleRow))) - { // some providers just just allow these, so: try again without them and stop issuing them - allowedCommandBehaviors = ~(CommandBehavior.SingleResult | CommandBehavior.SingleRow); - return true; - } - } - return false; - } + private static CommandBehavior GetBehavior(bool close, CommandBehavior @default) { - return (close ? (@default | CommandBehavior.CloseConnection) : @default) & allowedCommandBehaviors; + return (close ? (@default | CommandBehavior.CloseConnection) : @default) & Settings.AllowedCommandBehaviors; } - static IEnumerable MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Type[] types, Func map, string splitOn, IDataReader reader, Identity identity, bool finalize) + + private static IEnumerable MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Type[] types, Func map, string splitOn, IDataReader reader, Identity identity, bool finalize) { if (types.Length < 1) { @@ -1358,7 +1483,7 @@ static IEnumerable MultiMapImpl(this IDbConnection cnn, Comman IDbCommand ownedCommand = null; IDataReader ownedReader = null; - bool wasClosed = cnn != null && cnn.State == ConnectionState.Closed; + bool wasClosed = cnn?.State == ConnectionState.Closed; try { if (reader == null) @@ -1390,7 +1515,7 @@ static IEnumerable MultiMapImpl(this IDbConnection cnn, Comman } if (finalize) { - while (reader.NextResult()) { } + while (reader.NextResult()) { /* ignore subsequent result sets */ } command.OnCompleted(); } } @@ -1450,8 +1575,8 @@ static IEnumerable MultiMapImpl(this IDbConnection cnn, Comman { var deserializers = new List>(); var splits = splitOn.Split(',').Select(s => s.Trim()).ToArray(); - bool isMultiSplit = splits.Length > 1; - if (types.First() == typeof(object)) + bool isMultiSplit = splits.Length > 1; + if (types[0] == typeof(object)) { // we go left to right for dynamic multi-mapping so that the madness of TestMultiMappingVariations // is supported @@ -1471,7 +1596,7 @@ static IEnumerable MultiMapImpl(this IDbConnection cnn, Comman { currentSplit = splits[++splitIdx]; } - deserializers.Add((GetDeserializer(type, reader, currentPos, splitPoint - currentPos, !first))); + deserializers.Add(GetDeserializer(type, reader, currentPos, splitPoint - currentPos, !first)); currentPos = splitPoint; first = false; } @@ -1486,7 +1611,7 @@ static IEnumerable MultiMapImpl(this IDbConnection cnn, Comman for (var typeIdx = types.Length - 1; typeIdx >= 0; --typeIdx) { var type = types[typeIdx]; - if (type == typeof (DontMap)) + if (type == typeof(DontMap)) { continue; } @@ -1501,13 +1626,13 @@ static IEnumerable MultiMapImpl(this IDbConnection cnn, Comman } } - deserializers.Add((GetDeserializer(type, reader, splitPoint, currentPos - splitPoint, typeIdx > 0))); + deserializers.Add(GetDeserializer(type, reader, splitPoint, currentPos - splitPoint, typeIdx > 0)); currentPos = splitPoint; } deserializers.Reverse(); - } + return deserializers.ToArray(); } @@ -1554,10 +1679,9 @@ private static int GetNextSplit(int startIdx, string splitOn, IDataReader reader private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters, bool addToCache) { - CacheInfo info; - if (!TryGetQueryCache(identity, out info)) + if (!TryGetQueryCache(identity, out CacheInfo info)) { - if(GetMultiExec(exampleParameters) != null) + if (GetMultiExec(exampleParameters) != null) { throw new InvalidOperationException("An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context"); } @@ -1567,7 +1691,7 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter Action reader; if (exampleParameters is IDynamicParameters) { - reader = (cmd, obj) => { ((IDynamicParameters)obj).AddParameters(cmd, identity); }; + reader = (cmd, obj) => ((IDynamicParameters)obj).AddParameters(cmd, identity); } else if (exampleParameters is IEnumerable>) { @@ -1582,7 +1706,7 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter var literals = GetLiteralTokens(identity.sql); reader = CreateParamInfoGenerator(identity, false, true, literals); } - if((identity.commandType == null || identity.commandType == CommandType.Text) && ShouldPassByPosition(identity.sql)) + if ((identity.commandType == null || identity.commandType == CommandType.Text) && ShouldPassByPosition(identity.sql)) { var tail = reader; reader = (cmd, obj) => @@ -1593,14 +1717,14 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter } info.ParamReader = reader; } - if(addToCache) SetQueryCache(identity, info); + if (addToCache) SetQueryCache(identity, info); } return info; } private static bool ShouldPassByPosition(string sql) { - return sql != null && sql.IndexOf('?') >= 0 && pseudoPositional.IsMatch(sql); + return sql?.IndexOf('?') >= 0 && pseudoPositional.IsMatch(sql); } private static void PassByPosition(IDbCommand cmd) @@ -1609,7 +1733,7 @@ private static void PassByPosition(IDbCommand cmd) Dictionary parameters = new Dictionary(StringComparer.Ordinal); - foreach(IDbDataParameter param in cmd.Parameters) + foreach (IDbDataParameter param in cmd.Parameters) { if (!string.IsNullOrEmpty(param.ParameterName)) parameters[param.ParameterName] = param; } @@ -1618,14 +1742,13 @@ private static void PassByPosition(IDbCommand cmd) cmd.CommandText = pseudoPositional.Replace(cmd.CommandText, match => { string key = match.Groups[1].Value; - IDbDataParameter param; if (!consumed.Add(key)) { throw new InvalidOperationException("When passing parameters by position, each parameter can only be referenced once"); } - else if (parameters.TryGetValue(key, out param)) + else if (parameters.TryGetValue(key, out IDbDataParameter param)) { - if(firstMatch) + if (firstMatch) { firstMatch = false; cmd.Parameters.Clear(); // only clear if we are pretty positive that we've found this pattern successfully @@ -1646,19 +1769,16 @@ private static void PassByPosition(IDbCommand cmd) private static Func GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { - // dynamic is passed in as Object ... by c# design - if (type == typeof(object) - || type == typeof(DapperRow)) + if (type == typeof(object) || type == typeof(DapperRow)) { return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing); } Type underlyingType = null; - if (!(typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary || - (type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum))) + if (!(typeMap.ContainsKey(type) || type.IsEnum() || type.FullName == LinqBinary + || (type.IsValueType() && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum()))) { - ITypeHandler handler; - if (typeHandlers.TryGetValue(type, out handler)) + if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) { return GetHandlerDeserializer(handler, type, startBound); } @@ -1666,18 +1786,17 @@ private static void PassByPosition(IDbCommand cmd) } return GetStructDeserializer(type, underlyingType ?? type, startBound); } + private static Func GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound) { return reader => handler.Parse(type, reader.GetValue(startBound)); } - private static Exception MultiMapException(IDataRecord reader) { bool hasFields = false; - try { - hasFields = reader != null && reader.FieldCount != 0; - } catch { } + try { hasFields = reader != null && reader.FieldCount != 0; } + catch { /* don't throw when trying to throw */ } if (hasFields) return new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); else @@ -1746,11 +1865,10 @@ private static Exception MultiMapException(IDataRecord reader) }; } /// - /// Internal use only + /// Internal use only. /// - /// - /// -#if !NETSTANDARD2_0 + /// The object to convert to a character. +#if !NETSTANDARD1_3 [Browsable(false)] #endif [EditorBrowsable(EditorBrowsableState.Never)] @@ -1758,15 +1876,16 @@ private static Exception MultiMapException(IDataRecord reader) public static char ReadChar(object value) { if (value == null || value is DBNull) throw new ArgumentNullException(nameof(value)); - string s = value as string; + var s = value as string; if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", nameof(value)); return s[0]; } /// - /// Internal use only + /// Internal use only. /// -#if !NETSTANDARD2_0 + /// The object to convert to a character. +#if !NETSTANDARD1_3 [Browsable(false)] #endif [EditorBrowsable(EditorBrowsableState.Never)] @@ -1774,16 +1893,18 @@ public static char ReadChar(object value) public static char? ReadNullableChar(object value) { if (value == null || value is DBNull) return null; - string s = value as string; + var s = value as string; if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", nameof(value)); return s[0]; } - /// - /// Internal use only + /// Internal use only. /// -#if !NETSTANDARD2_0 + /// The parameter collection to search in. + /// The command for this fetch. + /// The name of the parameter to get. +#if !NETSTANDARD1_3 [Browsable(false)] #endif [EditorBrowsable(EditorBrowsableState.Never)] @@ -1806,7 +1927,7 @@ public static IDbDataParameter FindOrAddParameter(IDataParameterCollection param internal static int GetListPaddingExtraCount(int count) { - switch(count) + switch (count) { case 0: case 1: @@ -1833,11 +1954,15 @@ internal static int GetListPaddingExtraCount(int count) private static string GetInListRegex(string name, bool byPosition) => byPosition ? (@"(\?)" + Regex.Escape(name) + @"\?(?!\w)(\s+(?i)unknown(?-i))?") - : (@"([?@:]" + Regex.Escape(name) + @")(?!\w)(\s+(?i)unknown(?-i))?"); + : ("([?@:]" + Regex.Escape(name) + @")(?!\w)(\s+(?i)unknown(?-i))?"); + /// - /// Internal use only + /// Internal use only. /// -#if !NETSTANDARD2_0 + /// The command to pack parameters for. + /// The name prefix for these parameters. + /// The parameter value can be an +#if !NETSTANDARD1_3 [Browsable(false)] #endif [EditorBrowsable(EditorBrowsableState.Never)] @@ -1874,18 +1999,17 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj { if (++count == 1) // first item: fetch some type info { - if(item == null) + if (item == null) { throw new NotSupportedException("The first item in a list-expansion cannot be null"); } if (!isDbString) { - ITypeHandler handler; - dbType = LookupDbType(item.GetType(), "", true, out handler); + dbType = LookupDbType(item.GetType(), "", true, out ITypeHandler handler); } } var nextName = namePrefix + count.ToString(); - if (isDbString && item as DbString != null) + if (isDbString && item is DbString) { var str = item as DbString; str.AddParameter(command, nextName); @@ -1922,7 +2046,7 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj count++; var padParam = command.CreateParameter(); padParam.ParameterName = namePrefix + count.ToString(); - if(isString) padParam.Size = DbString.DefaultLength; + if (isString) padParam.Size = DbString.DefaultLength; padParam.DbType = dbType; padParam.Value = lastValue; command.Parameters.Add(padParam); @@ -1930,8 +2054,7 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj } } - - if(viaSplit) + if (viaSplit) { // already done } @@ -1945,8 +2068,8 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj var variableName = match.Groups[1].Value; if (match.Groups[2].Success) { - // looks like an optimize hint; leave it alone! - return match.Value; + // looks like an optimize hint; leave it alone! + return match.Value; } else { @@ -1965,8 +2088,8 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj var variableName = match.Groups[1].Value; if (match.Groups[2].Success) { - // looks like an optimize hint; expand it - var suffix = match.Groups[2].Value; + // looks like an optimize hint; expand it + var suffix = match.Groups[2].Value; var sb = GetStringBuilder().Append(variableName).Append(1).Append(suffix); for (int i = 2; i <= count; i++) @@ -1977,9 +2100,8 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj } else { - var sb = GetStringBuilder().Append('(').Append(variableName); - if(!byPosition) sb.Append(1); + if (!byPosition) sb.Append(1); for (int i = 2; i <= count; i++) { sb.Append(',').Append(variableName); @@ -1996,27 +2118,31 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj private static bool TryStringSplit(ref IEnumerable list, int splitAt, string namePrefix, IDbCommand command, bool byPosition) { if (list == null || splitAt < 0) return false; - if (list is IEnumerable) return TryStringSplit(ref list, splitAt, namePrefix, command, "int", byPosition, - (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); - if (list is IEnumerable) return TryStringSplit(ref list, splitAt, namePrefix, command, "bigint", byPosition, - (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); - if (list is IEnumerable) return TryStringSplit(ref list, splitAt, namePrefix, command, "smallint", byPosition, - (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); - if (list is IEnumerable) return TryStringSplit(ref list, splitAt, namePrefix, command, "tinyint", byPosition, - (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); + switch (list) + { + case IEnumerable l: + return TryStringSplit(ref l, splitAt, namePrefix, command, "int", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); + case IEnumerable l: + return TryStringSplit(ref l, splitAt, namePrefix, command, "bigint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); + case IEnumerable l: + return TryStringSplit(ref l, splitAt, namePrefix, command, "smallint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); + case IEnumerable l: + return TryStringSplit(ref l, splitAt, namePrefix, command, "tinyint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); + } return false; } - private static bool TryStringSplit(ref IEnumerable list, int splitAt, string namePrefix, IDbCommand command, string colType, bool byPosition, + + private static bool TryStringSplit(ref IEnumerable list, int splitAt, string namePrefix, IDbCommand command, string colType, bool byPosition, Action append) { - ICollection typed = list as ICollection; - if(typed == null) + var typed = list as ICollection; + if (typed == null) { - typed = ((IEnumerable)list).ToList(); + typed = list.ToList(); list = typed; // because we still need to be able to iterate it, even if we fail here } if (typed.Count < splitAt) return false; - + string varName = null; var regexIncludingUnknown = GetInListRegex(namePrefix, byPosition); var sql = Regex.Replace(command.CommandText, regexIncludingUnknown, match => @@ -2043,11 +2169,11 @@ private static bool TryStringSplit(ref IEnumerable list, int splitAt, string nam string val; using (var iter = typed.GetEnumerator()) { - if(iter.MoveNext()) + if (iter.MoveNext()) { var sb = GetStringBuilder(); append(sb, iter.Current); - while(iter.MoveNext()) + while (iter.MoveNext()) { append(sb.Append(','), iter.Current); } @@ -2066,6 +2192,7 @@ private static bool TryStringSplit(ref IEnumerable list, int splitAt, string nam /// /// OBSOLETE: For internal usage only. Sanitizes the paramter value with proper type casting. /// + /// The value to sanitize. [Obsolete(ObsoleteInternalUsageOnly, false)] public static object SanitizeParameterValue(object value) { @@ -2075,11 +2202,7 @@ public static object SanitizeParameterValue(object value) TypeCode typeCode; if (value is IConvertible) { -#if NETSTANDARD2_0 - typeCode = (TypeCode)((IConvertible)value).GetTypeCode(); -#else typeCode = ((IConvertible)value).GetTypeCode(); -#endif } else { @@ -2099,21 +2222,28 @@ public static object SanitizeParameterValue(object value) } return value; } + private static IEnumerable FilterParameters(IEnumerable parameters, string sql) { - return parameters.Where(p => Regex.IsMatch(sql, @"[?@:]" + p.Name + "([^a-z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)); + var list = new List(16); + foreach (var p in parameters) + { + if (Regex.IsMatch(sql, @"[?@:]" + p.Name + @"([^\p{L}\p{N}_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)) + list.Add(p); + } + return list; } // look for ? / @ / : *by itself* - static readonly Regex smellsLikeOleDb = new Regex(@"(? - /// Replace all literal tokens with their text form + /// Replace all literal tokens with their text form. /// + /// The parameter lookup to do replacements with. + /// The command to repalce parameters in. public static void ReplaceLiterals(this IParameterLookup parameters, IDbCommand command) { var tokens = GetLiteralTokens(command.CommandText); @@ -2121,9 +2251,11 @@ public static void ReplaceLiterals(this IParameterLookup parameters, IDbCommand } internal static readonly MethodInfo format = typeof(SqlMapper).GetMethod("Format", BindingFlags.Public | BindingFlags.Static); + /// - /// Convert numeric values to their string form for SQL literal purposes + /// Convert numeric values to their string form for SQL literal purposes. /// + /// The value to get a string for. [Obsolete(ObsoleteInternalUsageOnly)] public static string Format(object value) { @@ -2135,7 +2267,7 @@ public static string Format(object value) { switch (TypeExtensions.GetTypeCode(value.GetType())) { -#if !NETSTANDARD2_0 +#if !NETSTANDARD1_3 case TypeCode.DBNull: return "null"; #endif @@ -2165,13 +2297,13 @@ public static string Format(object value) return ((decimal)value).ToString(CultureInfo.InvariantCulture); default: var multiExec = GetMultiExec(value); - if(multiExec != null) + if (multiExec != null) { StringBuilder sb = null; bool first = true; foreach (object subval in multiExec) { - if(first) + if (first) { sb = GetStringBuilder().Append('('); first = false; @@ -2182,7 +2314,7 @@ public static string Format(object value) } sb.Append(Format(subval)); } - if(first) + if (first) { return "(select null where 1=0)"; } @@ -2196,7 +2328,6 @@ public static string Format(object value) } } - internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand command, IList tokens) { var sql = command.CommandText; @@ -2219,10 +2350,10 @@ internal static IList GetLiteralTokens(string sql) var matches = literalTokens.Matches(sql); var found = new HashSet(StringComparer.Ordinal); List list = new List(matches.Count); - foreach(Match match in matches) + foreach (Match match in matches) { string token = match.Value; - if(found.Add(match.Value)) + if (found.Add(match.Value)) { list.Add(new LiteralToken(token, match.Groups[1].Value)); } @@ -2231,17 +2362,46 @@ internal static IList GetLiteralTokens(string sql) } /// - /// Internal use only + /// Internal use only. /// - public static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused) + /// The identity of the generator. + /// Whether to check for duplicates. + /// Whether to remove unused parameters. + public static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused) => + CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql)); + + private static bool IsValueTuple(Type type) => type?.IsValueType() == true && type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal); + + private static List GetValueTupleMembers(Type type, string[] names) { - return CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql)); + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + var result = new List(names.Length); + for (int i = 0; i < names.Length; i++) + { + FieldInfo field = null; + string name = "Item" + (i + 1).ToString(CultureInfo.InvariantCulture); + foreach (var test in fields) + { + if (test.Name == name) + { + field = test; + break; + } + } + result.Add(field == null ? null : new SimpleMemberMap(string.IsNullOrWhiteSpace(names[i]) ? name : names[i], field)); + } + return result; } internal static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList literals) { Type type = identity.parametersType; + if (IsValueTuple(type)) + { + throw new NotSupportedException("ValueTuple should not be used for parameters - the language-level names are not available to use as parameter names, and it adds unnecessary boxing"); + } + bool filterParams = false; if (removeUnused && identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text) { @@ -2251,7 +2411,7 @@ internal static IList GetLiteralTokens(string sql) var il = dm.GetILGenerator(); - bool isStruct = type.IsValueType; + bool isStruct = type.IsValueType(); bool haveInt32Arg1 = false; il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param] if (isStruct) @@ -2269,43 +2429,51 @@ internal static IList GetLiteralTokens(string sql) il.Emit(OpCodes.Ldarg_0); // stack is now [command] il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty(nameof(IDbCommand.Parameters)).GetGetMethod(), null); // stack is now [parameters] - var propsArr = type.GetProperties().Where(p => p.GetIndexParameters().Length == 0).ToArray(); + var allTypeProps = type.GetProperties(); + var propsList = new List(allTypeProps.Length); + for (int i = 0; i < allTypeProps.Length; ++i) + { + var p = allTypeProps[i]; + if (p.GetIndexParameters().Length == 0) + propsList.Add(p); + } + var ctors = type.GetConstructors(); ParameterInfo[] ctorParams; IEnumerable props = null; // try to detect tuple patterns, e.g. anon-types, and use that to choose the order // otherwise: alphabetical - if (ctors.Length == 1 && propsArr.Length == (ctorParams = ctors[0].GetParameters()).Length) + if (ctors.Length == 1 && propsList.Count == (ctorParams = ctors[0].GetParameters()).Length) { // check if reflection was kind enough to put everything in the right order for us bool ok = true; - for (int i = 0; i < propsArr.Length; i++) + for (int i = 0; i < propsList.Count; i++) { - if (!string.Equals(propsArr[i].Name, ctorParams[i].Name, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(propsList[i].Name, ctorParams[i].Name, StringComparison.OrdinalIgnoreCase)) { ok = false; break; } } - if(ok) + if (ok) { // pre-sorted; the reflection gods have smiled upon us - props = propsArr; + props = propsList; } - else { // might still all be accounted for; check the hard way - var positionByName = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach(var param in ctorParams) + else + { // might still all be accounted for; check the hard way + var positionByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var param in ctorParams) { positionByName[param.Name] = param.Position; } - if (positionByName.Count == propsArr.Length) + if (positionByName.Count == propsList.Count) { - int[] positions = new int[propsArr.Length]; + int[] positions = new int[propsList.Count]; ok = true; - for (int i = 0; i < propsArr.Length; i++) + for (int i = 0; i < propsList.Count; i++) { - int pos; - if (!positionByName.TryGetValue(propsArr[i].Name, out pos)) + if (!positionByName.TryGetValue(propsList[i].Name, out int pos)) { ok = false; break; @@ -2314,13 +2482,17 @@ internal static IList GetLiteralTokens(string sql) } if (ok) { - Array.Sort(positions, propsArr); - props = propsArr; + props = propsList.ToArray(); + Array.Sort(positions, (PropertyInfo[])props); } } } } - if(props == null) props = propsArr.OrderBy(x => x.Name); + if (props == null) + { + propsList.Sort(new PropertyInfoByNameComparer()); + props = propsList; + } if (filterParams) { props = FilterParameters(props, identity.sql); @@ -2338,9 +2510,8 @@ internal static IList GetLiteralTokens(string sql) il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod(nameof(ICustomQueryParameter.AddParameter)), null); // stack is now [parameters] continue; } - ITypeHandler handler; #pragma warning disable 618 - DbType dbType = LookupDbType(prop.PropertyType, prop.Name, true, out handler); + DbType dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler handler); #pragma warning restore 618 if (dbType == DynamicParameters.EnumerableMultiParameter) { @@ -2349,7 +2520,7 @@ internal static IList GetLiteralTokens(string sql) il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name] il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value] - if (prop.PropertyType.IsValueType) + if (prop.PropertyType.IsValueType()) { il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value] } @@ -2401,15 +2572,15 @@ internal static IList GetLiteralTokens(string sql) il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value] bool checkForNull; - if (prop.PropertyType.IsValueType) + if (prop.PropertyType.IsValueType()) { var propType = prop.PropertyType; var nullType = Nullable.GetUnderlyingType(propType); bool callSanitize = false; - - if((nullType ?? propType).IsEnum) + + if ((nullType ?? propType).IsEnum()) { - if(nullType != null) + if (nullType != null) { // Nullable; we want to box as the underlying type; that's just *hard*; for // simplicity, box as Nullable and call SanitizeParameterValue @@ -2430,7 +2601,7 @@ internal static IList GetLiteralTokens(string sql) case TypeCode.UInt32: propType = typeof(uint); break; case TypeCode.UInt64: propType = typeof(ulong); break; } - } + } } else { @@ -2443,7 +2614,8 @@ internal static IList GetLiteralTokens(string sql) il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SanitizeParameterValue)), null); // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value] } - } else + } + else { checkForNull = true; // if not a value-type, need to check } @@ -2533,7 +2705,7 @@ internal static IList GetLiteralTokens(string sql) // stack is currently [parameters] il.Emit(OpCodes.Pop); // stack is now empty - if(literals.Count != 0 && propsArr != null) + if (literals.Count != 0 && propsList != null) { il.Emit(OpCodes.Ldarg_0); // command il.Emit(OpCodes.Ldarg_0); // command, command @@ -2546,13 +2718,13 @@ internal static IList GetLiteralTokens(string sql) // find the best member, preferring case-sensitive PropertyInfo exact = null, fallback = null; string huntName = literal.Member; - for(int i = 0; i < propsArr.Length;i++) + for (int i = 0; i < propsList.Count; i++) { - string thisName = propsArr[i].Name; - if(string.Equals(thisName, huntName, StringComparison.OrdinalIgnoreCase)) + string thisName = propsList[i].Name; + if (string.Equals(thisName, huntName, StringComparison.OrdinalIgnoreCase)) { - fallback = propsArr[i]; - if(string.Equals(thisName, huntName, StringComparison.Ordinal)) + fallback = propsList[i]; + if (string.Equals(thisName, huntName, StringComparison.Ordinal)) { exact = fallback; break; @@ -2561,7 +2733,7 @@ internal static IList GetLiteralTokens(string sql) } var prop = exact ?? fallback; - if(prop != null) + if (prop != null) { il.Emit(OpCodes.Ldstr, literal.Token); il.Emit(OpCodes.Ldloc_0); // command, sql, typed parameter @@ -2616,10 +2788,9 @@ internal static IList GetLiteralTokens(string sql) il.EmitCall(OpCodes.Call, convert, null); // command, sql, string value break; default: - if (propType.IsValueType) il.Emit(OpCodes.Box, propType); // command, sql, object value + if (propType.IsValueType()) il.Emit(OpCodes.Box, propType); // command, sql, object value il.EmitCall(OpCodes.Call, format, null); // command, sql, string value break; - } il.EmitCall(OpCodes.Callvirt, StringReplace, null); } @@ -2630,17 +2801,19 @@ internal static IList GetLiteralTokens(string sql) il.Emit(OpCodes.Ret); return (Action)dm.CreateDelegate(typeof(Action)); } - static readonly Dictionary toStrings = new[] + + private static readonly Dictionary toStrings = new[] { typeof(bool), typeof(sbyte), typeof(byte), typeof(ushort), typeof(short), typeof(uint), typeof(int), typeof(ulong), typeof(long), typeof(float), typeof(double), typeof(decimal) }.ToDictionary(x => TypeExtensions.GetTypeCode(x), x => x.GetPublicInstanceMethod(nameof(object.ToString), new[] { typeof(IFormatProvider) })); - static MethodInfo GetToString(TypeCode typeCode) + + private static MethodInfo GetToString(TypeCode typeCode) { - MethodInfo method; - return toStrings.TryGetValue(typeCode, out method) ? method : null; + return toStrings.TryGetValue(typeCode, out MethodInfo method) ? method : null; } - static readonly MethodInfo StringReplace = typeof(string).GetPublicInstanceMethod(nameof(string.Replace), new Type[] { typeof(string), typeof(string) }), + + private static readonly MethodInfo StringReplace = typeof(string).GetPublicInstanceMethod(nameof(string.Replace), new Type[] { typeof(string), typeof(string) }), InvariantCulture = typeof(CultureInfo).GetProperty(nameof(CultureInfo.InvariantCulture), BindingFlags.Public | BindingFlags.Static).GetGetMethod(); private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action paramReader) @@ -2679,7 +2852,7 @@ private static T ExecuteScalarImpl(IDbConnection cnn, ref CommandDefinition c { cmd = command.SetupCommand(cnn, paramReader); if (wasClosed) cnn.Open(); - result =cmd.ExecuteScalar(); + result = cmd.ExecuteScalar(); command.OnCompleted(); } finally @@ -2750,20 +2923,19 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin } #pragma warning restore 618 - if (effectiveType.IsEnum) + if (effectiveType.IsEnum()) { // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum return r => { var val = r.GetValue(index); - if(val is float || val is double || val is decimal) + if (val is float || val is double || val is decimal) { val = Convert.ChangeType(val, Enum.GetUnderlyingType(effectiveType), CultureInfo.InvariantCulture); } return val is DBNull ? null : Enum.ToObject(effectiveType, val); }; } - ITypeHandler handler; - if(typeHandlers.TryGetValue(type, out handler)) + if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) { return r => { @@ -2784,7 +2956,7 @@ private static T Parse(object value) if (value is T) return (T)value; var type = typeof(T); type = Nullable.GetUnderlyingType(type) ?? type; - if (type.IsEnum) + if (type.IsEnum()) { if (value is float || value is double || value is decimal) { @@ -2792,29 +2964,29 @@ private static T Parse(object value) } return (T)Enum.ToObject(type, value); } - ITypeHandler handler; - if (typeHandlers.TryGetValue(type, out handler)) + if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) { return (T)handler.Parse(type, value); } return (T)Convert.ChangeType(value, type, CultureInfo.InvariantCulture); } - static readonly MethodInfo + private static readonly MethodInfo enumParse = typeof(Enum).GetMethod(nameof(Enum.Parse), new Type[] { typeof(Type), typeof(string), typeof(bool) }), getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public) - .Where(p => p.GetIndexParameters().Any() && p.GetIndexParameters()[0].ParameterType == typeof(int)) + .Where(p => p.GetIndexParameters().Length > 0 && p.GetIndexParameters()[0].ParameterType == typeof(int)) .Select(p => p.GetGetMethod()).First(); /// /// Gets type-map for the given type /// /// Type map instance, default is to create new instance of DefaultTypeMap - public static Func TypeMapProvider = ( Type type ) => new DefaultTypeMap( type ); + public static Func TypeMapProvider = (Type type) => new DefaultTypeMap(type); /// - /// Gets type-map for the given type + /// Gets type-map for the given . /// + /// The type to get a map for. /// Type map implementation, DefaultTypeMap instance if no override present public static ITypeMap GetTypeMap(Type type) { @@ -2829,7 +3001,7 @@ public static ITypeMap GetTypeMap(Type type) if (map == null) { - map = TypeMapProvider( type ); + map = TypeMapProvider(type); _typeMaps[type] = map; } } @@ -2883,12 +3055,12 @@ public static void SetTypeMap(Type type, ITypeMap map) { return TypeDeserializerCache.GetReader(type, reader, startBound, length, returnNullIfFirstMissing); } - static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary locals, Type type, bool initAndLoad) + + private static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary locals, Type type, bool initAndLoad) { if (type == null) throw new ArgumentNullException(nameof(type)); - if (locals == null) locals = new Dictionary(); - LocalBuilder found; - if (!locals.TryGetValue(type, out found)) + locals = locals ?? new Dictionary(); + if (!locals.TryGetValue(type, out LocalBuilder found)) { found = il.DeclareLocal(type); locals.Add(type, found); @@ -2902,11 +3074,12 @@ static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary GetTypeDeserializerImpl( Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false ) { - var returnType = type.IsValueType ? typeof(object) : type; + var returnType = type.IsValueType() ? typeof(object) : type; var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, new[] { typeof(IDataReader) }, type, true); var il = dm.GetILGenerator(); il.DeclareLocal(typeof(int)); @@ -2929,14 +3102,13 @@ static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary structLocals = null; - if (type.IsValueType) + if (type.IsValueType()) { il.Emit(OpCodes.Ldloca_S, (byte)1); il.Emit(OpCodes.Initobj, type); @@ -2953,9 +3125,9 @@ static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary typeMap.GetConstructorParameter(specializedConstructor, n)) - : names.Select(n => typeMap.GetMember(n))).ToList(); + : names.Select(n => typeMap.GetMember(n))).ToList()); // stack is now [target] @@ -3058,12 +3230,12 @@ static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary for some T GetTempLocal(il, ref structLocals, memberType, true); // stack is now [target][target][null] } @@ -3168,7 +3340,7 @@ static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary double il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][data-typed-value] @@ -3315,7 +3487,7 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro } } - static MethodInfo GetOperator(Type from, Type to) + private static MethodInfo GetOperator(Type from, Type to) { if (to == null) return null; MethodInfo[] fromMethods, toMethods; @@ -3325,7 +3497,7 @@ static MethodInfo GetOperator(Type from, Type to) ?? ResolveOperator(toMethods, from, to, "op_Explicit"); } - static MethodInfo ResolveOperator(MethodInfo[] methods, Type from, Type to, string name) + private static MethodInfo ResolveOperator(MethodInfo[] methods, Type from, Type to, string name) { for (int i = 0; i < methods.Length; i++) { @@ -3358,6 +3530,7 @@ private static void LoadLocal(ILGenerator il, int index) break; } } + private static void StoreLocal(ILGenerator il, int index) { if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException(nameof(index)); @@ -3397,6 +3570,10 @@ private static void LoadLocalAddress(ILGenerator il, int index) /// /// Throws a data exception, only used internally /// + /// The exception to throw. + /// The index the exception occured at. + /// The reader the exception occured in. + /// The value that caused the exception. [Obsolete(ObsoleteInternalUsageOnly, false)] public static void ThrowDataException(Exception ex, int index, IDataReader reader, object value) { @@ -3470,25 +3647,28 @@ public static IEqualityComparer ConnectionStringComparer get { return connectionStringComparer; } set { connectionStringComparer = value ?? StringComparer.Ordinal; } } + private static IEqualityComparer connectionStringComparer = StringComparer.Ordinal; -#if !NETSTANDARD2_0 +#if !NETSTANDARD1_3 /// - /// Key used to indicate the type name associated with a DataTable + /// Key used to indicate the type name associated with a DataTable. /// private const string DataTableTypeNameKey = "dapper:TypeName"; /// - /// Used to pass a DataTable as a TableValuedParameter + /// Used to pass a DataTable as a . /// - public static ICustomQueryParameter AsTableValuedParameter(this DataTable table, string typeName = null) - { - return new TableValuedParameter(table, typeName); - } + /// The to create this parameter for. + /// The name of the type this parameter is for. + public static ICustomQueryParameter AsTableValuedParameter(this DataTable table, string typeName = null) => + new TableValuedParameter(table, typeName); /// - /// Associate a DataTable with a type name + /// Associate a DataTable with a type name. /// + /// The that does with the . + /// The name of the type this table is for. public static void SetTypeName(this DataTable table, string typeName) { if (table != null) @@ -3501,22 +3681,20 @@ public static void SetTypeName(this DataTable table, string typeName) } /// - /// Fetch the type name associated with a DataTable + /// Fetch the type name associated with a . /// - public static string GetTypeName(this DataTable table) - { - return table?.ExtendedProperties[DataTableTypeNameKey] as string; - } + /// The that has a type name associated with it. + public static string GetTypeName(this DataTable table) => + table?.ExtendedProperties[DataTableTypeNameKey] as string; +#endif /// - /// Used to pass a IEnumerable<SqlDataRecord> as a TableValuedParameter + /// Used to pass a IEnumerable<SqlDataRecord> as a TableValuedParameter. /// - public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string typeName = null) - { - return new SqlDataRecordListTVPParameter(list, typeName); - } - -#endif + /// The list of records to convert to TVPs. + /// The sql parameter type name. + public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string typeName = null) => + new SqlDataRecordListTVPParameter(list, typeName); // one per thread [ThreadStatic] @@ -3537,10 +3715,7 @@ private static string __ToStringRecycle(this StringBuilder obj) { if (obj == null) return ""; var s = obj.ToString(); - if(perThreadStringBuilderCache == null) - { - perThreadStringBuilderCache = obj; - } + perThreadStringBuilderCache = perThreadStringBuilderCache ?? obj; return s; } } diff --git a/src/ServiceStack.OrmLite/Dapper/TableValuedParameter.cs b/src/ServiceStack.OrmLite/Dapper/TableValuedParameter.cs index 3276ceac4..92e9afde9 100644 --- a/src/ServiceStack.OrmLite/Dapper/TableValuedParameter.cs +++ b/src/ServiceStack.OrmLite/Dapper/TableValuedParameter.cs @@ -2,30 +2,35 @@ using System.Data; using System.Reflection; -#if !NETSTANDARD2_0 +#if !NETSTANDARD1_3 namespace ServiceStack.OrmLite.Dapper { /// /// Used to pass a DataTable as a TableValuedParameter /// - sealed class TableValuedParameter : SqlMapper.ICustomQueryParameter + internal sealed class TableValuedParameter : SqlMapper.ICustomQueryParameter { private readonly DataTable table; private readonly string typeName; /// - /// Create a new instance of TableValuedParameter + /// Create a new instance of . /// - public TableValuedParameter(DataTable table) : this(table, null) { } + /// The to create this parameter for + public TableValuedParameter(DataTable table) : this(table, null) { /* run base */ } + /// - /// Create a new instance of TableValuedParameter + /// Create a new instance of . /// + /// The to create this parameter for. + /// The name of the type this parameter is for. public TableValuedParameter(DataTable table, string typeName) { this.table = table; this.typeName = typeName; } - static readonly Action setTypeName; + + private static readonly Action setTypeName; static TableValuedParameter() { var prop = typeof(System.Data.SqlClient.SqlParameter).GetProperty("TypeName", BindingFlags.Instance | BindingFlags.Public); @@ -35,6 +40,7 @@ static TableValuedParameter() Delegate.CreateDelegate(typeof(Action), prop.GetSetMethod()); } } + void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name) { var param = command.CreateParameter(); @@ -42,6 +48,7 @@ void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string nam Set(param, table, typeName); command.Parameters.Add(param); } + internal static void Set(IDbDataParameter parameter, DataTable table, string typeName) { #pragma warning disable 0618 @@ -51,14 +58,10 @@ internal static void Set(IDbDataParameter parameter, DataTable table, string typ { typeName = table.GetTypeName(); } - if (!string.IsNullOrEmpty(typeName)) + if (!string.IsNullOrEmpty(typeName) && (parameter is System.Data.SqlClient.SqlParameter sqlParam)) { - var sqlParam = parameter as System.Data.SqlClient.SqlParameter; - if (sqlParam != null) - { - setTypeName?.Invoke(sqlParam, typeName); - sqlParam.SqlDbType = SqlDbType.Structured; - } + setTypeName?.Invoke(sqlParam, typeName); + sqlParam.SqlDbType = SqlDbType.Structured; } } } diff --git a/src/ServiceStack.OrmLite/Dapper/TypeExtensions.cs b/src/ServiceStack.OrmLite/Dapper/TypeExtensions.cs index 424e44514..4bfc01c98 100644 --- a/src/ServiceStack.OrmLite/Dapper/TypeExtensions.cs +++ b/src/ServiceStack.OrmLite/Dapper/TypeExtensions.cs @@ -1,85 +1,58 @@ using System; using System.Reflection; using System.Collections.Generic; -using System.Linq; namespace ServiceStack.OrmLite.Dapper { internal static class TypeExtensions { - public static string Name(this Type type) - { -#if NETSTANDARD2_0 - return type.GetTypeInfo().Name; + public static string Name(this Type type) => +#if NETSTANDARD1_3 || NETCOREAPP1_0 + type.GetTypeInfo().Name; #else - return type.Name; + type.Name; #endif - } - public static bool IsValueType(this Type type) - { -#if NETSTANDARD2_0 - return type.GetTypeInfo().IsValueType; + public static bool IsValueType(this Type type) => +#if NETSTANDARD1_3 || NETCOREAPP1_0 + type.GetTypeInfo().IsValueType; #else - return type.IsValueType; + type.IsValueType; #endif - } - public static bool IsPrimitive(this Type type) - { -#if NETSTANDARD2_0 - return type.GetTypeInfo().IsPrimitive; + public static bool IsEnum(this Type type) => +#if NETSTANDARD1_3 || NETCOREAPP1_0 + type.GetTypeInfo().IsEnum; #else - return type.IsPrimitive; + type.IsEnum; #endif - } - public static bool IsEnum(this Type type) - { -#if NETSTANDARD2_0 - return type.GetTypeInfo().IsEnum; + public static bool IsGenericType(this Type type) => +#if NETSTANDARD1_3 || NETCOREAPP1_0 + type.GetTypeInfo().IsGenericType; #else - return type.IsEnum; + type.IsGenericType; #endif - } - public static bool IsGenericType(this Type type) - { -#if NETSTANDARD2_0 - return type.GetTypeInfo().IsGenericType; -#else - return type.IsGenericType; -#endif - } - public static bool IsInterface(this Type type) - { -#if NETSTANDARD2_0 - return type.GetTypeInfo().IsInterface; -#else - return type.IsInterface; -#endif - } - public static Type UnderlyingSystemType(this Type type) - { -#if NETSTANDARD2_0 - return type.GetTypeInfo().AsType(); + public static bool IsInterface(this Type type) => +#if NETSTANDARD1_3 || NETCOREAPP1_0 + type.GetTypeInfo().IsInterface; #else - return type.UnderlyingSystemType; + type.IsInterface; #endif - } -#if NETSTANDARD2_0 + +#if NETSTANDARD1_3 || NETCOREAPP1_0 public static IEnumerable GetCustomAttributes(this Type type, bool inherit) { - return type.GetTypeInfo().GetCustomAttributes(inherit).Cast(); + 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 (typeCodeLookup.TryGetValue(type, out TypeCode result)) return result; - if (type.IsEnum) + if (type.IsEnum()) { type = Enum.GetUnderlyingType(type); if (typeCodeLookup.TryGetValue(type, out result)) return result; @@ -87,71 +60,37 @@ public static TypeCode GetTypeCode(Type type) return TypeCode.Object; } - public static Type GetTypeFromTypeCode(this TypeCode typeCode) - { - Type result; - - if (typeFromTypeCodeLookup.TryGetValue(typeCode, out result)) - return result; - - return typeof(Object); - } - static readonly Dictionary typeCodeLookup = new Dictionary - { - {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 }, - }; - - static readonly Dictionary typeFromTypeCodeLookup = new Dictionary + private static readonly Dictionary typeCodeLookup = new Dictionary { - {TypeCode.Boolean, typeof(bool) }, - {TypeCode.Byte , typeof(byte) }, - {TypeCode.Char, typeof(char) }, - {TypeCode.DateTime, typeof(DateTime) }, - {TypeCode.Decimal, typeof(decimal) }, - {TypeCode.Double , typeof(double) }, - {TypeCode.Int16, typeof(short) }, - {TypeCode.Int32, typeof(int) }, - {TypeCode.Int64, typeof(long) }, - {TypeCode.Object, typeof(object) }, - {TypeCode.SByte, typeof(sbyte) }, - {TypeCode.Single, typeof(float) }, - {TypeCode.String, typeof(string) }, - {TypeCode.UInt16, typeof(ushort) }, - {TypeCode.UInt32, typeof(uint) }, - {TypeCode.UInt64, typeof(ulong) }, + [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); - } + public static TypeCode GetTypeCode(Type type) => Type.GetTypeCode(type); #endif + public static MethodInfo GetPublicInstanceMethod(this Type type, string name, Type[] types) { -#if NETSTANDARD2_0 +#if NETSTANDARD1_3 || NETCOREAPP1_0 var method = type.GetMethod(name, types); - return (method != null && method.IsPublic && !method.IsStatic) ? method : null; + return (method?.IsPublic == true && !method.IsStatic) ? method : null; #else return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public, null, types, null); #endif } - - } } diff --git a/src/ServiceStack.OrmLite/Dapper/UdtTypeHandler.cs b/src/ServiceStack.OrmLite/Dapper/UdtTypeHandler.cs index 585844e6b..f4f520ff9 100644 --- a/src/ServiceStack.OrmLite/Dapper/UdtTypeHandler.cs +++ b/src/ServiceStack.OrmLite/Dapper/UdtTypeHandler.cs @@ -3,9 +3,9 @@ namespace ServiceStack.OrmLite.Dapper { - partial class SqlMapper + public static partial class SqlMapper { -#if !NETSTANDARD2_0 +#if !NETSTANDARD1_3 && !NETSTANDARD2_0 /// /// A type handler for data-types that are supported by the underlying provider, but which need /// a well-known UdtTypeName to be specified @@ -14,13 +14,15 @@ public class UdtTypeHandler : ITypeHandler { private readonly string udtTypeName; /// - /// Creates a new instance of UdtTypeHandler with the specified UdtTypeName + /// Creates a new instance of UdtTypeHandler with the specified . /// + /// The user defined type name. 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; diff --git a/src/ServiceStack.OrmLite/Dapper/WrappedReader.cs b/src/ServiceStack.OrmLite/Dapper/WrappedReader.cs index 720296d7b..35e72853c 100644 --- a/src/ServiceStack.OrmLite/Dapper/WrappedReader.cs +++ b/src/ServiceStack.OrmLite/Dapper/WrappedReader.cs @@ -3,7 +3,7 @@ namespace ServiceStack.OrmLite.Dapper { - internal class WrappedReader : IDataReader, IWrappedDataReader + internal class WrappedReader : IWrappedDataReader { private IDataReader reader; private IDbCommand cmd; @@ -17,6 +17,7 @@ public IDataReader Reader return tmp; } } + IDbCommand IWrappedDataReader.Command { get @@ -26,35 +27,24 @@ IDbCommand IWrappedDataReader.Command return tmp; } } + public WrappedReader(IDbCommand cmd, IDataReader reader) { this.cmd = cmd; this.reader = reader; } - void IDataReader.Close() - { - reader?.Close(); - } + void IDataReader.Close() => reader?.Close(); int IDataReader.Depth => Reader.Depth; - DataTable IDataReader.GetSchemaTable() - { - return Reader.GetSchemaTable(); - } + DataTable IDataReader.GetSchemaTable() => Reader.GetSchemaTable(); bool IDataReader.IsClosed => reader?.IsClosed ?? true; - bool IDataReader.NextResult() - { - return Reader.NextResult(); - } + bool IDataReader.NextResult() => Reader.NextResult(); - bool IDataReader.Read() - { - return Reader.Read(); - } + bool IDataReader.Read() => Reader.Read(); int IDataReader.RecordsAffected => Reader.RecordsAffected; @@ -69,115 +59,51 @@ void IDisposable.Dispose() int IDataRecord.FieldCount => Reader.FieldCount; - bool IDataRecord.GetBoolean(int i) - { - return Reader.GetBoolean(i); - } + bool IDataRecord.GetBoolean(int i) => Reader.GetBoolean(i); - byte IDataRecord.GetByte(int i) - { - return Reader.GetByte(i); - } + byte IDataRecord.GetByte(int i) => Reader.GetByte(i); - long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) - { - return Reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); - } + long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) => + Reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); - char IDataRecord.GetChar(int i) - { - return Reader.GetChar(i); - } + char IDataRecord.GetChar(int i) => Reader.GetChar(i); - long IDataRecord.GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) - { - return Reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); - } + long IDataRecord.GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) => + Reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); - IDataReader IDataRecord.GetData(int i) - { - return Reader.GetData(i); - } + IDataReader IDataRecord.GetData(int i) => Reader.GetData(i); - string IDataRecord.GetDataTypeName(int i) - { - return Reader.GetDataTypeName(i); - } + string IDataRecord.GetDataTypeName(int i) => Reader.GetDataTypeName(i); - DateTime IDataRecord.GetDateTime(int i) - { - return Reader.GetDateTime(i); - } + DateTime IDataRecord.GetDateTime(int i) => Reader.GetDateTime(i); - decimal IDataRecord.GetDecimal(int i) - { - return Reader.GetDecimal(i); - } + decimal IDataRecord.GetDecimal(int i) => Reader.GetDecimal(i); - double IDataRecord.GetDouble(int i) - { - return Reader.GetDouble(i); - } + double IDataRecord.GetDouble(int i) => Reader.GetDouble(i); - Type IDataRecord.GetFieldType(int i) - { - return Reader.GetFieldType(i); - } + Type IDataRecord.GetFieldType(int i) => Reader.GetFieldType(i); - float IDataRecord.GetFloat(int i) - { - return Reader.GetFloat(i); - } + float IDataRecord.GetFloat(int i) => Reader.GetFloat(i); - Guid IDataRecord.GetGuid(int i) - { - return Reader.GetGuid(i); - } + Guid IDataRecord.GetGuid(int i) => Reader.GetGuid(i); - short IDataRecord.GetInt16(int i) - { - return Reader.GetInt16(i); - } + short IDataRecord.GetInt16(int i) => Reader.GetInt16(i); - int IDataRecord.GetInt32(int i) - { - return Reader.GetInt32(i); - } + int IDataRecord.GetInt32(int i) => Reader.GetInt32(i); - long IDataRecord.GetInt64(int i) - { - return Reader.GetInt64(i); - } + long IDataRecord.GetInt64(int i) => Reader.GetInt64(i); - string IDataRecord.GetName(int i) - { - return Reader.GetName(i); - } + string IDataRecord.GetName(int i) => Reader.GetName(i); - int IDataRecord.GetOrdinal(string name) - { - return Reader.GetOrdinal(name); - } + int IDataRecord.GetOrdinal(string name) => Reader.GetOrdinal(name); - string IDataRecord.GetString(int i) - { - return Reader.GetString(i); - } + string IDataRecord.GetString(int i) => Reader.GetString(i); - object IDataRecord.GetValue(int i) - { - return Reader.GetValue(i); - } + object IDataRecord.GetValue(int i) => Reader.GetValue(i); - int IDataRecord.GetValues(object[] values) - { - return Reader.GetValues(values); - } + int IDataRecord.GetValues(object[] values) => Reader.GetValues(values); - bool IDataRecord.IsDBNull(int i) - { - return Reader.IsDBNull(i); - } + bool IDataRecord.IsDBNull(int i) => Reader.IsDBNull(i); object IDataRecord.this[string name] => Reader[name]; diff --git a/src/ServiceStack.OrmLite/Dapper/XmlHandlers.cs b/src/ServiceStack.OrmLite/Dapper/XmlHandlers.cs index 3bbce6417..e40f03eca 100644 --- a/src/ServiceStack.OrmLite/Dapper/XmlHandlers.cs +++ b/src/ServiceStack.OrmLite/Dapper/XmlHandlers.cs @@ -1,6 +1,6 @@ using System.Data; using System.Xml; -//using System.Xml.Linq; +using System.Xml.Linq; namespace ServiceStack.OrmLite.Dapper { @@ -12,6 +12,7 @@ public override void SetValue(IDbDataParameter parameter, T value) parameter.DbType = DbType.Xml; } } + internal sealed class XmlDocumentHandler : XmlTypeHandler { protected override XmlDocument Parse(string xml) @@ -20,16 +21,19 @@ protected override XmlDocument Parse(string xml) doc.LoadXml(xml); return doc; } + protected override string Format(XmlDocument xml) => xml.OuterXml; } - //internal sealed class XDocumentHandler : XmlTypeHandler - //{ - // protected override XDocument Parse(string xml) => XDocument.Parse(xml); - // protected override string Format(XDocument xml) => xml.ToString(); - //} - //internal sealed class XElementHandler : XmlTypeHandler - //{ - // protected override XElement Parse(string xml) => XElement.Parse(xml); - // protected override string Format(XElement xml) => xml.ToString(); - //} + + internal sealed class XDocumentHandler : XmlTypeHandler + { + protected override XDocument Parse(string xml) => XDocument.Parse(xml); + protected override string Format(XDocument xml) => xml.ToString(); + } + + internal sealed class XElementHandler : XmlTypeHandler + { + protected override XElement Parse(string xml) => XElement.Parse(xml); + protected override string Format(XElement xml) => xml.ToString(); + } } diff --git a/src/ServiceStack.OrmLite/OrmLiteSPStatement.cs b/src/ServiceStack.OrmLite/OrmLiteSPStatement.cs index 22b90c3d4..de1a5d5b2 100644 --- a/src/ServiceStack.OrmLite/OrmLiteSPStatement.cs +++ b/src/ServiceStack.OrmLite/OrmLiteSPStatement.cs @@ -46,7 +46,7 @@ public OrmLiteSPStatement(IDbConnection db, IDbCommand dbCmd) public List ConvertToList() { - if (typeof(T).IsPrimitive() || typeof(T) == typeof(string)) + if (typeof(T).IsPrimitive || typeof(T) == typeof(string)) throw new Exception("Type " + typeof(T).Name + " is a primitive type. Use ConvertScalarToList function."); IDataReader reader = null; @@ -63,7 +63,7 @@ public List ConvertToList() public List ConvertToScalarList() { - if (!((typeof(T).IsPrimitive()) || typeof(T).IsValueType || (typeof(T) == typeof(string)) || (typeof(T) == typeof(String)))) + if (!((typeof(T).IsPrimitive) || typeof(T).IsValueType || (typeof(T) == typeof(string)) || (typeof(T) == typeof(String)))) throw new Exception("Type " + typeof(T).Name + " is a non primitive type. Use ConvertToList function."); IDataReader reader = null; @@ -80,7 +80,7 @@ public List ConvertToScalarList() public T ConvertTo() { - if (typeof(T).IsPrimitive() || typeof(T) == typeof(string)) + if (typeof(T).IsPrimitive || typeof(T) == typeof(string)) throw new Exception("Type " + typeof(T).Name + " is a primitive type. Use ConvertScalarTo function."); IDataReader reader = null; @@ -97,7 +97,7 @@ public T ConvertTo() public T ConvertToScalar() { - if (!((typeof(T).IsPrimitive()) || typeof(T).IsValueType || (typeof(T) == typeof(string)) || (typeof(T) == typeof(String)))) + if (!((typeof(T).IsPrimitive) || typeof(T).IsValueType || (typeof(T) == typeof(string)) || (typeof(T) == typeof(String)))) throw new Exception("Type " + typeof(T).Name + " is a non primitive type. Use ConvertTo function."); IDataReader reader = null; @@ -114,7 +114,7 @@ public T ConvertToScalar() public List ConvertFirstColumnToList() { - if (!((typeof(T).IsPrimitive()) || typeof(T).IsValueType || (typeof(T) == typeof(string)) || (typeof(T) == typeof(String)))) + if (!((typeof(T).IsPrimitive) || typeof(T).IsValueType || (typeof(T) == typeof(string)) || (typeof(T) == typeof(String)))) throw new Exception("Type " + typeof(T).Name + " is a non primitive type. Only primitive type can be used."); IDataReader reader = null; @@ -131,7 +131,7 @@ public List ConvertFirstColumnToList() public HashSet ConvertFirstColumnToListDistinct() { - if (!((typeof(T).IsPrimitive()) || typeof(T).IsValueType || (typeof(T) == typeof(string)) || (typeof(T) == typeof(String)))) + if (!(typeof(T).IsPrimitive) || typeof(T).IsValueType || (typeof(T) == typeof(string)) || (typeof(T) == typeof(String))) throw new Exception("Type " + typeof(T).Name + " is a non primitive type. Only primitive type can be used."); IDataReader reader = null; diff --git a/src/ServiceStack.OrmLite/OrmLiteUtils.cs b/src/ServiceStack.OrmLite/OrmLiteUtils.cs index 1dbe8a5e6..d10f1c6a7 100644 --- a/src/ServiceStack.OrmLite/OrmLiteUtils.cs +++ b/src/ServiceStack.OrmLite/OrmLiteUtils.cs @@ -781,7 +781,7 @@ private static int TryGuessColumnIndex(string fieldName, Dictionary public static bool IsRefType(this Type fieldType) { - return (!fieldType.UnderlyingSystemType().IsValueType + return (!fieldType.UnderlyingSystemType.IsValueType || JsConfig.TreatValueAsRefTypes.Contains(fieldType.IsGenericType ? fieldType.GetGenericTypeDefinition() : fieldType))