Skip to content

Commit

Permalink
Memoize flattened property lookup
Browse files Browse the repository at this point in the history
  • Loading branch information
ajcvickers committed Aug 17, 2023
1 parent 1ca26eb commit 95adcf1
Show file tree
Hide file tree
Showing 13 changed files with 323 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ protected virtual Expression CreateSnapshotExpression(
}

var memberInfo = propertyBase.GetMemberInfo(forMaterialization: false, forSet: false);
var memberAccess = PropertyBase.CreateMemberAccess(propertyBase, entityVariable!, memberInfo, fromStructuralType: false);
var memberAccess = PropertyBase.CreateMemberAccess(propertyBase, entityVariable!, memberInfo, fromContainingType: false);

if (memberAccess.Type != propertyBase.ClrType)
{
Expand Down
38 changes: 8 additions & 30 deletions src/EFCore/Metadata/ITypeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,43 +200,21 @@ public interface ITypeBase : IReadOnlyTypeBase, IAnnotatable
/// <returns>Type members.</returns>
new IEnumerable<IPropertyBase> FindMembersInHierarchy(string name);

/// <summary>
/// Returns all members that may need a snapshot value when change tracking.
/// </summary>
/// <returns>The members.</returns>
IEnumerable<IPropertyBase> GetSnapshottableMembers();

/// <summary>
/// Returns all properties that implement <see cref="IProperty"/>, including those on complex types.
/// </summary>
/// <returns>The properties.</returns>
IEnumerable<IProperty> GetFlattenedProperties()
{
foreach (var property in GetProperties())
{
yield return property;
}

foreach (var complexProperty in GetComplexProperties())
{
foreach (var property in complexProperty.ComplexType.GetFlattenedProperties())
{
yield return property;
}
}
}
IEnumerable<IProperty> GetFlattenedProperties();

/// <summary>
/// Returns all declared properties that implement <see cref="IProperty"/>, including those on complex types.
/// </summary>
/// <returns>The properties.</returns>
IEnumerable<IProperty> GetFlattenedDeclaredProperties()
{
foreach (var property in GetDeclaredProperties())
{
yield return property;
}

foreach (var complexProperty in GetDeclaredComplexProperties())
{
foreach (var property in complexProperty.ComplexType.GetFlattenedDeclaredProperties())
{
yield return property;
}
}
}
IEnumerable<IProperty> GetFlattenedDeclaredProperties();
}
6 changes: 3 additions & 3 deletions src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ protected override IClrPropertyGetter CreateGeneric<TEntity, TStructuralType, TV
Expression.Lambda<Func<TStructuralType, TValue>>(structuralReadExpression, structuralParameter).Compile(),
Expression.Lambda<Func<TStructuralType, bool>>(hasStructuralSentinelValueExpression, structuralParameter).Compile());

Expression CreateReadExpression(ParameterExpression parameter, bool fromStructuralType)
Expression CreateReadExpression(ParameterExpression parameter, bool fromContainingType)
{
if (memberInfo.DeclaringType!.IsAssignableFrom(propertyDeclaringType))
{
return PropertyBase.CreateMemberAccess(propertyBase, parameter, memberInfo, fromStructuralType);
return PropertyBase.CreateMemberAccess(propertyBase, parameter, memberInfo, fromContainingType);
}

// This path handles properties that exist only on proxy types and so only exist if the instance is a proxy
Expand All @@ -72,7 +72,7 @@ Expression CreateReadExpression(ParameterExpression parameter, bool fromStructur
Expression.Condition(
Expression.ReferenceEqual(converted, Expression.Constant(null)),
Expression.Default(memberInfo.GetMemberType()),
PropertyBase.CreateMemberAccess(propertyBase, converted, memberInfo, fromStructuralType))
PropertyBase.CreateMemberAccess(propertyBase, converted, memberInfo, fromContainingType))
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Expression CreateMemberAssignment(IPropertyBase? property, Expression typeParame
complexType.ComplexProperty,
typeParameter,
complexType.ComplexProperty.GetMemberInfo(forMaterialization: false, forSet: false),
fromStructuralType: false);
fromContainingType: false);
}

return propertyBase?.IsIndexerProperty() == true
Expand Down
9 changes: 9 additions & 0 deletions src/EFCore/Metadata/Internal/EntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2255,6 +2255,15 @@ public virtual PropertyCounts Counts
return entityType.CalculateCounts();
});

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public override IEnumerable<PropertyBase> GetSnapshottableMembers()
=> base.GetSnapshottableMembers().Concat(GetNavigations());

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
43 changes: 0 additions & 43 deletions src/EFCore/Metadata/Internal/IRuntimeEntityType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,49 +28,6 @@ public interface IRuntimeEntityType : IEntityType, IRuntimeTypeBase
/// </summary>
Func<InternalEntityEntry, ISnapshot> RelationshipSnapshotFactory { get; }

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
IEnumerable<IPropertyBase> GetSnapshottableMembers()
{
foreach (var property in GetProperties())
{
yield return property;
}

foreach (var property in ReturnComplexProperties(GetComplexProperties()))
{
yield return property;
}

IEnumerable<IPropertyBase> ReturnComplexProperties(IEnumerable<IComplexProperty> complexProperties)
{
foreach (var complexProperty in complexProperties)
{
yield return complexProperty;

var complexType = complexProperty.ComplexType;
foreach (var property in complexType.GetProperties())
{
yield return property;
}

foreach (var property in ReturnComplexProperties(complexType.GetComplexProperties()))
{
yield return property;
}
}
}

foreach (var navigation in GetNavigations())
{
yield return navigation;
}
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
2 changes: 1 addition & 1 deletion src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ private static Func<IInternalEntry, TProperty> CreateCurrentValueGetter<TPropert

var memberInfo = propertyBase.GetMemberInfo(forMaterialization: false, forSet: false);

currentValueExpression = PropertyBase.CreateMemberAccess(propertyBase, convertedExpression, memberInfo, fromStructuralType: false);
currentValueExpression = PropertyBase.CreateMemberAccess(propertyBase, convertedExpression, memberInfo, fromContainingType: false);
hasSentinelValueExpression = currentValueExpression.MakeHasSentinel(propertyBase);

if (currentValueExpression.Type != typeof(TProperty))
Expand Down
6 changes: 3 additions & 3 deletions src/EFCore/Metadata/Internal/PropertyBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ public static Expression CreateMemberAccess(
IPropertyBase? property,
Expression instanceExpression,
MemberInfo memberInfo,
bool fromStructuralType)
bool fromContainingType)
{
if (property?.IsIndexerProperty() == true)
{
Expand All @@ -446,14 +446,14 @@ public static Expression CreateMemberAccess(
return expression;
}

if (!fromStructuralType
if (!fromContainingType
&& property?.DeclaringType is IComplexType complexType)
{
instanceExpression = CreateMemberAccess(
complexType.ComplexProperty,
instanceExpression,
complexType.ComplexProperty.GetMemberInfo(forMaterialization: false, forSet: false),
fromStructuralType);
fromContainingType);

// TODO: Handle null/default complex types #31376
// if (!instanceExpression.Type.IsValueType)
Expand Down
107 changes: 101 additions & 6 deletions src/EFCore/Metadata/Internal/TypeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
public abstract class TypeBase : ConventionAnnotatable, IMutableTypeBase, IConventionTypeBase, ITypeBase
{
private readonly SortedDictionary<string, Property> _properties;

private readonly SortedDictionary<string, ComplexProperty> _complexProperties =
new SortedDictionary<string, ComplexProperty>(StringComparer.Ordinal);

private readonly Dictionary<string, ConfigurationSource> _ignoredMembers
= new(StringComparer.Ordinal);
private readonly SortedDictionary<string, ComplexProperty> _complexProperties = new(StringComparer.Ordinal);
private readonly Dictionary<string, ConfigurationSource> _ignoredMembers = new(StringComparer.Ordinal);

private TypeBase? _baseType;
private readonly SortedSet<TypeBase> _directlyDerivedTypes = new(TypeBaseNameComparer.Instance);
Expand Down Expand Up @@ -1310,6 +1306,84 @@ protected static IEnumerable<T> ToEnumerable<T>(T? element)
? Enumerable.Empty<T>()
: new[] { element };

/// <summary>
/// Returns all <see cref="IProperty"/> members from this type and all nested complex types, if any.
/// </summary>
/// <returns>The properties.</returns>
public virtual IEnumerable<Property> GetFlattenedProperties()
{
foreach (var property in GetProperties())
{
yield return property;
}

foreach (var complexProperty in GetComplexProperties())
{
foreach (var property in complexProperty.ComplexType.GetFlattenedProperties())
{
yield return property;
}
}
}

/// <summary>
/// Returns all <see cref="IProperty"/> members from this type and all nested complex types, if any.
/// </summary>
/// <returns>The properties.</returns>
public virtual IEnumerable<Property> GetFlattenedDeclaredProperties()
{
foreach (var property in GetDeclaredProperties())
{
yield return property;
}

foreach (var complexProperty in GetDeclaredComplexProperties())
{
foreach (var property in complexProperty.ComplexType.GetFlattenedDeclaredProperties())
{
yield return property;
}
}
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual IEnumerable<PropertyBase> GetSnapshottableMembers()
{
foreach (var property in GetProperties())
{
yield return property;
}

foreach (var property in ReturnComplexProperties(GetComplexProperties()))
{
yield return property;
}

static IEnumerable<PropertyBase> ReturnComplexProperties(IEnumerable<ComplexProperty> complexProperties)
{
foreach (var complexProperty in complexProperties)
{
yield return complexProperty;

var complexType = complexProperty.ComplexType;
foreach (var property in ((IComplexType)complexType).GetProperties())
{
yield return (PropertyBase)property;
}

foreach (var property in ReturnComplexProperties(complexType.GetComplexProperties()))
{
yield return property;
}
}
}
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down Expand Up @@ -2130,4 +2204,25 @@ IEnumerable<IConventionPropertyBase> IConventionTypeBase.FindMembersInHierarchy(
[DebuggerStepThrough]
IEnumerable<IPropertyBase> ITypeBase.FindMembersInHierarchy(string name)
=> FindMembersInHierarchy(name);

/// <summary>
/// Returns all properties that implement <see cref="IProperty"/>, including those on complex types.
/// </summary>
/// <returns>The properties.</returns>
IEnumerable<IPropertyBase> ITypeBase.GetSnapshottableMembers()
=> GetSnapshottableMembers();

/// <summary>
/// Returns all properties that implement <see cref="IProperty"/>, including those on complex types.
/// </summary>
/// <returns>The properties.</returns>
IEnumerable<IProperty> ITypeBase.GetFlattenedProperties()
=> GetFlattenedProperties();

/// <summary>
/// Returns all properties declared properties that implement <see cref="IProperty"/>, including those on complex types.
/// </summary>
/// <returns>The properties.</returns>
IEnumerable<IProperty> ITypeBase.GetFlattenedDeclaredProperties()
=> GetFlattenedDeclaredProperties();
}
47 changes: 47 additions & 0 deletions src/EFCore/Metadata/RuntimeComplexType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata;
/// </remarks>
public class RuntimeComplexType : RuntimeTypeBase, IRuntimeComplexType
{
// Warning: Never access these fields directly as access needs to be thread-safe
private InstantiationBinding? _constructorBinding;
private InstantiationBinding? _serviceOnlyConstructorBinding;
private RuntimePropertyBase[]? _snapshottableProperties;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -135,6 +137,51 @@ public virtual InstantiationBinding? ServiceOnlyConstructorBinding
set => _serviceOnlyConstructorBinding = value;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public override IEnumerable<RuntimePropertyBase> GetSnapshottableMembers()
{
return NonCapturingLazyInitializer.EnsureInitialized(
ref _snapshottableProperties, this,
static type => Create(type).ToArray());

static IEnumerable<RuntimePropertyBase> Create(RuntimeComplexType type)
{
foreach (var property in type.GetProperties())
{
yield return property;
}

foreach (var property in ReturnComplexProperties(type.GetComplexProperties()))
{
yield return property;
}

static IEnumerable<RuntimePropertyBase> ReturnComplexProperties(IEnumerable<RuntimeComplexProperty> complexProperties)
{
foreach (var complexProperty in complexProperties)
{
yield return complexProperty;

var complexType = complexProperty.ComplexType;
foreach (var property in ((IComplexType)complexType).GetProperties())
{
yield return (RuntimePropertyBase)property;
}

foreach (var property in ReturnComplexProperties(complexType.GetComplexProperties()))
{
yield return property;
}
}
}
}
}

/// <summary>
/// Returns a string that represents the current object.
/// </summary>
Expand Down
Loading

0 comments on commit 95adcf1

Please sign in to comment.