diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs
index e4b0d5fecec..53d434431ad 100644
--- a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs
+++ b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs
@@ -45,6 +45,26 @@ protected override void ValidateEntityType(
ValidateDiscriminatorMappings(entityType, logger);
}
+ ///
+ /// 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.
+ ///
+ protected override void ValidateAutoLoaded(
+ IProperty property,
+ ITypeBase structuralType,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidateAutoLoaded(property, structuralType, logger);
+
+ if (!property.IsAutoLoaded)
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.AutoLoadedCosmosProperty(property.Name, structuralType.DisplayName()));
+ }
+ }
+
///
/// 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.Cosmos/Properties/CosmosStrings.Designer.cs b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
index a93e93037ef..995c5cf7e94 100644
--- a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
@@ -31,6 +31,14 @@ public static string AnalyticalTTLMismatch(object? ttl1, object? entityType1, ob
GetString("AnalyticalTTLMismatch", nameof(ttl1), nameof(entityType1), nameof(entityType2), nameof(ttl2), nameof(container)),
ttl1, entityType1, entityType2, ttl2, container);
+ ///
+ /// The property '{property}' on type '{type}' cannot be configured as not auto-loaded. The Cosmos provider stores entire documents as JSON, so partial property loading is not supported.
+ ///
+ public static string AutoLoadedCosmosProperty(object? property, object? type)
+ => string.Format(
+ GetString("AutoLoadedCosmosProperty", nameof(property), nameof(type)),
+ property, type);
+
///
/// The type '{givenType}' cannot be mapped as a dictionary because it does not implement '{dictionaryType}'.
///
diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.resx b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
index 3e74e0e56e8..b3ec0d9d9b0 100644
--- a/src/EFCore.Cosmos/Properties/CosmosStrings.resx
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
@@ -120,6 +120,9 @@
The time to live for analytical store was configured to '{ttl1}' on '{entityType1}', but on '{entityType2}' it was configured to '{ttl2}'. All entity types mapped to the same container '{container}' must be configured with the same time to live for analytical store.
+
+ The property '{property}' on type '{type}' cannot be configured as not auto-loaded. The Cosmos provider doesn't support partial property loading.
+
The type '{givenType}' cannot be mapped as a dictionary because it does not implement '{dictionaryType}'.
diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
index 258ebc52c3e..b02f95d8df4 100644
--- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
@@ -1199,6 +1199,13 @@ private void Create(
.Append(_code.UnknownLiteral(sentinel));
}
+ if (!property.IsAutoLoaded)
+ {
+ mainBuilder.AppendLine(",")
+ .Append("autoLoaded: ")
+ .Append(_code.Literal(false));
+ }
+
var jsonValueReaderWriterType = (Type?)property[CoreAnnotationNames.JsonValueReaderWriterType];
if (jsonValueReaderWriterType != null)
{
diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
index a4b56cd2ae3..10168e4dd08 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
@@ -179,6 +179,22 @@ protected override void ValidateProperty(
ValidateBoolWithDefaults(property, logger);
}
+ ///
+ protected override void ValidateAutoLoaded(
+ IProperty property,
+ ITypeBase structuralType,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidateAutoLoaded(property, structuralType, logger);
+
+ if (!property.IsAutoLoaded
+ && structuralType.IsMappedToJson())
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.AutoLoadedJsonProperty(property.Name, structuralType.DisplayName()));
+ }
+ }
+
///
protected override void ValidateKey(
IKey key,
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index 879e4b3c93f..d1324086fae 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -34,6 +34,14 @@ public static string AbstractTpc(object? entityType, object? storeObject)
GetString("AbstractTpc", nameof(entityType), nameof(storeObject)),
entityType, storeObject);
+ ///
+ /// The property '{property}' on type '{type}' is mapped to a JSON entity and cannot be configured as not auto-loaded. JSON-mapped entities are always loaded as a unit.
+ ///
+ public static string AutoLoadedJsonProperty(object? property, object? type)
+ => string.Format(
+ GetString("AutoLoadedJsonProperty", nameof(property), nameof(type)),
+ property, type);
+
///
/// Unable to deserialize a sequence from model metadata. See inner exception for details.
///
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index 1dbddd15833..37b6701ce00 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -120,6 +120,9 @@
The entity type '{entityType}' cannot be instantiated because its corresponding CLR type is abstract, but the entity type was mapped to '{storeObject}' using the 'TPC' mapping strategy. Only instantiable types should be mapped. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.
+
+ The property '{property}' on type '{type}' is mapped to a JSON entity and cannot be configured as not auto-loaded. JSON-mapped entities are always loaded as a unit.
+
Unable to deserialize a sequence from model metadata. See inner exception for details.Obsolete
diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs
index 8361516677a..184b931d677 100644
--- a/src/EFCore.Relational/Update/ModificationCommand.cs
+++ b/src/EFCore.Relational/Update/ModificationCommand.cs
@@ -408,10 +408,11 @@ void HandleColumn(
// Note that for stored procedures we always need to send all parameters, regardless of whether the property
// actually changed.
writeValue = !columnPropagator?.TryPropagate(columnMapping, entry)
- ?? (entry.EntityState == EntityState.Added
- || entry.EntityState == EntityState.Deleted
- || ColumnModification.IsModified(entry, property)
- || StoreStoredProcedure is not null);
+ ?? (entry.IsLoaded(property)
+ && (entry.EntityState == EntityState.Added
+ || entry.EntityState == EntityState.Deleted
+ || ColumnModification.IsModified(entry, property)
+ || StoreStoredProcedure is not null));
}
}
@@ -1207,6 +1208,7 @@ public void RecordValue(IColumnMapping mapping, IUpdateEntry entry)
{
case EntityState.Modified:
if (!_write
+ && entry.IsLoaded(property)
&& Update.ColumnModification.IsModified(entry, property))
{
_write = true;
diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs
index 74b15342df1..9396be1c3f0 100644
--- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs
+++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs
@@ -50,10 +50,11 @@ protected override void ValidateProperty(
ITypeBase structuralType,
IDiagnosticsLogger logger)
{
+ ValidateVectorProperty(property, logger);
+
base.ValidateProperty(property, structuralType, logger);
ValidateDecimalColumn(property, logger);
- ValidateVectorProperty(property, logger);
}
///
diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerAutoLoadConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerAutoLoadConvention.cs
new file mode 100644
index 00000000000..2ccbeb8f21b
--- /dev/null
+++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerAutoLoadConvention.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Data.SqlTypes;
+using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
+
+///
+/// A convention that configures SQL Server vector properties as not auto-loaded.
+///
+///
+/// See Model building conventions for more information and examples.
+///
+/// Parameter object containing dependencies for this convention.
+public class SqlServerAutoLoadConvention(ProviderConventionSetBuilderDependencies dependencies) : AutoLoadConvention(dependencies)
+{
+ ///
+ protected override bool ShouldBeAutoLoaded(IConventionProperty property)
+ {
+ var typeMapping = property.FindTypeMapping();
+ if (typeMapping is not null)
+ {
+ return typeMapping is not SqlServerVectorTypeMapping;
+ }
+
+ // Fall back to CLR type check when type mapping hasn't been resolved yet.
+ // If there's a value converter, the CLR type may not reflect the store type,
+ // so we can only check for SqlVector<> when there's no converter.
+ return property.GetValueConverter() is not null
+ || property.ClrType.TryGetElementType(typeof(SqlVector<>)) is null;
+ }
+}
diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs
index 7cc34385776..753bc9f2090 100644
--- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs
+++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs
@@ -63,6 +63,7 @@ public override ConventionSet CreateConventionSet()
conventionSet.Replace(new SqlServerRuntimeModelConvention(Dependencies, RelationalDependencies));
conventionSet.Replace(
new SqlServerSharedTableConvention(Dependencies, RelationalDependencies));
+ conventionSet.Replace(new SqlServerAutoLoadConvention(Dependencies));
var sqlServerTemporalConvention = new SqlServerTemporalConvention(Dependencies, RelationalDependencies);
ConventionSet.AddBefore(
diff --git a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs
index 9fa75bc42c8..af2b1cd339b 100644
--- a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs
+++ b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs
@@ -281,6 +281,17 @@ private bool LocalDetectChanges(InternalEntryBase entry)
var changesFound = false;
foreach (var property in entry.StructuralType.GetFlattenedProperties())
{
+ if (!entry.IsLoaded(property))
+ {
+ if (!property.GetValueComparer().Equals(entry[property], property.Sentinel))
+ {
+ entry.SetPropertyModified(property);
+ changesFound = true;
+ }
+
+ continue;
+ }
+
if (property.GetOriginalValueIndex() >= 0
&& !entry.IsModified(property)
&& !entry.IsConceptualNull(property))
@@ -339,9 +350,10 @@ public virtual bool DetectComplexPropertyChange(InternalEntryBase entry, IComple
{
foreach (var innerProperty in complexProperty.ComplexType.GetFlattenedProperties())
{
- // Only mark properties that are tracked and can be modified
+ // Only mark properties that are tracked, can be modified, and are loaded
if (innerProperty.GetOriginalValueIndex() >= 0
- && innerProperty.GetAfterSaveBehavior() == PropertySaveBehavior.Save)
+ && innerProperty.GetAfterSaveBehavior() == PropertySaveBehavior.Save
+ && entry.IsLoaded(innerProperty))
{
entry.SetPropertyModified(innerProperty);
}
@@ -792,6 +804,11 @@ public bool DetectComplexCollectionChanges(InternalEntryBase entry, IComplexProp
///
public bool DetectValueChange(IInternalEntry entry, IProperty property)
{
+ if (!entry.IsLoaded(property))
+ {
+ return false;
+ }
+
var current = entry[property];
var original = entry.GetOriginalValue(property);
diff --git a/src/EFCore/ChangeTracking/Internal/IInternalEntry.cs b/src/EFCore/ChangeTracking/Internal/IInternalEntry.cs
index 0acc3977fc0..eac3ccbdd97 100644
--- a/src/EFCore/ChangeTracking/Internal/IInternalEntry.cs
+++ b/src/EFCore/ChangeTracking/Internal/IInternalEntry.cs
@@ -206,6 +206,22 @@ public object Entity
///
bool IsModified(IProperty 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.
+ ///
+ bool IsLoaded(IProperty 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.
+ ///
+ void SetIsLoaded(IProperty property, bool loaded);
+
///
/// 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/ChangeTracking/Internal/InternalEntryBase.StateData.cs b/src/EFCore/ChangeTracking/Internal/InternalEntryBase.StateData.cs
index 1a1eed21824..01320bd8ff4 100644
--- a/src/EFCore/ChangeTracking/Internal/InternalEntryBase.StateData.cs
+++ b/src/EFCore/ChangeTracking/Internal/InternalEntryBase.StateData.cs
@@ -59,7 +59,20 @@ protected internal enum PropertyFlag
/// 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.
///
- IsStoreGenerated = 5
+ IsStoreGenerated = 5,
+
+ ///
+ /// Tracks whether a property value has NOT been loaded (for properties with equal
+ /// to ). The default (false) means loaded; set to true for not-auto-loaded properties.
+ /// Distinct from which tracks navigation loaded state.
+ ///
+ ///
+ /// 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.
+ ///
+ IsPropertyNotLoaded = 6
}
///
@@ -68,6 +81,20 @@ protected internal enum PropertyFlag
/// 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.
///
+ ///
+ ///
+ /// Stores per-slot flags in a bit array. Each slot gets 8 bits.
+ /// The total number of slots is max(propertyCount, navigationCount).
+ ///
+ ///
+ /// Property flags use bits: Modified (0), Null (1), Unknown (2), IsTemporary (4),
+ /// IsStoreGenerated (5), IsPropertyNotLoaded (6). Bit 7 is unused/reserved.
+ ///
+ ///
+ /// Navigation flags use bit: IsLoaded (3). Since property flags and navigation flags occupy
+ /// distinct bits within the same 8-bit slot, they share the same slot array without conflict.
+ ///
+ ///
protected internal readonly struct StateData
{
private const int BitsPerInt = 32;
@@ -90,7 +117,7 @@ protected internal readonly struct StateData
///
public StateData(int propertyCount, int navigationCount)
{
- // Properties and navigations use different flags
+ // Properties and navigations share the same bit array, but use different bits within each slot
var bitsNumber = Math.Max(propertyCount, navigationCount) * BitsForPropertyFlags + BitsForAdditionalState - 1;
_bits = new int[(bitsNumber / BitsPerInt) + 1];
}
diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs b/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs
index 99dd4e0010e..e0bf02dda27 100644
--- a/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs
+++ b/src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs
@@ -232,6 +232,20 @@ protected virtual void SetEntityState(EntityState oldState, EntityState newState
{
var structuralType = StructuralType;
+ // When transitioning from Detached, check not-auto-loaded properties:
+ // if their current value equals the sentinel, mark them as not-loaded.
+ if (oldState == EntityState.Detached)
+ {
+ foreach (var property in structuralType.GetFlattenedProperties())
+ {
+ if (!property.IsAutoLoaded)
+ {
+ _stateData.FlagProperty(
+ property.GetIndex(), PropertyFlag.IsPropertyNotLoaded, HasSentinelValue(property));
+ }
+ }
+ }
+
// Prevent temp values from becoming permanent values
if (oldState == EntityState.Added
&& newState != EntityState.Added
@@ -264,6 +278,13 @@ protected virtual void SetEntityState(EntityState oldState, EntityState newState
{
_stateData.FlagProperty(property.GetIndex(), PropertyFlag.Modified, isFlagged: false);
}
+
+ // Properties that are not loaded (IsAutoLoaded = false and not yet loaded) should
+ // not be marked as modified when the entity state is set to Modified.
+ if (_stateData.IsPropertyFlagged(property.GetIndex(), PropertyFlag.IsPropertyNotLoaded))
+ {
+ _stateData.FlagProperty(property.GetIndex(), PropertyFlag.Modified, isFlagged: false);
+ }
}
foreach (var complexCollection in structuralType.GetFlattenedComplexProperties())
@@ -363,6 +384,15 @@ public virtual void MarkUnchangedFromQuery()
{
EntityState = EntityState.Unchanged;
+ foreach (var property in StructuralType.GetFlattenedProperties())
+ {
+ if (!property.IsAutoLoaded)
+ {
+ _stateData.FlagProperty(
+ property.GetIndex(), PropertyFlag.IsPropertyNotLoaded, HasSentinelValue(property));
+ }
+ }
+
foreach (var complexCollection in StructuralType.GetFlattenedComplexProperties())
{
if (complexCollection.IsCollection)
@@ -407,7 +437,8 @@ public bool IsModified(IProperty property)
return _stateData.EntityState == EntityState.Modified
&& _stateData.IsPropertyFlagged(propertyIndex, PropertyFlag.Modified)
- && !_stateData.IsPropertyFlagged(propertyIndex, PropertyFlag.Unknown);
+ && !_stateData.IsPropertyFlagged(propertyIndex, PropertyFlag.Unknown)
+ && !_stateData.IsPropertyFlagged(propertyIndex, PropertyFlag.IsPropertyNotLoaded);
}
///
@@ -423,6 +454,32 @@ public bool IsUnknown(IProperty property)
return _stateData.IsPropertyFlagged(property.GetIndex(), PropertyFlag.Unknown);
}
+ ///
+ /// 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 bool IsLoaded(IProperty property)
+ {
+ StructuralType.CheckContains(property);
+
+ return !_stateData.IsPropertyFlagged(property.GetIndex(), PropertyFlag.IsPropertyNotLoaded);
+ }
+
+ ///
+ /// 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 void SetIsLoaded(IProperty property, bool loaded)
+ {
+ StructuralType.CheckContains(property);
+
+ _stateData.FlagProperty(property.GetIndex(), PropertyFlag.IsPropertyNotLoaded, !loaded);
+ }
+
///
/// 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
@@ -440,6 +497,7 @@ public void SetPropertyModified(
var propertyIndex = property.GetIndex();
_stateData.FlagProperty(propertyIndex, PropertyFlag.Unknown, false);
+ _stateData.FlagProperty(propertyIndex, PropertyFlag.IsPropertyNotLoaded, false);
var currentState = _stateData.EntityState;
@@ -1626,6 +1684,7 @@ void CheckForNullCollection(IProperty property)
{
if (property.GetElementType() != null
&& !property.IsNullable
+ && IsLoaded(property)
&& GetCurrentValue(property) == null
&& (property.DeclaringType is not IComplexType complexType
|| GetCurrentValue(complexType.ComplexProperty) != null))
diff --git a/src/EFCore/ChangeTracking/PropertyEntry.cs b/src/EFCore/ChangeTracking/PropertyEntry.cs
index ea2bcd73f25..230d0d8a57e 100644
--- a/src/EFCore/ChangeTracking/PropertyEntry.cs
+++ b/src/EFCore/ChangeTracking/PropertyEntry.cs
@@ -66,6 +66,21 @@ public virtual bool IsTemporary
}
}
+ ///
+ /// Gets or sets a value indicating whether the value of this property has been loaded
+ /// from the database. When , the property value is considered
+ /// not present and will be excluded from update operations.
+ ///
+ ///
+ /// See Accessing tracked entities in EF Core for more information and
+ /// examples.
+ ///
+ public virtual bool IsLoaded
+ {
+ get => InternalEntry.IsLoaded(Metadata);
+ set => InternalEntry.SetIsLoaded(Metadata, value);
+ }
+
///
/// Gets the metadata that describes the facets of this property and how it maps to the database.
///
diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs
index d29918dce95..ceddc40f67a 100644
--- a/src/EFCore/Infrastructure/ModelValidator.cs
+++ b/src/EFCore/Infrastructure/ModelValidator.cs
@@ -167,6 +167,51 @@ protected virtual void ValidateProperty(
{
ValidateTypeMapping(property, logger);
ValidatePrimitiveCollection(property, logger);
+ ValidateAutoLoaded(property, structuralType, logger);
+ }
+
+ ///
+ /// Validates that a property configured as not auto-loaded is not a key, foreign key, concurrency token or discriminator.
+ ///
+ /// The property to validate.
+ /// The structural type containing the property.
+ /// The logger to use.
+ protected virtual void ValidateAutoLoaded(
+ IProperty property,
+ ITypeBase structuralType,
+ IDiagnosticsLogger logger)
+ {
+ if (property.IsAutoLoaded)
+ {
+ return;
+ }
+
+ var typeName = structuralType.DisplayName();
+
+ if (property.IsKey())
+ {
+ throw new InvalidOperationException(
+ CoreStrings.AutoLoadedKeyProperty(property.Name, typeName));
+ }
+
+ if (property.IsForeignKey())
+ {
+ throw new InvalidOperationException(
+ CoreStrings.AutoLoadedForeignKeyProperty(property.Name, typeName));
+ }
+
+ if (property.IsConcurrencyToken)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.AutoLoadedConcurrencyTokenProperty(property.Name, typeName));
+ }
+
+ if (structuralType is IEntityType entityType
+ && entityType.FindDiscriminatorProperty() == property)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.AutoLoadedDiscriminatorProperty(property.Name, typeName));
+ }
}
///
diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs
index 45bd923102d..1a24761eac9 100644
--- a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs
+++ b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs
@@ -104,6 +104,29 @@ public interface IConventionPropertyBuilder : IConventionPropertyBaseBuilder if the property can be configured as a concurrency token.
bool CanSetIsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation = false);
+ ///
+ /// Configures whether this property is automatically loaded when the entity is queried from the database.
+ ///
+ ///
+ /// A value indicating whether this property is automatically loaded.
+ /// to reset to default.
+ ///
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// otherwise.
+ ///
+ IConventionPropertyBuilder? IsAutoLoaded(bool? autoLoaded, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns a value indicating whether the property can be configured as auto-loaded
+ /// from the current configuration source.
+ ///
+ /// A value indicating whether this property is auto-loaded.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the auto-loaded can be configured for this property.
+ bool CanSetIsAutoLoaded(bool? autoLoaded, bool fromDataAnnotation = false);
+
///
/// Configures the value that will be used to determine if the property has been set or not. If the property is set to the
/// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of
diff --git a/src/EFCore/Metadata/Conventions/AutoLoadConvention.cs b/src/EFCore/Metadata/Conventions/AutoLoadConvention.cs
new file mode 100644
index 00000000000..0adc81b5129
--- /dev/null
+++ b/src/EFCore/Metadata/Conventions/AutoLoadConvention.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
+
+///
+/// A convention that configures properties based on provider-specific heuristics to determine
+/// whether they should be auto-loaded.
+/// Override to customize which properties are excluded from automatic loading.
+///
+///
+/// See Model building conventions for more information and examples.
+///
+/// Parameter object containing dependencies for this convention.
+public class AutoLoadConvention(ProviderConventionSetBuilderDependencies dependencies) : IModelFinalizingConvention
+{
+
+ ///
+ /// Dependencies for this service.
+ ///
+ protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } = dependencies;
+
+ ///
+ public virtual void ProcessModelFinalizing(
+ IConventionModelBuilder modelBuilder,
+ IConventionContext context)
+ {
+ foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
+ {
+ ProcessType(entityType);
+ }
+ }
+
+ private void ProcessType(IConventionTypeBase typeBase)
+ {
+ foreach (var property in typeBase.GetDeclaredProperties())
+ {
+ if (property.GetIsAutoLoadedConfigurationSource() == null
+ && !ShouldBeAutoLoaded(property))
+ {
+ property.Builder.IsAutoLoaded(false, fromDataAnnotation: false);
+ }
+ }
+
+ foreach (var complexProperty in typeBase.GetDeclaredComplexProperties())
+ {
+ // Properties on complex collections are always auto-loaded
+ if (!complexProperty.IsCollection)
+ {
+ ProcessType(complexProperty.ComplexType);
+ }
+ }
+ }
+
+ ///
+ /// Returns a value indicating whether the given property should be auto-loaded.
+ ///
+ /// The property to check.
+ /// if the property should be auto-loaded; otherwise.
+ protected virtual bool ShouldBeAutoLoaded(IConventionProperty property)
+ => true;
+}
diff --git a/src/EFCore/Metadata/Conventions/ConventionSet.cs b/src/EFCore/Metadata/Conventions/ConventionSet.cs
index 497c9faf075..ca8782b5d95 100644
--- a/src/EFCore/Metadata/Conventions/ConventionSet.cs
+++ b/src/EFCore/Metadata/Conventions/ConventionSet.cs
@@ -262,6 +262,11 @@ public class ConventionSet
///
public virtual List PropertyNullabilityChangedConventions { get; } = [];
+ ///
+ /// Conventions to run when the auto-load value of a property is changed.
+ ///
+ public virtual List PropertyAutoLoadChangedConventions { get; } = [];
+
///
/// Conventions to run when the field of a property is changed.
///
@@ -603,6 +608,12 @@ public virtual void Replace(TImplementation newConvention)
PropertyNullabilityChangedConventions.Add(propertyNullabilityChangedConvention);
}
+ if (newConvention is IPropertyAutoLoadChangedConvention propertyAutoLoadChangedConvention
+ && !Replace(PropertyAutoLoadChangedConventions, propertyAutoLoadChangedConvention, oldConventionType))
+ {
+ PropertyAutoLoadChangedConventions.Add(propertyAutoLoadChangedConvention);
+ }
+
if (newConvention is IPropertyFieldChangedConvention propertyFieldChangedConvention
&& !Replace(PropertyFieldChangedConventions, propertyFieldChangedConvention, oldConventionType))
{
@@ -937,6 +948,11 @@ public virtual void Add(IConvention convention)
PropertyNullabilityChangedConventions.Add(propertyNullabilityChangedConvention);
}
+ if (convention is IPropertyAutoLoadChangedConvention propertyAutoLoadChangedConvention)
+ {
+ PropertyAutoLoadChangedConventions.Add(propertyAutoLoadChangedConvention);
+ }
+
if (convention is IPropertyFieldChangedConvention propertyFieldChangedConvention)
{
PropertyFieldChangedConventions.Add(propertyFieldChangedConvention);
@@ -1280,6 +1296,11 @@ public virtual void Remove(Type conventionType)
Remove(PropertyNullabilityChangedConventions, conventionType);
}
+ if (typeof(IPropertyAutoLoadChangedConvention).IsAssignableFrom(conventionType))
+ {
+ Remove(PropertyAutoLoadChangedConventions, conventionType);
+ }
+
if (typeof(IPropertyFieldChangedConvention).IsAssignableFrom(conventionType))
{
Remove(PropertyFieldChangedConventions, conventionType);
diff --git a/src/EFCore/Metadata/Conventions/IPropertyAutoLoadChangedConvention.cs b/src/EFCore/Metadata/Conventions/IPropertyAutoLoadChangedConvention.cs
new file mode 100644
index 00000000000..6b5555994ab
--- /dev/null
+++ b/src/EFCore/Metadata/Conventions/IPropertyAutoLoadChangedConvention.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
+
+///
+/// Represents an operation that should be performed when the
+/// value for a property is changed.
+///
+///
+/// See Model building conventions for more information and examples.
+///
+public interface IPropertyAutoLoadChangedConvention : IConvention
+{
+ ///
+ /// Called after the value for a property is changed.
+ ///
+ /// The builder for the property.
+ /// Additional information associated with convention execution.
+ void ProcessPropertyAutoLoadChanged(
+ IConventionPropertyBuilder propertyBuilder,
+ IConventionContext context);
+}
diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs
index 10175ebd642..298eb968efb 100644
--- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs
+++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs
@@ -97,6 +97,7 @@ public virtual ConventionSet CreateConventionSet()
conventionSet.Add(new NonNullableNavigationConvention(Dependencies));
conventionSet.Add(new BackingFieldConvention(Dependencies));
conventionSet.Add(new QueryFilterRewritingConvention(Dependencies));
+ conventionSet.Add(new AutoLoadConvention(Dependencies));
conventionSet.Add(new RuntimeModelConvention(Dependencies));
conventionSet.Add(new ElementMappingConvention(Dependencies));
conventionSet.Add(new ElementTypeChangedConvention(Dependencies));
diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs
index 5a179129c29..58613ed8196 100644
--- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs
+++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs
@@ -242,6 +242,9 @@ public int GetLeafCount()
public abstract bool? OnPropertyNullabilityChanged(
IConventionPropertyBuilder propertyBuilder);
+ public abstract bool? OnPropertyAutoLoadChanged(
+ IConventionPropertyBuilder propertyBuilder);
+
public abstract IConventionProperty? OnPropertyRemoved(
IConventionTypeBaseBuilder typeBaseBuilder,
IConventionProperty property);
diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs
index 1a91165384b..aecb0ab8c61 100644
--- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs
+++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs
@@ -402,6 +402,12 @@ public override IConventionPropertyBuilder OnPropertyAdded(IConventionPropertyBu
return propertyBuilder.Metadata.IsNullable;
}
+ public override bool? OnPropertyAutoLoadChanged(IConventionPropertyBuilder propertyBuilder)
+ {
+ Add(new OnPropertyAutoLoadChangedNode(propertyBuilder));
+ return propertyBuilder.Metadata.IsAutoLoaded;
+ }
+
public override bool? OnElementTypeNullabilityChanged(IConventionElementTypeBuilder builder)
{
Add(new OnElementTypeNullabilityChangedNode(builder));
@@ -992,6 +998,14 @@ public override void Run(ConventionDispatcher dispatcher)
=> dispatcher._immediateConventionScope.OnPropertyNullabilityChanged(PropertyBuilder);
}
+ private sealed class OnPropertyAutoLoadChangedNode(IConventionPropertyBuilder propertyBuilder) : ConventionNode
+ {
+ public IConventionPropertyBuilder PropertyBuilder { get; } = propertyBuilder;
+
+ public override void Run(ConventionDispatcher dispatcher)
+ => dispatcher._immediateConventionScope.OnPropertyAutoLoadChanged(PropertyBuilder);
+ }
+
private sealed class OnElementTypeNullabilityChangedNode(IConventionElementTypeBuilder builder) : ConventionNode
{
public IConventionElementTypeBuilder ElementTypeBuilder { get; } = builder;
diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs
index aefed1db3d9..458caaef088 100644
--- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs
+++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs
@@ -1575,6 +1575,41 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB
return !propertyBuilder.Metadata.IsInModel ? null : _boolConventionContext.Result;
}
+ public override bool? OnPropertyAutoLoadChanged(IConventionPropertyBuilder propertyBuilder)
+ {
+ if (!propertyBuilder.Metadata.DeclaringType.IsInModel)
+ {
+ return null;
+ }
+#if DEBUG
+ var initialValue = propertyBuilder.Metadata.IsAutoLoaded;
+#endif
+ using (dispatcher.DelayConventions())
+ {
+ _boolConventionContext.ResetState(propertyBuilder.Metadata.IsAutoLoaded);
+ foreach (var propertyConvention in conventionSet.PropertyAutoLoadChangedConventions)
+ {
+ if (!propertyBuilder.Metadata.IsInModel)
+ {
+ return null;
+ }
+
+ propertyConvention.ProcessPropertyAutoLoadChanged(propertyBuilder, _boolConventionContext);
+ if (_boolConventionContext.ShouldStopProcessing())
+ {
+ return _boolConventionContext.Result;
+ }
+#if DEBUG
+ Check.DebugAssert(
+ initialValue == propertyBuilder.Metadata.IsAutoLoaded,
+ $"Convention {propertyConvention.GetType().Name} changed value without terminating");
+#endif
+ }
+ }
+
+ return !propertyBuilder.Metadata.IsInModel ? null : _boolConventionContext.Result;
+ }
+
public override bool? OnElementTypeNullabilityChanged(IConventionElementTypeBuilder builder)
{
if (!builder.Metadata.CollectionProperty.IsInModel)
diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs
index 4e480e5568a..64e14889935 100644
--- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs
+++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs
@@ -684,6 +684,15 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder
public virtual bool? OnPropertyNullabilityChanged(IConventionPropertyBuilder propertyBuilder)
=> _scope.OnPropertyNullabilityChanged(propertyBuilder);
+ ///
+ /// 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 bool? OnPropertyAutoLoadChanged(IConventionPropertyBuilder propertyBuilder)
+ => _scope.OnPropertyAutoLoadChanged(propertyBuilder);
+
///
/// 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/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
index f1025d6e5c3..30fc69b6a23 100644
--- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
+++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs
@@ -378,7 +378,8 @@ private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtim
providerValueComparer: property.GetProviderValueComparer(),
jsonValueReaderWriter: property.GetJsonValueReaderWriter(),
typeMapping: property.GetTypeMapping(),
- sentinel: property.Sentinel)
+ sentinel: property.Sentinel,
+ autoLoaded: property.IsAutoLoaded)
: ((RuntimeComplexType)runtimeType).AddProperty(
property.Name,
property.ClrType,
@@ -402,7 +403,8 @@ private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtim
providerValueComparer: property.GetProviderValueComparer(),
jsonValueReaderWriter: property.GetJsonValueReaderWriter(),
typeMapping: property.GetTypeMapping(),
- sentinel: property.Sentinel);
+ sentinel: property.Sentinel,
+ autoLoaded: property.IsAutoLoaded);
private static RuntimeElementType Create(RuntimeProperty runtimeProperty, IElementType element)
=> runtimeProperty.SetElementType(
diff --git a/src/EFCore/Metadata/IConventionProperty.cs b/src/EFCore/Metadata/IConventionProperty.cs
index c89367eb30d..6c84a182213 100644
--- a/src/EFCore/Metadata/IConventionProperty.cs
+++ b/src/EFCore/Metadata/IConventionProperty.cs
@@ -97,6 +97,23 @@ public interface IConventionProperty : IReadOnlyProperty, IConventionPropertyBas
/// The configuration source for .
ConfigurationSource? GetIsConcurrencyTokenConfigurationSource();
+ ///
+ /// Sets a value indicating whether this property is automatically loaded when the entity is queried from the database.
+ ///
+ ///
+ /// A value indicating whether this property is automatically loaded.
+ /// to reset to default.
+ ///
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ bool? SetIsAutoLoaded(bool? autoLoaded, bool fromDataAnnotation = false);
+
+ ///
+ /// Returns the configuration source for .
+ ///
+ /// The configuration source for .
+ ConfigurationSource? GetIsAutoLoadedConfigurationSource();
+
///
/// Returns a value indicating whether the property was created implicitly and isn't based on the CLR model.
///
diff --git a/src/EFCore/Metadata/IMutableProperty.cs b/src/EFCore/Metadata/IMutableProperty.cs
index 019e86cb3cc..44787f982c3 100644
--- a/src/EFCore/Metadata/IMutableProperty.cs
+++ b/src/EFCore/Metadata/IMutableProperty.cs
@@ -50,6 +50,13 @@ public interface IMutableProperty : IReadOnlyProperty, IMutablePropertyBase
///
new bool IsConcurrencyToken { get; set; }
+ ///
+ /// Gets or sets a value indicating whether this property is automatically loaded when the entity is queried
+ /// from the database. When set to , the property value will not be read from the database
+ /// and the property will be excluded from UPDATE statements unless explicitly loaded or modified.
+ ///
+ new bool IsAutoLoaded { get; set; }
+
///
/// Gets or sets the sentinel value that indicates that this property is not set.
///
diff --git a/src/EFCore/Metadata/IReadOnlyProperty.cs b/src/EFCore/Metadata/IReadOnlyProperty.cs
index be9a4c06802..b702054ba28 100644
--- a/src/EFCore/Metadata/IReadOnlyProperty.cs
+++ b/src/EFCore/Metadata/IReadOnlyProperty.cs
@@ -45,6 +45,14 @@ IReadOnlyEntityType DeclaringEntityType
///
bool IsConcurrencyToken { get; }
+ ///
+ /// Gets a value indicating whether this property is automatically loaded when the entity is queried from the database.
+ /// When set to , the property value will not be read from the database and the property will be
+ /// excluded from UPDATE statements unless explicitly loaded or modified.
+ ///
+ virtual bool IsAutoLoaded
+ => true;
+
///
/// Returns the for the given property from a finalized model.
///
@@ -391,6 +399,11 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt
builder.Append(" Concurrency");
}
+ if (!IsAutoLoaded)
+ {
+ builder.Append(" NoAutoLoad");
+ }
+
if (Sentinel != null && !Equals(Sentinel, ClrType.GetDefaultValue()))
{
builder.Append(" Sentinel:").Append(Sentinel);
diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
index 79502c4b441..1c82d09b6fc 100644
--- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
+++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs
@@ -147,6 +147,33 @@ public virtual bool CanSetIsConcurrencyToken(bool? concurrencyToken, Configurati
=> configurationSource.Overrides(Metadata.GetIsConcurrencyTokenConfigurationSource())
|| Metadata.IsConcurrencyToken == concurrencyToken;
+ ///
+ /// 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 InternalPropertyBuilder? IsAutoLoaded(bool? autoLoaded, ConfigurationSource configurationSource)
+ {
+ if (CanSetIsAutoLoaded(autoLoaded, configurationSource))
+ {
+ Metadata.SetIsAutoLoaded(autoLoaded, configurationSource);
+ return this;
+ }
+
+ return null;
+ }
+
+ ///
+ /// 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 bool CanSetIsAutoLoaded(bool? autoLoaded, ConfigurationSource? configurationSource)
+ => configurationSource.Overrides(Metadata.GetIsAutoLoadedConfigurationSource())
+ || Metadata.IsAutoLoaded == autoLoaded;
+
///
/// 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
@@ -928,6 +955,14 @@ public virtual bool CanSetElementType(Type? elementType, ConfigurationSource? co
oldIsConcurrencyTokenConfigurationSource.Value);
}
+ var oldIsAutoLoadedConfigurationSource = Metadata.GetIsAutoLoadedConfigurationSource();
+ if (oldIsAutoLoadedConfigurationSource.HasValue)
+ {
+ newPropertyBuilder.IsAutoLoaded(
+ Metadata.IsAutoLoaded,
+ oldIsAutoLoadedConfigurationSource.Value);
+ }
+
var oldValueGeneratedConfigurationSource = Metadata.GetValueGeneratedConfigurationSource();
if (oldValueGeneratedConfigurationSource.HasValue)
{
@@ -1167,6 +1202,26 @@ bool IConventionPropertyBuilder.CanSetIsConcurrencyToken(bool? concurrencyToken,
=> CanSetIsConcurrencyToken(
concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ ///
+ /// 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.
+ ///
+ IConventionPropertyBuilder? IConventionPropertyBuilder.IsAutoLoaded(bool? autoLoaded, bool fromDataAnnotation)
+ => IsAutoLoaded(
+ autoLoaded, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
+ ///
+ /// 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.
+ ///
+ bool IConventionPropertyBuilder.CanSetIsAutoLoaded(bool? autoLoaded, bool fromDataAnnotation)
+ => CanSetIsAutoLoaded(
+ autoLoaded, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
///
/// 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/Property.cs b/src/EFCore/Metadata/Internal/Property.cs
index b8209d2eea4..17480395373 100644
--- a/src/EFCore/Metadata/Internal/Property.cs
+++ b/src/EFCore/Metadata/Internal/Property.cs
@@ -19,6 +19,7 @@ public class Property : PropertyBase, IMutableProperty, IConventionProperty, IRu
private InternalPropertyBuilder? _builder;
private bool? _isConcurrencyToken;
+ private bool? _isAutoLoaded;
private bool? _isNullable;
private object? _sentinel;
private ValueGenerated? _valueGenerated;
@@ -30,6 +31,7 @@ public class Property : PropertyBase, IMutableProperty, IConventionProperty, IRu
private ConfigurationSource? _isNullableConfigurationSource;
private ConfigurationSource? _sentinelConfigurationSource;
private ConfigurationSource? _isConcurrencyTokenConfigurationSource;
+ private ConfigurationSource? _isAutoLoadedConfigurationSource;
private ConfigurationSource? _valueGeneratedConfigurationSource;
private ConfigurationSource? _typeMappingConfigurationSource;
@@ -339,6 +341,55 @@ private static bool DefaultIsConcurrencyToken
public virtual ConfigurationSource? GetIsConcurrencyTokenConfigurationSource()
=> _isConcurrencyTokenConfigurationSource;
+ ///
+ /// 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 bool IsAutoLoaded
+ {
+ get => _isAutoLoaded ?? DefaultIsAutoLoaded;
+ set => SetIsAutoLoaded(value, ConfigurationSource.Explicit);
+ }
+
+ ///
+ /// 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 bool? SetIsAutoLoaded(bool? autoLoaded, ConfigurationSource configurationSource)
+ {
+ EnsureMutable();
+
+ var isChanging = IsAutoLoaded != (autoLoaded ?? DefaultIsAutoLoaded);
+ if (isChanging)
+ {
+ _isAutoLoaded = autoLoaded;
+ }
+
+ _isAutoLoadedConfigurationSource = autoLoaded == null
+ ? null
+ : configurationSource.Max(_isAutoLoadedConfigurationSource);
+
+ return isChanging
+ ? DeclaringType.Model.ConventionDispatcher.OnPropertyAutoLoadChanged(Builder)
+ : autoLoaded;
+ }
+
+ private static bool DefaultIsAutoLoaded
+ => true;
+
+ ///
+ /// 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 ConfigurationSource? GetIsAutoLoadedConfigurationSource()
+ => _isAutoLoadedConfigurationSource;
+
///
/// 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
@@ -1793,6 +1844,17 @@ IEnumerable IProperty.GetContainingKeys()
=> SetIsConcurrencyToken(
concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+ ///
+ /// 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.
+ ///
+ [DebuggerStepThrough]
+ bool? IConventionProperty.SetIsAutoLoaded(bool? autoLoaded, bool fromDataAnnotation)
+ => SetIsAutoLoaded(
+ autoLoaded, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention);
+
///
/// 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/RuntimeProperty.cs b/src/EFCore/Metadata/RuntimeProperty.cs
index 54b65638029..94f69f49c93 100644
--- a/src/EFCore/Metadata/RuntimeProperty.cs
+++ b/src/EFCore/Metadata/RuntimeProperty.cs
@@ -20,6 +20,7 @@ public class RuntimeProperty : RuntimePropertyBase, IRuntimeProperty
private readonly bool _isNullable;
private readonly ValueGenerated _valueGenerated;
private readonly bool _isConcurrencyToken;
+ private readonly bool _isAutoLoaded;
private object? _sentinel;
private volatile object? _sentinelFromProviderValue;
private readonly PropertySaveBehavior _beforeSaveBehavior;
@@ -64,7 +65,8 @@ public RuntimeProperty(
ValueComparer? providerValueComparer,
JsonValueReaderWriter? jsonValueReaderWriter,
CoreTypeMapping? typeMapping,
- object? sentinel)
+ object? sentinel,
+ bool autoLoaded)
: base(name, propertyInfo, fieldInfo, propertyAccessMode)
{
DeclaringType = declaringType;
@@ -72,6 +74,7 @@ public RuntimeProperty(
_sentinel = sentinel;
_isNullable = nullable;
_isConcurrencyToken = concurrencyToken;
+ _isAutoLoaded = autoLoaded;
_valueGenerated = valueGenerated;
_beforeSaveBehavior = beforeSaveBehavior;
_afterSaveBehavior = afterSaveBehavior;
@@ -414,6 +417,13 @@ bool IReadOnlyProperty.IsConcurrencyToken
get => _isConcurrencyToken;
}
+ ///
+ bool IReadOnlyProperty.IsAutoLoaded
+ {
+ [DebuggerStepThrough]
+ get => _isAutoLoaded;
+ }
+
///
[DebuggerStepThrough]
int? IReadOnlyProperty.GetMaxLength()
diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs
index 683cf8c7788..c5b56d1e806 100644
--- a/src/EFCore/Metadata/RuntimeTypeBase.cs
+++ b/src/EFCore/Metadata/RuntimeTypeBase.cs
@@ -197,6 +197,7 @@ protected virtual IEnumerable GetDerivedTypes()
/// The for this property.
/// The for this property.
/// The property value to use to consider the property not set.
+ /// A value indicating whether this property is automatically loaded from the database.
/// The newly created property.
public virtual RuntimeProperty AddProperty(
string name,
@@ -221,7 +222,8 @@ public virtual RuntimeProperty AddProperty(
ValueComparer? providerValueComparer = null,
JsonValueReaderWriter? jsonValueReaderWriter = null,
CoreTypeMapping? typeMapping = null,
- object? sentinel = null)
+ object? sentinel = null,
+ bool autoLoaded = true)
{
var property = new RuntimeProperty(
name,
@@ -247,7 +249,8 @@ public virtual RuntimeProperty AddProperty(
providerValueComparer,
jsonValueReaderWriter,
typeMapping,
- sentinel);
+ sentinel,
+ autoLoaded);
_properties.Add(property.Name, property);
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index 193253511d8..491e64911ba 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -152,6 +152,38 @@ public static string AutoIncludeNavigationCycle(object? cycleNavigations)
GetString("AutoIncludeNavigationCycle", nameof(cycleNavigations)),
cycleNavigations);
+ ///
+ /// The property '{property}' on type '{type}' is a concurrency token and cannot be configured as not auto-loaded. Concurrency tokens must always be loaded.
+ ///
+ public static string AutoLoadedConcurrencyTokenProperty(object? property, object? type)
+ => string.Format(
+ GetString("AutoLoadedConcurrencyTokenProperty", nameof(property), nameof(type)),
+ property, type);
+
+ ///
+ /// The property '{property}' on type '{type}' is a discriminator and cannot be configured as not auto-loaded. Discriminator properties must always be loaded.
+ ///
+ public static string AutoLoadedDiscriminatorProperty(object? property, object? type)
+ => string.Format(
+ GetString("AutoLoadedDiscriminatorProperty", nameof(property), nameof(type)),
+ property, type);
+
+ ///
+ /// The property '{property}' on type '{type}' is part of a foreign key and cannot be configured as not auto-loaded. Foreign key properties must always be loaded.
+ ///
+ public static string AutoLoadedForeignKeyProperty(object? property, object? type)
+ => string.Format(
+ GetString("AutoLoadedForeignKeyProperty", nameof(property), nameof(type)),
+ property, type);
+
+ ///
+ /// The property '{property}' on type '{type}' is part of a key and cannot be configured as not auto-loaded. Key properties must always be loaded.
+ ///
+ public static string AutoLoadedKeyProperty(object? property, object? type)
+ => string.Format(
+ GetString("AutoLoadedKeyProperty", nameof(property), nameof(type)),
+ property, type);
+
///
/// The backing field '{field}' cannot be set for the indexer property '{entityType}.{property}'. Ensure no backing fields are specified for indexer properties.
///
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index 38331c9aad2..7c363a7b47f 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -165,6 +165,18 @@
Cycle detected while auto-including navigations: {cycleNavigations}. To fix this issue, either don't configure at least one navigation in the cycle as auto included in 'OnModelCreating' or call 'IgnoreAutoInclude' method on the query.
+
+ The property '{property}' on type '{type}' is a concurrency token and cannot be configured as not auto-loaded. Concurrency tokens must always be loaded.
+
+
+ The property '{property}' on type '{type}' is a discriminator and cannot be configured as not auto-loaded. Discriminator properties must always be loaded.
+
+
+ The property '{property}' on type '{type}' is part of a foreign key and cannot be configured as not auto-loaded. Foreign key properties must always be loaded.
+
+
+ The property '{property}' on type '{type}' is part of a key and cannot be configured as not auto-loaded. Key properties must always be loaded.
+
The backing field '{field}' cannot be set for the indexer property '{entityType}.{property}'. Ensure no backing fields are specified for indexer properties.
diff --git a/src/EFCore/Update/IUpdateEntry.cs b/src/EFCore/Update/IUpdateEntry.cs
index 6a7482ab25b..94b1ec41740 100644
--- a/src/EFCore/Update/IUpdateEntry.cs
+++ b/src/EFCore/Update/IUpdateEntry.cs
@@ -59,6 +59,14 @@ public interface IUpdateEntry
/// if the property is modified, otherwise .
bool IsModified(IProperty property);
+ ///
+ /// Gets a value indicating if the specified property is loaded. If , the property
+ /// value was not read from the database and the property should be excluded from update operations.
+ ///
+ /// The property to be checked.
+ /// if the property value has been loaded, otherwise .
+ bool IsLoaded(IProperty property);
+
///
/// Gets a value indicating if the specified complex property is modified. If ,
/// the current value assigned to the property should be saved to the database.
diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs
index 1bb335b8012..8c63b118deb 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/CompiledModelCosmosTest.cs
@@ -219,15 +219,13 @@ protected override void BuildBigModel(ModelBuilder modelBuilder, bool jsonColumn
});
modelBuilder.Entity>>(b =>
- {
// Cosmos provider cannot map collections of elements with converters. See Issue #34026.
b.OwnsMany(
typeof(OwnedType).FullName!, "ManyOwned", b =>
{
b.Ignore("RefTypeArray");
b.Ignore("RefTypeList");
- });
- });
+ }));
modelBuilder.Entity(b =>
{
@@ -636,9 +634,7 @@ protected override void BuildComplexTypesModel(ModelBuilder modelBuilder)
});
modelBuilder.Entity>>(
- eb =>
- {
- eb.ComplexCollection, OwnedType>(
+ eb => eb.ComplexCollection, OwnedType>(
"ManyOwned", "OwnedCollection", ob =>
{
ob.Ignore(e => e.RefTypeArray);
@@ -649,8 +645,7 @@ protected override void BuildComplexTypesModel(ModelBuilder modelBuilder)
cb.Ignore(e => e.RefTypeList);
cb.Ignore(e => e.RefTypeArray);
});
- });
- });
+ }));
}
protected override void AssertBigModel(IModel model, bool jsonColumns)
@@ -664,6 +659,9 @@ protected override void AssertBigModel(IModel model, bool jsonColumns)
protected override int ExpectedComplexTypeProperties
=> 12;
+ protected override bool SupportsNonAutoLoadedProperties
+ => false;
+
protected override TestHelpers TestHelpers
=> CosmosTestHelpers.Instance;
diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs
index b4ef5fa0749..0d56c570a44 100644
--- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs
@@ -630,6 +630,9 @@ public bool HasStoreGeneratedValue(IProperty property)
public bool IsModified(IProperty property)
=> throw new NotImplementedException();
+ public bool IsLoaded(IProperty property)
+ => throw new NotImplementedException();
+
public bool IsStoreGenerated(IProperty property)
=> throw new NotImplementedException();
diff --git a/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs b/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs
index 1945d63ed98..4bf4a2ae0f5 100644
--- a/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs
+++ b/test/EFCore.Cosmos.Tests/Infrastructure/CosmosModelValidatorTest.cs
@@ -615,6 +615,27 @@ public virtual void Passes_with_valid_triggers()
Validate(modelBuilder);
}
+ [ConditionalFact]
+ public virtual void Detects_cosmos_property_not_auto_loaded()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+
+ modelBuilder.Entity(
+ eb =>
+ {
+ eb.Property(e => e.Name);
+ eb.Property(e => e.PartitionId);
+ });
+
+ var model = modelBuilder.Model;
+ var property = model.FindEntityType(typeof(Customer))!.FindProperty(nameof(Customer.Name))!;
+ property.IsAutoLoaded = false;
+
+ VerifyError(
+ CosmosStrings.AutoLoadedCosmosProperty(nameof(Customer.Name), nameof(Customer)),
+ modelBuilder);
+ }
+
protected class SpecialCustomer : Customer
{
public string SpecialProperty { get; set; }
diff --git a/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs
index 47ff742e3f7..5dd899344ac 100644
--- a/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs
+++ b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs
@@ -13007,7 +13007,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
typeof(string),
propertyInfo: typeof(CompiledModelTestBase.ManyTypes).GetProperty("NullableString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(CompiledModelTestBase.ManyTypes).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
- nullable: true);
+ nullable: true,
+ autoLoaded: false);
nullableString.SetGetter(
string (CompiledModelTestBase.ManyTypes instance) => ManyTypesUnsafeAccessors.NullableString(instance),
bool (CompiledModelTestBase.ManyTypes instance) => ManyTypesUnsafeAccessors.NullableString(instance) == null);
diff --git a/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs
index 50f899d64e3..ff71c00cf9e 100644
--- a/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs
+++ b/test/EFCore.InMemory.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs
@@ -1567,7 +1567,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
typeof(string),
propertyInfo: typeof(CompiledModelTestBase.ManyTypes).GetProperty("NullableString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(CompiledModelTestBase.ManyTypes).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
- nullable: true);
+ nullable: true,
+ autoLoaded: false);
var nullableStringArray = runtimeEntityType.AddProperty(
"NullableStringArray",
diff --git a/test/EFCore.Relational.Specification.Tests/Update/NonSharedModelUpdatesTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/NonSharedModelUpdatesTestBase.cs
index fa2c97c10df..71538537d02 100644
--- a/test/EFCore.Relational.Specification.Tests/Update/NonSharedModelUpdatesTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Update/NonSharedModelUpdatesTestBase.cs
@@ -17,19 +17,13 @@ public virtual async Task Principal_and_dependent_roundtrips_with_cycle_breaking
var contextFactory = await InitializeNonSharedTest(
onModelCreating: mb =>
{
- mb.Entity(b =>
- {
- b.HasOne(a => a.AuthorsClub)
+ mb.Entity(b => b.HasOne(a => a.AuthorsClub)
.WithMany()
- .HasForeignKey(a => a.AuthorsClubId);
- });
+ .HasForeignKey(a => a.AuthorsClubId));
- mb.Entity(b =>
- {
- b.HasOne(book => book.Author)
+ mb.Entity(b => b.HasOne(book => book.Author)
.WithMany()
- .HasForeignKey(book => book.AuthorId);
- });
+ .HasForeignKey(book => book.AuthorId));
});
await ExecuteWithStrategyInTransactionAsync(
@@ -124,15 +118,111 @@ public class Blog
public string? Name { get; set; }
}
+ [ConditionalTheory, MemberData(nameof(IsAsyncData))]
+ public virtual async Task Update_entity_with_not_loaded_property_excludes_column_from_SQL(bool async)
+ {
+ var contextFactory = await InitializeNonSharedTest(
+ onModelCreating: mb => mb.Entity(
+ b => b.Property(e => e.Description).Metadata.IsAutoLoaded = false),
+ seed: async context =>
+ {
+ context.Add(new BlogWithDescription { Name = "EF Blog", Description = "Original description" });
+ await context.SaveChangesAsync();
+ });
+
+ await ExecuteWithStrategyInTransactionAsync(
+ contextFactory,
+ async context =>
+ {
+ var blog = new BlogWithDescription { Id = 1, Name = "Updated Blog" };
+ context.Update(blog);
+
+ var entry = context.Entry(blog);
+ // Description starts as not-loaded (IsAutoLoaded = false)
+ Assert.False(entry.Property(e => e.Description).IsLoaded);
+ Assert.False(entry.Property(e => e.Description).IsModified);
+ if (async)
+ {
+ await context.SaveChangesAsync();
+ }
+ else
+ {
+ context.SaveChanges();
+ }
+ },
+ async context =>
+ {
+ var blog = await context.Set().SingleAsync();
+ Assert.Equal("Updated Blog", blog.Name);
+ Assert.Equal("Original description", blog.Description);
+ });
+ }
+
+ [ConditionalTheory, MemberData(nameof(IsAsyncData))]
+ public virtual async Task Save_and_query_with_partially_loaded_primitive_collection(bool async)
+ {
+ var contextFactory = await InitializeNonSharedTest(
+ onModelCreating: mb => mb.Entity(
+ b =>
+ {
+ b.Property(e => e.Tags).Metadata.IsAutoLoaded = false;
+ b.Property(e => e.Tags).Metadata.Sentinel = new List();
+ }),
+ seed: async context =>
+ {
+ context.Add(new BlogWithTags { Name = "EF Blog", Tags = ["efcore", "dotnet"] });
+ await context.SaveChangesAsync();
+ });
+
+ await ExecuteWithStrategyInTransactionAsync(
+ contextFactory,
+ async context =>
+ {
+ var blog = new BlogWithTags { Id = 1, Name = "Updated Blog" };
+ context.Update(blog);
+
+ var entry = context.Entry(blog);
+ Assert.False(entry.Property(e => e.Tags).IsLoaded);
+ Assert.False(entry.Property(e => e.Tags).IsModified);
+
+ if (async)
+ {
+ await context.SaveChangesAsync();
+ }
+ else
+ {
+ context.SaveChanges();
+ }
+ },
+ async context =>
+ {
+ var blog = await context.Set().SingleAsync();
+ Assert.Equal("Updated Blog", blog.Name);
+ Assert.Equal(new[] { "efcore", "dotnet" }, blog.Tags);
+ });
+ }
+
+ private class BlogWithDescription
+ {
+ public int Id { get; set; }
+ public string? Name { get; set; }
+ public string? Description { get; set; }
+ }
+
+ private class BlogWithTags
+ {
+ public int Id { get; set; }
+ public string? Name { get; set; }
+ public List Tags { get; set; } = [];
+ }
+
[ConditionalTheory, MemberData(nameof(IsAsyncData))] // Issue #36059
public virtual async Task Replacing_owned_entity_with_FK_to_another_entity(bool async)
{
var contextFactory = await InitializeNonSharedTest(
onModelCreating: mb =>
{
- mb.Entity(b =>
- {
- b.OwnsOne(d => d.File, fb =>
+ mb.Entity(b => b.OwnsOne(d => d.File, fb =>
{
fb.Property(f => f.Id).ValueGeneratedNever();
fb.HasOne(f => f.Content)
@@ -140,13 +230,9 @@ public virtual async Task Replacing_owned_entity_with_FK_to_another_entity(bool
.HasForeignKey(f => f.ContentId)
.IsRequired()
.OnDelete(DeleteBehavior.Restrict);
- });
- });
+ }));
- mb.Entity(b =>
- {
- b.Property(c => c.Id).ValueGeneratedNever();
- });
+ mb.Entity(b => b.Property(c => c.Id).ValueGeneratedNever());
});
var oldContentId = Guid.NewGuid();
diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
index 0eb2ae524f5..8e5c381f46d 100644
--- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
+++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
@@ -3868,6 +3868,43 @@ public static IQueryable MethodF()
=> throw new NotImplementedException();
}
+ [ConditionalFact]
+ public virtual void Detects_json_mapped_property_not_auto_loaded()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+
+ modelBuilder.Entity(
+ eb =>
+ {
+ eb.OwnsOne(
+ e => e.Owned, ob =>
+ {
+ ob.Property(e => e.Details);
+ });
+ });
+
+ var model = modelBuilder.Model;
+ var ownedType = model.FindEntityType(typeof(AutoLoadJsonOwned))!;
+ ownedType.SetContainerColumnName("Owned");
+ var property = ownedType.FindProperty(nameof(AutoLoadJsonOwned.Details))!;
+ property.IsAutoLoaded = false;
+
+ VerifyError(
+ RelationalStrings.AutoLoadedJsonProperty(nameof(AutoLoadJsonOwned.Details), ownedType.DisplayName()),
+ modelBuilder);
+ }
+
+ protected class AutoLoadJsonPrincipal
+ {
+ public int Id { get; set; }
+ public AutoLoadJsonOwned Owned { get; set; } = null!;
+ }
+
+ protected class AutoLoadJsonOwned
+ {
+ public string Details { get; set; } = null!;
+ }
+
protected virtual TestHelpers.TestModelBuilder CreateModelBuilderWithoutConvention(bool sensitiveDataLoggingEnabled = false)
=> TestHelpers.CreateConventionBuilder(
CreateModelLogger(sensitiveDataLoggingEnabled), CreateValidationLogger(sensitiveDataLoggingEnabled),
diff --git a/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs b/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs
index 38084cfa33b..6db059e5fe1 100644
--- a/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs
+++ b/test/EFCore.Specification.Tests/Scaffolding/CompiledModelTestBase.cs
@@ -268,6 +268,11 @@ protected virtual void BuildBigModel(ModelBuilder modelBuilder, bool jsonColumns
b.Property(e => e.TimeSpanToTicksConverterProperty).HasConversion();
b.Property(e => e.UriToStringConverterProperty).HasConversion();
b.Property(e => e.NullIntToNullStringConverterProperty).HasConversion();
+
+ if (SupportsNonAutoLoadedProperties)
+ {
+ b.Property(e => e.NullableString).Metadata.IsAutoLoaded = false;
+ }
});
}
@@ -283,6 +288,9 @@ protected virtual void AssertBigModel(IModel model, bool jsonColumns)
Assert.Null(manyTypesType.FindIndexerPropertyInfo());
Assert.Equal(ChangeTrackingStrategy.Snapshot, manyTypesType.GetChangeTrackingStrategy());
+ var stringProp = manyTypesType.FindProperty(nameof(ManyTypes.NullableString))!;
+ Assert.Equal(!SupportsNonAutoLoadedProperties, stringProp.IsAutoLoaded);
+
var ipAddressCollection = manyTypesType.FindProperty(nameof(ManyTypes.IPAddressReadOnlyCollection));
if (ipAddressCollection != null)
{
@@ -1367,6 +1375,9 @@ protected virtual void AssertComplexTypes(IModel model)
protected virtual int ExpectedComplexTypeProperties
=> 14;
+ protected virtual bool SupportsNonAutoLoadedProperties
+ => true;
+
public class CustomValueComparer() : ValueComparer(false);
public class ManyTypesIdConverter() : ValueConverter(v => v.Id, v => new ManyTypesId(v));
diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs
index 622d5f6f1b4..55c2201c361 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs
@@ -13083,7 +13083,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
typeof(string),
propertyInfo: typeof(CompiledModelTestBase.ManyTypes).GetProperty("NullableString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(CompiledModelTestBase.ManyTypes).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
- nullable: true);
+ nullable: true,
+ autoLoaded: false);
nullableString.SetGetter(
string (CompiledModelTestBase.ManyTypes instance) => ManyTypesUnsafeAccessors.NullableString(instance),
bool (CompiledModelTestBase.ManyTypes instance) => ManyTypesUnsafeAccessors.NullableString(instance) == null);
diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs
index 622d5f6f1b4..55c2201c361 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs
@@ -13083,7 +13083,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
typeof(string),
propertyInfo: typeof(CompiledModelTestBase.ManyTypes).GetProperty("NullableString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(CompiledModelTestBase.ManyTypes).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
- nullable: true);
+ nullable: true,
+ autoLoaded: false);
nullableString.SetGetter(
string (CompiledModelTestBase.ManyTypes instance) => ManyTypesUnsafeAccessors.NullableString(instance),
bool (CompiledModelTestBase.ManyTypes instance) => ManyTypesUnsafeAccessors.NullableString(instance) == null);
diff --git a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs
index 8e8c4fa5fbf..c76e8641220 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs
@@ -1632,7 +1632,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
typeof(string),
propertyInfo: typeof(CompiledModelTestBase.ManyTypes).GetProperty("NullableString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(CompiledModelTestBase.ManyTypes).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
- nullable: true);
+ nullable: true,
+ autoLoaded: false);
nullableString.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None);
var nullableStringArray = runtimeEntityType.AddProperty(
diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/NonSharedModelUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/NonSharedModelUpdatesSqlServerTest.cs
index c222d2a1386..4c694f86785 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Update/NonSharedModelUpdatesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Update/NonSharedModelUpdatesSqlServerTest.cs
@@ -195,6 +195,50 @@ WHEN NOT MATCHED THEN
private void AssertSql(params string[] expected)
=> TestSqlLoggerFactory.AssertBaseline(expected);
+ public override async Task Update_entity_with_not_loaded_property_excludes_column_from_SQL(bool async)
+ {
+ await base.Update_entity_with_not_loaded_property_excludes_column_from_SQL(async);
+
+ AssertSql(
+ """
+@p1='1'
+@p0='Updated Blog' (Size = 4000)
+
+SET IMPLICIT_TRANSACTIONS OFF;
+SET NOCOUNT ON;
+UPDATE [BlogWithDescription] SET [Name] = @p0
+OUTPUT 1
+WHERE [Id] = @p1;
+""",
+ //
+ """
+SELECT TOP(2) [b].[Id], [b].[Description], [b].[Name]
+FROM [BlogWithDescription] AS [b]
+""");
+ }
+
+ public override async Task Save_and_query_with_partially_loaded_primitive_collection(bool async)
+ {
+ await base.Save_and_query_with_partially_loaded_primitive_collection(async);
+
+ AssertSql(
+ """
+@p1='1'
+@p0='Updated Blog' (Size = 4000)
+
+SET IMPLICIT_TRANSACTIONS OFF;
+SET NOCOUNT ON;
+UPDATE [BlogWithTags] SET [Name] = @p0
+OUTPUT 1
+WHERE [Id] = @p1;
+""",
+ //
+ """
+SELECT TOP(2) [b].[Id], [b].[Name], [b].[Tags]
+FROM [BlogWithTags] AS [b]
+""");
+ }
+
protected override ITestStoreFactory NonSharedTestStoreFactory
=> SqlServerTestStoreFactory.Instance;
}
diff --git a/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerAutoLoadConventionTest.cs b/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerAutoLoadConventionTest.cs
new file mode 100644
index 00000000000..4ae71a5ecf5
--- /dev/null
+++ b/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerAutoLoadConventionTest.cs
@@ -0,0 +1,90 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Data.SqlTypes;
+
+namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
+
+#nullable enable
+
+public class SqlServerAutoLoadConventionTest
+{
+ [ConditionalFact]
+ public void Vector_property_configured_as_not_auto_loaded_by_convention()
+ {
+ var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder();
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(e => e.Vector).HasColumnType("vector(3)");
+ b.Property(e => e.Name);
+ });
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entityType = model.FindEntityType(typeof(EntityWithVector))!;
+ Assert.False(entityType.FindProperty(nameof(EntityWithVector.Vector))!.IsAutoLoaded);
+ }
+
+ [ConditionalFact]
+ public void Vector_property_can_be_manually_configured_as_not_auto_loaded()
+ {
+ var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder();
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(e => e.Vector).HasColumnType("vector(3)");
+ b.Property(e => e.Name);
+ });
+
+ var model = modelBuilder.Model;
+ var property = model.FindEntityType(typeof(EntityWithVector))!.FindProperty(nameof(EntityWithVector.Vector))!;
+ property.IsAutoLoaded = false;
+
+ var finalModel = modelBuilder.FinalizeModel();
+ Assert.False(finalModel.FindEntityType(typeof(EntityWithVector))!.FindProperty(nameof(EntityWithVector.Vector))!.IsAutoLoaded);
+ }
+
+ [ConditionalFact]
+ public void Explicit_auto_load_overrides_convention()
+ {
+ var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder();
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(e => e.Vector).HasColumnType("vector(3)");
+ b.Property(e => e.Name);
+ });
+
+ var model = modelBuilder.Model;
+ var property = model.FindEntityType(typeof(EntityWithVector))!.FindProperty(nameof(EntityWithVector.Vector))!;
+ property.IsAutoLoaded = true;
+
+ var finalModel = modelBuilder.FinalizeModel();
+ Assert.True(finalModel.FindEntityType(typeof(EntityWithVector))!.FindProperty(nameof(EntityWithVector.Vector))!.IsAutoLoaded);
+ }
+
+ [ConditionalFact]
+ public void Non_vector_property_remains_auto_loaded()
+ {
+ var modelBuilder = SqlServerTestHelpers.Instance.CreateConventionBuilder();
+ modelBuilder.Entity(
+ b =>
+ {
+ b.Property(e => e.Vector).HasColumnType("vector(3)");
+ b.Property(e => e.Name);
+ });
+
+ var model = modelBuilder.FinalizeModel();
+
+ var entityType = model.FindEntityType(typeof(EntityWithVector))!;
+ Assert.True(entityType.FindProperty(nameof(EntityWithVector.Name))!.IsAutoLoaded);
+ }
+
+ private class EntityWithVector
+ {
+ public int Id { get; set; }
+ public string? Name { get; set; }
+ public SqlVector Vector { get; set; }
+ }
+}
diff --git a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs
index 816b1ffef03..280d6e6eaf1 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel/ManyTypesEntityType.cs
@@ -12056,7 +12056,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
typeof(string),
propertyInfo: typeof(CompiledModelTestBase.ManyTypes).GetProperty("NullableString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(CompiledModelTestBase.ManyTypes).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
- nullable: true);
+ nullable: true,
+ autoLoaded: false);
nullableString.SetGetter(
string (CompiledModelTestBase.ManyTypes instance) => ManyTypesUnsafeAccessors.NullableString(instance),
bool (CompiledModelTestBase.ManyTypes instance) => ManyTypesUnsafeAccessors.NullableString(instance) == null);
diff --git a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs
index 816b1ffef03..280d6e6eaf1 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/BigModel_with_JSON_columns/ManyTypesEntityType.cs
@@ -12056,7 +12056,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
typeof(string),
propertyInfo: typeof(CompiledModelTestBase.ManyTypes).GetProperty("NullableString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(CompiledModelTestBase.ManyTypes).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
- nullable: true);
+ nullable: true,
+ autoLoaded: false);
nullableString.SetGetter(
string (CompiledModelTestBase.ManyTypes instance) => ManyTypesUnsafeAccessors.NullableString(instance),
bool (CompiledModelTestBase.ManyTypes instance) => ManyTypesUnsafeAccessors.NullableString(instance) == null);
diff --git a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs
index 6247347dc2f..a78785e8cee 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Scaffolding/Baselines/No_NativeAOT/ManyTypesEntityType.cs
@@ -1444,7 +1444,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
typeof(string),
propertyInfo: typeof(CompiledModelTestBase.ManyTypes).GetProperty("NullableString", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
fieldInfo: typeof(CompiledModelTestBase.ManyTypes).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
- nullable: true);
+ nullable: true,
+ autoLoaded: false);
var nullableStringArray = runtimeEntityType.AddProperty(
"NullableStringArray",
diff --git a/test/EFCore.Tests/ChangeTracking/Internal/StateDataTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/StateDataTest.cs
index 54da7a148b1..9a930b4a890 100644
--- a/test/EFCore.Tests/ChangeTracking/Internal/StateDataTest.cs
+++ b/test/EFCore.Tests/ChangeTracking/Internal/StateDataTest.cs
@@ -17,7 +17,8 @@ public void Can_read_and_manipulate_modification_flags()
InternalEntryBase.PropertyFlag.Unknown,
InternalEntryBase.PropertyFlag.IsLoaded,
InternalEntryBase.PropertyFlag.IsTemporary,
- InternalEntryBase.PropertyFlag.IsStoreGenerated);
+ InternalEntryBase.PropertyFlag.IsStoreGenerated,
+ InternalEntryBase.PropertyFlag.IsPropertyNotLoaded);
}
}
@@ -33,7 +34,8 @@ public void Can_read_and_manipulate_null_flags()
InternalEntryBase.PropertyFlag.Unknown,
InternalEntryBase.PropertyFlag.IsLoaded,
InternalEntryBase.PropertyFlag.IsTemporary,
- InternalEntryBase.PropertyFlag.IsStoreGenerated);
+ InternalEntryBase.PropertyFlag.IsStoreGenerated,
+ InternalEntryBase.PropertyFlag.IsPropertyNotLoaded);
}
}
@@ -49,7 +51,8 @@ public void Can_read_and_manipulate_not_set_flags()
InternalEntryBase.PropertyFlag.Null,
InternalEntryBase.PropertyFlag.IsLoaded,
InternalEntryBase.PropertyFlag.IsTemporary,
- InternalEntryBase.PropertyFlag.IsStoreGenerated);
+ InternalEntryBase.PropertyFlag.IsStoreGenerated,
+ InternalEntryBase.PropertyFlag.IsPropertyNotLoaded);
}
}
@@ -65,7 +68,8 @@ public void Can_read_and_manipulate_is_loaded_flags()
InternalEntryBase.PropertyFlag.Null,
InternalEntryBase.PropertyFlag.Unknown,
InternalEntryBase.PropertyFlag.IsTemporary,
- InternalEntryBase.PropertyFlag.IsStoreGenerated);
+ InternalEntryBase.PropertyFlag.IsStoreGenerated,
+ InternalEntryBase.PropertyFlag.IsPropertyNotLoaded);
}
}
@@ -81,7 +85,8 @@ public void Can_read_and_manipulate_temporary_flags()
InternalEntryBase.PropertyFlag.Modified,
InternalEntryBase.PropertyFlag.Null,
InternalEntryBase.PropertyFlag.Unknown,
- InternalEntryBase.PropertyFlag.IsStoreGenerated);
+ InternalEntryBase.PropertyFlag.IsStoreGenerated,
+ InternalEntryBase.PropertyFlag.IsPropertyNotLoaded);
}
}
@@ -97,7 +102,25 @@ public void Can_read_and_manipulate_store_generated_flags()
InternalEntryBase.PropertyFlag.Modified,
InternalEntryBase.PropertyFlag.Null,
InternalEntryBase.PropertyFlag.Unknown,
- InternalEntryBase.PropertyFlag.IsTemporary);
+ InternalEntryBase.PropertyFlag.IsTemporary,
+ InternalEntryBase.PropertyFlag.IsPropertyNotLoaded);
+ }
+ }
+
+ [ConditionalFact]
+ public void Can_read_and_manipulate_is_property_loaded_flags()
+ {
+ for (var i = 0; i < 70; i++)
+ {
+ PropertyManipulation(
+ i,
+ InternalEntryBase.PropertyFlag.IsPropertyNotLoaded,
+ InternalEntryBase.PropertyFlag.Modified,
+ InternalEntryBase.PropertyFlag.Null,
+ InternalEntryBase.PropertyFlag.Unknown,
+ InternalEntryBase.PropertyFlag.IsLoaded,
+ InternalEntryBase.PropertyFlag.IsTemporary,
+ InternalEntryBase.PropertyFlag.IsStoreGenerated);
}
}
@@ -108,7 +131,8 @@ private void PropertyManipulation(
InternalEntryBase.PropertyFlag unusedFlag2,
InternalEntryBase.PropertyFlag unusedFlag3,
InternalEntryBase.PropertyFlag unusedFlag4,
- InternalEntryBase.PropertyFlag unusedFlag5)
+ InternalEntryBase.PropertyFlag unusedFlag5,
+ InternalEntryBase.PropertyFlag unusedFlag6)
{
var data = new InternalEntryBase.StateData(propertyCount, propertyCount);
@@ -118,6 +142,7 @@ private void PropertyManipulation(
Assert.False(data.AnyPropertiesFlagged(unusedFlag3));
Assert.False(data.AnyPropertiesFlagged(unusedFlag4));
Assert.False(data.AnyPropertiesFlagged(unusedFlag5));
+ Assert.False(data.AnyPropertiesFlagged(unusedFlag6));
for (var i = 0; i < propertyCount; i++)
{
@@ -131,6 +156,7 @@ private void PropertyManipulation(
Assert.False(data.IsPropertyFlagged(j, unusedFlag3));
Assert.False(data.IsPropertyFlagged(j, unusedFlag4));
Assert.False(data.IsPropertyFlagged(j, unusedFlag5));
+ Assert.False(data.IsPropertyFlagged(j, unusedFlag6));
}
Assert.True(data.AnyPropertiesFlagged(propertyFlag));
@@ -139,6 +165,7 @@ private void PropertyManipulation(
Assert.False(data.AnyPropertiesFlagged(unusedFlag3));
Assert.False(data.AnyPropertiesFlagged(unusedFlag4));
Assert.False(data.AnyPropertiesFlagged(unusedFlag5));
+ Assert.False(data.AnyPropertiesFlagged(unusedFlag6));
}
for (var i = 0; i < propertyCount; i++)
@@ -153,6 +180,7 @@ private void PropertyManipulation(
Assert.False(data.IsPropertyFlagged(j, unusedFlag3));
Assert.False(data.IsPropertyFlagged(j, unusedFlag4));
Assert.False(data.IsPropertyFlagged(j, unusedFlag5));
+ Assert.False(data.IsPropertyFlagged(j, unusedFlag6));
}
Assert.Equal(i < propertyCount - 1, data.AnyPropertiesFlagged(propertyFlag));
@@ -161,6 +189,7 @@ private void PropertyManipulation(
Assert.False(data.AnyPropertiesFlagged(unusedFlag3));
Assert.False(data.AnyPropertiesFlagged(unusedFlag4));
Assert.False(data.AnyPropertiesFlagged(unusedFlag5));
+ Assert.False(data.AnyPropertiesFlagged(unusedFlag6));
}
for (var i = 0; i < propertyCount; i++)
@@ -171,6 +200,7 @@ private void PropertyManipulation(
Assert.False(data.IsPropertyFlagged(i, unusedFlag3));
Assert.False(data.IsPropertyFlagged(i, unusedFlag4));
Assert.False(data.IsPropertyFlagged(i, unusedFlag5));
+ Assert.False(data.IsPropertyFlagged(i, unusedFlag6));
}
data.FlagAllProperties(propertyCount, propertyFlag, flagged: true);
@@ -181,6 +211,7 @@ private void PropertyManipulation(
Assert.False(data.AnyPropertiesFlagged(unusedFlag3));
Assert.False(data.AnyPropertiesFlagged(unusedFlag4));
Assert.False(data.AnyPropertiesFlagged(unusedFlag5));
+ Assert.False(data.AnyPropertiesFlagged(unusedFlag6));
for (var i = 0; i < propertyCount; i++)
{
@@ -190,6 +221,7 @@ private void PropertyManipulation(
Assert.False(data.IsPropertyFlagged(i, unusedFlag3));
Assert.False(data.IsPropertyFlagged(i, unusedFlag4));
Assert.False(data.IsPropertyFlagged(i, unusedFlag5));
+ Assert.False(data.IsPropertyFlagged(i, unusedFlag6));
}
data.FlagAllProperties(propertyCount, propertyFlag, flagged: false);
@@ -200,6 +232,7 @@ private void PropertyManipulation(
Assert.False(data.AnyPropertiesFlagged(unusedFlag3));
Assert.False(data.AnyPropertiesFlagged(unusedFlag4));
Assert.False(data.AnyPropertiesFlagged(unusedFlag5));
+ Assert.False(data.AnyPropertiesFlagged(unusedFlag6));
for (var i = 0; i < propertyCount; i++)
{
@@ -209,6 +242,7 @@ private void PropertyManipulation(
Assert.False(data.IsPropertyFlagged(i, unusedFlag3));
Assert.False(data.IsPropertyFlagged(i, unusedFlag4));
Assert.False(data.IsPropertyFlagged(i, unusedFlag5));
+ Assert.False(data.IsPropertyFlagged(i, unusedFlag6));
}
}
diff --git a/test/EFCore.Tests/ChangeTracking/PropertyEntryTest.cs b/test/EFCore.Tests/ChangeTracking/PropertyEntryTest.cs
index c84ac274908..bb940558c2f 100644
--- a/test/EFCore.Tests/ChangeTracking/PropertyEntryTest.cs
+++ b/test/EFCore.Tests/ChangeTracking/PropertyEntryTest.cs
@@ -4883,4 +4883,381 @@ private struct FieldTog
{
public string? Text;
}
+
+ #region IsAutoLoaded / IsLoaded change tracking tests
+
+ public static IEnumerable