Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/Nemo/DataReaderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ internal static class DataReaderExtensions
{
internal static ISet<string> GetColumns(this IDataRecord record)
{
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
int count = record.FieldCount;
#if NETSTANDARD2_0 || NET472
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
#else
var columns = new HashSet<string>(count, StringComparer.OrdinalIgnoreCase);
#endif
for (var i = 0; i < count; i++)
{
columns.Add(record.GetName(i));
Expand Down
39 changes: 20 additions & 19 deletions src/Nemo/ObjectFactory.Retrieve.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ private static IEnumerable<TResult> RetrieveImplemenation<TResult>(string operat
{
config ??= ConfigurationFactory.Get<TResult>();

queryKey = GetQueryKey<TResult>(operation, parameters ?? new Param[] { }, returnType);

Log.CaptureBegin($"Retrieving from L1 cache: {queryKey}", config);
queryKey = GetQueryKey<TResult>(operation, parameters ?? Array.Empty<Param>(), returnType);

if (returnType == OperationReturnType.MultiResult)
{
Expand All @@ -74,8 +72,6 @@ private static IEnumerable<TResult> RetrieveImplemenation<TResult>(string operat

if (result != null)
{
Log.Capture($"Found in L1 cache: {queryKey}", config);

if (returnType == OperationReturnType.MultiResult)
{
((IMultiResult)result).Reset();
Expand All @@ -84,8 +80,6 @@ private static IEnumerable<TResult> RetrieveImplemenation<TResult>(string operat
Log.CaptureEnd(config);
return result;
}
Log.Capture($"Not found in L1 cache: {queryKey}", config);
Log.CaptureEnd(config);
}

result = RetrieveItems(operation, parameters, operationType, returnType, connectionName, connection, types, map, schema, config, identityMap);
Expand Down Expand Up @@ -153,12 +147,25 @@ private static IEnumerable<T> RetrieveItems<T>(string operation, IList<Param> pa
return result;
}

private static string GetQueryKey<T>(string operation, IEnumerable<Param> parameters, OperationReturnType returnType)
private static string GetQueryKey<T>(string operation, IList<Param> parameters, OperationReturnType returnType)
{
var combined = new StringBuilder();
combined.Append(returnType.ToString()).Append("/").Append(operation).Append("/").Append(parameters.OrderBy(p => p.Name).Select(p => $"{p.Name}={p.Value}").ToDelimitedString(","));
var hash = Hash.Compute(Encoding.UTF8.GetBytes(combined.ToString()));
return typeof(T).FullName + "/" + hash;
var sb = new StringBuilder();
sb.Append(returnType.ToString()).Append("/").Append(operation);
if (parameters.Count > 0)
{
sb.Append("/");
var sorted = parameters.Count > 1;
IEnumerable<Param> paramSource = sorted ? parameters.OrderBy(p => p.Name) : parameters;
var first = true;
foreach (var p in paramSource)
{
if (!first) sb.Append(",");
sb.Append(p.Name).Append("=").Append(p.Value);
first = false;
}
}
var hash = Hash.Compute(Encoding.UTF8.GetBytes(sb.ToString()));
return string.Concat(typeof(T).FullName, "/", hash.ToString());
}

/// <summary>
Expand Down Expand Up @@ -344,9 +351,7 @@ private static async Task<IEnumerable<TResult>> RetrieveImplemenationAsync<TResu
{
config ??= ConfigurationFactory.Get<TResult>();

queryKey = GetQueryKey<TResult>(operation, parameters ?? new Param[] { }, returnType);

Log.CaptureBegin($"Retrieving from L1 cache: {queryKey}", config);
queryKey = GetQueryKey<TResult>(operation, parameters ?? Array.Empty<Param>(), returnType);

if (returnType == OperationReturnType.MultiResult)
{
Expand All @@ -360,8 +365,6 @@ private static async Task<IEnumerable<TResult>> RetrieveImplemenationAsync<TResu

if (result != null)
{
Log.Capture($"Found in L1 cache: {queryKey}", config);

if (returnType == OperationReturnType.MultiResult)
{
((IMultiResult)result).Reset();
Expand All @@ -370,8 +373,6 @@ private static async Task<IEnumerable<TResult>> RetrieveImplemenationAsync<TResu
Log.CaptureEnd(config);
return result;
}
Log.Capture($"Not found in L1 cache: {queryKey}", config);
Log.CaptureEnd(config);
}

result = await RetrieveItemsAsync(operation, parameters, operationType, returnType, connectionName, connection, types, map, schema, config, identityMap).ConfigureAwait(false);
Expand Down
99 changes: 75 additions & 24 deletions src/Nemo/ObjectFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -838,7 +838,14 @@ public static OperationResponse ExecuteProcedure(string procedure, bool nonQuery

private static OperationType GuessOperationType(string operation)
{
return operation.Any(char.IsWhiteSpace) ? OperationType.Sql : OperationType.StoredProcedure;
for (var i = 0; i < operation.Length; i++)
{
if (char.IsWhiteSpace(operation[i]))
{
return OperationType.Sql;
}
}
return OperationType.StoredProcedure;
}

#endregion
Expand Down Expand Up @@ -1070,6 +1077,19 @@ private static IEnumerable<T> ConvertDataReader<T>(IDataReader reader, Func<obje
var useMapper = !isInterface || config.DefaultMaterializationMode == MaterializationMode.Exact;
var columns = !isSimpleType ? reader.GetColumns() : null;
var isAnonymous = typeof(T) == typeof(object);
var autoTypeCoercion = config.AutoTypeCoercion;
var hasMap = map != null;
var typeCount = hasMap ? types.Count : 0;
var args = hasMap ? new object[typeCount] : null;
Mapper.PropertyMapper[] secondaryMappers = null;
if (hasMap && typeCount > 1 && useMapper)
{
secondaryMappers = new Mapper.PropertyMapper[typeCount];
for (var i = 1; i < typeCount; i++)
{
secondaryMappers[i] = Mapper.CreateDelegate(typeof(IDataRecord), types[i], true, autoTypeCoercion);
}
}
while (reader.Read())
{
if (isSimpleType)
Expand All @@ -1085,20 +1105,23 @@ private static IEnumerable<T> ConvertDataReader<T>(IDataReader reader, Func<obje
{
var item = Create<T>(isInterface);
var record = (IDataRecord)new WrappedRecord(reader, columns);
Map(record, item, config.AutoTypeCoercion);
if (autoTypeCoercion)
FastIndexerMapperWithTypeCoercion<IDataRecord, T>.Map(record, item);
else
FastIndexerMapper<IDataRecord, T>.Map(record, item);

TrySetObjectState(item);

if (map != null)
if (hasMap)
{
var args = new object[types.Count];
args[0] = item;
for (var i = 1; i < types.Count; i++)
for (var i = 1; i < typeCount; i++)
{
var identity = CreateIdentity(types[i], reader);
if (!references.TryGetValue(identity, out var reference))
{
reference = Map((object)record, types[i], config.AutoTypeCoercion);
reference = Create(types[i]);
secondaryMappers[i](record, reference);
TrySetObjectState(reference);
references.Add(identity, reference);
}
Expand Down Expand Up @@ -1126,11 +1149,10 @@ private static IEnumerable<T> ConvertDataReader<T>(IDataReader reader, Func<obje

TrySetObjectState(item);

if (map != null)
if (hasMap)
{
var args = new object[types.Count];
args[0] = item;
for (var i = 1; i < types.Count; i++)
for (var i = 1; i < typeCount; i++)
{
var identity = CreateIdentity(types[i], reader);
if (!references.TryGetValue(identity, out var reference))
Expand Down Expand Up @@ -1161,7 +1183,7 @@ private static IEnumerable<T> ConvertDataReader<T>(IDataReader reader, Func<obje
// Flush accumulating item
if (isAccumulator)
{
var mappedItem = map(new object[types.Count]);
var mappedItem = map(new object[typeCount]);
if (mappedItem != null)
{
yield return mappedItem;
Expand All @@ -1187,14 +1209,30 @@ private static IDictionary<string, object> CreateDynamicItem(IDataReader reader,
return bag;
}

private static readonly ConcurrentDictionary<Type, string[]> PrimaryKeyColumnCache = new ConcurrentDictionary<Type, string[]>();

private static string[] GetPrimaryKeyColumnNames(Type objectType)
{
return PrimaryKeyColumnCache.GetOrAdd(objectType, type =>
{
var nameMap = Reflector.GetPropertyNameMap(type);
return nameMap.Values.Where(p => p.IsPrimaryKey)
.Select(p => p.MappedColumnName ?? p.PropertyName)
.OrderBy(_ => _)
.ToArray();
});
}

private static Tuple<Type, string> CreateIdentity(Type objectType, IDataRecord record)
{
var nameMap = Reflector.GetPropertyNameMap(objectType);
var identity = Tuple.Create(objectType, string.Join(",", nameMap.Values.Where(p => p.IsPrimaryKey)
.Select(p => p.MappedColumnName ?? p.PropertyName)
.OrderBy(_ => _)
.Select(n => Convert.ToString(record.GetValue(record.GetOrdinal(n))))));
return identity;
var pkColumns = GetPrimaryKeyColumnNames(objectType);
var sb = new StringBuilder();
for (var i = 0; i < pkColumns.Length; i++)
{
if (i > 0) sb.Append(',');
sb.Append(Convert.ToString(record.GetValue(record.GetOrdinal(pkColumns[i]))));
}
return Tuple.Create(objectType, sb.ToString());
}

private static IEnumerable<MultiResultItem> ConvertDataReaderMultiResult(IDataReader reader, IList<Type> types, INemoConfiguration config)
Expand All @@ -1205,7 +1243,13 @@ void changeSkipNext()
skipNext = true;
}

var reflectedTypes = types.Select(t => Reflector.GetReflectedType(t)).ToList();
var reflectedTypes = new ReflectedType[types.Count];
for (var i = 0; i < types.Count; i++)
{
reflectedTypes[i] = Reflector.GetReflectedType(types[i]);
}

var autoTypeCoercion = config.AutoTypeCoercion;

try
{
Expand All @@ -1217,30 +1261,37 @@ void changeSkipNext()
var isSimpleType = reflectedTypes[resultIndex].IsSimpleType;
var useMapper = !isInterface || config.DefaultMaterializationMode == MaterializationMode.Exact;
var columns = !isSimpleType ? reader.GetColumns() : null;
var currentType = types[resultIndex];
Mapper.PropertyMapper resultMapper = null;
if (useMapper && !isSimpleType && !isAnonymous)
{
resultMapper = Mapper.CreateDelegate(typeof(IDataRecord), currentType, true, autoTypeCoercion);
}
while (reader.Read())
{
if (isSimpleType)
{
var item = reader.GetValue(0);
yield return new MultiResultItem { Item = Reflector.ChangeType(item, types[resultIndex]), ItemType = types[resultIndex], ItemTypeIndex = resultIndex, SkipNextCallback = changeSkipNext };
yield return new MultiResultItem { Item = Reflector.ChangeType(item, currentType), ItemType = currentType, ItemTypeIndex = resultIndex, SkipNextCallback = changeSkipNext };
}
else if (isAnonymous)
{
var item = CreateDynamicItem(reader, columns, true);
yield return new MultiResultItem { Item = item, ItemType = types[resultIndex], ItemTypeIndex = resultIndex, SkipNextCallback = changeSkipNext };
yield return new MultiResultItem { Item = item, ItemType = currentType, ItemTypeIndex = resultIndex, SkipNextCallback = changeSkipNext };
}
else if (useMapper)
{
var item = Map((object)new WrappedReader(reader, columns), types[resultIndex], config.AutoTypeCoercion);
TrySetObjectState(item);
yield return new MultiResultItem { Item = item, ItemType = types[resultIndex], ItemTypeIndex = resultIndex, SkipNextCallback = changeSkipNext };
var target = Create(currentType);
resultMapper(new WrappedReader(reader, columns), target);
TrySetObjectState(target);
yield return new MultiResultItem { Item = target, ItemType = currentType, ItemTypeIndex = resultIndex, SkipNextCallback = changeSkipNext };
}
else
{
var bag = CreateDynamicItem(reader, columns, false);
var item = Wrap(bag, types[resultIndex]);
var item = Wrap(bag, currentType);
TrySetObjectState(item);
yield return new MultiResultItem { Item = item, ItemType = types[resultIndex], ItemTypeIndex = resultIndex, SkipNextCallback = changeSkipNext };
yield return new MultiResultItem { Item = item, ItemType = currentType, ItemTypeIndex = resultIndex, SkipNextCallback = changeSkipNext };
}

if (skipNext)
Expand Down
63 changes: 29 additions & 34 deletions src/Nemo/Utilities/Hash.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,65 @@
{
public class Hash
{
private static uint _a;
private static uint _b;
private static uint _c;

private static void Mix()
private static void Mix(ref uint a, ref uint b, ref uint c)
{
_a -= _b; _a -= _c; _a ^= (_c >> 13);
_b -= _c; _b -= _a; _b ^= (_a << 8);
_c -= _a; _c -= _b; _c ^= (_b >> 13);
_a -= _b; _a -= _c; _a ^= (_c >> 12);
_b -= _c; _b -= _a; _b ^= (_a << 16);
_c -= _a; _c -= _b; _c ^= (_b >> 5);
_a -= _b; _a -= _c; _a ^= (_c >> 3);
_b -= _c; _b -= _a; _b ^= (_a << 10);
_c -= _a; _c -= _b; _c ^= (_b >> 15);
a -= b; a -= c; a ^= (c >> 13);
b -= c; b -= a; b ^= (a << 8);
c -= a; c -= b; c ^= (b >> 13);
a -= b; a -= c; a ^= (c >> 12);
b -= c; b -= a; b ^= (a << 16);
c -= a; c -= b; c ^= (b >> 5);
a -= b; a -= c; a ^= (c >> 3);
b -= c; b -= a; b ^= (a << 10);
c -= a; c -= b; c ^= (b >> 15);
}

public static uint Compute(byte[] data)
{
var len = data.Length;
_a = _b = 0x9e3779b9;
_c = 0;
uint a = 0x9e3779b9, b = 0x9e3779b9, c = 0;
var i = 0;
while (i + 12 <= len)
{
_a += data[i++] |
a += data[i++] |
((uint)data[i++] << 8) |
((uint)data[i++] << 16) |
((uint)data[i++] << 24);
_b += data[i++] |
b += data[i++] |
((uint)data[i++] << 8) |
((uint)data[i++] << 16) |
((uint)data[i++] << 24);
_c += data[i++] |
c += data[i++] |
((uint)data[i++] << 8) |
((uint)data[i++] << 16) |
((uint)data[i++] << 24);
Mix();
Mix(ref a, ref b, ref c);
}
_c += (uint)len;
c += (uint)len;
if (i < len)
_a += data[i++];
a += data[i++];
if (i < len)
_a += (uint)data[i++] << 8;
a += (uint)data[i++] << 8;
if (i < len)
_a += (uint)data[i++] << 16;
a += (uint)data[i++] << 16;
if (i < len)
_a += (uint)data[i++] << 24;
a += (uint)data[i++] << 24;
if (i < len)
_b += data[i++];
b += data[i++];
if (i < len)
_b += (uint)data[i++] << 8;
b += (uint)data[i++] << 8;
if (i < len)
_b += (uint)data[i++] << 16;
b += (uint)data[i++] << 16;
if (i < len)
_b += (uint)data[i++] << 24;
b += (uint)data[i++] << 24;
if (i < len)
_c += (uint)data[i++] << 8;
c += (uint)data[i++] << 8;
if (i < len)
_c += (uint)data[i++] << 16;
c += (uint)data[i++] << 16;
if (i < len)
_c += (uint)data[i++] << 24;
Mix();
return _c;
c += (uint)data[i++] << 24;
Mix(ref a, ref b, ref c);
return c;
}
}
}