- add Meziantou.Analyzer to help check async usage scenarios

- fixup or suppress some noise from IDE and MA areas
This commit is contained in:
mgravell
2020-12-07 16:05:02 +00:00
parent 6ab170c160
commit 2152634177
19 changed files with 199 additions and 171 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ namespace Dapper.Contrib.Extensions
if (!type.IsInterface)
return (await connection.QueryAsync<T>(sql, dynParams, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault();
if (!((await connection.QueryAsync<dynamic>(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() is IDictionary<string, object> res))
if ((await connection.QueryAsync<dynamic>(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() is not IDictionary<string, object> res)
{
return null;
}
+1 -1
View File
@@ -187,7 +187,7 @@ namespace Dapper.Contrib.Extensions
if (type.IsInterface)
{
if (!(connection.Query(sql, dynParams).FirstOrDefault() is IDictionary<string, object> res))
if (connection.Query(sql, dynParams).FirstOrDefault() is not IDictionary<string, object> res)
{
return null;
}
+3
View File
@@ -6,6 +6,7 @@
<Description>A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..</Description>
<Authors>Sam Saffron;Marc Gravell;Nick Craver</Authors>
<TargetFrameworks>net461;netstandard2.0;net5.0</TargetFrameworks>
<NoWarn>$(NoWarn);MA0006;MA0007;MA0009;MA0003;MA0023;MA0051;MA0071;MA0048;MA0018;MA0012</NoWarn>
</PropertyGroup>
<ItemGroup>
<Folder Include="Properties\" />
@@ -17,5 +18,7 @@
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageReference Include="Meziantou.Analyzer" Version="1.0.638" PrivateAssets="All" IncludeAssets="runtime; build; native; contentfiles; analyzers" />
</ItemGroup>
</Project>
+1 -1
View File
@@ -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)
+2
View File
@@ -177,6 +177,8 @@ namespace Dapper
/// <summary>
/// The settable properties for this typemap
/// </summary>
#pragma warning disable MA0016 // Prefer return collection abstraction instead of implementation
public List<PropertyInfo> Properties { get; }
#pragma warning restore MA0016 // Prefer return collection abstraction instead of implementation
}
}
+3 -3
View File
@@ -15,7 +15,7 @@ namespace Dapper
{
internal const DbType EnumerableMultiParameter = (DbType)(-1);
private static readonly Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>();
private readonly Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>();
private readonly Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>(StringComparer.Ordinal);
private List<object> templates;
object SqlMapper.IParameterLookup.this[string name] =>
@@ -162,7 +162,7 @@ namespace Dapper
/// <param name="identity">Information about the query</param>
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;
}
+4
View File
@@ -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<TTo>();
task.ContinueWith(OnTaskCompleted<TFrom, TTo>, state: source, TaskContinuationOptions.ExecuteSynchronously);
+1 -1
View File
@@ -6,7 +6,7 @@ namespace Dapper
/// <summary>
/// Handles variances in features per DBMS
/// </summary>
internal class FeatureSupport
internal sealed class FeatureSupport
{
private static readonly FeatureSupport
Default = new FeatureSupport(false),
+2
View File
@@ -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;
+1 -1
View File
@@ -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<IDataReader, object>[] OtherDeserializers { get; set; }
+1 -1
View File
@@ -5,6 +5,6 @@
/// <summary>
/// Dummy type for excluding from multi-map
/// </summary>
private class DontMap { /* hiding constructor */ }
private sealed class DontMap { /* hiding constructor */ }
}
}
+14 -14
View File
@@ -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<IEnumerable<T>> ReadAsyncImpl<T>(Type type, bool buffered)
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (_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<T>(gridIndex, deserializer.Func);
}
@@ -185,7 +185,7 @@ namespace Dapper
private Task<T> ReadRowAsyncImpl<T>(Type type, Row row)
{
if (reader is DbDataReader dbReader) return ReadRowAsyncImplViaDbReader<T>(dbReader, type, row);
if (_reader is DbDataReader dbReader) return ReadRowAsyncImplViaDbReader<T>(dbReader, type, row);
// no async API available; use non-async and fake it
return Task.FromResult(ReadRow<T>(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<T>();
while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false))
{
+36 -36
View File
@@ -12,17 +12,17 @@ namespace Dapper
/// </summary>
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;
}
/// <summary>
@@ -145,16 +145,16 @@ namespace Dapper
private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (_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<T>(Type type, Row row)
{
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (_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<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Delegate func, string splitOn)
{
var identity = this.identity.ForGrid<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(typeof(TReturn), gridIndex);
var identity = _identity.ForGrid<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(typeof(TReturn), gridIndex);
IsConsumed = true;
try
{
foreach (var r in MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default, func, splitOn, reader, identity, false))
foreach (var r in MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default, func, splitOn, _reader, identity, false))
{
yield return r;
}
@@ -223,10 +223,10 @@ namespace Dapper
private IEnumerable<TReturn> MultiReadInternal<TReturn>(Type[] types, Func<object[], TReturn> 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<TReturn>(null, default, types, map, splitOn, reader, identity, false))
foreach (var r in MultiMapImpl<TReturn>(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;
/// <summary>
/// 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
/// </summary>
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)
{
+36 -34
View File
@@ -93,15 +93,15 @@ namespace Dapper
internal virtual Type GetType(int index) => throw new IndexOutOfRangeException(nameof(index));
internal Identity ForGrid<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(Type primaryType, int gridIndex) =>
new Identity<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(sql, commandType, connectionString, primaryType, parametersType, gridIndex);
new Identity<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(_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);
/// <summary>
/// Create an identity for use with DynamicParameters, internal use only.
@@ -109,29 +109,31 @@ namespace Dapper
/// <param name="type">The parameters type to create an <see cref="Identity"/> for.</param>
/// <returns></returns>
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
/// <summary>
/// The raw SQL command.
/// </summary>
public readonly string sql;
public readonly string _sql;
/// <summary>
/// The SQL command type.
/// </summary>
public readonly CommandType? commandType;
public readonly CommandType? _commandType;
/// <summary>
/// The hash code of this Identity.
/// </summary>
public readonly int hashCode;
public readonly int _hashCode;
/// <summary>
/// The grid index (position in the reader) of this Identity.
/// </summary>
public readonly int gridIndex;
public readonly int _gridIndex;
/// <summary>
/// This <see cref="Type"/> of this Identity.
/// </summary>
public readonly Type type;
public readonly Type _type;
/// <summary>
/// The connection string for this Identity.
/// </summary>
public readonly string connectionString;
public readonly string _connectionString;
/// <summary>
/// The type of the parameters object for this Identity.
/// </summary>
public readonly Type parametersType;
public readonly Type _parametersType;
/// <summary>
/// Gets the hash code for this identity.
/// </summary>
/// <returns></returns>
public override int GetHashCode() => hashCode;
public override int GetHashCode() => _hashCode;
/// <summary>
/// See object.ToString()
/// </summary>
public override string ToString() => sql;
public override string ToString() => _sql;
/// <summary>
/// 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));
}
+1 -1
View File
@@ -11,7 +11,7 @@ namespace Dapper
/// </summary>
/// <typeparam name="TKey">The type to cache.</typeparam>
/// <typeparam name="TValue">The value type of the cache.</typeparam>
internal class Link<TKey, TValue> where TKey : class
internal sealed class Link<TKey, TValue> where TKey : class
{
public static bool TryGet(Link<TKey, TValue> link, TKey key, out TValue value)
{
+1 -1
View File
@@ -8,7 +8,7 @@ namespace Dapper
{
public static partial class SqlMapper
{
private class TypeDeserializerCache
private sealed class TypeDeserializerCache
{
private TypeDeserializerCache(Type type)
{
+87 -74
View File
@@ -27,7 +27,7 @@ namespace Dapper
/// </summary>
public static partial class SqlMapper
{
private class PropertyInfoByNameComparer : IComparer<PropertyInfo>
private sealed class PropertyInfoByNameComparer : IComparer<PropertyInfo>
{
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
/// <returns></returns>
public static IEnumerable<Tuple<string, string, int>> 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<int, int>();
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<Type, DbType> typeMap;
private static Dictionary<Type, DbType> s_typeMap;
static SqlMapper()
{
typeMap = new Dictionary<Type, DbType>(37)
s_typeMap = new Dictionary<Type, DbType>(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<Type, DbType>(snapshot) { [type] = dbType };
s_typeMap = new Dictionary<Type, DbType>(snapshot) { [type] = dbType };
}
/// <summary>
@@ -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<Type, DbType>(snapshot);
newCopy.Remove(type);
typeMap = newCopy;
s_typeMap = newCopy;
}
/// <summary>
@@ -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
/// </summary>
/// <typeparam name="T">The type of element in the list.</typeparam>
/// <param name="source">The enumerable to return as a list.</param>
#pragma warning disable MA0016 // Prefer return collection abstraction instead of implementation
public static List<T> AsList<T>(this IEnumerable<T> source) =>
#pragma warning restore MA0016 // Prefer return collection abstraction instead of implementation
(source == null || source is List<T>) ? (List<T>)source : source.ToList();
/// <summary>
@@ -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<TReturn> Impl(IDbConnection cnn, CommandDefinition command, Type[] types, Func<object[], TReturn> 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<IDataReader, object>[] 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<IDataReader, TReturn> 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<IDataReader, object>[] 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<IDataReader, TReturn> 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<IDbCommand, object> 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<string>;
bool isDbString = value is IEnumerable<DbString>;
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<T>(ref IEnumerable<T> list, int splitAt, string namePrefix, IDbCommand command, string colType, bool byPosition,
Action<StringBuilder, T> append)
{
if (!(list is ICollection<T> typed))
if (list is not ICollection<T> 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
/// <param name="checkForDuplicates">Whether to check for duplicates.</param>
/// <param name="removeUnused">Whether to remove unused parameters.</param>
public static Action<IDbCommand, object> 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<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList<LiteralToken> 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
/// </summary>
/// <returns>Type map instance, default is to create new instance of DefaultTypeMap</returns>
#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<Type, ITypeMap> 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
/// <summary>
/// Gets type-map for the given <see cref="Type"/>.
@@ -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
+3 -1
View File
@@ -25,7 +25,9 @@ namespace Dapper
[MethodImpl(MethodImplOptions.NoInlining)]
private async static Task<T> ThrowDisposedAsync<T>()
{
#pragma warning disable MA0042 // use ThrowDisposedAsync - that's us!
var result = ThrowDisposed<T>();
#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;
+1 -1
View File
@@ -13,7 +13,7 @@
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/StackExchange/Dapper</RepositoryUrl>
<Deterministic>false</Deterministic>
<NoWarn>$(NOWARN);IDE0056;IDE0057;IDE0079</NoWarn>
<NoWarn>$(NOWARN);IDE0056;IDE0057;IDE0079;IDE0090</NoWarn>
<DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType>
<DefaultLanguage>en-US</DefaultLanguage>