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();