diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs index d56e41a512c..8ac652f44a1 100644 --- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs @@ -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) { diff --git a/src/EFCore/Metadata/ITypeBase.cs b/src/EFCore/Metadata/ITypeBase.cs index ec15c3665e6..dd5666c2ec3 100644 --- a/src/EFCore/Metadata/ITypeBase.cs +++ b/src/EFCore/Metadata/ITypeBase.cs @@ -200,43 +200,21 @@ public interface ITypeBase : IReadOnlyTypeBase, IAnnotatable /// Type members. new IEnumerable FindMembersInHierarchy(string name); + /// + /// Returns all members that may need a snapshot value when change tracking. + /// + /// The members. + IEnumerable GetSnapshottableMembers(); + /// /// Returns all properties that implement , including those on complex types. /// /// The properties. - IEnumerable 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 GetFlattenedProperties(); /// /// Returns all declared properties that implement , including those on complex types. /// /// The properties. - IEnumerable 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 GetFlattenedDeclaredProperties(); } diff --git a/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs b/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs index 898b9f7b406..e98b055ec5a 100644 --- a/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs +++ b/src/EFCore/Metadata/Internal/ClrPropertyGetterFactory.cs @@ -52,11 +52,11 @@ protected override IClrPropertyGetter CreateGeneric>(structuralReadExpression, structuralParameter).Compile(), Expression.Lambda>(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 @@ -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)) }); } diff --git a/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs b/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs index 73609297cb6..92dddfd55be 100644 --- a/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs +++ b/src/EFCore/Metadata/Internal/ClrPropertySetterFactory.cs @@ -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 diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index 43027ad6756..e9b012c5066 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -2255,6 +2255,15 @@ public virtual PropertyCounts Counts return entityType.CalculateCounts(); }); + /// + /// 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. + /// + public override IEnumerable GetSnapshottableMembers() + => base.GetSnapshottableMembers().Concat(GetNavigations()); + /// /// 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 diff --git a/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs b/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs index c8d93b191a1..cd2c5449b96 100644 --- a/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs +++ b/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs @@ -28,49 +28,6 @@ public interface IRuntimeEntityType : IEntityType, IRuntimeTypeBase /// Func RelationshipSnapshotFactory { get; } - /// - /// 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. - /// - IEnumerable GetSnapshottableMembers() - { - foreach (var property in GetProperties()) - { - yield return property; - } - - foreach (var property in ReturnComplexProperties(GetComplexProperties())) - { - yield return property; - } - - IEnumerable ReturnComplexProperties(IEnumerable 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; - } - } - /// /// 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 diff --git a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs index 1084726317d..6abbbd08aec 100644 --- a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs +++ b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs @@ -71,7 +71,7 @@ private static Func CreateCurrentValueGetter _properties; - - private readonly SortedDictionary _complexProperties = - new SortedDictionary(StringComparer.Ordinal); - - private readonly Dictionary _ignoredMembers - = new(StringComparer.Ordinal); + private readonly SortedDictionary _complexProperties = new(StringComparer.Ordinal); + private readonly Dictionary _ignoredMembers = new(StringComparer.Ordinal); private TypeBase? _baseType; private readonly SortedSet _directlyDerivedTypes = new(TypeBaseNameComparer.Instance); @@ -1310,6 +1306,84 @@ protected static IEnumerable ToEnumerable(T? element) ? Enumerable.Empty() : new[] { element }; + /// + /// Returns all members from this type and all nested complex types, if any. + /// + /// The properties. + public virtual IEnumerable GetFlattenedProperties() + { + foreach (var property in GetProperties()) + { + yield return property; + } + + foreach (var complexProperty in GetComplexProperties()) + { + foreach (var property in complexProperty.ComplexType.GetFlattenedProperties()) + { + yield return property; + } + } + } + + /// + /// Returns all members from this type and all nested complex types, if any. + /// + /// The properties. + public virtual IEnumerable GetFlattenedDeclaredProperties() + { + foreach (var property in GetDeclaredProperties()) + { + yield return property; + } + + foreach (var complexProperty in GetDeclaredComplexProperties()) + { + foreach (var property in complexProperty.ComplexType.GetFlattenedDeclaredProperties()) + { + yield return property; + } + } + } + + /// + /// 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. + /// + public virtual IEnumerable GetSnapshottableMembers() + { + foreach (var property in GetProperties()) + { + yield return property; + } + + foreach (var property in ReturnComplexProperties(GetComplexProperties())) + { + yield return property; + } + + static IEnumerable ReturnComplexProperties(IEnumerable 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; + } + } + } + } + /// /// 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 @@ -2130,4 +2204,25 @@ IEnumerable IConventionTypeBase.FindMembersInHierarchy( [DebuggerStepThrough] IEnumerable ITypeBase.FindMembersInHierarchy(string name) => FindMembersInHierarchy(name); + + /// + /// Returns all properties that implement , including those on complex types. + /// + /// The properties. + IEnumerable ITypeBase.GetSnapshottableMembers() + => GetSnapshottableMembers(); + + /// + /// Returns all properties that implement , including those on complex types. + /// + /// The properties. + IEnumerable ITypeBase.GetFlattenedProperties() + => GetFlattenedProperties(); + + /// + /// Returns all properties declared properties that implement , including those on complex types. + /// + /// The properties. + IEnumerable ITypeBase.GetFlattenedDeclaredProperties() + => GetFlattenedDeclaredProperties(); } diff --git a/src/EFCore/Metadata/RuntimeComplexType.cs b/src/EFCore/Metadata/RuntimeComplexType.cs index 7dee1893ddd..2f885c3c892 100644 --- a/src/EFCore/Metadata/RuntimeComplexType.cs +++ b/src/EFCore/Metadata/RuntimeComplexType.cs @@ -15,8 +15,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// 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; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -135,6 +137,51 @@ public virtual InstantiationBinding? ServiceOnlyConstructorBinding set => _serviceOnlyConstructorBinding = value; } + /// + /// 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. + /// + public override IEnumerable GetSnapshottableMembers() + { + return NonCapturingLazyInitializer.EnsureInitialized( + ref _snapshottableProperties, this, + static type => Create(type).ToArray()); + + static IEnumerable Create(RuntimeComplexType type) + { + foreach (var property in type.GetProperties()) + { + yield return property; + } + + foreach (var property in ReturnComplexProperties(type.GetComplexProperties())) + { + yield return property; + } + + static IEnumerable ReturnComplexProperties(IEnumerable 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; + } + } + } + } + } + /// /// Returns a string that represents the current object. /// diff --git a/src/EFCore/Metadata/RuntimeEntityType.cs b/src/EFCore/Metadata/RuntimeEntityType.cs index b409c61baa0..36b1bdb0fb9 100644 --- a/src/EFCore/Metadata/RuntimeEntityType.cs +++ b/src/EFCore/Metadata/RuntimeEntityType.cs @@ -58,6 +58,7 @@ private readonly SortedDictionary _triggers private Func? _storeGeneratedValuesFactory; private Func? _shadowValuesFactory; private Func? _emptyShadowValuesFactory; + private RuntimePropertyBase[]? _snapshottableProperties; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -749,9 +750,7 @@ private IEnumerable GetTriggers() /// [EntityFrameworkInternal] public virtual void SetRelationshipSnapshotFactory(Func factory) - { - _relationshipSnapshotFactory = factory; - } + => _relationshipSnapshotFactory = factory; /// /// Gets or sets the for the preferred constructor. @@ -798,6 +797,56 @@ public virtual InstantiationBinding? ServiceOnlyConstructorBinding public virtual PropertyCounts Counts => NonCapturingLazyInitializer.EnsureInitialized(ref _counts, this, static entityType => entityType.CalculateCounts()); + /// + /// 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. + /// + public override IEnumerable GetSnapshottableMembers() + { + return NonCapturingLazyInitializer.EnsureInitialized( + ref _snapshottableProperties, this, + static type => Create(type).ToArray()); + + static IEnumerable Create(RuntimeEntityType type) + { + foreach (var property in type.GetProperties()) + { + yield return property; + } + + foreach (var property in ReturnComplexProperties(type.GetComplexProperties())) + { + yield return property; + } + + static IEnumerable ReturnComplexProperties(IEnumerable 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; + } + } + } + + foreach (var navigation in type.GetNavigations()) + { + yield return navigation; + } + } + } + /// /// Returns a string that represents the current object. /// diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs index a96c92f059a..d10237f7ea2 100644 --- a/src/EFCore/Metadata/RuntimeTypeBase.cs +++ b/src/EFCore/Metadata/RuntimeTypeBase.cs @@ -27,6 +27,10 @@ public abstract class RuntimeTypeBase : AnnotatableBase, IRuntimeTypeBase private readonly bool _isPropertyBag; private readonly ChangeTrackingStrategy _changeTrackingStrategy; + // Warning: Never access these fields directly as access needs to be thread-safe + private RuntimeProperty[]? _flattenedProperties; + private RuntimeProperty[]? _flattenedDeclaredProperties; + /// /// 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 @@ -34,7 +38,7 @@ public abstract class RuntimeTypeBase : AnnotatableBase, IRuntimeTypeBase /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public RuntimeTypeBase( + protected RuntimeTypeBase( string name, [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, RuntimeModel model, @@ -474,6 +478,68 @@ private IEnumerable FindDerivedComplexProperties(string /// public abstract InstantiationBinding? ConstructorBinding { get; set; } + /// + /// Returns all members from this type and all nested complex types, if any. + /// + /// The properties. + public virtual IEnumerable GetFlattenedProperties() + { + return NonCapturingLazyInitializer.EnsureInitialized( + ref _flattenedProperties, this, + static type => Create(type).ToArray()); + + static IEnumerable Create(RuntimeTypeBase type) + { + foreach (var property in type.GetProperties()) + { + yield return property; + } + + foreach (var complexProperty in type.GetComplexProperties()) + { + foreach (var property in complexProperty.ComplexType.GetFlattenedProperties()) + { + yield return property; + } + } + } + } + + /// + /// Returns all members from this type and all nested complex types, if any. + /// + /// The properties. + public virtual IEnumerable GetFlattenedDeclaredProperties() + { + return NonCapturingLazyInitializer.EnsureInitialized( + ref _flattenedDeclaredProperties, this, + static type => Create(type).ToArray()); + + static IEnumerable Create(RuntimeTypeBase type) + { + foreach (var property in type.GetDeclaredProperties()) + { + yield return property; + } + + foreach (var complexProperty in type.GetDeclaredComplexProperties()) + { + foreach (var property in complexProperty.ComplexType.GetFlattenedDeclaredProperties()) + { + yield return property; + } + } + } + } + + /// + /// 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. + /// + public abstract IEnumerable GetSnapshottableMembers(); + /// /// 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 @@ -660,4 +726,25 @@ IEnumerable IReadOnlyTypeBase.FindMembersInHierarchy(stri [DebuggerStepThrough] IEnumerable ITypeBase.FindMembersInHierarchy(string name) => FindMembersInHierarchy(name); + + /// + /// Returns all members that may need a snapshot value when change tracking. + /// + /// The members. + IEnumerable ITypeBase.GetSnapshottableMembers() + => GetSnapshottableMembers(); + + /// + /// Returns all properties that implement , including those on complex types. + /// + /// The properties. + IEnumerable ITypeBase.GetFlattenedProperties() + => GetFlattenedProperties(); + + /// + /// Returns all properties declared properties that implement , including those on complex types. + /// + /// The properties. + IEnumerable ITypeBase.GetFlattenedDeclaredProperties() + => GetFlattenedDeclaredProperties(); } diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index c5506ba03c1..6544f0744e3 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -709,6 +709,15 @@ public IPropertyBase FindMember(string name) public IEnumerable FindMembersInHierarchy(string name) => throw new NotImplementedException(); + public IEnumerable GetSnapshottableMembers() + => throw new NotImplementedException(); + + public IEnumerable GetFlattenedProperties() + => throw new NotImplementedException(); + + public IEnumerable GetFlattenedDeclaredProperties() + => throw new NotImplementedException(); + IEnumerable IReadOnlyTypeBase.GetMembers() => throw new NotImplementedException();