diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
index 1dc52c82723..8250dac0e8e 100644
--- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
+++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs
@@ -315,13 +315,13 @@ protected virtual void GenerateSequence(
if (sequence.Type != Sequence.DefaultClrType)
{
sequenceBuilderNameBuilder
- .Append("<")
+ .Append('<')
.Append(Code.Reference(sequence.Type))
- .Append(">");
+ .Append('>');
}
sequenceBuilderNameBuilder
- .Append("(")
+ .Append('(')
.Append(Code.Literal(sequence.Name));
if (!string.IsNullOrEmpty(sequence.ModelSchema))
@@ -331,7 +331,7 @@ protected virtual void GenerateSequence(
.Append(Code.Literal(sequence.ModelSchema));
}
- sequenceBuilderNameBuilder.Append(")");
+ sequenceBuilderNameBuilder.Append(')');
var sequenceBuilderName = sequenceBuilderNameBuilder.ToString();
stringBuilder
@@ -347,7 +347,7 @@ protected virtual void GenerateSequence(
.AppendLine()
.Append(".StartsAt(")
.Append(Code.Literal(sequence.StartValue))
- .Append(")");
+ .Append(')');
}
if (sequence.IncrementBy != Sequence.DefaultIncrementBy)
@@ -356,7 +356,7 @@ protected virtual void GenerateSequence(
.AppendLine()
.Append(".IncrementsBy(")
.Append(Code.Literal(sequence.IncrementBy))
- .Append(")");
+ .Append(')');
}
if (sequence.MinValue != Sequence.DefaultMinValue)
@@ -374,7 +374,7 @@ protected virtual void GenerateSequence(
.AppendLine()
.Append(".HasMax(")
.Append(Code.Literal(sequence.MaxValue))
- .Append(")");
+ .Append(')');
}
if (sequence.IsCyclic != Sequence.DefaultIsCyclic)
@@ -586,7 +586,7 @@ protected virtual void GenerateComplexProperty(
private static string GenerateNestedBuilderName(string builderName)
{
- if (builderName.StartsWith("b", StringComparison.Ordinal))
+ if (builderName.StartsWith('b'))
{
// ReSharper disable once InlineOutVariableDeclaration
var counter = 1;
@@ -876,7 +876,7 @@ protected virtual void GenerateEntityTypeAnnotations(
stringBuilder
.AppendLine()
.Append(entityTypeBuilderName)
- .Append(".")
+ .Append('.')
.Append("HasDiscriminator");
if (discriminatorPropertyAnnotation?.Value != null)
@@ -886,11 +886,11 @@ protected virtual void GenerateEntityTypeAnnotations(
.MakeNullable(discriminatorProperty.IsNullable)
?? discriminatorProperty.ClrType;
stringBuilder
- .Append("<")
+ .Append('<')
.Append(Code.Reference(propertyClrType))
.Append(">(")
.Append(Code.Literal((string)discriminatorPropertyAnnotation.Value))
- .Append(")");
+ .Append(')');
}
else
{
@@ -903,11 +903,11 @@ protected virtual void GenerateEntityTypeAnnotations(
var value = (bool)discriminatorMappingCompleteAnnotation.Value;
stringBuilder
- .Append(".")
+ .Append('.')
.Append("IsComplete")
- .Append("(")
+ .Append('(')
.Append(Code.Literal(value))
- .Append(")");
+ .Append(')');
}
if (discriminatorValueAnnotation?.Value != null)
@@ -924,11 +924,11 @@ protected virtual void GenerateEntityTypeAnnotations(
}
stringBuilder
- .Append(".")
+ .Append('.')
.Append("HasValue")
- .Append("(")
+ .Append('(')
.Append(Code.UnknownLiteral(value))
- .Append(")");
+ .Append(')');
}
stringBuilder.AppendLine(";");
diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
index 36a1bbc2f65..4cab4805dea 100644
--- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
+++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs
@@ -4,7 +4,6 @@
using System.Text;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Internal;
-using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal;
@@ -652,7 +651,7 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator
mainBuilder.AppendLine(",")
.Append("indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(")
.Append(_code.Literal(entityType.ClrType))
- .Append(")");
+ .Append(')');
}
if (entityType.IsPropertyBag)
@@ -991,7 +990,7 @@ private void PropertyBaseParameters(
.Append(".GetProperty(")
.Append(_code.Literal(propertyInfo.Name))
.Append(", ")
- .Append(propertyInfo.GetAccessors().Any() ? "BindingFlags.Public" : "BindingFlags.NonPublic")
+ .Append(propertyInfo.GetAccessors().Length != 0 ? "BindingFlags.Public" : "BindingFlags.NonPublic")
.Append(propertyInfo.IsStatic() ? " | BindingFlags.Static" : " | BindingFlags.Instance")
.Append(" | BindingFlags.DeclaredOnly)");
}
@@ -1055,12 +1054,12 @@ private void FindProperties(
.Append(entityTypeVariable)
.Append(".FindProperty(")
.Append(_code.Literal(property.Name))
- .Append(")");
+ .Append(')');
if (nullable)
{
mainBuilder
- .Append("!");
+ .Append('!');
}
}
}
diff --git a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs
index 197a53c31b6..d5ab67321f6 100644
--- a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs
@@ -216,7 +216,7 @@ private static void TryUniquifyColumnNames(
in StoreObjectIdentifier storeObject,
int maxLength)
{
- foreach (var property in type.GetDeclaredProperties())
+ foreach (var property in type.GetProperties())
{
var columnName = property.GetColumnName(storeObject);
if (columnName == null)
@@ -224,7 +224,8 @@ private static void TryUniquifyColumnNames(
continue;
}
- if (!columns.TryGetValue(columnName, out var otherProperty))
+ if (!columns.TryGetValue(columnName, out var otherProperty)
+ || property == otherProperty)
{
columns[columnName] = property;
continue;
@@ -237,13 +238,16 @@ private static void TryUniquifyColumnNames(
|| (property.IsConcurrencyToken && otherProperty.IsConcurrencyToken)
|| (!property.Builder.CanSetColumnName(null) && !otherProperty.Builder.CanSetColumnName(null)))
{
+ // Handle this with a default value convention #9329
if (property.GetAfterSaveBehavior() == PropertySaveBehavior.Save
- && otherProperty.GetAfterSaveBehavior() == PropertySaveBehavior.Save
- && property.ValueGenerated is ValueGenerated.Never or ValueGenerated.OnUpdateSometimes
- && otherProperty.ValueGenerated is ValueGenerated.Never or ValueGenerated.OnUpdateSometimes)
+ && property.ValueGenerated is ValueGenerated.Never or ValueGenerated.OnUpdateSometimes)
{
- // Handle this with a default value convention #9329
property.Builder.ValueGenerated(ValueGenerated.OnUpdateSometimes);
+ }
+
+ if (otherProperty.GetAfterSaveBehavior() == PropertySaveBehavior.Save
+ && otherProperty.ValueGenerated is ValueGenerated.Never or ValueGenerated.OnUpdateSometimes)
+ {
otherProperty.Builder.ValueGenerated(ValueGenerated.OnUpdateSometimes);
}
diff --git a/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs b/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs
index 9dd98b18a63..1322127f8a4 100644
--- a/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs
+++ b/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using Microsoft.EntityFrameworkCore.Metadata.Internal;
-
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
///
@@ -79,6 +77,8 @@ public virtual void ProcessModelFinalizing(
Dictionary? entityTypesMissingConcurrencyColumn = null;
foreach (var entityType in mappedTypes)
{
+ Check.DebugAssert(readOnlyProperties.Count != 0, $"No properties mapped to column '{concurrencyColumnName}'");
+
var foundMappedProperty = !IsConcurrencyTokenMissing(readOnlyProperties, entityType, mappedTypes)
|| entityType.GetProperties()
.Any(p => p.GetColumnName(StoreObjectIdentifier.Table(name, schema)) == concurrencyColumnName);
@@ -87,12 +87,8 @@ public virtual void ProcessModelFinalizing(
{
entityTypesMissingConcurrencyColumn ??= new Dictionary();
- // store the entity type which is missing the
- // concurrency token property, mapped to an example
- // property which _is_ mapped to this concurrency token
- // column and which will be used later as a template
- entityTypesMissingConcurrencyColumn.Add(
- entityType, readOnlyProperties.First());
+ // store the concurrency token property to be used later as a template
+ entityTypesMissingConcurrencyColumn.Add(entityType, readOnlyProperties.First());
}
}
@@ -168,7 +164,17 @@ public virtual void ProcessModelFinalizing(
nonHierarchyTypesCount++;
}
- foreach (var property in entityType.GetDeclaredProperties())
+ concurrencyColumns = FindConcurrencyColumns(entityType, storeObject, concurrencyColumns);
+ }
+
+ return nonHierarchyTypesCount < 2 ? null : concurrencyColumns;
+
+ static Dictionary>? FindConcurrencyColumns(
+ IReadOnlyTypeBase structuralType,
+ StoreObjectIdentifier storeObject,
+ Dictionary>? concurrencyColumns)
+ {
+ foreach (var property in structuralType.GetDeclaredProperties())
{
if (!property.IsConcurrencyToken
|| (property.ValueGenerated & ValueGenerated.OnUpdate) == 0)
@@ -183,7 +189,6 @@ public virtual void ProcessModelFinalizing(
}
concurrencyColumns ??= new Dictionary>();
-
if (!concurrencyColumns.TryGetValue(columnName, out var properties))
{
properties = new List();
@@ -192,9 +197,14 @@ public virtual void ProcessModelFinalizing(
properties.Add(property);
}
- }
- return nonHierarchyTypesCount < 2 ? null : concurrencyColumns;
+ foreach (var complexProperty in structuralType.GetDeclaredComplexProperties())
+ {
+ concurrencyColumns = FindConcurrencyColumns(complexProperty.ComplexType, storeObject, concurrencyColumns);
+ }
+
+ return concurrencyColumns;
+ }
}
///
@@ -209,8 +219,7 @@ public static bool IsConcurrencyTokenMissing(
IReadOnlyEntityType entityType,
IReadOnlyList mappedTypes)
{
- if (entityType.FindPrimaryKey() == null
- || propertiesMappedToConcurrencyColumn.Count == 0)
+ if (entityType.FindPrimaryKey() == null)
{
return false;
}
@@ -218,20 +227,18 @@ public static bool IsConcurrencyTokenMissing(
var propertyMissing = true;
foreach (var mappedProperty in propertiesMappedToConcurrencyColumn)
{
- var declaringType = mappedProperty.DeclaringType;
- var declaringEntityType = declaringType as IEntityType;
- if (declaringType.IsAssignableFrom(entityType)
- || entityType.IsAssignableFrom(declaringType)
- || declaringEntityType != null
- && (declaringEntityType.IsInOwnershipPath(entityType)
- || entityType.IsInOwnershipPath(declaringEntityType)))
+ var containingEntityType = mappedProperty.DeclaringType.ContainingEntityType;
+ if (containingEntityType.IsAssignableFrom(entityType)
+ || entityType.IsAssignableFrom(containingEntityType)
+ || containingEntityType.IsInOwnershipPath(entityType)
+ || entityType.IsInOwnershipPath(containingEntityType))
{
- // The concurrency token is on the base type, derived type or in the same aggregate
+ // The concurrency token is on the base type, derived type, a contained complex type or in the same aggregate
propertyMissing = false;
continue;
}
- var linkingFks = declaringEntityType?.FindForeignKeys(declaringEntityType.FindPrimaryKey()!.Properties)
+ var linkingFks = containingEntityType.FindForeignKeys(containingEntityType.FindPrimaryKey()!.Properties)
.Where(fk => fk.PrincipalKey.IsPrimaryKey()
&& mappedTypes.Contains(fk.PrincipalEntityType)).ToList();
if (linkingFks != null
@@ -259,9 +266,8 @@ private static void RemoveDerivedEntityTypes(
var baseType = entityType.BaseType;
while (baseType != null)
{
- if (entityTypeDictionary.ContainsKey(baseType))
+ if (entityTypeDictionary.Remove(entityType))
{
- entityTypeDictionary.Remove(entityType);
removed = true;
break;
}
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
index e3c8d25ea62..0a911611338 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs
@@ -758,8 +758,10 @@ private static void AddSqlQueries(RelationalModel databaseModel, IEntityType ent
var column = sqlQuery.FindColumn(columnName);
if (column == null)
{
- column = new SqlQueryColumn(columnName, property.GetColumnType(mappedQuery), sqlQuery);
- column.IsNullable = property.IsColumnNullable(mappedQuery);
+ column = new SqlQueryColumn(columnName, property.GetColumnType(mappedQuery), sqlQuery)
+ {
+ IsNullable = property.IsColumnNullable(mappedQuery)
+ };
sqlQuery.Columns.Add(columnName, column);
}
else if (!property.IsColumnNullable(mappedQuery))
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index a78aed237c8..313c27fc4bd 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -128,7 +128,7 @@ public static string ConflictingEnlistedTransaction
=> GetString("ConflictingEnlistedTransaction");
///
- /// An instance of entity type '{firstEntityType}' and an instance of entity type '{secondEntityType}' are mapped to the same row, but have different original property values for the properties {firstProperty} and {secondProperty} mapped to '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values.
+ /// Instances of types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row, but have different original property values for the properties {firstProperty} and {secondProperty} mapped to '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values.
///
public static string ConflictingOriginalRowValues(object? firstEntityType, object? secondEntityType, object? firstProperty, object? secondProperty, object? column)
=> string.Format(
@@ -136,7 +136,7 @@ public static string ConflictingOriginalRowValues(object? firstEntityType, objec
firstEntityType, secondEntityType, firstProperty, secondProperty, column);
///
- /// Instances of entity types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different original property values {firstConflictingValues} and {secondConflictingValues} for the column '{column}'.
+ /// Instances of types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different original property values {firstConflictingValues} and {secondConflictingValues} for the column '{column}'.
///
public static string ConflictingOriginalRowValuesSensitive(object? firstEntityType, object? secondEntityType, object? keyValue, object? firstConflictingValues, object? secondConflictingValues, object? column)
=> string.Format(
@@ -160,7 +160,7 @@ public static string ConflictingRowUpdateTypesSensitive(object? firstEntityType,
firstEntityType, firstKeyValue, firstState, secondEntityType, secondKeyValue, secondState);
///
- /// Instances of entity types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row, but have different property values for the properties {firstProperty} and {secondProperty} mapped to '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values.
+ /// Instances of types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row, but have different property values for the properties {firstProperty} and {secondProperty} mapped to '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values.
///
public static string ConflictingRowValues(object? firstEntityType, object? secondEntityType, object? firstProperty, object? secondProperty, object? column)
=> string.Format(
@@ -168,7 +168,7 @@ public static string ConflictingRowValues(object? firstEntityType, object? secon
firstEntityType, secondEntityType, firstProperty, secondProperty, column);
///
- /// Instances of entity types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different property values '{firstConflictingValue}' and '{secondConflictingValue}' for the column '{column}'.
+ /// Instances of types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different property values '{firstConflictingValue}' and '{secondConflictingValue}' for the column '{column}'.
///
public static string ConflictingRowValuesSensitive(object? firstEntityType, object? secondEntityType, object? keyValue, object? firstConflictingValue, object? secondConflictingValue, object? column)
=> string.Format(
@@ -1034,7 +1034,7 @@ public static string JsonEntityMappedToDifferentViewThanOwner(object? jsonType,
jsonType, viewName, ownerType, ownerViewName);
///
- /// Multiple owned root entities are mapped to the same JSON column '{column}' in table '{table}'. Each owned root entity must map to a different column.
+ /// JSON entity '{jsonEntity}' is missing key information. This is not allowed for tracking queries since EF can't correctly build identity for this entity object.
///
public static string JsonEntityMissingKeyInformation(object? jsonEntity)
=> string.Format(
@@ -1155,6 +1155,12 @@ public static string JsonNodeMustBeHandledByProviderSpecificVisitor
public static string JsonPropertyNameShouldBeConfiguredOnNestedNavigation
=> GetString("JsonPropertyNameShouldBeConfiguredOnNestedNavigation");
+ ///
+ /// Composing LINQ operators over collections inside JSON documents isn't supported or hasn't been implemented by your EF provider.
+ ///
+ public static string JsonQueryLinqOperatorsNotSupported
+ => GetString("JsonQueryLinqOperatorsNotSupported");
+
///
/// Invalid token type: '{tokenType}'.
///
@@ -1163,12 +1169,6 @@ public static string JsonReaderInvalidTokenType(object? tokenType)
GetString("JsonReaderInvalidTokenType", nameof(tokenType)),
tokenType);
- ///
- /// Composing LINQ operators over collections inside JSON documents isn't supported or hasn't been implemented by your EF provider.
- ///
- public static string JsonQueryLinqOperatorsNotSupported
- => GetString("JsonQueryLinqOperatorsNotSupported");
-
///
/// Entity {entity} is required but the JSON element containing it is null.
///
@@ -1544,12 +1544,12 @@ public static string SetOperationsOnDifferentStoreTypes
=> GetString("SetOperationsOnDifferentStoreTypes");
///
- /// A set operation 'setOperationType' requires valid type mapping for at least one of its sides.
+ /// A set operation '{setOperationType}' requires valid type mapping for at least one of its sides.
///
public static string SetOperationsRequireAtLeastOneSideWithValidTypeMapping(object? setOperationType)
- => string.Format(
- GetString("SetOperationsRequireAtLeastOneSideWithValidTypeMapping", nameof(setOperationType)),
- setOperationType);
+ => string.Format(
+ GetString("SetOperationsRequireAtLeastOneSideWithValidTypeMapping", nameof(setOperationType)),
+ setOperationType);
///
/// The SetProperty<TProperty> method can only be used within 'ExecuteUpdate' method.
@@ -2038,7 +2038,7 @@ public static string UnsupportedOperatorForSqlExpression(object? nodeType, objec
nodeType, expressionType);
///
- /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'.
+ /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'.
///
public static string UnsupportedPropertyType(object? entity, object? property, object? clrType)
=> string.Format(
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index 6ca3e9b0d28..81522992c44 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -161,10 +161,10 @@
The connection is currently enlisted in a transaction. The enlisted transaction needs to be completed before starting a new transaction.
- An instance of entity type '{firstEntityType}' and an instance of entity type '{secondEntityType}' are mapped to the same row, but have different original property values for the properties {firstProperty} and {secondProperty} mapped to '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values.
+ Instances of types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row, but have different original property values for the properties {firstProperty} and {secondProperty} mapped to '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values.
- Instances of entity types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different original property values {firstConflictingValues} and {secondConflictingValues} for the column '{column}'.
+ Instances of types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different original property values {firstConflictingValues} and {secondConflictingValues} for the column '{column}'.An instance of entity type '{firstEntityType}' is marked as '{firstState}', but an instance of entity type '{secondEntityType}' is marked as '{secondState}' and both are mapped to the same row. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values.
@@ -173,10 +173,10 @@
The instance of entity type '{firstEntityType}' with the key value '{firstKeyValue}' is marked as '{firstState}', but the instance of entity type '{secondEntityType}' with the key value '{secondKeyValue}' is marked as '{secondState}' and both are mapped to the same row.
- Instances of entity types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row, but have different property values for the properties {firstProperty} and {secondProperty} mapped to '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values.
+ Instances of types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row, but have different property values for the properties {firstProperty} and {secondProperty} mapped to '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values.
- Instances of entity types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different property values '{firstConflictingValue}' and '{secondConflictingValue}' for the column '{column}'.
+ Instances of types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different property values '{firstConflictingValue}' and '{secondConflictingValue}' for the column '{column}'.A seed entity for entity type '{entityType}' has the same key value as another seed entity mapped to the same table '{table}', but have different values for the column '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values.
@@ -553,12 +553,12 @@
The JSON property name should only be configured on nested owned navigations.
-
- Invalid token type: '{tokenType}'.
-
Composing LINQ operators over collections inside JSON documents isn't supported or hasn't been implemented by your EF provider.
+
+ Invalid token type: '{tokenType}'.
+
Entity {entity} is required but the JSON element containing it is null.
@@ -1008,7 +1008,7 @@
Unable to translate set operation when matching columns on both sides have different store types.
- A set operation 'setOperationType' requires valid type mapping for at least one of its sides.
+ A set operation '{setOperationType}' requires valid type mapping for at least one of its sides.The SetProperty<TProperty> method can only be used within 'ExecuteUpdate' method.
@@ -1235,4 +1235,4 @@
'VisitChildren' must be overridden in the class deriving from 'SqlExpression'.
-
+
\ No newline at end of file
diff --git a/src/EFCore.Relational/Update/ColumnModification.cs b/src/EFCore.Relational/Update/ColumnModification.cs
index 594c177d64a..62698cfeaae 100644
--- a/src/EFCore.Relational/Update/ColumnModification.cs
+++ b/src/EFCore.Relational/Update/ColumnModification.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Data;
+using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
namespace Microsoft.EntityFrameworkCore.Update;
@@ -127,8 +128,8 @@ public virtual object? OriginalValue
get => Entry == null
? _originalValue
: Entry.SharedIdentityEntry == null
- ? Entry.GetOriginalValue(Property!)
- : Entry.SharedIdentityEntry.GetOriginalValue(Property!);
+ ? GetOriginalValue(Entry, Property!)
+ : GetOriginalValue(Entry.SharedIdentityEntry, Property!);
set
{
if (Entry == null)
@@ -137,7 +138,7 @@ public virtual object? OriginalValue
}
else
{
- Entry.SetOriginalValue(Property!, value);
+ SetOriginalValue(value);
if (_sharedColumnModifications != null)
{
foreach (var sharedModification in _sharedColumnModifications)
@@ -156,7 +157,7 @@ public virtual object? Value
? _value
: Entry.EntityState == EntityState.Deleted
? null
- : Entry.GetCurrentValue(Property!);
+ : GetCurrentValue(Entry, Property!);
set
{
if (Entry == null)
@@ -165,7 +166,7 @@ public virtual object? Value
}
else
{
- Entry.SetStoreGeneratedValue(Property!, value);
+ SetStoreGeneratedValue(Entry, Property!, value);
if (_sharedColumnModifications != null)
{
foreach (var sharedModification in _sharedColumnModifications)
@@ -177,6 +178,85 @@ public virtual object? Value
}
}
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ ///
+ /// 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 static object? GetOriginalValue(IUpdateEntry entry, IProperty property)
+ => GetEntry((IInternalEntry)entry, property).GetOriginalValue(property);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static object? GetOriginalProviderValue(IUpdateEntry entry, IProperty property)
+ => GetEntry((IInternalEntry)entry, property).GetOriginalProviderValue(property);
+
+ private void SetOriginalValue(object? value)
+ => GetEntry((IInternalEntry)Entry!, Property!).SetOriginalValue(Property!, value);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static object? GetCurrentValue(IUpdateEntry entry, IProperty property)
+ => GetEntry((IInternalEntry)entry, property).GetCurrentValue(property);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static object? GetCurrentProviderValue(IUpdateEntry entry, IProperty property)
+ => GetEntry((IInternalEntry)entry, property).GetCurrentProviderValue(property);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static void SetStoreGeneratedValue(IUpdateEntry entry, IProperty property, object? value)
+ => GetEntry((IInternalEntry)entry, property).SetStoreGeneratedValue(property, value);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static bool IsModified(IUpdateEntry entry, IProperty property)
+ => GetEntry((IInternalEntry)entry, property).IsModified(property);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static bool IsStoreGenerated(IUpdateEntry entry, IProperty property)
+ => GetEntry((IInternalEntry)entry, property).IsStoreGenerated(property);
+
+ private static IInternalEntry GetEntry(IInternalEntry entry, IPropertyBase property)
+ {
+ if (property.DeclaringType.IsAssignableFrom(entry.StructuralType))
+ {
+ return entry;
+ }
+
+ var complexProperty = ((IComplexType)property.DeclaringType).ComplexProperty;
+ return GetEntry(entry, complexProperty).GetComplexPropertyEntry(complexProperty);
+ }
+#pragma warning restore EF1001 // Internal EF Core API usage.
+
///
public virtual string? JsonPath { get; }
@@ -192,47 +272,54 @@ public virtual void AddSharedColumnModification(IColumnModification modification
if (UseCurrentValueParameter
&& !Property.GetProviderValueComparer().Equals(
- Entry.GetCurrentProviderValue(Property),
- modification.Entry.GetCurrentProviderValue(modification.Property)))
+ GetCurrentProviderValue(Entry, Property),
+ GetCurrentProviderValue(modification.Entry, modification.Property)))
{
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ var existingEntry = GetEntry((IInternalEntry)Entry!, Property);
+ var newEntry = GetEntry((IInternalEntry)modification.Entry, modification.Property);
+
if (_sensitiveLoggingEnabled)
{
throw new InvalidOperationException(
RelationalStrings.ConflictingRowValuesSensitive(
- Entry.EntityType.DisplayName(),
- modification.Entry!.EntityType.DisplayName(),
+ existingEntry.StructuralType.DisplayName(),
+ newEntry.StructuralType.DisplayName(),
Entry.BuildCurrentValuesString(Entry.EntityType.FindPrimaryKey()!.Properties),
- Entry.BuildCurrentValuesString(new[] { Property }),
- modification.Entry.BuildCurrentValuesString(new[] { modification.Property }),
+ GetEntry((IInternalEntry)Entry!, Property).BuildCurrentValuesString(new[] { Property }),
+ newEntry.BuildCurrentValuesString(new[] { modification.Property }),
ColumnName));
}
throw new InvalidOperationException(
RelationalStrings.ConflictingRowValues(
- Entry.EntityType.DisplayName(),
- modification.Entry.EntityType.DisplayName(),
+ existingEntry.StructuralType.DisplayName(),
+ newEntry.StructuralType.DisplayName(),
new[] { Property }.Format(),
new[] { modification.Property }.Format(),
ColumnName));
+#pragma warning restore EF1001 // Internal EF Core API usage.
}
- if (UseOriginalValueParameter
- && !Property.GetProviderValueComparer().Equals(
- Entry.SharedIdentityEntry == null
- ? Entry.GetOriginalProviderValue(Property)
- : Entry.SharedIdentityEntry.GetOriginalProviderValue(Property),
- modification.Entry.SharedIdentityEntry == null
- ? modification.Entry.GetOriginalProviderValue(modification.Property)
- : modification.Entry.SharedIdentityEntry.GetOriginalProviderValue(modification.Property)))
+ if (UseOriginalValueParameter)
{
+ var originalValue = Entry.SharedIdentityEntry == null
+ ? GetOriginalProviderValue(Entry, Property)
+ : GetOriginalProviderValue(Entry.SharedIdentityEntry, Property);
+ if (Property.GetProviderValueComparer().Equals(
+ originalValue,
+ modification.Entry.SharedIdentityEntry == null
+ ? GetOriginalProviderValue(modification.Entry, modification.Property)
+ : GetOriginalProviderValue(modification.Entry.SharedIdentityEntry, modification.Property)))
+ {
+ _sharedColumnModifications.Add(modification);
+ return;
+ }
+
if (Entry.EntityState == EntityState.Modified
&& modification.Entry.EntityState == EntityState.Added
&& modification.Entry.SharedIdentityEntry == null)
{
- var originalValue = Entry.SharedIdentityEntry == null
- ? Entry.GetOriginalProviderValue(Property)
- : Entry.SharedIdentityEntry.GetOriginalProviderValue(Property);
-
var typeMapping = modification.Property.GetTypeMapping();
var converter = typeMapping.Converter;
if (converter != null)
@@ -244,25 +331,29 @@ public virtual void AddSharedColumnModification(IColumnModification modification
}
else
{
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ var existingEntry = GetEntry((IInternalEntry)Entry!, Property);
+ var newEntry = GetEntry((IInternalEntry)modification.Entry, modification.Property);
if (_sensitiveLoggingEnabled)
{
throw new InvalidOperationException(
RelationalStrings.ConflictingOriginalRowValuesSensitive(
- Entry.EntityType.DisplayName(),
- modification.Entry.EntityType.DisplayName(),
+ existingEntry.StructuralType.DisplayName(),
+ newEntry.StructuralType.DisplayName(),
Entry.BuildCurrentValuesString(Entry.EntityType.FindPrimaryKey()!.Properties),
- Entry.BuildOriginalValuesString(new[] { Property }),
- modification.Entry.BuildOriginalValuesString(new[] { modification.Property }),
+ existingEntry.BuildOriginalValuesString(new[] { Property }),
+ newEntry.BuildOriginalValuesString(new[] { modification.Property }),
ColumnName));
}
throw new InvalidOperationException(
RelationalStrings.ConflictingOriginalRowValues(
- Entry.EntityType.DisplayName(),
- modification.Entry.EntityType.DisplayName(),
+ existingEntry.StructuralType.DisplayName(),
+ newEntry.StructuralType.DisplayName(),
new[] { Property }.Format(),
new[] { modification.Property }.Format(),
ColumnName));
+#pragma warning restore EF1001 // Internal EF Core API usage.
}
}
diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
index 4b2f86e7288..694c31232b4 100644
--- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
+++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs
@@ -448,7 +448,7 @@ private void Format(
var dependentCommand = reverseDependency ? target : source;
var dependentEntry = dependentCommand.Entries.First(e => foreignKey.DeclaringEntityType.IsAssignableFrom(e.EntityType));
builder.Append(dependentEntry.BuildCurrentValuesString(foreignKey.Properties))
- .Append(" ");
+ .Append(' ');
if (!reverseDependency)
{
@@ -507,7 +507,7 @@ private void Format(IKey key, IReadOnlyModificationCommand source, IReadOnlyModi
? dependentEntry.BuildCurrentValuesString(key.Properties)
: dependentEntry.BuildOriginalValuesString(key.Properties));
- builder.Append(" ");
+ builder.Append(' ');
if (!reverseDependency)
{
@@ -1122,15 +1122,15 @@ private void AddUniqueValueEdges()
continue;
}
- var indexValue = ((TableIndex)index).GetRowIndexValueFactory()
+ var (value, _) = ((TableIndex)index).GetRowIndexValueFactory()
.CreateEquatableIndexValue(command, fromOriginalValues: true);
- if (indexValue.Value != null)
+ if (value != null)
{
indexPredecessorsMap ??= new Dictionary
- public virtual Func TemporaryValuesFactory
+ public virtual Func TemporaryValuesFactory
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _temporaryValuesFactory, this,
static complexType =>
diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs
index fd8cdc105b2..e286ec5be4b 100644
--- a/src/EFCore/Metadata/Internal/EntityType.cs
+++ b/src/EFCore/Metadata/Internal/EntityType.cs
@@ -61,8 +61,8 @@ private readonly SortedDictionary _triggers
private InstantiationBinding? _serviceOnlyConstructorBinding;
private Func? _relationshipSnapshotFactory;
- private Func? _originalValuesFactory;
- private Func? _temporaryValuesFactory;
+ private Func? _originalValuesFactory;
+ private Func? _temporaryValuesFactory;
private Func? _storeGeneratedValuesFactory;
private Func? _shadowValuesFactory;
private Func? _emptyShadowValuesFactory;
@@ -221,7 +221,7 @@ public virtual void SetRemovedFromModel()
/// 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.
///
- new public virtual EntityType? BaseType
+ public new virtual EntityType? BaseType
=> (EntityType?)base.BaseType;
///
@@ -1828,9 +1828,8 @@ public virtual IEnumerable FindSkipNavigationsInHierarchy(string
var removed = _skipNavigations.Remove(navigation.Name);
Check.DebugAssert(removed, "Expected the navigation to be removed");
- removed = navigation.ForeignKey is ForeignKey foreignKey
- ? foreignKey.ReferencingSkipNavigations!.Remove(navigation)
- : true;
+ removed = navigation.ForeignKey is not ForeignKey foreignKey
+ || foreignKey.ReferencingSkipNavigations!.Remove(navigation);
Check.DebugAssert(removed, "removed is false");
removed = navigation.TargetEntityType.DeclaredReferencingSkipNavigations!.Remove(navigation);
@@ -2277,7 +2276,7 @@ public virtual Func RelationshipSnapshotFactory
/// 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 Func OriginalValuesFactory
+ public virtual Func OriginalValuesFactory
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _originalValuesFactory, this,
static entityType =>
@@ -2307,7 +2306,7 @@ public virtual Func StoreGeneratedValuesFactory
/// 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 Func TemporaryValuesFactory
+ public virtual Func TemporaryValuesFactory
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _temporaryValuesFactory, this,
static entityType =>
@@ -2959,14 +2958,9 @@ private void CheckDiscriminatorProperty(Property? property)
///
/// The name of the property that will be used for storing a discriminator value.
public virtual string? GetDiscriminatorPropertyName()
- {
- if (BaseType != null)
- {
- return ((IReadOnlyEntityType)this).GetRootType().GetDiscriminatorPropertyName();
- }
-
- return (string?)this[CoreAnnotationNames.DiscriminatorProperty];
- }
+ => BaseType != null
+ ? ((IReadOnlyEntityType)this).GetRootType().GetDiscriminatorPropertyName()
+ : (string?)this[CoreAnnotationNames.DiscriminatorProperty];
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs
index bbe05b057be..841abb56138 100644
--- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs
+++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs
@@ -172,6 +172,7 @@ public static PropertyCounts CalculateCounts(this IRuntimeEntityType entityType)
{
propertyIndex = baseCounts.PropertyCount;
navigationIndex = baseCounts.NavigationCount;
+ complexPropertyIndex = baseCounts.ComplexPropertyCount;
originalValueIndex = baseCounts.OriginalValueCount;
shadowIndex = baseCounts.ShadowCount;
relationshipIndex = baseCounts.RelationshipCount;
diff --git a/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs b/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs
index 6bfa4bbd60f..43fcd3992c6 100644
--- a/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs
+++ b/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs
@@ -19,7 +19,7 @@ public interface IRuntimeTypeBase : ITypeBase
/// 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.
///
- Func OriginalValuesFactory { get; }
+ Func OriginalValuesFactory { get; }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -35,7 +35,7 @@ public interface IRuntimeTypeBase : ITypeBase
/// 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.
///
- Func TemporaryValuesFactory { get; }
+ Func TemporaryValuesFactory { get; }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -115,6 +115,15 @@ int RelationshipPropertyCount
int NavigationCount
=> Counts.NavigationCount;
+ ///
+ /// 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.
+ ///
+ int ComplexPropertyCount
+ => Counts.ComplexPropertyCount;
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
index 512d781c674..4152e5ed1ff 100644
--- a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
+++ b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs
@@ -42,12 +42,12 @@ private static PropertyAccessors CreateGeneric(IPropertyBase property
property == null ? null : CreateValueBufferGetter(property));
}
- private static Func CreateCurrentValueGetter(
+ private static Func CreateCurrentValueGetter(
IPropertyBase propertyBase,
bool useStoreGeneratedValues)
{
var entityClrType = propertyBase.DeclaringType.ClrType;
- var entryParameter = Expression.Parameter(typeof(InternalEntityEntry), "entry");
+ var entryParameter = Expression.Parameter(typeof(IInternalEntry), "entry");
var propertyIndex = propertyBase.GetIndex();
var shadowIndex = propertyBase.GetShadowIndex();
var storeGeneratedIndex = propertyBase.GetStoreGeneratedIndex();
@@ -66,7 +66,7 @@ private static Func CreateCurrentValueGetter CreateCurrentValueGetter CreateCurrentValueGetter CreateCurrentValueGetter CreateCurrentValueGetter>(
+ return Expression.Lambda>(
currentValueExpression,
entryParameter)
.Compile();
}
- private static Func CreateOriginalValueGetter(IProperty property)
+ private static Func CreateOriginalValueGetter(IProperty property)
{
- var entryParameter = Expression.Parameter(typeof(InternalEntityEntry), "entry");
+ var entryParameter = Expression.Parameter(typeof(IInternalEntry), "entry");
var originalValuesIndex = property.GetOriginalValueIndex();
- return Expression.Lambda>(
+ return Expression.Lambda>(
originalValuesIndex >= 0
? Expression.Call(
entryParameter,
diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs
index cf020c65607..8c194549d2e 100644
--- a/src/EFCore/Metadata/RuntimeTypeBase.cs
+++ b/src/EFCore/Metadata/RuntimeTypeBase.cs
@@ -23,16 +23,15 @@ public abstract class RuntimeTypeBase : AnnotatableBase, IRuntimeTypeBase
private readonly SortedSet _directlyDerivedTypes = new(TypeBaseNameComparer.Instance);
private readonly SortedDictionary _properties;
- private readonly SortedDictionary _complexProperties =
- new SortedDictionary(StringComparer.Ordinal);
+ private readonly SortedDictionary _complexProperties = new(StringComparer.Ordinal);
private readonly PropertyInfo? _indexerPropertyInfo;
private readonly bool _isPropertyBag;
private readonly ChangeTrackingStrategy _changeTrackingStrategy;
// Warning: Never access these fields directly as access needs to be thread-safe
- private Func? _originalValuesFactory;
- private Func? _temporaryValuesFactory;
+ private Func? _originalValuesFactory;
+ private Func? _temporaryValuesFactory;
private Func? _storeGeneratedValuesFactory;
private Func? _shadowValuesFactory;
private Func? _emptyShadowValuesFactory;
@@ -491,7 +490,7 @@ private IEnumerable FindDerivedComplexProperties(string
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
[EntityFrameworkInternal]
- public virtual void SetOriginalValuesFactory(Func factory)
+ public virtual void SetOriginalValuesFactory(Func factory)
{
_originalValuesFactory = factory;
}
@@ -515,7 +514,7 @@ public virtual void SetStoreGeneratedValuesFactory(Func factory)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
[EntityFrameworkInternal]
- public virtual void SetTemporaryValuesFactory(Func factory)
+ public virtual void SetTemporaryValuesFactory(Func factory)
{
_temporaryValuesFactory = factory;
}
@@ -688,7 +687,7 @@ PropertyCounts IRuntimeTypeBase.Counts
}
///
- Func IRuntimeTypeBase.OriginalValuesFactory
+ Func IRuntimeTypeBase.OriginalValuesFactory
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _originalValuesFactory, this,
static complexType => RuntimeFeature.IsDynamicCodeSupported
@@ -704,7 +703,7 @@ Func IRuntimeTypeBase.StoreGeneratedValuesFactory
: throw new InvalidOperationException(CoreStrings.NativeAotNoCompiledModel));
///
- Func IRuntimeTypeBase.TemporaryValuesFactory
+ Func IRuntimeTypeBase.TemporaryValuesFactory
=> NonCapturingLazyInitializer.EnsureInitialized(
ref _temporaryValuesFactory, this,
static complexType => RuntimeFeature.IsDynamicCodeSupported
diff --git a/src/EFCore/Update/UpdateEntryExtensions.cs b/src/EFCore/Update/UpdateEntryExtensions.cs
index c6dd69bc28e..33efe07bb6e 100644
--- a/src/EFCore/Update/UpdateEntryExtensions.cs
+++ b/src/EFCore/Update/UpdateEntryExtensions.cs
@@ -25,6 +25,25 @@ public static class UpdateEntryExtensions
/// The property to get the value for.
/// The value for the property.
public static object? GetCurrentProviderValue(this IUpdateEntry updateEntry, IProperty property)
+ => GetCurrentProviderValue((IInternalEntry)updateEntry, property);
+
+ ///
+ /// Gets the original value that was assigned to the property and converts it to the provider-expected value.
+ ///
+ /// The entry.
+ /// The property to get the value for.
+ /// The value for the property.
+ public static object? GetOriginalProviderValue(this IUpdateEntry updateEntry, IProperty property)
+ => GetOriginalProviderValue((IInternalEntry)updateEntry, 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.
+ ///
+ [EntityFrameworkInternal]
+ public static object? GetCurrentProviderValue(this IInternalEntry updateEntry, IProperty property)
{
var value = updateEntry.GetCurrentValue(property);
var typeMapping = property.GetTypeMapping();
@@ -42,12 +61,13 @@ public static class UpdateEntryExtensions
}
///
- /// Gets the original value that was assigned to the property and converts it to the provider-expected value.
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
///
- /// The entry.
- /// The property to get the value for.
- /// The value for the property.
- public static object? GetOriginalProviderValue(this IUpdateEntry updateEntry, IProperty property)
+ [EntityFrameworkInternal]
+ public static object? GetOriginalProviderValue(this IInternalEntry updateEntry, IProperty property)
{
var value = updateEntry.GetOriginalValue(property);
var typeMapping = property.GetTypeMapping();
@@ -284,6 +304,31 @@ void AppendRelatedKey(IEntityType targetType, object value)
public static string BuildCurrentValuesString(
this IUpdateEntry entry,
IEnumerable properties)
+ => BuildCurrentValuesString((IInternalEntry)entry, properties);
+
+ ///
+ /// Creates a formatted string representation of the given properties and their original
+ /// values such as is useful when throwing exceptions about keys, indexes, etc. that use
+ /// the properties.
+ ///
+ /// The entry from which values will be obtained.
+ /// The properties to format.
+ /// The string representation.
+ public static string BuildOriginalValuesString(
+ this IUpdateEntry entry,
+ IEnumerable properties)
+ => BuildOriginalValuesString((IInternalEntry)entry, properties);
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public static string BuildCurrentValuesString(
+ this IInternalEntry entry,
+ IEnumerable properties)
=> "{"
+ string.Join(
", ", properties.Select(
@@ -299,15 +344,14 @@ public static string BuildCurrentValuesString(
+ "}";
///
- /// Creates a formatted string representation of the given properties and their original
- /// values such as is useful when throwing exceptions about keys, indexes, etc. that use
- /// the properties.
+ /// 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.
///
- /// The entry from which values will be obtained.
- /// The properties to format.
- /// The string representation.
+ [EntityFrameworkInternal]
public static string BuildOriginalValuesString(
- this IUpdateEntry entry,
+ this IInternalEntry entry,
IEnumerable properties)
=> "{"
+ string.Join(
diff --git a/test/EFCore.Cosmos.FunctionalTests/F1CosmosFixture.cs b/test/EFCore.Cosmos.FunctionalTests/F1CosmosFixture.cs
index 5401a33ba3a..3e2668bb365 100644
--- a/test/EFCore.Cosmos.FunctionalTests/F1CosmosFixture.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/F1CosmosFixture.cs
@@ -43,6 +43,7 @@ protected override void BuildModelExternal(ModelBuilder modelBuilder)
});
modelBuilder.Entity()
+ .Ignore(s => s.Details)
.OwnsOne(
s => s.Details, eb =>
{
diff --git a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs
index 553890e9114..1b21da63934 100644
--- a/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/TableSplittingTestBase.cs
@@ -14,32 +14,6 @@ protected TableSplittingTestBase(ITestOutputHelper testOutputHelper)
// TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper);
}
- [ConditionalFact]
- public virtual async Task Can_update_just_dependents()
- {
- await InitializeAsync(OnModelCreating);
-
- Operator firstOperator;
- Engine firstEngine;
- using (var context = CreateContext())
- {
- firstOperator = context.Set().OrderBy(o => o.VehicleName).First();
- firstOperator.Name += "1";
- firstEngine = context.Set().OrderBy(o => o.VehicleName).First();
- firstEngine.Description += "1";
-
- context.SaveChanges();
-
- Assert.Empty(context.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged));
- }
-
- using (var context = CreateContext())
- {
- Assert.Equal(firstOperator.Name, context.Set().OrderBy(o => o.VehicleName).First().Name);
- Assert.Equal(firstEngine.Description, context.Set().OrderBy(o => o.VehicleName).First().Description);
- }
- }
-
[ConditionalFact]
public virtual async Task Can_query_shared()
{
@@ -189,6 +163,52 @@ await InitializeAsync(
}
}
+ [ConditionalFact]
+ public virtual async Task Can_share_required_columns_with_complex_types()
+ {
+ await InitializeAsync(
+ modelBuilder =>
+ {
+ OnModelCreatingComplex(modelBuilder);
+ modelBuilder.Entity(
+ vb =>
+ {
+ vb.Property(v => v.SeatingCapacity).HasColumnName("SeatingCapacity");
+ });
+ modelBuilder.Entity(
+ vb =>
+ {
+ vb.ComplexProperty(v => v.Engine, eb =>
+ {
+ eb.Property("SeatingCapacity").HasColumnName("SeatingCapacity");
+ });
+ });
+ }, seed: false);
+
+ using (var context = CreateContext())
+ {
+ var scooterEntry = await context.AddAsync(
+ new PoweredVehicle
+ {
+ Name = "Electric scooter",
+ SeatingCapacity = 1,
+ Engine = new Engine(),
+ Operator = new Operator { Name = "Kai Saunders", Details = new OperatorDetails() }
+ });
+
+ context.SaveChanges();
+
+ //Assert.Equal(scooter.SeatingCapacity, scooterEntry.ComplexProperty(v => v.Engine).TargetEntry.Property("SeatingCapacity").CurrentValue);
+ }
+
+ //using (var context = CreateContext())
+ //{
+ // var scooter = context.Set().Single(v => v.Name == "Electric scooter");
+
+ // Assert.Equal(scooter.SeatingCapacity, context.Entry(scooter).ComplexProperty(v => v.Engine).TargetEntry.Property("SeatingCapacity").CurrentValue);
+ //}
+ }
+
[ConditionalFact]
public virtual async Task Can_use_optional_dependents_with_shared_concurrency_tokens()
{
@@ -260,6 +280,79 @@ await InitializeAsync(
}
}
+ [ConditionalFact]
+ public virtual async Task Can_use_optional_dependents_with_shared_concurrency_tokens_with_complex_types()
+ {
+ await InitializeAsync(
+ modelBuilder =>
+ {
+ OnModelCreatingComplex(modelBuilder);
+ modelBuilder.Entity(
+ vb =>
+ {
+ vb.Property(v => v.SeatingCapacity).HasColumnName("SeatingCapacity").IsConcurrencyToken();
+ });
+ modelBuilder.Entity(
+ vb =>
+ {
+ vb.ComplexProperty(v => v.Engine, eb =>
+ {
+ eb.Property("SeatingCapacity").HasColumnName("SeatingCapacity").IsConcurrencyToken();
+ });
+ });
+ }, seed: false);
+
+ using (var context = CreateContext())
+ {
+ var scooterEntry = await context.AddAsync(
+ new PoweredVehicle
+ {
+ Name = "Electric scooter",
+ SeatingCapacity = 1,
+ Engine = new Engine(),
+ Operator = new Operator { Name = "Kai Saunders", Details = new OperatorDetails() }
+ });
+
+ context.SaveChanges();
+ }
+
+ //using (var context = CreateContext())
+ //{
+ // var scooter = context.Set().Single(v => v.Name == "Electric scooter");
+
+ // Assert.Equal(1, scooter.SeatingCapacity);
+
+ // scooter.Engine = new Engine();
+
+ // var engineCapacityEntry = context.Entry(scooter).ComplexProperty(v => v.Engine).TargetEntry.Property("SeatingCapacity");
+
+ // Assert.Equal(0, engineCapacityEntry.OriginalValue);
+
+ // context.SaveChanges();
+
+ // Assert.Equal(0, engineCapacityEntry.OriginalValue);
+ // Assert.Equal(0, engineCapacityEntry.CurrentValue);
+ //}
+
+ //using (var context = CreateContext())
+ //{
+ // var scooter = context.Set().Single(v => v.Name == "Electric scooter");
+
+ // Assert.Equal(scooter.SeatingCapacity, context.Entry(scooter).ComplexProperty(v => v.Engine).TargetEntry.Property("SeatingCapacity").CurrentValue);
+
+ // scooter.SeatingCapacity = 2;
+ // context.SaveChanges();
+ //}
+
+ //using (var context = CreateContext())
+ //{
+ // var scooter = context.Set().Include(v => v.Engine).Single(v => v.Name == "Electric scooter");
+
+ // Assert.Equal(2, scooter.SeatingCapacity);
+ // Assert.Equal(2, context.Entry(scooter).ComplexProperty(v => v.Engine).TargetEntry.Property("SeatingCapacity").CurrentValue);
+ //}
+ }
+
protected async Task Test_roundtrip(Action onModelCreating)
{
await InitializeAsync(onModelCreating);
@@ -351,28 +444,71 @@ await InitializeAsync(
}
}
- [ConditionalFact(Skip = "Issue #24970")]
+ [ConditionalFact]
+ public virtual async Task Can_update_just_dependents()
+ {
+ await InitializeAsync(OnModelCreating);
+
+ Operator firstOperator;
+ Engine firstEngine;
+ using (var context = CreateContext())
+ {
+ firstOperator = context.Set().OrderBy(o => o.VehicleName).First();
+ firstOperator.Name += "1";
+ firstEngine = context.Set().OrderBy(o => o.VehicleName).First();
+ firstEngine.Description += "1";
+
+ context.SaveChanges();
+
+ Assert.Empty(context.ChangeTracker.Entries().Where(e => e.State != EntityState.Unchanged));
+ }
+
+ using (var context = CreateContext())
+ {
+ Assert.Equal(firstOperator.Name, context.Set().OrderBy(o => o.VehicleName).First().Name);
+ Assert.Equal(firstEngine.Description, context.Set().OrderBy(o => o.VehicleName).First().Description);
+ }
+ }
+
+ [ConditionalFact]
public virtual async Task Can_insert_dependent_with_just_one_parent()
{
await InitializeAsync(OnModelCreating);
- using var context = CreateContext();
- await context.AddAsync(
- new PoweredVehicle
- {
- Name = "Fuel transport",
- SeatingCapacity = 1,
- Operator = new LicensedOperator { Name = "Jack Jackson", LicenseType = "Class A CDC" }
- });
- await context.AddAsync(
- new FuelTank
- {
- Capacity = 10000_1,
- FuelType = "Gas",
- VehicleName = "Fuel transport"
- });
+ using (var context = CreateContext())
+ {
+ await context.AddAsync(
+ new PoweredVehicle
+ {
+ Name = "Fuel transport",
+ SeatingCapacity = 1,
+ Operator = new LicensedOperator { Name = "Jack Jackson", LicenseType = "Class A CDC" }
+ });
+ await context.AddAsync(
+ new FuelTank
+ {
+ Capacity = 10000_1,
+ FuelType = "Gas",
+ VehicleName = "Fuel transport"
+ });
- context.SaveChanges();
+ context.SaveChanges();
+
+ var savedEntries = context.ChangeTracker.Entries().ToList();
+ Assert.Equal(3, savedEntries.Count);
+ Assert.All(savedEntries, e => Assert.Equal(EntityState.Unchanged, e.State));
+ }
+
+ using (var context = CreateContext())
+ {
+ var transport = context.Vehicles.Include(v => v.Operator)
+ .Single(v => v.Name == "Fuel transport");
+ var tank = context.Set().Include(v => v.Vehicle)
+ .Single(v => v.VehicleName == "Fuel transport");
+ Assert.NotNull(transport.Operator.Name);
+ Assert.Null(tank.Engine);
+ Assert.Same(transport, tank.Vehicle);
+ }
}
[ConditionalFact]
@@ -798,6 +934,43 @@ protected virtual void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity().ToTable("Vehicles");
}
+ protected virtual void OnModelCreatingComplex(ModelBuilder modelBuilder)
+ {
+ OnModelCreating(modelBuilder);
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Ignore();
+ modelBuilder.Entity(
+ vb =>
+ {
+ vb.Property(v => v.Name).HasColumnName("Name");
+ vb.Ignore(v => v.Operator);
+ vb.ComplexProperty(v => v.Operator, ob =>
+ {
+ ob.IsRequired();
+ ob.Property(o => o.VehicleName).HasColumnName("Name");
+ ob.ComplexProperty(o => o.Details)
+ .IsRequired()
+ .Property(o => o.VehicleName).HasColumnName("Name");
+ });
+ });
+ modelBuilder.Entity(
+ vb =>
+ {
+ vb.Ignore(v => v.Engine);
+ vb.ComplexProperty(v => v.Engine, eb =>
+ {
+ eb.IsRequired();
+ eb.Property(o => o.VehicleName).HasColumnName("Name");
+ });
+ });
+ }
+
protected virtual void OnSharedModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity(
diff --git a/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs
index 6a6add4cc3a..eaf6b6f47d0 100644
--- a/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Update/UpdateSqlGeneratorTestBase.cs
@@ -244,7 +244,7 @@ protected IModificationCommand CreateInsertCommand(bool identityKey = true, bool
entry.SetEntityState(EntityState.Added);
var generator = new ParameterNameGenerator();
- var duckType = model.FindEntityType(typeof(Duck));
+ var duckType = entry.EntityType;
var idProperty = duckType.FindProperty(nameof(Duck.Id));
var nameProperty = duckType.FindProperty(nameof(Duck.Name));
var quacksProperty = duckType.FindProperty(nameof(Duck.Quacks));
@@ -286,7 +286,7 @@ protected IModificationCommand CreateUpdateCommand(bool isComputed = true, bool
entry.SetEntityState(EntityState.Modified);
var generator = new ParameterNameGenerator();
- var duckType = model.FindEntityType(typeof(Duck));
+ var duckType = entry.EntityType;
var idProperty = duckType.FindProperty(nameof(Duck.Id));
var nameProperty = duckType.FindProperty(nameof(Duck.Name));
var quacksProperty = duckType.FindProperty(nameof(Duck.Quacks));
@@ -317,13 +317,12 @@ protected IModificationCommand CreateUpdateCommand(bool isComputed = true, bool
protected IModificationCommand CreateDeleteCommand(bool concurrencyToken = true)
{
- var model = GetDuckModel();
- var stateManager = TestHelpers.CreateContextServices(model).GetRequiredService();
+ var stateManager = TestHelpers.CreateContextServices(GetDuckModel()).GetRequiredService();
var entry = stateManager.GetOrCreateEntry(new Duck());
entry.SetEntityState(EntityState.Deleted);
var generator = new ParameterNameGenerator();
- var duckType = model.FindEntityType(typeof(Duck));
+ var duckType = entry.EntityType;
var idProperty = duckType.FindProperty(nameof(Duck.Id));
var concurrencyProperty = duckType.FindProperty(nameof(Duck.ConcurrencyToken));
diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs
index b567393708e..acd7f8b1eb7 100644
--- a/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs
+++ b/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs
@@ -152,6 +152,56 @@ public virtual void Missing_concurrency_token_property_is_created_on_the_sharing
Assert.Equal(ValueGenerated.OnAddOrUpdate, concurrencyProperty.ValueGenerated);
}
+ [ConditionalFact]
+ public virtual void Missing_concurrency_token_property_is_created_on_the_sharing_type_with_complex_property()
+ {
+ var modelBuilder = GetModelBuilder();
+ modelBuilder.Entity().HasKey(a => a.Id);
+ modelBuilder.Entity().ToTable(nameof(Animal));
+ modelBuilder.Entity(ab =>
+ {
+ ab.HasKey(a => a.Id);
+ ab.HasOne(a => a.FavoritePerson).WithOne().HasForeignKey(p => p.Id);
+ ab.ComplexProperty(a => a.Dwelling)
+ .Property("Version").IsRowVersion().HasColumnName("Version");
+ });
+
+ var model = modelBuilder.Model;
+ model.FinalizeModel();
+
+ var personEntityType = model.FindEntityType(typeof(Person));
+ var concurrencyProperty = personEntityType.FindProperty("_TableSharingConcurrencyTokenConvention_Version");
+ Assert.True(concurrencyProperty.IsConcurrencyToken);
+ Assert.True(concurrencyProperty.IsShadowProperty());
+ Assert.Equal("Version", concurrencyProperty.GetColumnName());
+ Assert.Equal(ValueGenerated.OnAddOrUpdate, concurrencyProperty.ValueGenerated);
+
+ var animalEntityType = model.FindEntityType(typeof(Animal));
+ Assert.All(animalEntityType.GetProperties(), p => Assert.NotEqual(typeof(byte[]), p.ClrType));
+ }
+
+ [ConditionalFact]
+ public virtual void Concurrency_token_property_is_not_created_on_the_sharing_when_on_complex_property()
+ {
+ var modelBuilder = GetModelBuilder();
+ modelBuilder.Entity().HasKey(a => a.Id);
+ modelBuilder.Entity().ToTable(nameof(Animal));
+ modelBuilder.Entity().Property("Version").IsRowVersion().HasColumnName("Version");
+ modelBuilder.Entity(ab =>
+ {
+ ab.HasKey(a => a.Id);
+ ab.HasOne(a => a.FavoritePerson).WithOne().HasForeignKey(p => p.Id);
+ ab.ComplexProperty(a => a.Dwelling)
+ .Property("Version").IsRowVersion().HasColumnName("Version");
+ });
+
+ var model = modelBuilder.Model;
+ model.FinalizeModel();
+
+ var animalEntityType = model.FindEntityType(typeof(Animal));
+ Assert.All(animalEntityType.GetProperties(), p => Assert.NotEqual(typeof(byte[]), p.ClrType));
+ }
+
protected class Animal
{
public int Id { get; set; }
diff --git a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs
index 54b290dbbb1..d0453ce9ed2 100644
--- a/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs
+++ b/test/EFCore.Relational.Tests/Update/CommandBatchPreparerTest.cs
@@ -851,9 +851,7 @@ public void BatchCommands_creates_batch_on_incomplete_updates_for_shared_table_n
if (state == EntityState.Deleted)
{
- // Detect indirect update dependencies. Issue #17947.
- Assert.Throws(
- () => Assert.Single(commandBatches));
+ Assert.Single(commandBatches);
}
else
{
diff --git a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs
index 6375c8f448c..e9344938065 100644
--- a/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs
+++ b/test/EFCore.Relational.Tests/Update/ReaderModificationCommandBatchTest.cs
@@ -37,7 +37,7 @@ public void TryAddCommand_adds_command_if_batch_is_valid()
});
var entry2 = CreateEntry(EntityState.Modified);
- var property2 = entry1.EntityType.FindProperty("Name")!;
+ var property2 = entry2.EntityType.FindProperty("Name")!;
var command2 = CreateModificationCommand(
"T2",
null,
@@ -53,8 +53,10 @@ public void TryAddCommand_adds_command_if_batch_is_valid()
false, true, false, false, true)
});
- var batch = new ModificationCommandBatchFake();
- batch.ShouldBeValid = true;
+ var batch = new ModificationCommandBatchFake
+ {
+ ShouldBeValid = true
+ };
Assert.True(batch.TryAddCommand(command1));
Assert.True(batch.TryAddCommand(command2));
batch.Complete(moreBatchesExpected: false);
@@ -97,7 +99,7 @@ public void TryAddCommand_does_not_add_command_batch_is_invalid()
});
var entry2 = CreateEntry(EntityState.Modified);
- var property2 = entry1.EntityType.FindProperty("Name")!;
+ var property2 = entry2.EntityType.FindProperty("Name")!;
var command2 = CreateModificationCommand(
"T2",
null,
diff --git a/test/EFCore.Specification.Tests/F1FixtureBase.cs b/test/EFCore.Specification.Tests/F1FixtureBase.cs
index 95fa9edb64a..81907ab8ffc 100644
--- a/test/EFCore.Specification.Tests/F1FixtureBase.cs
+++ b/test/EFCore.Specification.Tests/F1FixtureBase.cs
@@ -166,7 +166,15 @@ protected virtual void BuildModelExternal(ModelBuilder modelBuilder)
modelBuilder.Entity(
b =>
{
- b.OwnsOne(s => s.Details);
+ // TODO: Configure as ComplexProperty when optional complex types are supported
+ // Issue #31376
+ b.OwnsOne(
+ s => s.Details, eb =>
+ {
+ eb.Property(d => d.Space);
+ eb.Property("Version").IsRowVersion();
+ eb.Property(Sponsor.ClientTokenPropertyName).IsConcurrencyToken();
+ });
ConfigureConstructorBinding(b.Metadata);
});
@@ -184,15 +192,6 @@ protected virtual void BuildModelExternal(ModelBuilder modelBuilder)
eb.Property(Sponsor.ClientTokenPropertyName);
});
- modelBuilder.Entity()
- .OwnsOne(
- s => s.Details, eb =>
- {
- eb.Property(d => d.Space);
- eb.Property("Version").IsRowVersion();
- eb.Property(Sponsor.ClientTokenPropertyName).IsConcurrencyToken();
- });
-
modelBuilder.Entity();
modelBuilder.Entity();
modelBuilder.Entity();
diff --git a/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs
index a45c0fec087..78b0c53df2b 100644
--- a/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/OptimisticConcurrencySqlServerTest.cs
@@ -283,12 +283,6 @@ await c.Database.CreateExecutionStrategy().ExecuteAsync(
circuitInner.Name += "-";
}
- if (mapping == Mapping.Tpc) // Issue #29751.
- {
- await Assert.ThrowsAsync(() => innerContext.SaveChangesAsync());
- return;
- }
-
await innerContext.SaveChangesAsync();
if (updateDependentFirst && mapping == Mapping.Tpt) // Issue #22060
diff --git a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs
index abea81ef11a..f09fe59dcfb 100644
--- a/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/TPTTableSplittingSqlServerTest.cs
@@ -246,4 +246,10 @@ WHEN [t0].[Active] IS NOT NULL THEN [t0].[Name]
ORDER BY [v].[Name]
""");
}
+
+ public override Task Can_insert_dependent_with_just_one_parent()
+ {
+ // This scenario is not valid for TPT
+ return Task.CompletedTask;
+ }
}
diff --git a/test/EFCore.Sqlite.FunctionalTests/TPTTableSplittingSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/TPTTableSplittingSqliteTest.cs
index 2a0a49fedc9..7dcf136b6ca 100644
--- a/test/EFCore.Sqlite.FunctionalTests/TPTTableSplittingSqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/TPTTableSplittingSqliteTest.cs
@@ -10,6 +10,12 @@ public TPTTableSplittingSqliteTest(ITestOutputHelper testOutputHelper)
{
}
+ public override Task Can_insert_dependent_with_just_one_parent()
+ {
+ // This scenario is not valid for TPT
+ return Task.CompletedTask;
+ }
+
protected override ITestStoreFactory TestStoreFactory
=> SqliteTestStoreFactory.Instance;
}
diff --git a/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs
index 48d1ec582a2..a94caa63b5d 100644
--- a/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs
+++ b/test/EFCore.Tests/ChangeTracking/Internal/ChangeDetectorTest.cs
@@ -1256,7 +1256,6 @@ public void Handles_notification_of_principal_key_change()
{
var contextServices = CreateContextServices(BuildNotifyingModel());
- var changeDetector = contextServices.GetRequiredService();
var stateManager = contextServices.GetRequiredService();
var category = new NotifyingCategory { Id = -1, PrincipalId = 77 };
@@ -1682,7 +1681,6 @@ public void Brings_in_single_new_entity_on_notification_of_set_on_collection_nav
{
var contextServices = CreateContextServices(BuildNotifyingModel());
- var changeDetector = contextServices.GetRequiredService();
var stateManager = contextServices.GetRequiredService();
var product1 = new NotifyingProduct { Id = Guid.NewGuid(), DependentId = 77 };
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
index 43db9739093..4d12d811509 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs
@@ -3,8 +3,6 @@
#nullable enable
-using System.Numerics;
-using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
// ReSharper disable InconsistentNaming
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs
index 95491e3608b..d9a779b3924 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs
@@ -3,8 +3,6 @@
#nullable enable
-using Microsoft.EntityFrameworkCore.Metadata;
-
namespace Microsoft.EntityFrameworkCore.ModelBuilding;
public class ModelBuilderNonGenericTest : ModelBuilderTest
diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs
index 7a6a14f6e3b..900429fc617 100644
--- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs
+++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs
@@ -4,8 +4,6 @@
#nullable enable
using Microsoft.EntityFrameworkCore.Diagnostics.Internal;
-using Microsoft.EntityFrameworkCore.Metadata;
-using static Microsoft.EntityFrameworkCore.ModelBuilding.ModelBuilderTest;
// ReSharper disable InconsistentNaming
namespace Microsoft.EntityFrameworkCore.ModelBuilding;