diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index c93e39a4..5b39910a 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -40,7 +40,7 @@ namespace Dapper.Contrib.Extensions if (!type.IsInterface) return (await connection.QueryAsync(sql, dynParams, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault(); - if (!((await connection.QueryAsync(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() is IDictionary res)) + if ((await connection.QueryAsync(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() is not IDictionary res) { return null; } diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index eb05d190..940bec66 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -187,7 +187,7 @@ namespace Dapper.Contrib.Extensions if (type.IsInterface) { - if (!(connection.Query(sql, dynParams).FirstOrDefault() is IDictionary res)) + if (connection.Query(sql, dynParams).FirstOrDefault() is not IDictionary res) { return null; } diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 25f87ccf..17a555d5 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -6,6 +6,7 @@ A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver net461;netstandard2.0;net5.0 + $(NoWarn);MA0006;MA0007;MA0009;MA0003;MA0023;MA0051;MA0071;MA0048;MA0018;MA0012 @@ -17,5 +18,7 @@ + + diff --git a/Dapper/DataTableHandler.cs b/Dapper/DataTableHandler.cs index df4dc07a..359e3240 100644 --- a/Dapper/DataTableHandler.cs +++ b/Dapper/DataTableHandler.cs @@ -6,7 +6,7 @@ namespace Dapper { public object Parse(Type destinationType, object value) { - throw new NotImplementedException(); + throw new NotSupportedException(); } public void SetValue(IDbDataParameter parameter, object value) diff --git a/Dapper/DefaultTypeMap.cs b/Dapper/DefaultTypeMap.cs index b278250c..7e8c3af5 100644 --- a/Dapper/DefaultTypeMap.cs +++ b/Dapper/DefaultTypeMap.cs @@ -177,6 +177,8 @@ namespace Dapper /// /// The settable properties for this typemap /// +#pragma warning disable MA0016 // Prefer return collection abstraction instead of implementation public List Properties { get; } +#pragma warning restore MA0016 // Prefer return collection abstraction instead of implementation } } diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index dc4f5a36..2dffb67a 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -15,7 +15,7 @@ namespace Dapper { internal const DbType EnumerableMultiParameter = (DbType)(-1); private static readonly Dictionary> paramReaderCache = new Dictionary>(); - private readonly Dictionary parameters = new Dictionary(); + private readonly Dictionary parameters = new Dictionary(StringComparer.Ordinal); private List templates; object SqlMapper.IParameterLookup.this[string name] => @@ -162,7 +162,7 @@ namespace Dapper /// Information about the query protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) { - var literals = SqlMapper.GetLiteralTokens(identity.sql); + var literals = SqlMapper.GetLiteralTokens(identity._sql); if (templates != null) { @@ -316,7 +316,7 @@ namespace Dapper { if (default(T) != null) { - throw new ApplicationException("Attempting to cast a DBNull to a non nullable type! Note that out/return parameters will not have updated values until the data stream completes (after the 'foreach' for Query(..., buffered: false), or after the GridReader has been disposed for QueryMultiple)"); + throw new InvalidOperationException("Attempting to cast a DBNull to a non nullable type! Note that out/return parameters will not have updated values until the data stream completes (after the 'foreach' for Query(..., buffered: false), or after the GridReader has been disposed for QueryMultiple)"); } return default; } diff --git a/Dapper/Extensions.cs b/Dapper/Extensions.cs index 6bd91529..02c9f9bf 100644 --- a/Dapper/Extensions.cs +++ b/Dapper/Extensions.cs @@ -15,7 +15,11 @@ namespace Dapper if (task is null) throw new ArgumentNullException(nameof(task)); if (task.Status == TaskStatus.RanToCompletion) + { +#pragma warning disable MA0042 // use await instead of Result; nope, we've already checked return Task.FromResult((TTo)task.Result); +#pragma warning restore MA0042 + } var source = new TaskCompletionSource(); task.ContinueWith(OnTaskCompleted, state: source, TaskContinuationOptions.ExecuteSynchronously); diff --git a/Dapper/FeatureSupport.cs b/Dapper/FeatureSupport.cs index 089e846c..f0d3589b 100644 --- a/Dapper/FeatureSupport.cs +++ b/Dapper/FeatureSupport.cs @@ -6,7 +6,7 @@ namespace Dapper /// /// Handles variances in features per DBMS /// - internal class FeatureSupport + internal sealed class FeatureSupport { private static readonly FeatureSupport Default = new FeatureSupport(false), diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 3e648742..077719bc 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -946,7 +946,9 @@ namespace Dapper { if (types.Length < 1) { +#pragma warning disable MA0015 // missing name throw new ArgumentException("you must provide at least one type to deserialize"); +#pragma warning restore MA0015 // missing name } object param = command.Parameters; diff --git a/Dapper/SqlMapper.CacheInfo.cs b/Dapper/SqlMapper.CacheInfo.cs index 409ea341..90223178 100644 --- a/Dapper/SqlMapper.CacheInfo.cs +++ b/Dapper/SqlMapper.CacheInfo.cs @@ -6,7 +6,7 @@ namespace Dapper { public static partial class SqlMapper { - private class CacheInfo + private sealed class CacheInfo { public DeserializerState Deserializer { get; set; } public Func[] OtherDeserializers { get; set; } diff --git a/Dapper/SqlMapper.DontMap.cs b/Dapper/SqlMapper.DontMap.cs index a97c5811..765c7b93 100644 --- a/Dapper/SqlMapper.DontMap.cs +++ b/Dapper/SqlMapper.DontMap.cs @@ -5,6 +5,6 @@ /// /// Dummy type for excluding from multi-map /// - private class DontMap { /* hiding constructor */ } + private sealed class DontMap { /* hiding constructor */ } } } diff --git a/Dapper/SqlMapper.GridReader.Async.cs b/Dapper/SqlMapper.GridReader.Async.cs index 139b8b27..1f7cae21 100644 --- a/Dapper/SqlMapper.GridReader.Async.cs +++ b/Dapper/SqlMapper.GridReader.Async.cs @@ -139,7 +139,7 @@ namespace Dapper private async Task NextResultAsync() { - if (await ((DbDataReader)reader).NextResultAsync(cancel).ConfigureAwait(false)) + if (await ((DbDataReader)_reader).NextResultAsync(cancel).ConfigureAwait(false)) { // readCount++; gridIndex++; @@ -149,29 +149,29 @@ namespace Dapper { // happy path; close the reader cleanly - no // need for "Cancel" etc - reader.Dispose(); - reader = null; - callbacks?.OnCompleted(); + _reader.Dispose(); + _reader = null; + _callbacks?.OnCompleted(); Dispose(); } } private Task> ReadAsyncImpl(Type type, bool buffered) { - if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); + if (_reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); - var typedIdentity = identity.ForGrid(type, gridIndex); - CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); + var typedIdentity = _identity.ForGrid(type, gridIndex); + CacheInfo cache = GetCacheInfo(typedIdentity, null, _addToCache); var deserializer = cache.Deserializer; - int hash = GetColumnHash(reader); + int hash = GetColumnHash(_reader); if (deserializer.Func == null || deserializer.Hash != hash) { - deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); + deserializer = new DeserializerState(hash, GetDeserializer(type, _reader, 0, -1, false)); cache.Deserializer = deserializer; } IsConsumed = true; - if (buffered && reader is DbDataReader) + if (buffered && _reader is DbDataReader) { return ReadBufferedAsync(gridIndex, deserializer.Func); } @@ -185,7 +185,7 @@ namespace Dapper private Task ReadRowAsyncImpl(Type type, Row row) { - if (reader is DbDataReader dbReader) 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)); @@ -200,8 +200,8 @@ namespace Dapper T result = default; if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) { - var typedIdentity = identity.ForGrid(type, gridIndex); - CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); + var typedIdentity = _identity.ForGrid(type, gridIndex); + CacheInfo cache = GetCacheInfo(typedIdentity, null, _addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); @@ -226,7 +226,7 @@ namespace Dapper { try { - var reader = (DbDataReader)this.reader; + var reader = (DbDataReader)_reader; var buffer = new List(); while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false)) { diff --git a/Dapper/SqlMapper.GridReader.cs b/Dapper/SqlMapper.GridReader.cs index f5882200..49fad805 100644 --- a/Dapper/SqlMapper.GridReader.cs +++ b/Dapper/SqlMapper.GridReader.cs @@ -12,17 +12,17 @@ namespace Dapper /// public partial class GridReader : IDisposable { - private IDataReader reader; - private readonly Identity identity; - private readonly bool addToCache; + private IDataReader _reader; + private readonly Identity _identity; + private readonly bool _addToCache; internal GridReader(IDbCommand command, IDataReader reader, Identity identity, IParameterCallbacks callbacks, bool addToCache) { Command = command; - this.reader = reader; - this.identity = identity; - this.callbacks = callbacks; - this.addToCache = addToCache; + _reader = reader; + _identity = identity; + _callbacks = callbacks; + _addToCache = addToCache; } /// @@ -145,16 +145,16 @@ namespace Dapper private IEnumerable ReadImpl(Type type, bool buffered) { - if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); + if (_reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); - var typedIdentity = identity.ForGrid(type, gridIndex); - CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); + var typedIdentity = _identity.ForGrid(type, gridIndex); + CacheInfo cache = GetCacheInfo(typedIdentity, null, _addToCache); var deserializer = cache.Deserializer; - int hash = GetColumnHash(reader); + int hash = GetColumnHash(_reader); if (deserializer.Func == null || deserializer.Hash != hash) { - deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); + deserializer = new DeserializerState(hash, GetDeserializer(type, _reader, 0, -1, false)); cache.Deserializer = deserializer; } IsConsumed = true; @@ -164,24 +164,24 @@ namespace Dapper private T ReadRow(Type type, Row row) { - if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); + if (_reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); IsConsumed = true; T result = default; - if (reader.Read() && reader.FieldCount != 0) + if (_reader.Read() && _reader.FieldCount != 0) { - var typedIdentity = identity.ForGrid(type, gridIndex); - CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); + var typedIdentity = _identity.ForGrid(type, gridIndex); + CacheInfo cache = GetCacheInfo(typedIdentity, null, _addToCache); var deserializer = cache.Deserializer; - int hash = GetColumnHash(reader); + int hash = GetColumnHash(_reader); if (deserializer.Func == null || deserializer.Hash != hash) { - deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); + deserializer = new DeserializerState(hash, GetDeserializer(type, _reader, 0, -1, false)); cache.Deserializer = deserializer; } - object val = deserializer.Func(reader); + object val = deserializer.Func(_reader); if (val == null || val is T) { result = (T)val; @@ -191,8 +191,8 @@ namespace Dapper 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()) { /* ignore subsequent rows */ } + if ((row & Row.Single) != 0 && _reader.Read()) ThrowMultipleRows(row); + while (_reader.Read()) { /* ignore subsequent rows */ } } else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { @@ -204,13 +204,13 @@ namespace Dapper private IEnumerable MultiReadInternal(Delegate func, string splitOn) { - var identity = this.identity.ForGrid(typeof(TReturn), gridIndex); + var identity = _identity.ForGrid(typeof(TReturn), gridIndex); IsConsumed = true; try { - foreach (var r in MultiMapImpl(null, default, func, splitOn, reader, identity, false)) + foreach (var r in MultiMapImpl(null, default, func, splitOn, _reader, identity, false)) { yield return r; } @@ -223,10 +223,10 @@ namespace Dapper private IEnumerable MultiReadInternal(Type[] types, Func map, string splitOn) { - var identity = this.identity.ForGrid(typeof(TReturn), types, gridIndex); + var identity = _identity.ForGrid(typeof(TReturn), types, gridIndex); try { - foreach (var r in MultiMapImpl(null, default, types, map, splitOn, reader, identity, false)) + foreach (var r in MultiMapImpl(null, default, types, map, splitOn, _reader, identity, false)) { yield return r; } @@ -361,9 +361,9 @@ namespace Dapper try { var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; - while (index == gridIndex && reader.Read()) + while (index == gridIndex && _reader.Read()) { - object val = deserializer(reader); + object val = deserializer(_reader); if (val == null || val is T) { yield return (T)val; @@ -384,7 +384,7 @@ namespace Dapper } private int gridIndex; //, readCount; - private readonly IParameterCallbacks callbacks; + private readonly IParameterCallbacks _callbacks; /// /// Has the underlying reader been consumed? @@ -398,7 +398,7 @@ namespace Dapper private void NextResult() { - if (reader.NextResult()) + if (_reader.NextResult()) { // readCount++; gridIndex++; @@ -408,9 +408,9 @@ namespace Dapper { // happy path; close the reader cleanly - no // need for "Cancel" etc - reader.Dispose(); - reader = null; - callbacks?.OnCompleted(); + _reader.Dispose(); + _reader = null; + _callbacks?.OnCompleted(); Dispose(); } } @@ -420,11 +420,11 @@ namespace Dapper /// public void Dispose() { - if (reader != null) + if (_reader != null) { - if (!reader.IsClosed) Command?.Cancel(); - reader.Dispose(); - reader = null; + if (!_reader.IsClosed) Command?.Cancel(); + _reader.Dispose(); + _reader = null; } if (Command != null) { diff --git a/Dapper/SqlMapper.Identity.cs b/Dapper/SqlMapper.Identity.cs index acaf2989..f6f310e8 100644 --- a/Dapper/SqlMapper.Identity.cs +++ b/Dapper/SqlMapper.Identity.cs @@ -93,15 +93,15 @@ namespace Dapper internal virtual Type GetType(int index) => throw new IndexOutOfRangeException(nameof(index)); internal Identity ForGrid(Type primaryType, int gridIndex) => - new Identity(sql, commandType, connectionString, primaryType, parametersType, gridIndex); + new Identity(_sql, _commandType, _connectionString, primaryType, _parametersType, gridIndex); internal Identity ForGrid(Type primaryType, int gridIndex) => - new Identity(sql, commandType, connectionString, primaryType, parametersType, 0, gridIndex); + new Identity(_sql, _commandType, _connectionString, primaryType, _parametersType, 0, gridIndex); internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) => (otherTypes == null || otherTypes.Length == 0) - ? new Identity(sql, commandType, connectionString, primaryType, parametersType, 0, gridIndex) - : new IdentityWithTypes(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); + ? new Identity(_sql, _commandType, _connectionString, primaryType, _parametersType, 0, gridIndex) + : new IdentityWithTypes(_sql, _commandType, _connectionString, primaryType, _parametersType, otherTypes, gridIndex); /// /// Create an identity for use with DynamicParameters, internal use only. @@ -109,29 +109,31 @@ namespace Dapper /// The parameters type to create an for. /// public Identity ForDynamicParameters(Type type) => - new Identity(sql, commandType, connectionString, this.type, type, 0, -1); + new Identity(_sql, _commandType, _connectionString, _type, type, 0, -1); internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType) : this(sql, commandType, connection.ConnectionString, type, parametersType, 0, 0) { /* base call */ } private protected Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, int otherTypesHash, int gridIndex) { - this.sql = sql; - this.commandType = commandType; - this.connectionString = connectionString; - this.type = type; - this.parametersType = parametersType; - this.gridIndex = gridIndex; + _sql = sql; + _commandType = commandType; + _connectionString = connectionString; + _type = type; + _parametersType = parametersType; + _gridIndex = gridIndex; unchecked { - hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this - hashCode = (hashCode * 23) + commandType.GetHashCode(); - hashCode = (hashCode * 23) + gridIndex.GetHashCode(); - hashCode = (hashCode * 23) + (sql?.GetHashCode() ?? 0); - hashCode = (hashCode * 23) + (type?.GetHashCode() ?? 0); - hashCode = (hashCode * 23) + otherTypesHash; - hashCode = (hashCode * 23) + (connectionString == null ? 0 : connectionStringComparer.GetHashCode(connectionString)); - hashCode = (hashCode * 23) + (parametersType?.GetHashCode() ?? 0); + _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(); +#pragma warning disable MA0021 // use specific StringComparer to compute hash codes + _hashCode = (_hashCode * 23) + (sql?.GetHashCode() ?? 0); +#pragma warning restore MA0021 + _hashCode = (_hashCode * 23) + (type?.GetHashCode() ?? 0); + _hashCode = (_hashCode * 23) + otherTypesHash; + _hashCode = (_hashCode * 23) + (connectionString == null ? 0 : connectionStringComparer.GetHashCode(connectionString)); + _hashCode = (_hashCode * 23) + (parametersType?.GetHashCode() ?? 0); } } @@ -144,48 +146,48 @@ namespace Dapper /// /// The raw SQL command. /// - public readonly string sql; + public readonly string _sql; /// /// The SQL command type. /// - public readonly CommandType? commandType; + public readonly CommandType? _commandType; /// /// The hash code of this Identity. /// - public readonly int hashCode; + public readonly int _hashCode; /// /// The grid index (position in the reader) of this Identity. /// - public readonly int gridIndex; + public readonly int _gridIndex; /// /// This of this Identity. /// - public readonly Type type; + public readonly Type _type; /// /// The connection string for this Identity. /// - public readonly string connectionString; + public readonly string _connectionString; /// /// The type of the parameters object for this Identity. /// - public readonly Type parametersType; + public readonly Type _parametersType; /// /// Gets the hash code for this identity. /// /// - public override int GetHashCode() => hashCode; + public override int GetHashCode() => _hashCode; /// /// See object.ToString() /// - public override string ToString() => sql; + public override string ToString() => _sql; /// /// Compare 2 Identity objects @@ -198,12 +200,12 @@ namespace Dapper if (other is null) return false; int typeCount; - return gridIndex == other.gridIndex - && type == other.type - && sql == other.sql - && commandType == other.commandType - && connectionStringComparer.Equals(connectionString, other.connectionString) - && parametersType == other.parametersType + return _gridIndex == other._gridIndex + && _type == other._type + && _sql == other._sql + && _commandType == other._commandType + && connectionStringComparer.Equals(_connectionString, other._connectionString) + && _parametersType == other._parametersType && (typeCount = TypeCount) == other.TypeCount && (typeCount == 0 || TypesEqual(this, other, typeCount)); } diff --git a/Dapper/SqlMapper.Link.cs b/Dapper/SqlMapper.Link.cs index 277f2a9e..498491dc 100644 --- a/Dapper/SqlMapper.Link.cs +++ b/Dapper/SqlMapper.Link.cs @@ -11,7 +11,7 @@ namespace Dapper /// /// The type to cache. /// The value type of the cache. - internal class Link where TKey : class + internal sealed class Link where TKey : class { public static bool TryGet(Link link, TKey key, out TValue value) { diff --git a/Dapper/SqlMapper.TypeDeserializerCache.cs b/Dapper/SqlMapper.TypeDeserializerCache.cs index 088f464c..aa7fc316 100644 --- a/Dapper/SqlMapper.TypeDeserializerCache.cs +++ b/Dapper/SqlMapper.TypeDeserializerCache.cs @@ -8,7 +8,7 @@ namespace Dapper { public static partial class SqlMapper { - private class TypeDeserializerCache + private sealed class TypeDeserializerCache { private TypeDeserializerCache(Type type) { diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index b468d7b3..077f0048 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -27,7 +27,7 @@ namespace Dapper /// public static partial class SqlMapper { - private class PropertyInfoByNameComparer : IComparer + private sealed class PropertyInfoByNameComparer : IComparer { public int Compare(PropertyInfo x, PropertyInfo y) => string.CompareOrdinal(x.Name, y.Name); } @@ -112,7 +112,7 @@ namespace Dapper { foreach (var entry in _queryCache) { - if (entry.Key.type == type) + if (entry.Key._type == type) _queryCache.TryRemove(entry.Key, out CacheInfo _); } TypeDeserializerCache.Purge(type); @@ -134,7 +134,7 @@ namespace 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())); + var data = _queryCache.Select(pair => Tuple.Create(pair.Key._connectionString, pair.Key._sql, pair.Value.GetHitCount())); return (ignoreHitCountAbove < int.MaxValue) ? data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove) : data; @@ -149,13 +149,13 @@ namespace Dapper var counts = new Dictionary(); foreach (var key in _queryCache.Keys) { - if (!counts.TryGetValue(key.hashCode, out int count)) + if (!counts.TryGetValue(key._hashCode, out int count)) { - counts.Add(key.hashCode, 1); + counts.Add(key._hashCode, 1); } else { - counts[key.hashCode] = count + 1; + counts[key._hashCode] = count + 1; } } return from pair in counts @@ -163,11 +163,11 @@ namespace Dapper select Tuple.Create(pair.Key, pair.Value); } - private static Dictionary typeMap; + private static Dictionary s_typeMap; static SqlMapper() { - typeMap = new Dictionary(37) + s_typeMap = new Dictionary(37) { [typeof(byte)] = DbType.Byte, [typeof(sbyte)] = DbType.SByte, @@ -232,11 +232,11 @@ namespace Dapper public static void AddTypeMap(Type type, DbType dbType) { // use clone, mutate, replace to avoid threading issues - var snapshot = typeMap; + var snapshot = s_typeMap; if (snapshot.TryGetValue(type, out DbType oldValue) && oldValue == dbType) return; // nothing to do - typeMap = new Dictionary(snapshot) { [type] = dbType }; + s_typeMap = new Dictionary(snapshot) { [type] = dbType }; } /// @@ -246,14 +246,14 @@ namespace Dapper public static void RemoveTypeMap(Type type) { // use clone, mutate, replace to avoid threading issues - var snapshot = typeMap; + var snapshot = s_typeMap; if (!snapshot.ContainsKey(type)) return; // nothing to do var newCopy = new Dictionary(snapshot); newCopy.Remove(type); - typeMap = newCopy; + s_typeMap = newCopy; } /// @@ -358,11 +358,11 @@ namespace Dapper handler = null; var nullUnderlyingType = Nullable.GetUnderlyingType(type); if (nullUnderlyingType != null) type = nullUnderlyingType; - if (type.IsEnum && !typeMap.ContainsKey(type)) + if (type.IsEnum && !s_typeMap.ContainsKey(type)) { type = Enum.GetUnderlyingType(type); } - if (typeMap.TryGetValue(type, out DbType dbType)) + if (s_typeMap.TryGetValue(type, out DbType dbType)) { return dbType; } @@ -424,7 +424,9 @@ namespace Dapper /// /// The type of element in the list. /// The enumerable to return as a list. +#pragma warning disable MA0016 // Prefer return collection abstraction instead of implementation public static List AsList(this IEnumerable source) => +#pragma warning restore MA0016 // Prefer return collection abstraction instead of implementation (source == null || source is List) ? (List)source : source.ToList(); /// @@ -1150,7 +1152,9 @@ namespace Dapper { _ = row switch { // get the standard exception from the runtime +#pragma warning disable MA0098 // use [0] instead of First; nope: I want the error type/message! Row.First => ErrZeroRows.First(), +#pragma warning restore MA0098 // use [0] instead of First Row.Single => ErrZeroRows.Single(), _ => throw new InvalidOperationException(), }; @@ -1498,63 +1502,70 @@ namespace Dapper { if (types.Length < 1) { +#pragma warning disable MA0015 // missing name throw new ArgumentException("you must provide at least one type to deserialize"); +#pragma warning restore MA0015 // missing name } object param = command.Parameters; identity ??= new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); - IDbCommand ownedCommand = null; - IDataReader ownedReader = null; + return Impl(cnn, command, types, map, splitOn, reader, identity, finalize, cinfo); - bool wasClosed = cnn?.State == ConnectionState.Closed; - try + static IEnumerable Impl(IDbConnection cnn, CommandDefinition command, Type[] types, Func map, string splitOn, IDataReader reader, Identity identity, bool finalize, CacheInfo cinfo) { - if (reader == null) - { - ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader); - if (wasClosed) cnn.Open(); - ownedReader = ExecuteReaderWithFlagsFallback(ownedCommand, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); - reader = ownedReader; - } - DeserializerState deserializer; - Func[] otherDeserializers; + IDbCommand ownedCommand = null; + IDataReader ownedReader = null; - int hash = GetColumnHash(reader); - if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash) - { - var deserializers = GenerateDeserializers(identity, splitOn, reader); - deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); - otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray(); - SetQueryCache(identity, cinfo); - } - - Func mapIt = GenerateMapper(types.Length, deserializer.Func, otherDeserializers, map); - - if (mapIt != null) - { - while (reader.Read()) - { - yield return mapIt(reader); - } - if (finalize) - { - while (reader.NextResult()) { /* ignore subsequent result sets */ } - command.OnCompleted(); - } - } - } - finally - { + bool wasClosed = cnn?.State == ConnectionState.Closed; try { - ownedReader?.Dispose(); + if (reader == null) + { + ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader); + if (wasClosed) cnn.Open(); + ownedReader = ExecuteReaderWithFlagsFallback(ownedCommand, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); + reader = ownedReader; + } + DeserializerState deserializer; + Func[] otherDeserializers; + + int hash = GetColumnHash(reader); + if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash) + { + var deserializers = GenerateDeserializers(identity, splitOn, reader); + deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); + otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray(); + SetQueryCache(identity, cinfo); + } + + Func mapIt = GenerateMapper(types.Length, deserializer.Func, otherDeserializers, map); + + if (mapIt != null) + { + while (reader.Read()) + { + yield return mapIt(reader); + } + if (finalize) + { + while (reader.NextResult()) { /* ignore subsequent result sets */ } + command.OnCompleted(); + } + } } finally { - ownedCommand?.Dispose(); - if (wasClosed) cnn.Close(); + try + { + ownedReader?.Dispose(); + } + finally + { + ownedCommand?.Dispose(); + if (wasClosed) cnn.Close(); + } } } } @@ -1706,7 +1717,7 @@ namespace Dapper throw new InvalidOperationException("An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context"); } info = new CacheInfo(); - if (identity.parametersType != null) + if (identity._parametersType != null) { Action reader; if (exampleParameters is IDynamicParameters) @@ -1723,10 +1734,10 @@ namespace Dapper } else { - var literals = GetLiteralTokens(identity.sql); + 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) => @@ -1795,7 +1806,7 @@ namespace Dapper return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing); } Type underlyingType = null; - if (!(typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary + if (!(s_typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary || (type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum))) { if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) @@ -1819,9 +1830,9 @@ namespace Dapper catch { /* don't throw when trying to throw */ } if (hasFields) { -#pragma warning disable CA2208 // Instantiate argument exceptions correctly +#pragma warning disable CA2208, MA0015 // Instantiate argument exceptions correctly return new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); -#pragma warning restore CA2208 // Instantiate argument exceptions correctly +#pragma warning restore CA2208, MA0015 // Instantiate argument exceptions correctly } else { @@ -2004,7 +2015,7 @@ namespace Dapper var count = 0; bool isString = value is IEnumerable; bool isDbString = value is IEnumerable; - DbType dbType = 0; + DbType dbType = default; int splitAt = SqlMapper.Settings.InListStringSplitCount; bool viaSplit = splitAt >= 0 @@ -2026,7 +2037,7 @@ namespace Dapper dbType = LookupDbType(item.GetType(), "", true, out ITypeHandler handler); } } - var nextName = namePrefix + count.ToString(); + var nextName = namePrefix + count.ToString(CultureInfo.InvariantCulture); if (isDbString && item is DbString) { var str = item as DbString; @@ -2063,7 +2074,7 @@ namespace Dapper { count++; var padParam = command.CreateParameter(); - padParam.ParameterName = namePrefix + count.ToString(); + padParam.ParameterName = namePrefix + count.ToString(CultureInfo.InvariantCulture); if (isString) padParam.Size = DbString.DefaultLength; padParam.DbType = dbType; padParam.Value = lastValue; @@ -2149,7 +2160,7 @@ namespace Dapper private static bool TryStringSplit(ref IEnumerable list, int splitAt, string namePrefix, IDbCommand command, string colType, bool byPosition, Action append) { - if (!(list is ICollection typed)) + if (list is not ICollection typed) { typed = list.ToList(); list = typed; // because we still need to be able to iterate it, even if we fail here @@ -2374,7 +2385,7 @@ namespace Dapper /// 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)); + CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity._sql)); private static bool IsValueTuple(Type type) => (type?.IsValueType == true && type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal)) @@ -2382,7 +2393,7 @@ namespace Dapper internal static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList literals) { - Type type = identity.parametersType; + Type type = identity._parametersType; if (IsValueTuple(type)) { @@ -2390,9 +2401,9 @@ namespace Dapper } bool filterParams = false; - if (removeUnused && identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text) + if (removeUnused && identity._commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text) { - filterParams = !smellsLikeOleDb.IsMatch(identity.sql); + filterParams = !smellsLikeOleDb.IsMatch(identity._sql); } var dm = new DynamicMethod("ParamInfo" + Guid.NewGuid().ToString(), null, new[] { typeof(IDbCommand), typeof(object) }, type, true); @@ -2485,7 +2496,7 @@ namespace Dapper } if (filterParams) { - props = FilterParameters(props, identity.sql); + props = FilterParameters(props, identity._sql); } var callOpCode = isStruct ? OpCodes.Call : OpCodes.Callvirt; @@ -2967,9 +2978,9 @@ namespace Dapper /// Gets type-map for the given type /// /// Type map instance, default is to create new instance of DefaultTypeMap -#pragma warning disable CA2211 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API +#pragma warning disable CA2211, MA0069 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API public static Func TypeMapProvider = (Type type) => new DefaultTypeMap(type); -#pragma warning restore CA2211 // Non-constant fields should not be visible +#pragma warning restore CA2211, MA0069 // Non-constant fields should not be visible /// /// Gets type-map for the given . @@ -3648,7 +3659,7 @@ namespace Dapper } else { - formattedValue = Convert.ToString(value) + " - " + Type.GetTypeCode(value.GetType()); + formattedValue = Convert.ToString(value, CultureInfo.InvariantCulture) + " - " + Type.GetTypeCode(value.GetType()); } } catch (Exception valEx) @@ -3656,7 +3667,9 @@ namespace Dapper formattedValue = valEx.Message; } } +#pragma warning disable MA0076 // do not use implicit culture-sensitive ToString - I'm fine with it in this context toThrow = new DataException($"Error parsing column {index} ({name}={formattedValue})", ex); +#pragma warning restore MA0076 } catch { // throw the **original** exception, wrapped as DataException diff --git a/Dapper/WrappedReader.cs b/Dapper/WrappedReader.cs index f092823f..1f655306 100644 --- a/Dapper/WrappedReader.cs +++ b/Dapper/WrappedReader.cs @@ -25,7 +25,9 @@ namespace Dapper [MethodImpl(MethodImplOptions.NoInlining)] private async static Task ThrowDisposedAsync() { +#pragma warning disable MA0042 // use ThrowDisposedAsync - that's us! var result = ThrowDisposed(); +#pragma warning restore MA0042 await Task.Yield(); // will never hit this - already thrown and handled return result; } @@ -213,7 +215,7 @@ namespace Dapper protected override DbDataReader GetDbDataReader(int ordinal) => (((IDataReader)_reader).GetData(ordinal) as DbDataReader) ?? throw new NotSupportedException(); } - internal class BasicWrappedReader : IWrappedDataReader + internal sealed class BasicWrappedReader : IWrappedDataReader { private IDataReader _reader; private IDbCommand _cmd; diff --git a/Directory.Build.props b/Directory.Build.props index 9d62e68e..adcfac58 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,7 +13,7 @@ git https://github.com/StackExchange/Dapper false - $(NOWARN);IDE0056;IDE0057;IDE0079 + $(NOWARN);IDE0056;IDE0057;IDE0079;IDE0090 true embedded en-US