Skip to content

Commit c900ded

Browse files
More caching
1 parent 779399f commit c900ded

File tree

3 files changed

+107
-46
lines changed

3 files changed

+107
-46
lines changed

Magic.IndexedDb/Helpers/PropertyMappingCache.cs

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Concurrent;
77
using System.Collections.Generic;
88
using System.Linq;
9+
using System.Linq.Expressions;
910
using System.Reflection;
1011
using System.Text;
1112
using System.Threading.Tasks;
@@ -14,23 +15,46 @@ namespace Magic.IndexedDb.Helpers
1415
{
1516
public struct SearchPropEntry
1617
{
17-
public SearchPropEntry(Dictionary<string, MagicPropertyEntry> _propertyEntries)
18+
public SearchPropEntry(Dictionary<string, MagicPropertyEntry> _propertyEntries,
19+
ConstructorInfo? constructor, ParameterInfo[]? constructorParams)
1820
{
1921
propertyEntries = _propertyEntries;
2022
jsNameToCsName = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
23+
ConstructorParameterMappings = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
2124

2225
foreach (var entry in propertyEntries)
2326
{
2427
jsNameToCsName[entry.Value.JsPropertyName] = entry.Value.CsharpPropertyName;
2528
}
29+
30+
// Store constructor parameters and their indexes
31+
if (constructor != null && constructorParams != null)
32+
{
33+
for (int i = 0; i < constructorParams.Length; i++)
34+
{
35+
ConstructorParameterMappings[constructorParams[i].Name!] = i;
36+
}
37+
38+
// Compile a fast instance creator delegate if there is a parameterized constructor
39+
InstanceCreator = (args) => constructor.Invoke(args);
40+
}
41+
else
42+
{
43+
// If there's no constructor, use parameterless instance creation
44+
InstanceCreator = (_) => Activator.CreateInstance(_propertyEntries.First().Value.Property.DeclaringType!);
45+
}
2646
}
2747

2848
public Dictionary<string, MagicPropertyEntry> propertyEntries { get; }
2949
public Dictionary<string, string> jsNameToCsName { get; }
50+
51+
public Dictionary<string, int> ConstructorParameterMappings { get; } // ✅ Stores constructor parameter indexes
52+
public Func<object?[], object?> InstanceCreator { get; } // ✅ Cached constructor invocation
3053
}
3154

3255

3356

57+
3458
public static class PropertyMappingCache
3559
{
3660
internal static readonly ConcurrentDictionary<string, SearchPropEntry> _propertyCache = new();
@@ -58,6 +82,23 @@ public static SearchPropEntry GetTypeOfTProperties(Type type)
5882
throw new Exception("Something went very wrong getting GetTypeOfTProperties");
5983
}
6084

85+
private static readonly ConcurrentDictionary<Type, bool> _simpleTypeCache = new();
86+
87+
public static bool IsSimpleType(Type type)
88+
{
89+
return _simpleTypeCache.GetOrAdd(type, t =>
90+
t.IsPrimitive ||
91+
t.IsEnum ||
92+
t == typeof(string) ||
93+
t == typeof(decimal) ||
94+
t == typeof(DateTime) ||
95+
t == typeof(DateTimeOffset) ||
96+
t == typeof(Guid) ||
97+
t == typeof(Uri) ||
98+
t == typeof(TimeSpan));
99+
}
100+
101+
/*
61102
public static bool IsSimpleType(Type type)
62103
{
63104
return type.IsPrimitive ||
@@ -69,7 +110,7 @@ public static bool IsSimpleType(Type type)
69110
type == typeof(Guid) ||
70111
type == typeof(Uri) ||
71112
type == typeof(TimeSpan);
72-
}
113+
}*/
73114

74115

75116
public static IEnumerable<Type> GetAllNestedComplexTypes(IEnumerable<PropertyInfo> properties)
@@ -115,7 +156,34 @@ public static IEnumerable<Type> GetAllNestedComplexTypes(IEnumerable<PropertyInf
115156
|| type.IsArray); // Arrays are collections too
116157
}*/
117158

159+
private static readonly ConcurrentDictionary<Type, bool> _complexTypeCache = new();
160+
118161
public static bool IsComplexType(Type type)
162+
{
163+
return _complexTypeCache.GetOrAdd(type, t =>
164+
{
165+
if (IsSimpleType(t) || t == typeof(string))
166+
return false;
167+
168+
if (t.IsGenericType)
169+
{
170+
Type genericTypeDef = t.GetGenericTypeDefinition();
171+
if (typeof(IEnumerable<>).IsAssignableFrom(genericTypeDef))
172+
{
173+
return IsComplexType(t.GetGenericArguments()[0]);
174+
}
175+
return t.GetGenericArguments().Any(IsComplexType);
176+
}
177+
178+
if (typeof(IEnumerable).IsAssignableFrom(t) || t.IsArray)
179+
return false;
180+
181+
return true;
182+
});
183+
}
184+
185+
186+
/*public static bool IsComplexType(Type type)
119187
{
120188
if (IsSimpleType(type) || type == typeof(string))
121189
return false;
@@ -141,7 +209,7 @@ public static bool IsComplexType(Type type)
141209
return false;
142210
143211
return true; // Consider anything else a complex object
144-
}
212+
}*/
145213

146214

147215

@@ -336,8 +404,13 @@ internal static void EnsureTypeIsCached(Type type)
336404
propertyEntries[propertyKey] = magicEntry; // Store property entry with string key
337405
}
338406

407+
// 🔥 Extract constructor metadata
408+
var constructor = type.GetConstructors().FirstOrDefault();
409+
var constructorParams = constructor?.GetParameters();
410+
339411
// Cache the properties for this type
340-
_propertyCache[typeKey] = new SearchPropEntry(propertyEntries);
412+
_propertyCache[typeKey] = new SearchPropEntry(propertyEntries,
413+
constructor, constructorParams ?? Array.Empty<ParameterInfo>());
341414

342415
var complexTypes = GetAllNestedComplexTypes(newMagicPropertyEntry.Select(x => x.Property));
343416
if (complexTypes != null && complexTypes.Any())

Magic.IndexedDb/Models/MagicContractResolver.cs

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -57,47 +57,39 @@ internal class MagicContractResolver<T> : JsonConverter<T>
5757
throw new JsonException($"Unexpected JSON token: {reader.TokenType} when deserializing {typeToConvert.Name}.");
5858
}
5959

60-
private object CreateObjectFromDictionary(Type type, Dictionary<string, object?> propertyValues)
60+
private object CreateObjectFromDictionary(Type type, Dictionary<string, object?> propertyValues, SearchPropEntry search)
6161
{
62-
var constructor = type.GetConstructors().FirstOrDefault();
63-
64-
// 🔥 If there's a constructor AND it has parameters, we use it (for records like QuotaUsage)
65-
if (constructor != null && constructor.GetParameters().Length > 0)
62+
// 🚀 If there's a constructor with parameters, use it
63+
if (search.ConstructorParameterMappings.Count > 0)
6664
{
67-
var parameters = constructor.GetParameters()
68-
.Select(p =>
69-
{
70-
if (propertyValues.TryGetValue(p.Name!, out var value))
71-
return value;
65+
var constructorArgs = new object?[search.ConstructorParameterMappings.Count];
7266

73-
var matchedKey = propertyValues.Keys
74-
.FirstOrDefault(k => string.Equals(k, p.Name, StringComparison.OrdinalIgnoreCase));
75-
76-
return matchedKey != null ? propertyValues[matchedKey] : GetDefaultValue(p.ParameterType);
77-
})
78-
.ToArray();
67+
foreach (var (paramName, index) in search.ConstructorParameterMappings)
68+
{
69+
if (propertyValues.TryGetValue(paramName, out var value))
70+
constructorArgs[index] = value;
71+
else
72+
constructorArgs[index] = GetDefaultValue(type.GetProperty(paramName)?.PropertyType ?? typeof(object));
73+
}
7974

80-
return constructor.Invoke(parameters);
75+
return search.InstanceCreator(constructorArgs) ?? throw new InvalidOperationException($"Failed to create instance of type {type.Name}.");
8176
}
8277

83-
// 🔥 Handle non-constructor classes (e.g., Person class)
84-
var instance = Activator.CreateInstance(type);
85-
if (instance == null)
86-
throw new InvalidOperationException($"Failed to create instance of type {type.Name}.");
78+
// 🚀 Use parameterless constructor
79+
var obj = search.InstanceCreator(Array.Empty<object?>()) ?? throw new InvalidOperationException($"Failed to create instance of type {type.Name}.");
8780

81+
// 🚀 Assign property values
8882
foreach (var (propName, value) in propertyValues)
8983
{
90-
var property = type.GetProperty(propName, BindingFlags.Public | BindingFlags.Instance);
91-
if (property != null && property.CanWrite)
84+
if (search.propertyEntries.TryGetValue(propName, out var propEntry))
9285
{
93-
property.SetValue(instance, value);
86+
propEntry.Setter(obj, value);
9487
}
9588
}
9689

97-
return instance;
90+
return obj;
9891
}
9992

100-
10193
private bool IsSimpleJsonElement(JsonElement element)
10294
{
10395
return element.ValueKind == JsonValueKind.String ||
@@ -130,7 +122,7 @@ private bool IsSimpleJsonNull(JsonElement element)
130122
if (reader.TokenType == JsonTokenType.EndObject)
131123
{
132124
// 🔥 Step 3: Convert the dictionary into the final object
133-
var result = CreateObjectFromDictionary(type, propertyValues);
125+
var result = CreateObjectFromDictionary(type, propertyValues, properties);
134126
return result;
135127
}
136128

@@ -407,17 +399,9 @@ private void SerializeComplexProperties(Utf8JsonWriter writer, object value, Dic
407399
if (mpe.NotMapped)
408400
continue;
409401

410-
object? propValue = null;
411-
try
412-
{
413-
propValue = mpe.Getter(value);
414-
}
415-
catch
416-
{
417-
propValue = null;
418-
}
402+
object? propValue = mpe.Getter(value);
419403

420-
if (mpe.PrimaryKey && IsDefaultValue(propValue))
404+
if (mpe.PrimaryKey && IsDefaultValue(propValue, mpe))
421405
{
422406
continue;
423407
}
@@ -445,14 +429,13 @@ private void SerializeComplexProperties(Utf8JsonWriter writer, object value, Dic
445429
}
446430
}
447431

448-
private bool IsDefaultValue(object? value)
432+
private bool IsDefaultValue(object? value, MagicPropertyEntry mpe)
449433
{
450-
if (value == null)
434+
if (value == null)
451435
return true;
452436

453-
Type type = value.GetType();
454-
var isDefault = value.Equals(Activator.CreateInstance(type));
455-
return isDefault;
437+
return value.Equals(mpe.DefaultValue); // ✅ Use precomputed default value
456438
}
439+
457440
}
458441
}

Magic.IndexedDb/Models/Structs/MagicPropertyEntry.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ public MagicPropertyEntry(PropertyInfo property, IColumnNamed? columnNamedAttrib
3232
var declaringType = property.DeclaringType!;
3333
var constructor = declaringType.GetConstructors().FirstOrDefault();
3434

35+
// 🔥 Precompute and cache the default value
36+
DefaultValue = property.PropertyType.IsValueType ? Activator.CreateInstance(property.PropertyType) : null;
37+
3538
// 🔥 Create delegates for performance 🔥
3639
if (property.CanRead)
3740
{
@@ -52,6 +55,8 @@ public MagicPropertyEntry(PropertyInfo property, IColumnNamed? columnNamedAttrib
5255
}
5356
}
5457

58+
public object? DefaultValue { get; }
59+
5560
/// <summary>
5661
/// If any Magic attribute was placed on a property. We never
5762
/// camel case if that's the current json setting. These must stay

0 commit comments

Comments
 (0)