Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ protected override void ValidateEntityType(
ValidateDiscriminatorMappings(entityType, logger);
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
protected override void ValidateAutoLoaded(
IProperty property,
ITypeBase structuralType,
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{
base.ValidateAutoLoaded(property, structuralType, logger);

if (!property.IsAutoLoaded)
{
throw new InvalidOperationException(
CosmosStrings.AutoLoadedCosmosProperty(property.Name, structuralType.DisplayName()));
}
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
8 changes: 8 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Cosmos/Properties/CosmosStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@
<data name="AnalyticalTTLMismatch" xml:space="preserve">
<value>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.</value>
</data>
<data name="AutoLoadedCosmosProperty" xml:space="preserve">
<value>The property '{property}' on type '{type}' cannot be configured as not auto-loaded. The Cosmos provider doesn't support partial property loading.</value>
</data>
<data name="BadDictionaryType" xml:space="preserve">
<value>The type '{givenType}' cannot be mapped as a dictionary because it does not implement '{dictionaryType}'.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,22 @@ protected override void ValidateProperty(
ValidateBoolWithDefaults(property, logger);
}

/// <inheritdoc />
protected override void ValidateAutoLoaded(
IProperty property,
ITypeBase structuralType,
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{
base.ValidateAutoLoaded(property, structuralType, logger);

if (!property.IsAutoLoaded
&& structuralType.IsMappedToJson())
{
throw new InvalidOperationException(
RelationalStrings.AutoLoadedJsonProperty(property.Name, structuralType.DisplayName()));
}
}

/// <inheritdoc />
protected override void ValidateKey(
IKey key,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@
<data name="AbstractTpc" xml:space="preserve">
<value>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.</value>
</data>
<data name="AutoLoadedJsonProperty" xml:space="preserve">
<value>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.</value>
</data>
<data name="BadSequenceString" xml:space="preserve">
<value>Unable to deserialize a sequence from model metadata. See inner exception for details.</value>
<comment>Obsolete</comment>
Expand Down
10 changes: 6 additions & 4 deletions src/EFCore.Relational/Update/ModificationCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ protected override void ValidateProperty(
ITypeBase structuralType,
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{
ValidateVectorProperty(property, logger);

base.ValidateProperty(property, structuralType, logger);

ValidateDecimalColumn(property, logger);
ValidateVectorProperty(property, logger);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// A convention that configures SQL Server vector properties as not auto-loaded.
/// </summary>
/// <remarks>
/// See <see href="https://aka.ms/efcore-docs-conventions">Model building conventions</see> for more information and examples.
/// </remarks>
/// <param name="dependencies">Parameter object containing dependencies for this convention.</param>
public class SqlServerAutoLoadConvention(ProviderConventionSetBuilderDependencies dependencies) : AutoLoadConvention(dependencies)
{
/// <inheritdoc />
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public override ConventionSet CreateConventionSet()
conventionSet.Replace<RuntimeModelConvention>(new SqlServerRuntimeModelConvention(Dependencies, RelationalDependencies));
conventionSet.Replace<SharedTableConvention>(
new SqlServerSharedTableConvention(Dependencies, RelationalDependencies));
conventionSet.Replace<AutoLoadConvention>(new SqlServerAutoLoadConvention(Dependencies));

var sqlServerTemporalConvention = new SqlServerTemporalConvention(Dependencies, RelationalDependencies);
ConventionSet.AddBefore(
Expand Down
21 changes: 19 additions & 2 deletions src/EFCore/ChangeTracking/Internal/ChangeDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -792,6 +804,11 @@ public bool DetectComplexCollectionChanges(InternalEntryBase entry, IComplexProp
/// </summary>
public bool DetectValueChange(IInternalEntry entry, IProperty property)
{
if (!entry.IsLoaded(property))
{
return false;
}

var current = entry[property];
var original = entry.GetOriginalValue(property);

Expand Down
16 changes: 16 additions & 0 deletions src/EFCore/ChangeTracking/Internal/IInternalEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,22 @@ public object Entity
/// </summary>
bool IsModified(IProperty property);

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

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

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
IsStoreGenerated = 5
IsStoreGenerated = 5,

/// <summary>
/// Tracks whether a property value has NOT been loaded (for properties with <see cref="IReadOnlyProperty.IsAutoLoaded" /> equal
/// to <see langword="false" />). The default (false) means loaded; set to true for not-auto-loaded properties.
/// Distinct from <see cref="IsLoaded" /> which tracks navigation loaded state.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
IsPropertyNotLoaded = 6
}

/// <summary>
Expand All @@ -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.
/// </summary>
/// <remarks>
/// <para>
/// Stores per-slot flags in a bit array. Each slot gets 8 bits.
/// The total number of slots is <c>max(propertyCount, navigationCount)</c>.
/// </para>
/// <para>
/// Property flags use bits: Modified (0), Null (1), Unknown (2), IsTemporary (4),
/// IsStoreGenerated (5), IsPropertyNotLoaded (6). Bit 7 is unused/reserved.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
protected internal readonly struct StateData
{
private const int BitsPerInt = 32;
Expand All @@ -90,7 +117,7 @@ protected internal readonly struct StateData
/// </summary>
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];
}
Expand Down
Loading
Loading