Commit ae9d6da3 authored by mgravell's avatar mgravell

fix bug with Identity not applying type equality correctly; to avoid overhead,...

fix bug with Identity not applying type equality correctly; to avoid overhead, subclass Identity so we don't always need the arrays
parent 23ad634a
......@@ -406,7 +406,7 @@ private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, ID
private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, Type effectiveType, CommandDefinition command)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null);
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
var cancel = command.CancellationToken;
......@@ -470,7 +470,7 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn,
private static async Task<T> QueryRowAsync<T>(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null);
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
var cancel = command.CancellationToken;
......@@ -594,7 +594,7 @@ private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandD
isFirst = false;
cmd = command.TrySetupAsyncCommand(cnn, null);
masterSql = cmd.CommandText;
var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType());
info = GetCacheInfo(identity, obj, command.AddToCache);
}
else if (pending.Count >= MAX_PENDING)
......@@ -642,7 +642,7 @@ private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandD
{
masterSql = cmd.CommandText;
isFirst = false;
var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType());
info = GetCacheInfo(identity, obj, command.AddToCache);
}
else
......@@ -667,7 +667,7 @@ private static async Task<int> ExecuteMultiImplAsync(IDbConnection cnn, CommandD
private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object param)
{
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType(), null);
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType());
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader))
......@@ -935,7 +935,7 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
private static async Task<IEnumerable<TReturn>> MultiMapAsync<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
var identity = new Identity<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType());
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
try
......@@ -985,7 +985,7 @@ private static async Task<IEnumerable<TReturn>> MultiMapAsync<TReturn>(this IDbC
}
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types);
var identity = new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types);
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
try
......@@ -1037,7 +1037,7 @@ private static IEnumerable<T> ExecuteReaderSync<T>(IDataReader reader, Func<IDat
public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command)
{
object param = command.Parameters;
var 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());
CacheInfo info = GetCacheInfo(identity, param, command.AddToCache);
DbCommand cmd = null;
......@@ -1233,7 +1233,7 @@ private static async Task<T> ExecuteScalarImplAsync<T>(IDbConnection cnn, Comman
object param = command.Parameters;
if (param != null)
{
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType());
paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader;
}
......
......@@ -204,15 +204,7 @@ private T ReadRow<T>(Type type, Row row)
private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Delegate func, string splitOn)
{
var identity = this.identity.ForGrid(typeof(TReturn), new Type[] {
typeof(TFirst),
typeof(TSecond),
typeof(TThird),
typeof(TFourth),
typeof(TFifth),
typeof(TSixth),
typeof(TSeventh)
}, gridIndex);
var identity = this.identity.ForGrid<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(typeof(TReturn), gridIndex);
IsConsumed = true;
......
using System;
using System.Data;
using System.Runtime.CompilerServices;
namespace Dapper
{
public static partial class SqlMapper
{
internal sealed class Identity<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh> : Identity
{
private static readonly int s_typeHash;
private static readonly int s_typeCount = CountNonTrivial(out s_typeHash);
internal Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, int gridIndex = 0)
: base(sql, commandType, connectionString, type, parametersType, s_typeHash, gridIndex)
{}
internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, int gridIndex = 0)
: base(sql, commandType, connection.ConnectionString, type, parametersType, s_typeHash, gridIndex)
{ }
static int CountNonTrivial(out int hashCode)
{
int hashCodeLocal = 0;
int count = 0;
bool Map<T>()
{
if(typeof(T) != typeof(DontMap))
{
count++;
hashCodeLocal = (hashCodeLocal * 23) + (typeof(T).GetHashCode());
return true;
}
return false;
}
_ = Map<TFirst>() && Map<TSecond>() && Map<TThird>()
&& Map<TFourth>() && Map<TFifth>() && Map<TSixth>()
&& Map<TSeventh>();
hashCode = hashCodeLocal;
return count;
}
internal override int TypeCount => s_typeCount;
internal override Type GetType(int index)
{
switch (index)
{
case 0: return typeof(TFirst);
case 1: return typeof(TSecond);
case 2: return typeof(TThird);
case 3: return typeof(TFourth);
case 4: return typeof(TFifth);
case 5: return typeof(TSixth);
case 6: return typeof(TSeventh);
default: return base.GetType(index);
}
}
}
internal sealed class IdentityWithTypes : Identity
{
private readonly Type[] _types;
internal IdentityWithTypes(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, Type[] otherTypes, int gridIndex = 0)
: base(sql, commandType, connectionString, type, parametersType, HashTypes(otherTypes), gridIndex)
{
_types = otherTypes ?? Type.EmptyTypes;
}
internal IdentityWithTypes(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes, int gridIndex = 0)
: base(sql, commandType, connection.ConnectionString, type, parametersType, HashTypes(otherTypes), gridIndex)
{
_types = otherTypes ?? Type.EmptyTypes;
}
internal override int TypeCount => _types.Length;
internal override Type GetType(int index) => _types[index];
static int HashTypes(Type[] types)
{
var hashCode = 0;
if (types != null)
{
foreach (var t in types)
{
hashCode = (hashCode * 23) + (t?.GetHashCode() ?? 0);
}
}
return hashCode;
}
}
/// <summary>
/// Identity of a cached query in Dapper, used for extensibility.
/// </summary>
public class Identity : IEquatable<Identity>
{
internal virtual int TypeCount => 0;
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);
internal Identity ForGrid(Type primaryType, int gridIndex) =>
new Identity(sql, commandType, connectionString, primaryType, parametersType, null, gridIndex);
new Identity(sql, commandType, connectionString, primaryType, parametersType, 0, gridIndex);
internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) =>
new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex);
(otherTypes == null || otherTypes.Length == 0)
? 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.
......@@ -22,12 +113,12 @@ public class Identity : IEquatable<Identity>
/// <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, null, -1);
new Identity(sql, commandType, connectionString, this.type, type, 0, -1);
internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes)
: this(sql, commandType, connection.ConnectionString, type, parametersType, otherTypes, 0) { /* base call */ }
internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType)
: this(sql, commandType, connection.ConnectionString, type, parametersType, 0, 0) { /* base call */ }
private Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, Type[] otherTypes, int gridIndex)
private protected Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, int otherTypesHash, int gridIndex)
{
this.sql = sql;
this.commandType = commandType;
......@@ -42,13 +133,7 @@ private Identity(string sql, CommandType? commandType, string connectionString,
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) + otherTypesHash;
hashCode = (hashCode * 23) + (connectionString == null ? 0 : connectionStringComparer.GetHashCode(connectionString));
hashCode = (hashCode * 23) + (parametersType?.GetHashCode() ?? 0);
}
......@@ -101,6 +186,11 @@ private Identity(string sql, CommandType? commandType, string connectionString,
/// <returns></returns>
public override int GetHashCode() => hashCode;
/// <summary>
/// See object.ToString()
/// </summary>
public override string ToString() => sql;
/// <summary>
/// Compare 2 Identity objects
/// </summary>
......@@ -108,13 +198,30 @@ private Identity(string sql, CommandType? commandType, string connectionString,
/// <returns>Whether the two are equal</returns>
public bool Equals(Identity other)
{
return other != null
&& gridIndex == other.gridIndex
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(other, 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;
&& parametersType == other.parametersType
&& (typeCount = TypeCount) == other.TypeCount
&& (typeCount == 0 || TypesEqual(this, other, typeCount));
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool TypesEqual(Identity x, Identity y, int count)
{
if (y.TypeCount != count) return false;
for(int i = 0; i < count; i++)
{
if (x.GetType(i) != y.GetType(i))
return false;
}
return true;
}
}
}
......
......@@ -538,7 +538,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com
{
masterSql = cmd.CommandText;
isFirst = false;
identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType());
info = GetCacheInfo(identity, obj, command.AddToCache);
}
else
......@@ -562,7 +562,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com
// nice and simple
if (param != null)
{
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType());
info = GetCacheInfo(identity, param, command.AddToCache);
}
return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader);
......@@ -1007,7 +1007,7 @@ public static GridReader QueryMultiple(this IDbConnection cnn, string sql, objec
private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandDefinition command)
{
object param = command.Parameters;
var 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());
CacheInfo info = GetCacheInfo(identity, param, command.AddToCache);
IDbCommand cmd = null;
......@@ -1064,7 +1064,7 @@ private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool w
private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command, Type effectiveType)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null);
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
var info = GetCacheInfo(identity, param, command.AddToCache);
IDbCommand cmd = null;
......@@ -1162,7 +1162,7 @@ private static void ThrowZeroRows(Row row)
private static T QueryRowImpl<T>(IDbConnection cnn, Row row, ref CommandDefinition command, Type effectiveType)
{
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null);
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType());
var info = GetCacheInfo(identity, param, command.AddToCache);
IDbCommand cmd = null;
......@@ -1405,7 +1405,7 @@ public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string
private static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(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) });
identity = identity ?? new Identity<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh>(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType());
CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache);
IDbCommand ownedCommand = null;
......@@ -1427,7 +1427,7 @@ public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string
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(identity, splitOn, reader);
deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]);
otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray();
if (command.AddToCache) SetQueryCache(identity, cinfo);
......@@ -1475,7 +1475,7 @@ private static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn
}
object param = command.Parameters;
identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types);
identity = identity ?? new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types);
CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache);
IDbCommand ownedCommand = null;
......@@ -1497,7 +1497,7 @@ private static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn
int hash = GetColumnHash(reader);
if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash)
{
var deserializers = GenerateDeserializers(types, splitOn, reader);
var deserializers = GenerateDeserializers(identity, splitOn, reader);
deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]);
otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray();
SetQueryCache(identity, cinfo);
......@@ -1569,12 +1569,14 @@ private static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn
};
}
private static Func<IDataReader, object>[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader)
private static Func<IDataReader, object>[] GenerateDeserializers(Identity identity, string splitOn, IDataReader reader)
{
var deserializers = new List<Func<IDataReader, object>>();
var splits = splitOn.Split(',').Select(s => s.Trim()).ToArray();
bool isMultiSplit = splits.Length > 1;
if (types[0] == typeof(object))
int typeCount = identity.TypeCount;
if (identity.GetType(0) == typeof(object))
{
// we go left to right for dynamic multi-mapping so that the madness of TestMultiMappingVariations
// is supported
......@@ -1582,8 +1584,10 @@ private static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn
int currentPos = 0;
int splitIdx = 0;
string currentSplit = splits[splitIdx];
foreach (var type in types)
for (int i = 0; i < typeCount; i++)
{
Type type = identity.GetType(i);
if (type == typeof(DontMap))
{
break;
......@@ -1606,9 +1610,9 @@ private static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn
int currentPos = reader.FieldCount;
int splitIdx = splits.Length - 1;
var currentSplit = splits[splitIdx];
for (var typeIdx = types.Length - 1; typeIdx >= 0; --typeIdx)
for (var typeIdx = typeCount - 1; typeIdx >= 0; --typeIdx)
{
var type = types[typeIdx];
var type = identity.GetType(typeIdx);
if (type == typeof(DontMap))
{
continue;
......@@ -2807,7 +2811,7 @@ private static T ExecuteScalarImpl<T>(IDbConnection cnn, ref CommandDefinition c
object param = command.Parameters;
if (param != null)
{
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType());
paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader;
}
......@@ -2864,7 +2868,7 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin
// nice and simple
if (param != null)
{
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType());
info = GetCacheInfo(identity, param, command.AddToCache);
}
var paramReader = info?.ParamReader;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment