diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 8e047d015fc..25823e37aa6 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -76,6 +76,7 @@ If you are not sure, do not guess, just tell that you don't know or ask clarifyi
- Use nullable reference types
- Use proper null-checking patterns
- Use the null-conditional operator (`?.`) and null-coalescing operator (`??`) when appropriate
+- Don't disable nullability with a preprocessor directive (`#nullable disable`)
## Architecture and Design Patterns
diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs
index 85bfafde2f1..e4b0d5fecec 100644
--- a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs
+++ b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs
@@ -13,19 +13,8 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal;
/// 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 class CosmosModelValidator : ModelValidator
+public class CosmosModelValidator(ModelValidatorDependencies dependencies) : ModelValidator(dependencies)
{
- ///
- /// 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 CosmosModelValidator(ModelValidatorDependencies dependencies)
- : base(dependencies)
- {
- }
-
///
/// 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
@@ -36,13 +25,7 @@ public override void Validate(IModel model, IDiagnosticsLogger
@@ -51,41 +34,15 @@ public override void Validate(IModel model, IDiagnosticsLogger
- protected virtual void ValidateCollectionElementTypes(
- IModel model,
+ protected override void ValidateEntityType(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
- {
- ValidateType(entityType, logger);
- }
-
- static void ValidateType(ITypeBase typeBase, IDiagnosticsLogger logger)
- {
- foreach (var property in typeBase.GetDeclaredProperties())
- {
- var typeMapping = property.GetElementType()?.GetTypeMapping();
- while (typeMapping != null)
- {
- if (typeMapping.Converter != null)
- {
- throw new InvalidOperationException(
- CosmosStrings.ElementWithValueConverter(
- property.ClrType.ShortDisplayName(),
- typeBase.ShortName(),
- property.Name,
- typeMapping.ClrType.ShortDisplayName()));
- }
-
- typeMapping = typeMapping.ElementTypeMapping;
- }
- }
+ base.ValidateEntityType(entityType, logger);
- foreach (var complexProperty in typeBase.GetDeclaredComplexProperties())
- {
- ValidateType(complexProperty.ComplexType, logger);
- }
- }
+ ValidateKeys(entityType, logger);
+ ValidateDatabaseProperties(entityType, logger);
+ ValidateDiscriminatorMappings(entityType, logger);
}
///
@@ -332,133 +289,188 @@ protected virtual void ValidateSharedContainerCompatibility(
}
}
+
+
///
/// 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 virtual void ValidateOnlyETagConcurrencyToken(
- IModel model,
+ protected virtual void ValidateKeys(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ var primaryKey = entityType.FindPrimaryKey();
+ if (primaryKey == null
+ || !entityType.IsDocumentRoot())
+ {
+ return;
+ }
+
+ var idProperty = entityType.GetProperties()
+ .FirstOrDefault(p => p.GetJsonPropertyName() == CosmosJsonIdConvention.IdPropertyJsonName);
+ if (idProperty == null)
{
- foreach (var property in entityType.GetDeclaredProperties())
+ throw new InvalidOperationException(CosmosStrings.NoIdProperty(entityType.DisplayName()));
+ }
+
+ var idType = idProperty.GetTypeMapping().Converter?.ProviderClrType
+ ?? idProperty.ClrType;
+ if (idType != typeof(string))
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.IdNonStringStoreType(idProperty.Name, entityType.DisplayName(), idType.ShortDisplayName()));
+ }
+
+ var partitionKeyPropertyNames = entityType.GetPartitionKeyPropertyNames();
+ if (partitionKeyPropertyNames.Count == 0)
+ {
+ logger.NoPartitionKeyDefined(entityType);
+ }
+ else
+ {
+ if (entityType.BaseType != null
+ && entityType.FindAnnotation(CosmosAnnotationNames.PartitionKeyNames)?.Value != null)
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.PartitionKeyNotOnRoot(entityType.DisplayName(), entityType.BaseType.DisplayName()));
+ }
+
+ foreach (var partitionKeyPropertyName in partitionKeyPropertyNames)
{
- if (property.IsConcurrencyToken)
+ var partitionKey = entityType.FindProperty(partitionKeyPropertyName);
+ if (partitionKey == null)
{
- var storeName = property.GetJsonPropertyName();
- if (storeName != "_etag")
- {
- throw new InvalidOperationException(CosmosStrings.NonETagConcurrencyToken(entityType.DisplayName(), storeName));
- }
+ throw new InvalidOperationException(
+ CosmosStrings.PartitionKeyMissingProperty(entityType.DisplayName(), partitionKeyPropertyName));
+ }
- var etagType = property.GetTypeMapping().Converter?.ProviderClrType ?? property.ClrType;
- if (etagType != typeof(string))
- {
- throw new InvalidOperationException(
- CosmosStrings.ETagNonStringStoreType(property.Name, entityType.DisplayName(), etagType.ShortDisplayName()));
- }
+ var partitionKeyType = (partitionKey.GetTypeMapping().Converter?.ProviderClrType
+ ?? partitionKey.ClrType).UnwrapNullableType();
+ if (partitionKeyType != typeof(string)
+ && !partitionKeyType.IsNumeric()
+ && partitionKeyType != typeof(bool))
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.PartitionKeyBadStoreType(
+ partitionKeyPropertyName,
+ entityType.DisplayName(),
+ partitionKeyType.ShortDisplayName()));
}
}
}
}
+ ///
+ /// Validates that a key doesn't have mutable properties.
+ ///
+ /// The key to validate.
+ /// The logger to use.
+ protected override void ValidateMutableKey(
+ IKey key,
+ IDiagnosticsLogger logger)
+ {
+ var mutableProperty = key.Properties.FirstOrDefault(p => p.ValueGenerated.HasFlag(ValueGenerated.OnUpdate));
+ if (mutableProperty != null
+ && !mutableProperty.IsOrdinalKeyProperty())
+ {
+ throw new InvalidOperationException(CoreStrings.MutableKeyProperty(mutableProperty.Name));
+ }
+ }
+
///
/// 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 virtual void ValidateKeys(
- IModel model,
+ protected virtual void ValidateDatabaseProperties(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ var properties = new Dictionary();
+ foreach (var property in entityType.GetProperties())
{
- var primaryKey = entityType.FindPrimaryKey();
- if (primaryKey == null
- || !entityType.IsDocumentRoot())
+ var jsonName = property.GetJsonPropertyName();
+ if (string.IsNullOrWhiteSpace(jsonName))
{
continue;
}
- var idProperty = entityType.GetProperties()
- .FirstOrDefault(p => p.GetJsonPropertyName() == CosmosJsonIdConvention.IdPropertyJsonName);
- if (idProperty == null)
+ if (properties.TryGetValue(jsonName, out var otherProperty))
{
- throw new InvalidOperationException(CosmosStrings.NoIdProperty(entityType.DisplayName()));
+ throw new InvalidOperationException(
+ CosmosStrings.JsonPropertyCollision(property.Name, otherProperty.Name, entityType.DisplayName(), jsonName));
}
- var idType = idProperty.GetTypeMapping().Converter?.ProviderClrType
- ?? idProperty.ClrType;
- if (idType != typeof(string))
+ properties[jsonName] = property;
+ }
+
+ foreach (var navigation in entityType.GetNavigations())
+ {
+ if (!navigation.IsEmbedded())
{
- throw new InvalidOperationException(
- CosmosStrings.IdNonStringStoreType(idProperty.Name, entityType.DisplayName(), idType.ShortDisplayName()));
+ continue;
}
- var partitionKeyPropertyNames = entityType.GetPartitionKeyPropertyNames();
- if (partitionKeyPropertyNames.Count == 0)
+ var jsonName = navigation.TargetEntityType.GetContainingPropertyName()!;
+ if (properties.TryGetValue(jsonName, out var otherProperty))
{
- logger.NoPartitionKeyDefined(entityType);
+ throw new InvalidOperationException(
+ CosmosStrings.JsonPropertyCollision(navigation.Name, otherProperty.Name, entityType.DisplayName(), jsonName));
}
- else
- {
- if (entityType.BaseType != null
- && entityType.FindAnnotation(CosmosAnnotationNames.PartitionKeyNames)?.Value != null)
- {
- throw new InvalidOperationException(
- CosmosStrings.PartitionKeyNotOnRoot(entityType.DisplayName(), entityType.BaseType.DisplayName()));
- }
- foreach (var partitionKeyPropertyName in partitionKeyPropertyNames)
- {
- var partitionKey = entityType.FindProperty(partitionKeyPropertyName);
- if (partitionKey == null)
- {
- throw new InvalidOperationException(
- CosmosStrings.PartitionKeyMissingProperty(entityType.DisplayName(), partitionKeyPropertyName));
- }
+ properties[jsonName] = navigation;
+ }
+ }
- var partitionKeyType = (partitionKey.GetTypeMapping().Converter?.ProviderClrType
- ?? partitionKey.ClrType).UnwrapNullableType();
- if (partitionKeyType != typeof(string)
- && !partitionKeyType.IsNumeric()
- && partitionKeyType != typeof(bool))
- {
- throw new InvalidOperationException(
- CosmosStrings.PartitionKeyBadStoreType(
- partitionKeyPropertyName,
- entityType.DisplayName(),
- partitionKeyType.ShortDisplayName()));
- }
- }
- }
+ ///
+ /// 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 virtual void ValidateDiscriminatorMappings(
+ IEntityType entityType,
+ IDiagnosticsLogger logger)
+ {
+ if (!entityType.IsDocumentRoot()
+ && entityType.FindAnnotation(CosmosAnnotationNames.DiscriminatorInKey) != null)
+ {
+ throw new InvalidOperationException(CosmosStrings.DiscriminatorInKeyOnNonRoot(entityType.DisplayName()));
+ }
+
+ if (!entityType.IsDocumentRoot()
+ && entityType.FindAnnotation(CosmosAnnotationNames.HasShadowId) != null)
+ {
+ throw new InvalidOperationException(CosmosStrings.HasShadowIdOnNonRoot(entityType.DisplayName()));
}
}
///
- /// Validates the mapping/configuration of mutable in the model.
+ /// 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 model to validate.
- /// The logger to use.
- protected override void ValidateNoMutableKeys(
- IModel model,
+ protected override void ValidateIndex(
+ IIndex index,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ base.ValidateIndex(index, logger);
+
+ if (index.GetVectorIndexType() != null)
{
- foreach (var key in entityType.GetDeclaredKeys())
- {
- var mutableProperty = key.Properties.FirstOrDefault(p => p.ValueGenerated.HasFlag(ValueGenerated.OnUpdate));
- if (mutableProperty != null
- && !mutableProperty.IsOrdinalKeyProperty())
- {
- throw new InvalidOperationException(CoreStrings.MutableKeyProperty(mutableProperty.Name));
- }
- }
+ ValidateVectorIndex(index, logger);
+ }
+ else if (index.IsFullTextIndex() == true)
+ {
+ ValidateFullTextIndex(index, logger);
+ }
+ else
+ {
+ ValidateUnsupportedIndex(index, logger);
}
}
@@ -468,46 +480,109 @@ protected override void ValidateNoMutableKeys(
/// 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 virtual void ValidateDatabaseProperties(
- IModel model,
+ protected virtual void ValidateVectorIndex(
+ IIndex index,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ var entityType = index.DeclaringEntityType;
+
+ if (index.Properties.Count > 1)
{
- var properties = new Dictionary();
- foreach (var property in entityType.GetProperties())
- {
- var jsonName = property.GetJsonPropertyName();
- if (string.IsNullOrWhiteSpace(jsonName))
- {
- continue;
- }
+ throw new InvalidOperationException(
+ CosmosStrings.CompositeVectorIndex(
+ entityType.DisplayName(),
+ string.Join(",", index.Properties.Select(e => e.Name))));
+ }
- if (properties.TryGetValue(jsonName, out var otherProperty))
- {
- throw new InvalidOperationException(
- CosmosStrings.JsonPropertyCollision(property.Name, otherProperty.Name, entityType.DisplayName(), jsonName));
- }
+ if (index.Properties[0].GetVectorDistanceFunction() == null
+ || index.Properties[0].GetVectorDimensions() == null)
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.VectorIndexOnNonVector(
+ entityType.DisplayName(),
+ index.Properties[0].Name));
+ }
+ }
- properties[jsonName] = 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.
+ ///
+ protected virtual void ValidateFullTextIndex(
+ IIndex index,
+ IDiagnosticsLogger logger)
+ {
+ if (index.Properties.Count > 1)
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.CompositeFullTextIndex(
+ index.DeclaringEntityType.DisplayName(),
+ string.Join(",", index.Properties.Select(e => e.Name))));
+ }
- foreach (var navigation in entityType.GetNavigations())
- {
- if (!navigation.IsEmbedded())
- {
- continue;
- }
+ if (index.Properties[0].GetIsFullTextSearchEnabled() != true)
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.FullTextIndexOnNonFullTextProperty(
+ index.DeclaringEntityType.DisplayName(),
+ index.Properties[0].Name,
+ nameof(CosmosPropertyBuilderExtensions.EnableFullTextSearch)));
+ }
+ }
- var jsonName = navigation.TargetEntityType.GetContainingPropertyName()!;
- if (properties.TryGetValue(jsonName, out var otherProperty))
- {
- throw new InvalidOperationException(
- CosmosStrings.JsonPropertyCollision(navigation.Name, otherProperty.Name, entityType.DisplayName(), jsonName));
- }
+ ///
+ /// 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 virtual void ValidateUnsupportedIndex(
+ IIndex index,
+ IDiagnosticsLogger logger)
+ {
+ var entityType = index.DeclaringEntityType;
+ throw new InvalidOperationException(
+ CosmosStrings.IndexesExist(
+ entityType.DisplayName(),
+ string.Join(",", index.Properties.Select(e => e.Name))));
+ }
- properties[jsonName] = navigation;
- }
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// 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 ValidateProperty(
+ IProperty property,
+ ITypeBase structuralType,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidateProperty(property, structuralType, logger);
+
+ ValidateVectorProperty(property, structuralType, logger);
+ ValidateElementConverters(property, structuralType, logger);
+ ValidateConcurrencyToken(property, structuralType, 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 virtual void ValidateVectorProperty(
+ IProperty property,
+ ITypeBase structuralType,
+ IDiagnosticsLogger logger)
+ {
+ if (property.GetVectorDistanceFunction() is not null
+ && property.GetVectorDimensions() is not null)
+ {
+ // Will throw if the data type is not set and cannot be inferred.
+ CosmosVectorType.CreateDefaultVectorDataType(property.ClrType);
}
}
@@ -517,23 +592,25 @@ protected virtual void ValidateDatabaseProperties(
/// 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 virtual void ValidateDiscriminatorMappings(
- IModel model,
+ protected virtual void ValidateElementConverters(
+ IProperty property,
+ ITypeBase structuralType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ var typeMapping = property.GetElementType()?.GetTypeMapping();
+ while (typeMapping != null)
{
- if (!entityType.IsDocumentRoot()
- && entityType.FindAnnotation(CosmosAnnotationNames.DiscriminatorInKey) != null)
+ if (typeMapping.Converter != null)
{
- throw new InvalidOperationException(CosmosStrings.DiscriminatorInKeyOnNonRoot(entityType.DisplayName()));
+ throw new InvalidOperationException(
+ CosmosStrings.ElementWithValueConverter(
+ property.ClrType.ShortDisplayName(),
+ structuralType.ShortName(),
+ property.Name,
+ typeMapping.ClrType.ShortDisplayName()));
}
- if (!entityType.IsDocumentRoot()
- && entityType.FindAnnotation(CosmosAnnotationNames.HasShadowId) != null)
- {
- throw new InvalidOperationException(CosmosStrings.HasShadowIdOnNonRoot(entityType.DisplayName()));
- }
+ typeMapping = typeMapping.ElementTypeMapping;
}
}
@@ -543,59 +620,24 @@ protected virtual void ValidateDiscriminatorMappings(
/// 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 virtual void ValidateIndexes(
- IModel model,
+ protected virtual void ValidateConcurrencyToken(
+ IProperty property,
+ ITypeBase structuralType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ if (property.IsConcurrencyToken)
{
- foreach (var index in entityType.GetDeclaredIndexes())
+ var storeName = property.GetJsonPropertyName();
+ if (storeName != "_etag")
{
- if (index.GetVectorIndexType() != null)
- {
- if (index.Properties.Count > 1)
- {
- throw new InvalidOperationException(
- CosmosStrings.CompositeVectorIndex(
- entityType.DisplayName(),
- string.Join(",", index.Properties.Select(e => e.Name))));
- }
-
- if (index.Properties[0].GetVectorDistanceFunction() == null
- || index.Properties[0].GetVectorDimensions() == null)
- {
- throw new InvalidOperationException(
- CosmosStrings.VectorIndexOnNonVector(
- entityType.DisplayName(),
- index.Properties[0].Name));
- }
- }
- else if (index.IsFullTextIndex() == true)
- {
- if (index.Properties.Count > 1)
- {
- throw new InvalidOperationException(
- CosmosStrings.CompositeFullTextIndex(
- index.DeclaringEntityType.DisplayName(),
- string.Join(",", index.Properties.Select(e => e.Name))));
- }
+ throw new InvalidOperationException(CosmosStrings.NonETagConcurrencyToken(structuralType.DisplayName(), storeName));
+ }
- if (index.Properties[0].GetIsFullTextSearchEnabled() != true)
- {
- throw new InvalidOperationException(
- CosmosStrings.FullTextIndexOnNonFullTextProperty(
- index.DeclaringEntityType.DisplayName(),
- index.Properties[0].Name,
- nameof(CosmosPropertyBuilderExtensions.EnableFullTextSearch)));
- }
- }
- else
- {
- throw new InvalidOperationException(
- CosmosStrings.IndexesExist(
- entityType.DisplayName(),
- string.Join(",", index.Properties.Select(e => e.Name))));
- }
+ var etagType = property.GetTypeMapping().Converter?.ProviderClrType ?? property.ClrType;
+ if (etagType != typeof(string))
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.ETagNonStringStoreType(property.Name, structuralType.DisplayName(), etagType.ShortDisplayName()));
}
}
}
@@ -606,23 +648,33 @@ protected virtual void ValidateIndexes(
/// 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 ValidatePropertyMapping(
- IModel model,
+ protected override void ValidateTrigger(
+ ITrigger trigger,
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- base.ValidatePropertyMapping(model, logger);
+ base.ValidateTrigger(trigger, entityType, logger);
- foreach (var entityType in model.GetEntityTypes())
+ ValidateTriggerOnRootType(trigger, entityType, logger);
+ ValidateTriggerType(trigger, entityType, logger);
+ ValidateTriggerOperation(trigger, 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 virtual void ValidateTriggerOnRootType(
+ ITrigger trigger,
+ IEntityType entityType,
+ IDiagnosticsLogger logger)
+ {
+ if (entityType.BaseType != null)
{
- foreach (var property in entityType.GetDeclaredProperties())
- {
- if (property.GetVectorDistanceFunction() is not null
- && property.GetVectorDimensions() is not null)
- {
- // Will throw if the data type is not set and cannot be inferred.
- CosmosVectorType.CreateDefaultVectorDataType(property.ClrType);
- }
- }
+ throw new InvalidOperationException(
+ CosmosStrings.TriggerOnDerivedType(trigger.ModelName, entityType.DisplayName(), entityType.BaseType.DisplayName()));
}
}
@@ -632,34 +684,33 @@ protected override void ValidatePropertyMapping(
/// 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 ValidateTriggers(
- IModel model,
+ protected virtual void ValidateTriggerType(
+ ITrigger trigger,
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- base.ValidateTriggers(model, logger);
-
- foreach (var entityType in model.GetEntityTypes())
+ if (trigger.GetTriggerType() == null)
{
- foreach (var trigger in entityType.GetDeclaredTriggers())
- {
- if (entityType.BaseType != null)
- {
- throw new InvalidOperationException(
- CosmosStrings.TriggerOnDerivedType(trigger.ModelName, entityType.DisplayName(), entityType.BaseType.DisplayName()));
- }
-
- if (trigger.GetTriggerType() == null)
- {
- throw new InvalidOperationException(
- CosmosStrings.TriggerMissingType(trigger.ModelName, entityType.DisplayName()));
- }
+ throw new InvalidOperationException(
+ CosmosStrings.TriggerMissingType(trigger.ModelName, entityType.DisplayName()));
+ }
+ }
- if (trigger.GetTriggerOperation() == null)
- {
- throw new InvalidOperationException(
- CosmosStrings.TriggerMissingOperation(trigger.ModelName, entityType.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
+ /// 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 virtual void ValidateTriggerOperation(
+ ITrigger trigger,
+ IEntityType entityType,
+ IDiagnosticsLogger logger)
+ {
+ if (trigger.GetTriggerOperation() == null)
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.TriggerMissingOperation(trigger.ModelName, entityType.DisplayName()));
}
}
}
diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
index e4bb8529c3f..56d632959e6 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs
@@ -99,7 +99,7 @@ private enum Id
// Model validation events
ModelValidationKeyDefaultValueWarning = CoreEventId.RelationalBaseId + 600,
BoolWithDefaultWarning,
- AllIndexPropertiesNotToMappedToAnyTable,
+ AllIndexPropertiesNotMappedToAnyTable,
IndexPropertiesBothMappedAndNotMappedToTable,
IndexPropertiesMappedToNonOverlappingTables,
ForeignKeyPropertiesMappedToUnrelatedTables,
@@ -914,8 +914,8 @@ private static EventId MakeValidationId(Id id)
/// This event uses the payload when used with a .
///
///
- public static readonly EventId AllIndexPropertiesNotToMappedToAnyTable =
- MakeValidationId(Id.AllIndexPropertiesNotToMappedToAnyTable);
+ public static readonly EventId AllIndexPropertiesNotMappedToAnyTable =
+ MakeValidationId(Id.AllIndexPropertiesNotMappedToAnyTable);
///
/// An index specifies properties some of which are mapped and some of which are not mapped to a column in a table.
diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
index 08913cc8397..d58ecfbb303 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs
@@ -2868,19 +2868,19 @@ private static string BatchSmallerThanMinBatchSize(EventDefinitionBase definitio
}
///
- /// Logs the event.
+ /// Logs the event.
///
/// The diagnostics logger to use.
/// The entity type on which the index is defined.
/// The index on the entity type.
- public static void AllIndexPropertiesNotToMappedToAnyTable(
+ public static void AllIndexPropertiesNotMappedToAnyTable(
this IDiagnosticsLogger diagnostics,
IEntityType entityType,
IIndex index)
{
if (index.Name == null)
{
- var definition = RelationalResources.LogUnnamedIndexAllPropertiesNotToMappedToAnyTable(diagnostics);
+ var definition = RelationalResources.LogUnnamedIndexAllPropertiesNotMappedToAnyTable(diagnostics);
if (diagnostics.ShouldLog(definition))
{
@@ -2904,7 +2904,7 @@ public static void AllIndexPropertiesNotToMappedToAnyTable(
}
else
{
- var definition = RelationalResources.LogNamedIndexAllPropertiesNotToMappedToAnyTable(diagnostics);
+ var definition = RelationalResources.LogNamedIndexAllPropertiesNotMappedToAnyTable(diagnostics);
if (diagnostics.ShouldLog(definition))
{
diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
index a4c49f8b04f..8eaa87bfda9 100644
--- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
+++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs
@@ -518,7 +518,7 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
[EntityFrameworkInternal]
- public EventDefinitionBase? LogNamedIndexAllPropertiesNotToMappedToAnyTable;
+ public EventDefinitionBase? LogNamedIndexAllPropertiesNotMappedToAnyTable;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -527,7 +527,7 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
[EntityFrameworkInternal]
- public EventDefinitionBase? LogUnnamedIndexAllPropertiesNotToMappedToAnyTable;
+ public EventDefinitionBase? LogUnnamedIndexAllPropertiesNotMappedToAnyTable;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs
index 2f27af24c08..7b0c50f9326 100644
--- a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs
+++ b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs
@@ -35,7 +35,20 @@ public static class RelationalIndexExtensions
/// The identifier of the store object.
/// The name of the index in the database.
public static string? GetDatabaseName(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject)
- => index.GetDatabaseName(storeObject, null);
+ {
+ if (storeObject.StoreObjectType != StoreObjectType.Table)
+ {
+ return null;
+ }
+
+ var defaultName = index.GetDefaultDatabaseName(storeObject);
+ var annotation = index.FindAnnotation(RelationalAnnotationNames.Name);
+ return annotation != null && defaultName != null
+ ? (string?)annotation.Value
+ : defaultName != null
+ ? index.Name ?? defaultName
+ : defaultName;
+ }
///
/// Returns the default name that would be used for this index.
@@ -67,7 +80,60 @@ public static class RelationalIndexExtensions
/// The identifier of the store object.
/// The default name that would be used for this index.
public static string? GetDefaultDatabaseName(this IReadOnlyIndex index, in StoreObjectIdentifier storeObject)
- => index.GetDefaultDatabaseName(storeObject, null);
+ {
+ if (storeObject.StoreObjectType != StoreObjectType.Table)
+ {
+ return null;
+ }
+
+ var columnNames = index.Properties.GetColumnNames(storeObject);
+ if (columnNames == null)
+ {
+ return null;
+ }
+
+ var rootIndex = index;
+
+ // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later)
+ // Using a hashset is detrimental to the perf when there are no cycles
+ for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++)
+ {
+ IReadOnlyIndex? linkedIndex = null;
+ foreach (var otherIndex in rootIndex.DeclaringEntityType
+ .FindRowInternalForeignKeys(storeObject)
+ .SelectMany(fk => fk.PrincipalEntityType.GetIndexes()))
+ {
+ var otherColumnNames = otherIndex.Properties.GetColumnNames(storeObject);
+ if ((otherColumnNames != null)
+ && otherColumnNames.SequenceEqual(columnNames))
+ {
+ linkedIndex = otherIndex;
+ break;
+ }
+ }
+
+ if (linkedIndex == null)
+ {
+ break;
+ }
+
+ rootIndex = linkedIndex;
+ }
+
+ if (rootIndex != index)
+ {
+ return rootIndex.GetDatabaseName(storeObject);
+ }
+
+ var baseName = new StringBuilder()
+ .Append("IX_")
+ .Append(storeObject.Name)
+ .Append('_')
+ .AppendJoin(columnNames, "_")
+ .ToString();
+
+ return Uniquifier.Truncate(baseName, index.DeclaringEntityType.Model.GetMaxIdentifierLength());
+ }
///
/// Sets the name of the index in the database.
diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
index 77a1246bc17..c73e5c2b3e3 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.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;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
@@ -10,6 +11,8 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure;
///
/// The validator that enforces rules common for all relational providers.
///
+/// Parameter object containing dependencies for this service.
+/// Parameter object containing relational dependencies for this service.
///
///
/// The service lifetime is . This means a single instance
@@ -21,23 +24,16 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure;
/// for more information and examples.
///
///
-public class RelationalModelValidator : ModelValidator
+public class RelationalModelValidator(
+ ModelValidatorDependencies dependencies,
+ RelationalModelValidatorDependencies relationalDependencies)
+ : ModelValidator(dependencies)
{
- ///
- /// Creates a new instance of .
- ///
- /// Parameter object containing dependencies for this service.
- /// Parameter object containing relational dependencies for this service.
- public RelationalModelValidator(
- ModelValidatorDependencies dependencies,
- RelationalModelValidatorDependencies relationalDependencies)
- : base(dependencies)
- => RelationalDependencies = relationalDependencies;
///
/// Relational provider-specific dependencies for this service.
///
- protected virtual RelationalModelValidatorDependencies RelationalDependencies { get; }
+ protected virtual RelationalModelValidatorDependencies RelationalDependencies { get; } = relationalDependencies;
///
/// Validates a model, throwing an exception if any errors are found.
@@ -48,58 +44,175 @@ public override void Validate(IModel model, IDiagnosticsLogger>();
+ var views = new Dictionary>();
+ var storedProcedures = new Dictionary>();
+ foreach (var entityType in model.GetEntityTypes())
+ {
+ var tableId = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
+ if (tableId != null)
+ {
+ var table = tableId.Value;
+ if (!tables.TryGetValue(table, out var tableMappedTypes))
+ {
+ tableMappedTypes = [];
+ tables[table] = tableMappedTypes;
+ }
+
+ tableMappedTypes.Add(entityType);
+ }
+
+ var viewId = StoreObjectIdentifier.Create(entityType, StoreObjectType.View);
+ if (viewId != null)
+ {
+ var view = viewId.Value;
+ if (!views.TryGetValue(view, out var viewMappedTypes))
+ {
+ viewMappedTypes = [];
+ views[view] = viewMappedTypes;
+ }
+
+ viewMappedTypes.Add(entityType);
+ }
+
+ AddStoredProcedure(StoreObjectType.DeleteStoredProcedure, entityType, storedProcedures);
+ AddStoredProcedure(StoreObjectType.InsertStoredProcedure, entityType, storedProcedures);
+ AddStoredProcedure(StoreObjectType.UpdateStoredProcedure, entityType, storedProcedures);
+ }
+
+ foreach (var (table, mappedTypes) in tables)
+ {
+ ValidateTable(mappedTypes, table, logger);
+ }
+
+ foreach (var (view, mappedTypes) in views)
+ {
+ ValidateView(mappedTypes, view, logger);
+ }
+
+ foreach (var dbFunction in model.GetDbFunctions())
+ {
+ ValidateDbFunction(dbFunction, logger);
+ }
+
+ foreach (var sequence in model.GetSequences())
+ {
+ ValidateSequence(sequence, logger);
+ }
+
+ foreach (var (sproc, mappedTypes) in storedProcedures)
+ {
+ ValidateStoredProcedure(mappedTypes, sproc, logger);
+ }
+
+ static void AddStoredProcedure(
+ StoreObjectType storedProcedureType,
+ IEntityType entityType,
+ Dictionary> storedProcedures)
+ {
+ var sprocId = StoreObjectIdentifier.Create(entityType, storedProcedureType);
+ if (sprocId == null)
+ {
+ return;
+ }
+
+ if (!storedProcedures.TryGetValue(sprocId.Value, out var mappedTypes))
+ {
+ mappedTypes = [];
+ storedProcedures[sprocId.Value] = mappedTypes;
+ }
+
+ mappedTypes.Add(entityType);
+ }
+ }
+
+ ///
+ protected override void ValidateEntityType(
+ IEntityType entityType,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidateEntityType(entityType, logger);
+
+ ValidateMappingFragment(entityType, logger);
+ ValidateSqlQuery(entityType, logger);
+ ValidateDbFunctionMapping(entityType, logger);
+ ValidateStoredProcedures(entityType, logger);
+ ValidateContainerColumnType(entityType, logger);
+ ValidateTphTriggers(entityType, logger);
+ // TODO: support this for raw SQL and function mappings in #19970 and #21627 and remove the check
+ ValidateJsonEntityOwnerMappedToTableOrView(entityType, logger);
}
///
- /// Validates the mapping of primitive collection properties the model.
+ /// Logs a warning if triggers are defined on a non-root entity type using TPH mapping strategy.
///
- /// The model to validate.
+ /// The entity type to validate.
/// The logger to use.
- protected override void ValidatePrimitiveCollections(
- IModel model,
+ protected virtual void ValidateTphTriggers(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- base.ValidatePrimitiveCollections(model, logger);
-
- foreach (var entityType in model.GetEntityTypes())
+ if (entityType.GetDeclaredTriggers().Any()
+ && entityType.BaseType is not null
+ && entityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy)
{
- ValidateType(entityType);
+ logger.TriggerOnNonRootTphEntity(entityType);
}
+ }
+
+ ///
+ protected override void ValidateProperty(
+ IProperty property,
+ ITypeBase structuralType,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidateProperty(property, structuralType, logger);
- static void ValidateType(ITypeBase typeBase)
+ if (RelationalPropertyOverrides.Get(property) is { } storeObjectOverrides)
{
- foreach (var property in typeBase.GetDeclaredProperties())
+ foreach (var storeObjectOverride in storeObjectOverrides)
{
- if (property is { IsPrimitiveCollection: true }
- && property.GetTypeMapping().ElementTypeMapping?.ElementTypeMapping != null)
- {
- throw new InvalidOperationException(
- RelationalStrings.NestedCollectionsNotSupported(
- property.ClrType.ShortDisplayName(), typeBase.DisplayName(), property.Name));
- }
+ ValidatePropertyOverride(property, storeObjectOverride, logger);
}
+ }
- foreach (var complexProperty in typeBase.GetDeclaredComplexProperties())
- {
- ValidateType(complexProperty.ComplexType);
- }
+ ValidateBoolWithDefaults(property, logger);
+ }
+
+ ///
+ protected override void ValidateKey(
+ IKey key,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidateKey(key, logger);
+
+ ValidateDefaultValuesOnKey(key, logger);
+ ValidateValueGeneration(key, logger);
+ }
+
+ ///
+ /// Validates a primitive collection property.
+ ///
+ /// The property to validate.
+ /// The logger to use.
+ protected override void ValidatePrimitiveCollection(
+ IProperty property,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidatePrimitiveCollection(property, logger);
+
+ if (property is { IsPrimitiveCollection: true }
+ && property.GetTypeMapping().ElementTypeMapping?.ElementTypeMapping != null)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.NestedCollectionsNotSupported(
+ property.ClrType.ShortDisplayName(), property.DeclaringType.DisplayName(), property.Name));
}
}
///
protected override void ValidatePropertyMapping(
- IConventionComplexProperty complexProperty,
+ IComplexProperty complexProperty,
IDiagnosticsLogger logger)
{
base.ValidatePropertyMapping(complexProperty, logger);
@@ -155,210 +268,203 @@ protected override void ValidatePropertyMapping(
}
///
- /// Validates the mapping/configuration of SQL queries in the model.
+ /// Validates the SQL query mapping for an entity type.
///
- /// The model to validate.
+ /// The entity type to validate.
/// The logger to use.
- protected virtual void ValidateSqlQueries(
- IModel model,
+ protected virtual void ValidateSqlQuery(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ var sqlQuery = entityType.GetSqlQuery();
+ if (sqlQuery == null)
{
- var sqlQuery = entityType.GetSqlQuery();
- if (sqlQuery == null)
- {
- continue;
- }
+ return;
+ }
- if (entityType.BaseType != null
- && (entityType.FindDiscriminatorProperty() == null
- || sqlQuery != entityType.BaseType.GetSqlQuery()))
- {
- throw new InvalidOperationException(
- RelationalStrings.InvalidMappedSqlQueryDerivedType(
- entityType.DisplayName(), entityType.BaseType.DisplayName()));
- }
+ if (entityType.BaseType != null
+ && (entityType.FindDiscriminatorProperty() == null
+ || sqlQuery != entityType.BaseType.GetSqlQuery()))
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.InvalidMappedSqlQueryDerivedType(
+ entityType.DisplayName(), entityType.BaseType.DisplayName()));
}
}
///
- /// Validates the mapping/configuration of functions in the model.
+ /// Validates a single sequence.
///
- /// The model to validate.
+ /// The sequence to validate.
/// The logger to use.
- protected virtual void ValidateDbFunctions(
- IModel model,
+ protected virtual void ValidateSequence(
+ ISequence sequence,
IDiagnosticsLogger logger)
{
- foreach (var dbFunction in model.GetDbFunctions())
- {
- if (dbFunction.IsScalar)
- {
- if (dbFunction.TypeMapping == null)
- {
- throw new InvalidOperationException(
- RelationalStrings.DbFunctionInvalidReturnType(
- dbFunction.ModelName,
- dbFunction.ReturnType.ShortDisplayName()));
- }
- }
- else
- {
- var elementType = dbFunction.ReturnType.GetGenericArguments()[0];
- var entityType = model.FindEntityType(elementType);
-
- if (entityType?.IsOwned() == true
- || ((IConventionModel)model).IsOwned(elementType)
- || (entityType == null && model.GetEntityTypes().Any(e => e.ClrType == elementType)))
- {
- throw new InvalidOperationException(
- RelationalStrings.DbFunctionInvalidIQueryableOwnedReturnType(
- dbFunction.ModelName, elementType.ShortDisplayName()));
- }
-
- if (entityType == null)
- {
- throw new InvalidOperationException(
- RelationalStrings.DbFunctionInvalidReturnEntityType(
- dbFunction.ModelName, dbFunction.ReturnType.ShortDisplayName(), elementType.ShortDisplayName()));
- }
-
- if ((entityType.BaseType != null || entityType.GetDerivedTypes().Any())
- && entityType.FindDiscriminatorProperty() == null)
- {
- throw new InvalidOperationException(
- RelationalStrings.TableValuedFunctionNonTph(dbFunction.ModelName, entityType.DisplayName()));
- }
- }
+ }
- foreach (var parameter in dbFunction.Parameters)
+ ///
+ /// Validates a single database function.
+ ///
+ /// The database function to validate.
+ /// The logger to use.
+ protected virtual void ValidateDbFunction(
+ IDbFunction dbFunction,
+ IDiagnosticsLogger logger)
+ {
+ var model = dbFunction.Model;
+ if (dbFunction.IsScalar)
+ {
+ if (dbFunction.TypeMapping == null)
{
- if (parameter.TypeMapping == null)
- {
- throw new InvalidOperationException(
- RelationalStrings.DbFunctionInvalidParameterType(
- parameter.Name,
- dbFunction.ModelName,
- parameter.ClrType.ShortDisplayName()));
- }
+ throw new InvalidOperationException(
+ RelationalStrings.DbFunctionInvalidReturnType(
+ dbFunction.ModelName,
+ dbFunction.ReturnType.ShortDisplayName()));
}
}
-
- foreach (var entityType in model.GetEntityTypes())
+ else
{
- var mappedFunctionName = entityType.GetFunctionName();
- if (mappedFunctionName == null)
- {
- continue;
- }
+ var elementType = dbFunction.ReturnType.GetGenericArguments()[0];
+ var entityType = model.FindEntityType(elementType);
- var mappedFunction = model.FindDbFunction(mappedFunctionName);
- if (mappedFunction == null)
+ if (entityType?.IsOwned() == true
+ || ((IConventionModel)model).IsOwned(elementType)
+ || (entityType == null && model.GetEntityTypes().Any(e => e.ClrType == elementType)))
{
throw new InvalidOperationException(
- RelationalStrings.MappedFunctionNotFound(entityType.DisplayName(), mappedFunctionName));
+ RelationalStrings.DbFunctionInvalidIQueryableOwnedReturnType(
+ dbFunction.ModelName, elementType.ShortDisplayName()));
}
- if (entityType.BaseType != null)
+ if (entityType == null)
{
throw new InvalidOperationException(
- RelationalStrings.InvalidMappedFunctionDerivedType(
- entityType.DisplayName(), mappedFunctionName, entityType.BaseType.DisplayName()));
+ RelationalStrings.DbFunctionInvalidReturnEntityType(
+ dbFunction.ModelName, dbFunction.ReturnType.ShortDisplayName(), elementType.ShortDisplayName()));
}
- if (mappedFunction.IsScalar
- || mappedFunction.ReturnType.GetGenericArguments()[0] != entityType.ClrType)
+ if ((entityType.BaseType != null || entityType.GetDerivedTypes().Any())
+ && entityType.FindDiscriminatorProperty() == null)
{
throw new InvalidOperationException(
- RelationalStrings.InvalidMappedFunctionUnmatchedReturn(
- entityType.DisplayName(),
- mappedFunctionName,
- mappedFunction.ReturnType.ShortDisplayName(),
- entityType.ClrType.ShortDisplayName()));
+ RelationalStrings.TableValuedFunctionNonTph(dbFunction.ModelName, entityType.DisplayName()));
}
+ }
- if (mappedFunction.Parameters.Count > 0)
+ foreach (var parameter in dbFunction.Parameters)
+ {
+ if (parameter.TypeMapping == null)
{
- var parameters = "{"
- + string.Join(
- ", ",
- mappedFunction.Parameters.Select(p => "'" + p.Name + "'"))
- + "}";
throw new InvalidOperationException(
- RelationalStrings.InvalidMappedFunctionWithParameters(
- entityType.DisplayName(), mappedFunctionName, parameters));
+ RelationalStrings.DbFunctionInvalidParameterType(
+ parameter.Name,
+ dbFunction.ModelName,
+ parameter.ClrType.ShortDisplayName()));
}
}
}
///
- /// Validates the mapping/configuration of stored procedures in the model.
+ /// Validates the function mapping for an entity type.
///
- /// The model to validate.
+ /// The entity type to validate.
/// The logger to use.
- protected virtual void ValidateStoredProcedures(
- IModel model,
+ protected virtual void ValidateDbFunctionMapping(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- var storedProcedures = new Dictionary>();
- foreach (var entityType in model.GetEntityTypes())
+ var mappedFunctionName = entityType.GetFunctionName();
+ if (mappedFunctionName == null)
{
- var mappingStrategy = entityType.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy;
+ return;
+ }
- var sprocCount = 0;
- var deleteStoredProcedure = entityType.GetDeleteStoredProcedure();
- if (deleteStoredProcedure != null)
- {
- AddSproc(StoreObjectType.DeleteStoredProcedure, entityType, storedProcedures);
- ValidateSproc(deleteStoredProcedure, mappingStrategy, logger);
- sprocCount++;
- }
+ var mappedFunction = entityType.Model.FindDbFunction(mappedFunctionName);
+ if (mappedFunction == null)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.MappedFunctionNotFound(entityType.DisplayName(), mappedFunctionName));
+ }
- var insertStoredProcedure = entityType.GetInsertStoredProcedure();
- if (insertStoredProcedure != null)
- {
- AddSproc(StoreObjectType.InsertStoredProcedure, entityType, storedProcedures);
- ValidateSproc(insertStoredProcedure, mappingStrategy, logger);
- sprocCount++;
- }
+ if (entityType.BaseType != null)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.InvalidMappedFunctionDerivedType(
+ entityType.DisplayName(), mappedFunctionName, entityType.BaseType.DisplayName()));
+ }
- var updateStoredProcedure = entityType.GetUpdateStoredProcedure();
- if (updateStoredProcedure != null)
- {
- AddSproc(StoreObjectType.UpdateStoredProcedure, entityType, storedProcedures);
- ValidateSproc(updateStoredProcedure, mappingStrategy, logger);
- sprocCount++;
- }
+ if (mappedFunction.IsScalar
+ || mappedFunction.ReturnType.GetGenericArguments()[0] != entityType.ClrType)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.InvalidMappedFunctionUnmatchedReturn(
+ entityType.DisplayName(),
+ mappedFunctionName,
+ mappedFunction.ReturnType.ShortDisplayName(),
+ entityType.ClrType.ShortDisplayName()));
+ }
- if (sprocCount > 0
- // TODO: Support this with #28703
- //&& sprocCount < 3
- && entityType.GetTableName() == null)
- {
- throw new InvalidOperationException(RelationalStrings.StoredProcedureUnmapped(entityType.DisplayName()));
- }
+ if (mappedFunction.Parameters.Count > 0)
+ {
+ var parameters = "{"
+ + string.Join(
+ ", ",
+ mappedFunction.Parameters.Select(p => "'" + p.Name + "'"))
+ + "}";
+ throw new InvalidOperationException(
+ RelationalStrings.InvalidMappedFunctionWithParameters(
+ entityType.DisplayName(), mappedFunctionName, parameters));
}
+ }
- foreach (var (sproc, mappedTypes) in storedProcedures)
+ ///
+ /// Validates the stored procedures for a entity type.
+ ///
+ /// The entity type to validate.
+ /// The logger to use.
+ protected virtual void ValidateStoredProcedures(
+ IEntityType entityType,
+ IDiagnosticsLogger logger)
+ {
+ var mappingStrategy = entityType.GetMappingStrategy() ?? RelationalAnnotationNames.TphMappingStrategy;
+
+ var sprocCount = 0;
+ var deleteStoredProcedure = entityType.GetDeleteStoredProcedure();
+ if (deleteStoredProcedure != null)
{
- foreach (var mappedType in mappedTypes)
- {
- if (mappedTypes[0].GetRootType() != mappedType.GetRootType())
- {
- throw new InvalidOperationException(
- RelationalStrings.StoredProcedureTableSharing(
- mappedTypes[0].DisplayName(),
- mappedType.DisplayName(),
- sproc.DisplayName()));
- }
- }
+ ValidateStoredProcedureName(StoreObjectType.DeleteStoredProcedure, entityType);
+ ValidateSproc(deleteStoredProcedure, mappingStrategy, logger);
+ sprocCount++;
+ }
+
+ var insertStoredProcedure = entityType.GetInsertStoredProcedure();
+ if (insertStoredProcedure != null)
+ {
+ ValidateStoredProcedureName(StoreObjectType.InsertStoredProcedure, entityType);
+ ValidateSproc(insertStoredProcedure, mappingStrategy, logger);
+ sprocCount++;
+ }
+
+ var updateStoredProcedure = entityType.GetUpdateStoredProcedure();
+ if (updateStoredProcedure != null)
+ {
+ ValidateStoredProcedureName(StoreObjectType.UpdateStoredProcedure, entityType);
+ ValidateSproc(updateStoredProcedure, mappingStrategy, logger);
+ sprocCount++;
+ }
+
+ if (sprocCount > 0
+ // TODO: Support this with #28703
+ //&& sprocCount < 3
+ && entityType.GetTableName() == null)
+ {
+ throw new InvalidOperationException(RelationalStrings.StoredProcedureUnmapped(entityType.DisplayName()));
}
- static void AddSproc(
+ static void ValidateStoredProcedureName(
StoreObjectType storedProcedureType,
- IEntityType entityType,
- Dictionary> storedProcedures)
+ IEntityType entityType)
{
var sprocId = StoreObjectIdentifier.Create(entityType, storedProcedureType);
if (sprocId == null)
@@ -367,14 +473,44 @@ static void AddSproc(
RelationalStrings.StoredProcedureNoName(
entityType.DisplayName(), storedProcedureType));
}
+ }
+ }
- if (!storedProcedures.TryGetValue(sprocId.Value, out var mappedTypes))
+ ///
+ /// Validates a single stored procedure and all entity types mapped to it.
+ ///
+ /// The entity types mapped to the stored procedure.
+ /// The stored procedure identifier.
+ /// The logger to use.
+ protected virtual void ValidateStoredProcedure(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier storedProcedure,
+ IDiagnosticsLogger logger)
+ {
+ ValidateStoredProcedureCompatibility(mappedTypes, storedProcedure, logger);
+ }
+
+ ///
+ /// Validates that a stored procedure is not shared across unrelated entity types.
+ ///
+ /// The entity types mapped to the stored procedure.
+ /// The stored procedure identifier.
+ /// The logger to use.
+ protected virtual void ValidateStoredProcedureCompatibility(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier storedProcedure,
+ IDiagnosticsLogger logger)
+ {
+ foreach (var mappedType in mappedTypes)
+ {
+ if (mappedTypes[0].GetRootType() != mappedType.GetRootType())
{
- mappedTypes = [];
- storedProcedures[sprocId.Value] = mappedTypes;
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureTableSharing(
+ mappedTypes[0].DisplayName(),
+ mappedType.DisplayName(),
+ storedProcedure.DisplayName()));
}
-
- mappedTypes.Add(entityType);
}
}
@@ -752,241 +888,219 @@ private static void ValidateSproc(
}
///
- /// Validates the mapping/configuration of properties in the model.
+ /// Validates a property with defaults.
///
- /// The model to validate.
+ /// The property to validate.
/// The logger to use.
- protected virtual void ValidateBoolsWithDefaults(
- IModel model,
+ protected virtual void ValidateBoolWithDefaults(
+ IProperty property,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ if (!property.ClrType.IsNullableType()
+ && (property.ClrType.IsEnum || property.ClrType == typeof(bool))
+ && property.ValueGenerated != ValueGenerated.Never
+ && property.FieldInfo?.FieldType.IsNullableType() != true
+ && !((IConventionProperty)property).GetSentinelConfigurationSource().HasValue
+ && StoreObjectIdentifier.Create(property.DeclaringType, StoreObjectType.Table) is { } table
+ && (IsNotNullAndNotDefault(property.GetDefaultValue(table))
+ || property.GetDefaultValueSql(table) != null))
{
- foreach (var property in entityType.GetDeclaredProperties())
- {
- if (!property.ClrType.IsNullableType()
- && (property.ClrType.IsEnum || property.ClrType == typeof(bool))
- && property.ValueGenerated != ValueGenerated.Never
- && property.FieldInfo?.FieldType.IsNullableType() != true
- && !((IConventionProperty)property).GetSentinelConfigurationSource().HasValue
- && (StoreObjectIdentifier.Create(property.DeclaringType, StoreObjectType.Table) is { } table
- && (IsNotNullAndNotDefault(property.GetDefaultValue(table))
- || property.GetDefaultValueSql(table) != null)))
- {
- logger.BoolWithDefaultWarning(property);
- }
+ logger.BoolWithDefaultWarning(property);
+ }
- bool IsNotNullAndNotDefault(object? value)
- => value != null
+ bool IsNotNullAndNotDefault(object? value)
+ => value != null
#pragma warning disable EF1001 // Internal EF Core API usage.
- && !property.ClrType.IsDefaultValue(value);
+ && !property.ClrType.IsDefaultValue(value);
#pragma warning restore EF1001 // Internal EF Core API usage.
- }
- }
}
///
- /// Validates the mapping/configuration of default values in the model.
+ /// Validates default values on a key.
///
- /// The model to validate.
+ /// The key to validate.
/// The logger to use.
- protected virtual void ValidateDefaultValuesOnKeys(
- IModel model,
+ protected virtual void ValidateDefaultValuesOnKey(
+ IKey key,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ IProperty? propertyWithDefault = null;
+ foreach (var property in key.Properties)
{
- foreach (var key in entityType.GetDeclaredKeys())
+ var defaultValue = (IConventionAnnotation?)property.FindAnnotation(RelationalAnnotationNames.DefaultValue);
+ if (!property.IsForeignKey()
+ && defaultValue?.Value != null
+ && defaultValue.GetConfigurationSource().Overrides(ConfigurationSource.DataAnnotation))
{
- IProperty? propertyWithDefault = null;
- foreach (var property in key.Properties)
- {
- var defaultValue = (IConventionAnnotation?)property.FindAnnotation(RelationalAnnotationNames.DefaultValue);
- if (!property.IsForeignKey()
- && defaultValue?.Value != null
- && defaultValue.GetConfigurationSource().Overrides(ConfigurationSource.DataAnnotation))
- {
- propertyWithDefault ??= property;
- }
- else
- {
- propertyWithDefault = null;
- break;
- }
- }
-
- if (propertyWithDefault != null)
- {
- logger.ModelValidationKeyDefaultValueWarning(propertyWithDefault);
- }
+ propertyWithDefault ??= property;
+ }
+ else
+ {
+ propertyWithDefault = null;
+ break;
}
}
+
+ if (propertyWithDefault != null)
+ {
+ logger.ModelValidationKeyDefaultValueWarning(propertyWithDefault);
+ }
}
///
- /// Validates the mapping/configuration of mutable in the model.
+ /// Validates that a key doesn't have mutable properties.
///
- /// The model to validate.
+ /// The key to validate.
/// The logger to use.
- protected override void ValidateNoMutableKeys(
- IModel model,
+ protected override void ValidateMutableKey(
+ IKey key,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ var mutableProperty = key.Properties.FirstOrDefault(p => p.ValueGenerated.HasFlag(ValueGenerated.OnUpdate));
+ if (mutableProperty != null
+ && !mutableProperty.IsOrdinalKeyProperty())
{
- foreach (var key in entityType.GetDeclaredKeys())
- {
- var mutableProperty = key.Properties.FirstOrDefault(p => p.ValueGenerated.HasFlag(ValueGenerated.OnUpdate));
- if (mutableProperty != null
- && !mutableProperty.IsOrdinalKeyProperty())
- {
- throw new InvalidOperationException(CoreStrings.MutableKeyProperty(mutableProperty.Name));
- }
- }
+ throw new InvalidOperationException(CoreStrings.MutableKeyProperty(mutableProperty.Name));
}
}
///
- /// Validates the mapping/configuration of shared tables in the model.
+ /// Validates a single table and all entity types mapped to it.
///
- /// The model to validate.
+ /// The entity types mapped to the table.
+ /// The table identifier.
/// The logger to use.
- protected virtual void ValidateSharedTableCompatibility(
- IModel model,
+ protected virtual void ValidateTable(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
- var tables = BuildSharedTableEntityMap(model.GetEntityTypes().Where(e => !e.IsMappedToJson()));
- foreach (var (table, mappedTypes) in tables)
+ var nonJsonMappedTypes = mappedTypes.Where(e => !e.IsMappedToJson()).ToList();
+ if (nonJsonMappedTypes.Count > 0)
+ {
+ ValidateSharedTableCompatibility(nonJsonMappedTypes, table, logger);
+ ValidateSharedColumnsCompatibility(nonJsonMappedTypes, table, logger);
+ ValidateSharedKeysCompatibility(nonJsonMappedTypes, table, logger);
+ ValidateSharedForeignKeysCompatibility(nonJsonMappedTypes, table, logger);
+ ValidateSharedIndexesCompatibility(nonJsonMappedTypes, table, logger);
+ ValidateSharedCheckConstraintCompatibility(nonJsonMappedTypes, table, logger);
+ ValidateSharedTriggerCompatibility(nonJsonMappedTypes, table, logger);
+ ValidateOptionalDependents(nonJsonMappedTypes, table, logger);
+ }
+
+ if (mappedTypes.Count != nonJsonMappedTypes.Count)
+ {
+ ValidateJsonTable(mappedTypes, table, logger);
+ }
+ }
+
+ ///
+ /// Validates that optional dependents have an identifying non-nullable property.
+ ///
+ /// The mapped entity types.
+ /// The table identifier.
+ /// The logger to use.
+ protected virtual void ValidateOptionalDependents(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier table,
+ IDiagnosticsLogger logger)
+ {
+ if (mappedTypes.Count == 1)
{
- ValidateSharedTableCompatibility(mappedTypes, table, logger);
- ValidateSharedColumnsCompatibility(mappedTypes, table, logger);
- ValidateSharedKeysCompatibility(mappedTypes, table, logger);
- ValidateSharedForeignKeysCompatibility(mappedTypes, table, logger);
- ValidateSharedIndexesCompatibility(mappedTypes, table, logger);
- ValidateSharedCheckConstraintCompatibility(mappedTypes, table, logger);
- ValidateSharedTriggerCompatibility(mappedTypes, table, logger);
+ return;
+ }
- // Validate optional dependents
- if (mappedTypes.Count == 1)
+ var tableIdentifier = table;
+ var principalEntityTypesMap = new Dictionary EntityTypes, bool Optional)>();
+ foreach (var entityType in mappedTypes)
+ {
+ if (entityType.BaseType != null
+ || entityType.FindPrimaryKey() == null)
{
continue;
}
- var principalEntityTypesMap = new Dictionary EntityTypes, bool Optional)>();
- foreach (var entityType in mappedTypes)
+ var (principalEntityTypes, optional) = GetPrincipalEntityTypes(entityType);
+ if (!optional)
{
- if (entityType.BaseType != null
- || entityType.FindPrimaryKey() == null)
- {
- continue;
- }
+ continue;
+ }
- var (principalEntityTypes, optional) = GetPrincipalEntityTypes(entityType);
- if (!optional)
+ var principalColumns = principalEntityTypes.SelectMany(e => e.GetProperties())
+ .Select(e => e.GetColumnName(tableIdentifier))
+ .Where(e => e != null)
+ .ToList();
+ var requiredNonSharedColumnFound = false;
+ foreach (var property in entityType.GetProperties())
+ {
+ if (property.IsPrimaryKey()
+ || property.IsNullable)
{
continue;
}
- var principalColumns = principalEntityTypes.SelectMany(e => e.GetProperties())
- .Select(e => e.GetColumnName(table))
- .Where(e => e != null)
- .ToList();
- var requiredNonSharedColumnFound = false;
- foreach (var property in entityType.GetProperties())
- {
- if (property.IsPrimaryKey()
- || property.IsNullable)
- {
- continue;
- }
-
- var columnName = property.GetColumnName(table);
- if (columnName != null)
- {
- if (!principalColumns.Contains(columnName))
- {
- requiredNonSharedColumnFound = true;
- break;
- }
- }
- }
-
- if (!requiredNonSharedColumnFound)
+ var columnName = property.GetColumnName(tableIdentifier);
+ if (columnName != null)
{
- if (entityType.GetReferencingForeignKeys().Select(e => e.DeclaringEntityType).Any(t => mappedTypes.Contains(t)))
+ if (!principalColumns.Contains(columnName))
{
- throw new InvalidOperationException(
- RelationalStrings.OptionalDependentWithDependentWithoutIdentifyingProperty(entityType.DisplayName()));
+ requiredNonSharedColumnFound = true;
+ break;
}
-
- logger.OptionalDependentWithoutIdentifyingPropertyWarning(entityType);
}
}
- (List EntityTypes, bool Optional) GetPrincipalEntityTypes(IEntityType entityType)
+ if (!requiredNonSharedColumnFound)
{
- if (!principalEntityTypesMap.TryGetValue(entityType, out var tuple))
+ if (entityType.GetReferencingForeignKeys().Select(e => e.DeclaringEntityType).Any(t => mappedTypes.Contains(t)))
{
- var list = new List();
- var optional = false;
- foreach (var foreignKey in entityType.FindForeignKeys(entityType.FindPrimaryKey()!.Properties))
- {
- var principalEntityType = foreignKey.PrincipalEntityType;
- if (foreignKey.PrincipalEntityType.IsAssignableFrom(foreignKey.DeclaringEntityType)
- || !mappedTypes.Contains(principalEntityType))
- {
- continue;
- }
-
- list.Add(principalEntityType);
- var (entityTypes, _) = GetPrincipalEntityTypes(principalEntityType.GetRootType());
- list.AddRange(entityTypes);
-
- optional |= !foreignKey.IsRequiredDependent;
- }
-
- tuple = (list, optional);
- principalEntityTypesMap.Add(entityType, tuple);
+ throw new InvalidOperationException(
+ RelationalStrings.OptionalDependentWithDependentWithoutIdentifyingProperty(entityType.DisplayName()));
}
- return tuple;
+ logger.OptionalDependentWithoutIdentifyingPropertyWarning(entityType);
}
}
- }
- private Dictionary> BuildSharedTableEntityMap(IEnumerable entityTypes)
- {
- var result = new Dictionary>();
- foreach (var entityType in entityTypes)
+ (List EntityTypes, bool Optional) GetPrincipalEntityTypes(IEntityType entityType)
{
- var tableId = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
- if (tableId == null)
+ if (!principalEntityTypesMap.TryGetValue(entityType, out var tuple))
{
- continue;
- }
+ var list = new List();
+ var optional = false;
+ foreach (var foreignKey in entityType.FindForeignKeys(entityType.FindPrimaryKey()!.Properties))
+ {
+ var principalEntityType = foreignKey.PrincipalEntityType;
+ if (foreignKey.PrincipalEntityType.IsAssignableFrom(foreignKey.DeclaringEntityType)
+ || !mappedTypes.Contains(principalEntityType))
+ {
+ continue;
+ }
- var table = tableId.Value;
- if (!result.TryGetValue(table, out var mappedTypes))
- {
- mappedTypes = [];
- result[table] = mappedTypes;
+ list.Add(principalEntityType);
+ var (entityTypes, _) = GetPrincipalEntityTypes(principalEntityType.GetRootType());
+ list.AddRange(entityTypes);
+
+ optional |= !foreignKey.IsRequiredDependent;
+ }
+
+ tuple = (list, optional);
+ principalEntityTypesMap.Add(entityType, tuple);
}
- mappedTypes.Add(entityType);
+ return tuple;
}
-
- return result;
}
///
/// Validates the compatibility of entity types sharing a given table.
///
/// The mapped entity types.
- /// The table identifier.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateSharedTableCompatibility(
IReadOnlyList mappedTypes,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
if (mappedTypes.Count == 1)
@@ -1014,7 +1128,7 @@ protected virtual void ValidateSharedTableCompatibility(
{
throw new InvalidOperationException(
RelationalStrings.IncompatibleTableDerivedRelationship(
- storeObject.DisplayName(),
+ table.DisplayName(),
mappedType.DisplayName(),
linkingFK.PrincipalEntityType.DisplayName()));
}
@@ -1026,7 +1140,7 @@ protected virtual void ValidateSharedTableCompatibility(
{
throw new InvalidOperationException(
RelationalStrings.IncompatibleTableNoRelationship(
- storeObject.DisplayName(),
+ table.DisplayName(),
mappedType.DisplayName(),
root.DisplayName()));
}
@@ -1044,7 +1158,7 @@ protected virtual void ValidateSharedTableCompatibility(
var entityType = typesToValidate.Dequeue();
var key = entityType.FindPrimaryKey();
var comment = entityType.GetComment();
- var isExcluded = entityType.IsTableExcludedFromMigrations(storeObject);
+ var isExcluded = entityType.IsTableExcludedFromMigrations(table);
var typesToValidateLeft = typesToValidate.Count;
var directlyConnectedTypes = unvalidatedTypes.Where(unvalidatedType =>
entityType.IsAssignableFrom(unvalidatedType)
@@ -1055,16 +1169,16 @@ protected virtual void ValidateSharedTableCompatibility(
if (key != null)
{
var otherKey = nextEntityType.FindPrimaryKey()!;
- if (key.GetName(storeObject) != otherKey.GetName(storeObject))
+ if (key.GetName(table) != otherKey.GetName(table))
{
throw new InvalidOperationException(
RelationalStrings.IncompatibleTableKeyNameMismatch(
- storeObject.DisplayName(),
+ table.DisplayName(),
entityType.DisplayName(),
nextEntityType.DisplayName(),
- key.GetName(storeObject),
+ key.GetName(table),
key.Properties.Format(),
- otherKey.GetName(storeObject),
+ otherKey.GetName(table),
otherKey.Properties.Format()));
}
}
@@ -1077,7 +1191,7 @@ protected virtual void ValidateSharedTableCompatibility(
{
throw new InvalidOperationException(
RelationalStrings.IncompatibleTableCommentMismatch(
- storeObject.DisplayName(),
+ table.DisplayName(),
entityType.DisplayName(),
nextEntityType.DisplayName(),
comment,
@@ -1089,11 +1203,11 @@ protected virtual void ValidateSharedTableCompatibility(
comment = nextComment;
}
- if (isExcluded.Equals(!nextEntityType.IsTableExcludedFromMigrations(storeObject)))
+ if (isExcluded.Equals(!nextEntityType.IsTableExcludedFromMigrations(table)))
{
throw new InvalidOperationException(
RelationalStrings.IncompatibleTableExcludedMismatch(
- storeObject.DisplayName(),
+ table.DisplayName(),
entityType.DisplayName(),
nextEntityType.DisplayName()));
}
@@ -1117,44 +1231,33 @@ protected virtual void ValidateSharedTableCompatibility(
Check.DebugAssert(root != null, "root is null");
throw new InvalidOperationException(
RelationalStrings.IncompatibleTableNoRelationship(
- storeObject.DisplayName(),
+ table.DisplayName(),
invalidEntityType.DisplayName(),
root.DisplayName()));
}
}
///
- /// Validates the mapping/configuration of shared views in the model.
+ /// Validates a single view and all entity types mapped to it.
///
- /// The model to validate.
+ /// The entity types mapped to the view.
+ /// The view identifier.
/// The logger to use.
- protected virtual void ValidateSharedViewCompatibility(
- IModel model,
+ protected virtual void ValidateView(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier view,
IDiagnosticsLogger logger)
{
- var views = new Dictionary>();
- foreach (var entityType in model.GetEntityTypes().Where(e => !e.IsMappedToJson()))
+ var nonJsonMappedTypes = mappedTypes.Where(e => !e.IsMappedToJson()).ToList();
+ if (nonJsonMappedTypes.Count > 0)
{
- var viewsName = entityType.GetViewName();
- if (viewsName == null)
- {
- continue;
- }
-
- var view = StoreObjectIdentifier.View(viewsName, entityType.GetViewSchema());
- if (!views.TryGetValue(view, out var mappedTypes))
- {
- mappedTypes = [];
- views[view] = mappedTypes;
- }
-
- mappedTypes.Add(entityType);
+ ValidateSharedViewCompatibility(nonJsonMappedTypes, view.Name, view.Schema, logger);
+ ValidateSharedColumnsCompatibility(nonJsonMappedTypes, view, logger);
}
- foreach (var (view, mappedTypes) in views)
+ if (mappedTypes.Count != nonJsonMappedTypes.Count)
{
- ValidateSharedViewCompatibility(mappedTypes, view.Name, view.Schema, logger);
- ValidateSharedColumnsCompatibility(mappedTypes, view, logger);
+ ValidateJsonView(mappedTypes, view, logger);
}
}
@@ -1664,14 +1767,14 @@ protected virtual void ValidateCompatible(
/// Validates the compatibility of foreign keys in a given shared table.
///
/// The mapped entity types.
- /// The identifier of the store object.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateSharedForeignKeysCompatibility(
IReadOnlyList mappedTypes,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
- if (storeObject.StoreObjectType != StoreObjectType.Table)
+ if (table.StoreObjectType != StoreObjectType.Table)
{
return;
}
@@ -1688,7 +1791,7 @@ protected virtual void ValidateSharedForeignKeysCompatibility(
continue;
}
- var foreignKeyName = foreignKey.GetConstraintName(storeObject, principalTable.Value, logger);
+ var foreignKeyName = foreignKey.GetConstraintName(table, principalTable.Value, logger);
if (foreignKeyName == null)
{
continue;
@@ -1700,7 +1803,7 @@ protected virtual void ValidateSharedForeignKeysCompatibility(
continue;
}
- ValidateCompatible(foreignKey, duplicateForeignKey, foreignKeyName, storeObject, logger);
+ ValidateCompatible(foreignKey, duplicateForeignKey, foreignKeyName, table, logger);
}
}
@@ -1710,33 +1813,34 @@ protected virtual void ValidateSharedForeignKeysCompatibility(
/// A foreign key.
/// Another foreign key.
/// The foreign key constraint name.
- /// The identifier of the store object.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateCompatible(
IForeignKey foreignKey,
IForeignKey duplicateForeignKey,
string foreignKeyName,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
- => foreignKey.AreCompatible(duplicateForeignKey, storeObject, shouldThrow: true);
+ => foreignKey.AreCompatible(duplicateForeignKey, table, shouldThrow: true);
///
/// Validates the compatibility of indexes in a given shared table.
///
/// The mapped entity types.
- /// The identifier of the store object.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateSharedIndexesCompatibility(
IReadOnlyList mappedTypes,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
var indexMappings = new Dictionary();
foreach (var index in mappedTypes.SelectMany(et => et.GetDeclaredIndexes()))
{
- var indexName = index.GetDatabaseName(storeObject, logger);
+ var indexName = index.GetDatabaseName(table);
if (indexName == null)
{
+ ValidateIndexPropertyMapping(index, logger);
continue;
}
@@ -1746,7 +1850,7 @@ protected virtual void ValidateSharedIndexesCompatibility(
continue;
}
- ValidateCompatible(index, duplicateIndex, indexName, storeObject, logger);
+ ValidateCompatible(index, duplicateIndex, indexName, table, logger);
}
}
@@ -1756,31 +1860,31 @@ protected virtual void ValidateSharedIndexesCompatibility(
/// An index.
/// Another index.
/// The name of the index.
- /// The identifier of the store object.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateCompatible(
IIndex index,
IIndex duplicateIndex,
string indexName,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
- => index.AreCompatible(duplicateIndex, storeObject, shouldThrow: true);
+ => index.AreCompatible(duplicateIndex, table, shouldThrow: true);
///
/// Validates the compatibility of primary and alternate keys in a given shared table.
///
/// The mapped entity types.
- /// The identifier of the store object.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateSharedKeysCompatibility(
IReadOnlyList mappedTypes,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
var keyMappings = new Dictionary();
foreach (var key in mappedTypes.SelectMany(et => et.GetDeclaredKeys()))
{
- var keyName = key.GetName(storeObject, logger);
+ var keyName = key.GetName(table, logger);
if (keyName == null)
{
continue;
@@ -1792,7 +1896,7 @@ protected virtual void ValidateSharedKeysCompatibility(
continue;
}
- ValidateCompatible(key, duplicateKey, keyName, storeObject, logger);
+ ValidateCompatible(key, duplicateKey, keyName, table, logger);
}
}
@@ -1802,31 +1906,31 @@ protected virtual void ValidateSharedKeysCompatibility(
/// A key.
/// Another key.
/// The name of the unique constraint.
- /// The identifier of the store object.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateCompatible(
IKey key,
IKey duplicateKey,
string keyName,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
- => key.AreCompatible(duplicateKey, storeObject, shouldThrow: true);
+ => key.AreCompatible(duplicateKey, table, shouldThrow: true);
///
/// Validates the compatibility of check constraints in a given shared table.
///
/// The mapped entity types.
- /// The identifier of the store object.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateSharedCheckConstraintCompatibility(
IReadOnlyList mappedTypes,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
var checkConstraintMappings = new Dictionary();
foreach (var checkConstraint in mappedTypes.SelectMany(et => et.GetDeclaredCheckConstraints()))
{
- var checkConstraintName = checkConstraint.GetName(storeObject);
+ var checkConstraintName = checkConstraint.GetName(table);
if (checkConstraintName == null)
{
continue;
@@ -1838,7 +1942,7 @@ protected virtual void ValidateSharedCheckConstraintCompatibility(
continue;
}
- ValidateCompatible(checkConstraint, duplicateCheckConstraint, checkConstraintName, storeObject, logger);
+ ValidateCompatible(checkConstraint, duplicateCheckConstraint, checkConstraintName, table, logger);
}
}
@@ -1848,31 +1952,31 @@ protected virtual void ValidateSharedCheckConstraintCompatibility(
/// A check constraint.
/// Another check constraint.
/// The name of the check constraint.
- /// The identifier of the store object.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateCompatible(
ICheckConstraint checkConstraint,
ICheckConstraint duplicateCheckConstraint,
string indexName,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
- => CheckConstraint.AreCompatible(checkConstraint, duplicateCheckConstraint, storeObject, shouldThrow: true);
+ => CheckConstraint.AreCompatible(checkConstraint, duplicateCheckConstraint, table, shouldThrow: true);
///
/// Validates the compatibility of triggers in a given shared table.
///
/// The mapped entity types.
- /// The identifier of the store object.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateSharedTriggerCompatibility(
IReadOnlyList mappedTypes,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
var triggerMappings = new Dictionary();
foreach (var trigger in mappedTypes.SelectMany(et => et.GetDeclaredTriggers()))
{
- var triggerName = trigger.GetDatabaseName(storeObject);
+ var triggerName = trigger.GetDatabaseName(table);
if (triggerName == null)
{
continue;
@@ -1884,7 +1988,7 @@ protected virtual void ValidateSharedTriggerCompatibility(
continue;
}
- ValidateCompatible(trigger, duplicateTrigger, triggerName, storeObject, logger);
+ ValidateCompatible(trigger, duplicateTrigger, triggerName, table, logger);
}
}
@@ -1894,132 +1998,124 @@ protected virtual void ValidateSharedTriggerCompatibility(
/// A trigger.
/// Another trigger.
/// The name of the trigger.
- /// The identifier of the store object.
+ /// The table identifier.
/// The logger to use.
protected virtual void ValidateCompatible(
ITrigger trigger,
ITrigger duplicateTrigger,
string indexName,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
}
///
- /// Validates the mapping/configuration of inheritance in the model.
+ /// Validates inheritance mapping for an entity type.
///
- /// The model to validate.
+ /// The entity type to validate.
/// The logger to use.
protected override void ValidateInheritanceMapping(
- IModel model,
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
- {
- var mappingStrategy = (string?)entityType[RelationalAnnotationNames.MappingStrategy];
- if (mappingStrategy != null)
- {
- ValidateMappingStrategy(entityType, mappingStrategy);
- var storeObject = entityType.GetSchemaQualifiedTableName()
- ?? entityType.GetSchemaQualifiedViewName()
- ?? entityType.GetFunctionName()
- ?? entityType.GetSqlQuery()
- ?? entityType.GetInsertStoredProcedure()?.GetSchemaQualifiedName()
- ?? entityType.GetDeleteStoredProcedure()?.GetSchemaQualifiedName()
- ?? entityType.GetUpdateStoredProcedure()?.GetSchemaQualifiedName();
- if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy
- && !entityType.ClrType.IsInstantiable()
- && storeObject != null)
- {
- throw new InvalidOperationException(
- RelationalStrings.AbstractTpc(entityType.DisplayName(), storeObject));
- }
+ var mappingStrategy = (string?)entityType[RelationalAnnotationNames.MappingStrategy];
+ if (mappingStrategy != null)
+ {
+ ValidateMappingStrategy(entityType, mappingStrategy);
+ var storeObjectName = entityType.GetSchemaQualifiedTableName()
+ ?? entityType.GetSchemaQualifiedViewName()
+ ?? entityType.GetFunctionName()
+ ?? entityType.GetSqlQuery()
+ ?? entityType.GetInsertStoredProcedure()?.GetSchemaQualifiedName()
+ ?? entityType.GetDeleteStoredProcedure()?.GetSchemaQualifiedName()
+ ?? entityType.GetUpdateStoredProcedure()?.GetSchemaQualifiedName();
+ if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy
+ && !entityType.ClrType.IsInstantiable()
+ && storeObjectName != null)
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.AbstractTpc(entityType.DisplayName(), storeObjectName));
}
+ }
- foreach (var key in entityType.GetKeys())
+ if (entityType.BaseType != null)
+ {
+ if (mappingStrategy != null
+ && mappingStrategy != (string?)entityType.BaseType[RelationalAnnotationNames.MappingStrategy])
{
- ValidateValueGeneration(entityType, key, logger);
+ throw new InvalidOperationException(
+ RelationalStrings.DerivedStrategy(entityType.DisplayName(), mappingStrategy));
}
- if (entityType.BaseType != null)
+ return;
+ }
+
+ // Hierarchy mapping strategy must be the same across all types of mappings (only for root types)
+ if (entityType.FindDiscriminatorProperty() != null)
+ {
+ if (mappingStrategy != null
+ && mappingStrategy != RelationalAnnotationNames.TphMappingStrategy)
{
- if (mappingStrategy != null
- && mappingStrategy != (string?)entityType.BaseType[RelationalAnnotationNames.MappingStrategy])
- {
- throw new InvalidOperationException(
- RelationalStrings.DerivedStrategy(entityType.DisplayName(), mappingStrategy));
- }
+ throw new InvalidOperationException(
+ RelationalStrings.NonTphMappingStrategy(mappingStrategy, entityType.DisplayName()));
+ }
- continue;
+ ValidateTphMapping(entityType, StoreObjectType.Table);
+ ValidateTphMapping(entityType, StoreObjectType.View);
+ ValidateTphMapping(entityType, StoreObjectType.Function);
+ ValidateTphMapping(entityType, StoreObjectType.InsertStoredProcedure);
+ ValidateTphMapping(entityType, StoreObjectType.DeleteStoredProcedure);
+ ValidateTphMapping(entityType, StoreObjectType.UpdateStoredProcedure);
+
+ ValidateDiscriminatorValues(entityType);
+ }
+ else
+ {
+ if (mappingStrategy != RelationalAnnotationNames.TpcMappingStrategy
+ && entityType.FindPrimaryKey() == null
+ && entityType.GetDirectlyDerivedTypes().Any())
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.KeylessMappingStrategy(
+ mappingStrategy ?? RelationalAnnotationNames.TptMappingStrategy, entityType.DisplayName()));
}
- // Hierarchy mapping strategy must be the same across all types of mappings
- if (entityType.FindDiscriminatorProperty() != null)
+ ValidateNonTphMapping(entityType, StoreObjectType.Table);
+ ValidateNonTphMapping(entityType, StoreObjectType.View);
+ ValidateNonTphMapping(entityType, StoreObjectType.InsertStoredProcedure);
+ ValidateNonTphMapping(entityType, StoreObjectType.DeleteStoredProcedure);
+ ValidateNonTphMapping(entityType, StoreObjectType.UpdateStoredProcedure);
+
+ var derivedTypes = entityType.GetDerivedTypesInclusive().ToList();
+ var discriminatorValues = new Dictionary();
+ foreach (var derivedType in derivedTypes)
{
- if (mappingStrategy != null
- && mappingStrategy != RelationalAnnotationNames.TphMappingStrategy)
+ foreach (var complexProperty in derivedType.GetDeclaredComplexProperties())
{
- throw new InvalidOperationException(
- RelationalStrings.NonTphMappingStrategy(mappingStrategy, entityType.DisplayName()));
+ ValidateDiscriminatorValues(complexProperty.ComplexType);
}
- ValidateTphMapping(entityType, StoreObjectType.Table);
- ValidateTphMapping(entityType, StoreObjectType.View);
- ValidateTphMapping(entityType, StoreObjectType.Function);
- ValidateTphMapping(entityType, StoreObjectType.InsertStoredProcedure);
- ValidateTphMapping(entityType, StoreObjectType.DeleteStoredProcedure);
- ValidateTphMapping(entityType, StoreObjectType.UpdateStoredProcedure);
+ var discriminatorValue = derivedType.GetDiscriminatorValue();
+ if (!derivedType.ClrType.IsInstantiable()
+ || discriminatorValue is null)
+ {
+ continue;
+ }
- ValidateDiscriminatorValues(entityType);
- }
- else
- {
- if (mappingStrategy != RelationalAnnotationNames.TpcMappingStrategy
- && entityType.FindPrimaryKey() == null
- && entityType.GetDirectlyDerivedTypes().Any())
+ if (discriminatorValue is not string valueString)
{
throw new InvalidOperationException(
- RelationalStrings.KeylessMappingStrategy(
- mappingStrategy ?? RelationalAnnotationNames.TptMappingStrategy, entityType.DisplayName()));
+ RelationalStrings.NonTphDiscriminatorValueNotString(discriminatorValue, derivedType.DisplayName()));
}
- ValidateNonTphMapping(entityType, StoreObjectType.Table);
- ValidateNonTphMapping(entityType, StoreObjectType.View);
- ValidateNonTphMapping(entityType, StoreObjectType.InsertStoredProcedure);
- ValidateNonTphMapping(entityType, StoreObjectType.DeleteStoredProcedure);
- ValidateNonTphMapping(entityType, StoreObjectType.UpdateStoredProcedure);
-
- var derivedTypes = entityType.GetDerivedTypesInclusive().ToList();
- var discriminatorValues = new Dictionary();
- foreach (var derivedType in derivedTypes)
+ if (discriminatorValues.TryGetValue(valueString, out var duplicateEntityType))
{
- foreach (var complexProperty in derivedType.GetDeclaredComplexProperties())
- {
- ValidateDiscriminatorValues(complexProperty.ComplexType);
- }
-
- var discriminatorValue = derivedType.GetDiscriminatorValue();
- if (!derivedType.ClrType.IsInstantiable()
- || discriminatorValue is null)
- {
- continue;
- }
-
- if (discriminatorValue is not string valueString)
- {
- throw new InvalidOperationException(
- RelationalStrings.NonTphDiscriminatorValueNotString(discriminatorValue, derivedType.DisplayName()));
- }
-
- if (discriminatorValues.TryGetValue(valueString, out var duplicateEntityType))
- {
- throw new InvalidOperationException(
- RelationalStrings.EntityShortNameNotUnique(
- derivedType.Name, discriminatorValue, duplicateEntityType.Name));
- }
-
- discriminatorValues[valueString] = derivedType;
+ throw new InvalidOperationException(
+ RelationalStrings.EntityShortNameNotUnique(
+ derivedType.Name, discriminatorValue, duplicateEntityType.Name));
}
+
+ discriminatorValues[valueString] = derivedType;
}
}
}
@@ -2027,14 +2123,13 @@ protected override void ValidateInheritanceMapping(
///
/// Validates the key value generation is valid.
///
- /// The entity type.
/// The key.
/// The logger to use.
protected virtual void ValidateValueGeneration(
- IEntityType entityType,
IKey key,
IDiagnosticsLogger logger)
{
+ var entityType = key.DeclaringEntityType;
if (entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy
&& entityType.BaseType == null)
{
@@ -2219,114 +2314,111 @@ protected override bool IsRedundant(IForeignKey foreignKey)
&& !foreignKey.DeclaringEntityType.GetMappingFragments().Any();
///
- /// Validates the entity type mapping fragments.
+ /// Validates the mapping fragments for an entity type.
///
- /// The model to validate.
+ /// The entity type to validate.
/// The logger to use.
- protected virtual void ValidateMappingFragments(
- IModel model,
+ protected virtual void ValidateMappingFragment(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ var fragments = EntityTypeMappingFragment.Get(entityType);
+ if (fragments == null)
+ {
+ return;
+ }
+
+ if (entityType.BaseType != null
+ || entityType.GetDirectlyDerivedTypes().Any())
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.EntitySplittingHierarchy(entityType.DisplayName(), fragments.First().StoreObject.DisplayName()));
+ }
+
+ var anyTableFragments = false;
+ var anyViewFragments = false;
+ foreach (var fragment in fragments)
{
- var fragments = EntityTypeMappingFragment.Get(entityType);
- if (fragments == null)
+ var mainStoreObject = StoreObjectIdentifier.Create(entityType, fragment.StoreObject.StoreObjectType);
+ if (mainStoreObject == null)
{
- continue;
+ throw new InvalidOperationException(
+ RelationalStrings.EntitySplittingUnmappedMainFragment(
+ entityType.DisplayName(), fragment.StoreObject.DisplayName(), fragment.StoreObject.StoreObjectType));
}
- if (entityType.BaseType != null
- || entityType.GetDirectlyDerivedTypes().Any())
+ if (fragment.StoreObject == mainStoreObject)
{
throw new InvalidOperationException(
- RelationalStrings.EntitySplittingHierarchy(entityType.DisplayName(), fragments.First().StoreObject.DisplayName()));
+ RelationalStrings.EntitySplittingConflictingMainFragment(
+ entityType.DisplayName(), fragment.StoreObject.DisplayName()));
}
- var anyTableFragments = false;
- var anyViewFragments = false;
- foreach (var fragment in fragments)
+ foreach (var foreignKey in entityType.FindRowInternalForeignKeys(fragment.StoreObject))
{
- var mainStoreObject = StoreObjectIdentifier.Create(entityType, fragment.StoreObject.StoreObjectType);
- if (mainStoreObject == null)
- {
- throw new InvalidOperationException(
- RelationalStrings.EntitySplittingUnmappedMainFragment(
- entityType.DisplayName(), fragment.StoreObject.DisplayName(), fragment.StoreObject.StoreObjectType));
- }
-
- if (fragment.StoreObject == mainStoreObject)
+ var principalMainFragment = StoreObjectIdentifier.Create(
+ foreignKey.PrincipalEntityType, fragment.StoreObject.StoreObjectType)!.Value;
+ if (principalMainFragment != mainStoreObject)
{
throw new InvalidOperationException(
- RelationalStrings.EntitySplittingConflictingMainFragment(
- entityType.DisplayName(), fragment.StoreObject.DisplayName()));
+ RelationalStrings.EntitySplittingUnmatchedMainTableSplitting(
+ entityType.DisplayName(),
+ fragment.StoreObject.DisplayName(),
+ foreignKey.PrincipalEntityType.DisplayName(),
+ principalMainFragment.DisplayName()));
}
+ }
- foreach (var foreignKey in entityType.FindRowInternalForeignKeys(fragment.StoreObject))
+ var propertiesFound = false;
+ foreach (var property in entityType.GetProperties())
+ {
+ var columnName = property.GetColumnName(fragment.StoreObject);
+ if (columnName == null)
{
- var principalMainFragment = StoreObjectIdentifier.Create(
- foreignKey.PrincipalEntityType, fragment.StoreObject.StoreObjectType)!.Value;
- if (principalMainFragment != mainStoreObject)
+ if (property.IsPrimaryKey())
{
throw new InvalidOperationException(
- RelationalStrings.EntitySplittingUnmatchedMainTableSplitting(
- entityType.DisplayName(),
- fragment.StoreObject.DisplayName(),
- foreignKey.PrincipalEntityType.DisplayName(),
- principalMainFragment.DisplayName()));
+ RelationalStrings.EntitySplittingMissingPrimaryKey(
+ entityType.DisplayName(), fragment.StoreObject.DisplayName()));
}
- }
- var propertiesFound = false;
- foreach (var property in entityType.GetProperties())
- {
- var columnName = property.GetColumnName(fragment.StoreObject);
- if (columnName == null)
- {
- if (property.IsPrimaryKey())
- {
- throw new InvalidOperationException(
- RelationalStrings.EntitySplittingMissingPrimaryKey(
- entityType.DisplayName(), fragment.StoreObject.DisplayName()));
- }
-
- continue;
- }
-
- if (!property.IsPrimaryKey())
- {
- propertiesFound = true;
- }
- }
-
- if (!propertiesFound)
- {
- throw new InvalidOperationException(
- RelationalStrings.EntitySplittingMissingProperties(
- entityType.DisplayName(), fragment.StoreObject.DisplayName()));
+ continue;
}
- switch (fragment.StoreObject.StoreObjectType)
+ if (!property.IsPrimaryKey())
{
- case StoreObjectType.Table:
- anyTableFragments = true;
- break;
- case StoreObjectType.View:
- anyViewFragments = true;
- break;
+ propertiesFound = true;
}
}
- if (anyTableFragments)
+ if (!propertiesFound)
{
- ValidateMainMapping(entityType, StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value);
+ throw new InvalidOperationException(
+ RelationalStrings.EntitySplittingMissingProperties(
+ entityType.DisplayName(), fragment.StoreObject.DisplayName()));
}
- if (anyViewFragments)
+ switch (fragment.StoreObject.StoreObjectType)
{
- ValidateMainMapping(entityType, StoreObjectIdentifier.Create(entityType, StoreObjectType.View)!.Value);
+ case StoreObjectType.Table:
+ anyTableFragments = true;
+ break;
+ case StoreObjectType.View:
+ anyViewFragments = true;
+ break;
}
}
+ if (anyTableFragments)
+ {
+ ValidateMainMapping(entityType, StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value);
+ }
+
+ if (anyViewFragments)
+ {
+ ValidateMainMapping(entityType, StoreObjectIdentifier.Create(entityType, StoreObjectType.View)!.Value);
+ }
+
static StoreObjectIdentifier? ValidateMainMapping(IEntityType entityType, StoreObjectIdentifier mainObject)
{
var nonSharedRequiredPropertyFound =
@@ -2375,67 +2467,54 @@ protected virtual void ValidateMappingFragments(
}
///
- /// Validates the table-specific property overrides.
+ /// Validates a table-specific property override for a property.
///
- /// The model to validate.
+ /// The property to validate.
+ /// The property override to validate.
/// The logger to use.
- protected virtual void ValidatePropertyOverrides(
- IModel model,
+ protected virtual void ValidatePropertyOverride(
+ IProperty property,
+ IReadOnlyRelationalPropertyOverrides propertyOverride,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ if (GetAllMappedStoreObjects(property, propertyOverride.StoreObject.StoreObjectType)
+ .Any(o => o == propertyOverride.StoreObject))
{
- foreach (var property in entityType.GetDeclaredProperties())
- {
- var storeObjectOverrides = RelationalPropertyOverrides.Get(property);
- if (storeObjectOverrides == null)
- {
- continue;
- }
-
- foreach (var storeObjectOverride in storeObjectOverrides)
- {
- if (GetAllMappedStoreObjects(property, storeObjectOverride.StoreObject.StoreObjectType)
- .Any(o => o == storeObjectOverride.StoreObject))
- {
- continue;
- }
+ return;
+ }
- var storeObject = storeObjectOverride.StoreObject;
- switch (storeObject.StoreObjectType)
- {
- case StoreObjectType.Table:
- throw new InvalidOperationException(
- RelationalStrings.TableOverrideMismatch(
- entityType.DisplayName() + "." + property.Name,
- storeObjectOverride.StoreObject.DisplayName()));
- case StoreObjectType.View:
- throw new InvalidOperationException(
- RelationalStrings.ViewOverrideMismatch(
- entityType.DisplayName() + "." + property.Name,
- storeObjectOverride.StoreObject.DisplayName()));
- case StoreObjectType.SqlQuery:
- throw new InvalidOperationException(
- RelationalStrings.SqlQueryOverrideMismatch(
- entityType.DisplayName() + "." + property.Name,
- storeObjectOverride.StoreObject.DisplayName()));
- case StoreObjectType.Function:
- throw new InvalidOperationException(
- RelationalStrings.FunctionOverrideMismatch(
- entityType.DisplayName() + "." + property.Name,
- storeObjectOverride.StoreObject.DisplayName()));
- case StoreObjectType.InsertStoredProcedure:
- case StoreObjectType.DeleteStoredProcedure:
- case StoreObjectType.UpdateStoredProcedure:
- throw new InvalidOperationException(
- RelationalStrings.StoredProcedureOverrideMismatch(
- entityType.DisplayName() + "." + property.Name,
- storeObjectOverride.StoreObject.DisplayName()));
- default:
- throw new NotSupportedException(storeObject.StoreObjectType.ToString());
- }
- }
- }
+ var storeObject = propertyOverride.StoreObject;
+ switch (storeObject.StoreObjectType)
+ {
+ case StoreObjectType.Table:
+ throw new InvalidOperationException(
+ RelationalStrings.TableOverrideMismatch(
+ property.DeclaringType.DisplayName() + "." + property.Name,
+ propertyOverride.StoreObject.DisplayName()));
+ case StoreObjectType.View:
+ throw new InvalidOperationException(
+ RelationalStrings.ViewOverrideMismatch(
+ property.DeclaringType.DisplayName() + "." + property.Name,
+ propertyOverride.StoreObject.DisplayName()));
+ case StoreObjectType.SqlQuery:
+ throw new InvalidOperationException(
+ RelationalStrings.SqlQueryOverrideMismatch(
+ property.DeclaringType.DisplayName() + "." + property.Name,
+ propertyOverride.StoreObject.DisplayName()));
+ case StoreObjectType.Function:
+ throw new InvalidOperationException(
+ RelationalStrings.FunctionOverrideMismatch(
+ property.DeclaringType.DisplayName() + "." + property.Name,
+ propertyOverride.StoreObject.DisplayName()));
+ case StoreObjectType.InsertStoredProcedure:
+ case StoreObjectType.DeleteStoredProcedure:
+ case StoreObjectType.UpdateStoredProcedure:
+ throw new InvalidOperationException(
+ RelationalStrings.StoredProcedureOverrideMismatch(
+ property.DeclaringType.DisplayName() + "." + property.Name,
+ propertyOverride.StoreObject.DisplayName()));
+ default:
+ throw new NotSupportedException(storeObject.StoreObjectType.ToString());
}
}
@@ -2443,10 +2522,18 @@ private static IEnumerable GetAllMappedStoreObjects(
IReadOnlyProperty property,
StoreObjectType storeObjectType)
{
- var mappingStrategy = property.DeclaringType.GetMappingStrategy();
+ var declaringType = property.DeclaringType;
+
+ // Complex types inherit their table mapping from the containing entity type
+ if (declaringType is IReadOnlyComplexType)
+ {
+ declaringType = property.DeclaringType.ContainingEntityType;
+ }
+
+ var mappingStrategy = declaringType.GetMappingStrategy();
if (property.IsPrimaryKey())
{
- var declaringStoreObject = StoreObjectIdentifier.Create(property.DeclaringType, storeObjectType);
+ var declaringStoreObject = StoreObjectIdentifier.Create(declaringType, storeObjectType);
if (declaringStoreObject != null)
{
yield return declaringStoreObject.Value;
@@ -2457,12 +2544,12 @@ private static IEnumerable GetAllMappedStoreObjects(
yield break;
}
- foreach (var fragment in property.DeclaringType.GetMappingFragments(storeObjectType))
+ foreach (var fragment in declaringType.GetMappingFragments(storeObjectType))
{
yield return fragment.StoreObject;
}
- if (property.DeclaringType is IReadOnlyEntityType entityType)
+ if (declaringType is IReadOnlyEntityType entityType)
{
foreach (var containingType in entityType.GetDerivedTypes())
{
@@ -2481,7 +2568,7 @@ private static IEnumerable GetAllMappedStoreObjects(
}
else
{
- var declaringStoreObject = StoreObjectIdentifier.Create(property.DeclaringType, storeObjectType);
+ var declaringStoreObject = StoreObjectIdentifier.Create(declaringType, storeObjectType);
if (storeObjectType is StoreObjectType.Function or StoreObjectType.SqlQuery)
{
if (declaringStoreObject != null)
@@ -2494,7 +2581,7 @@ private static IEnumerable GetAllMappedStoreObjects(
if (declaringStoreObject != null)
{
- var fragments = property.DeclaringType.GetMappingFragments(storeObjectType).ToList();
+ var fragments = declaringType.GetMappingFragments(storeObjectType).ToList();
if (fragments.Count > 0)
{
var overrides = RelationalPropertyOverrides.Find(property, declaringStoreObject.Value);
@@ -2522,7 +2609,7 @@ private static IEnumerable GetAllMappedStoreObjects(
}
}
- if (property.DeclaringType is not IReadOnlyEntityType entityType)
+ if (declaringType is not IReadOnlyEntityType entityType)
{
yield break;
}
@@ -2530,7 +2617,8 @@ private static IEnumerable GetAllMappedStoreObjects(
var tableFound = false;
var queue = new Queue();
queue.Enqueue(entityType);
- while (queue.Count > 0 && !tableFound)
+ while (queue.Count > 0
+ && (!tableFound || mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy))
{
foreach (var containingType in queue.Dequeue().GetDirectlyDerivedTypes())
{
@@ -2556,226 +2644,304 @@ private static IEnumerable GetAllMappedStoreObjects(
}
///
- /// Validates that the properties of any one index are all mapped to columns on at least one common table.
+ /// Validates a single index.
///
- /// The model to validate.
+ /// The index to validate.
/// The logger to use.
- protected virtual void ValidateIndexProperties(
- IModel model,
+ protected override void ValidateIndex(
+ IIndex index,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ base.ValidateIndex(index, logger);
+
+ ValidateIndexMappedToTable(index, logger);
+ }
+
+ ///
+ /// Validates that the properties of the index are all mapped to columns on at least one table.
+ ///
+ /// The index to validate.
+ /// The logger to use.
+ protected virtual void ValidateIndexMappedToTable(
+ IIndex index,
+ IDiagnosticsLogger logger)
+ {
+ if (index.DeclaringEntityType.GetTableName() != null
+ || ((IConventionIndex)index).GetConfigurationSource() == ConfigurationSource.Convention)
+ {
+ return;
+ }
+
+ // The case where the index declaring type is mapped to a table is handled in ValidateSharedIndexesCompatibility
+ ValidateIndexPropertyMapping(index, logger);
+ }
+
+ ///
+ /// Validates that the properties of the index are all mapped to columns on a given table.
+ ///
+ /// The index to validate.
+ /// The logger to use.
+ protected virtual void ValidateIndexPropertyMapping(
+ IIndex index,
+ IDiagnosticsLogger logger)
+ {
+ if (((IConventionIndex)index).GetConfigurationSource() == ConfigurationSource.Convention)
{
- if (entityType.GetTableName() != null)
+ return;
+ }
+
+ IReadOnlyProperty? propertyNotMappedToAnyTable = null;
+ (string, List)? firstPropertyTables = null;
+ (string, List)? lastPropertyTables = null;
+ HashSet? overlappingTables = null;
+ foreach (var property in index.Properties)
+ {
+ var tablesMappedToProperty = property.GetMappedStoreObjects(StoreObjectType.Table).ToList();
+ if (tablesMappedToProperty.Count == 0)
{
+ propertyNotMappedToAnyTable = property;
+ overlappingTables = null;
+
+ if (firstPropertyTables != null)
+ {
+ // Property is not mapped but we already found a property that is mapped.
+ break;
+ }
+
continue;
}
- foreach (var index in entityType.GetDeclaredIndexes())
+ if (firstPropertyTables == null)
+ {
+ firstPropertyTables = (property.Name, tablesMappedToProperty);
+ }
+ else
+ {
+ lastPropertyTables = (property.Name, tablesMappedToProperty);
+ }
+
+ if (propertyNotMappedToAnyTable != null)
{
- if (ConfigurationSource.Convention != ((IConventionIndex)index).GetConfigurationSource())
+ // Property is mapped but we already found a property that is not mapped.
+ overlappingTables = null;
+ break;
+ }
+
+ if (overlappingTables == null)
+ {
+ overlappingTables = [..tablesMappedToProperty];
+ }
+ else
+ {
+ overlappingTables.IntersectWith(tablesMappedToProperty);
+ if (overlappingTables.Count == 0)
{
- index.GetDatabaseName(StoreObjectIdentifier.Table(""), logger);
+ break;
}
}
}
+
+ if (overlappingTables == null)
+ {
+ if (firstPropertyTables == null)
+ {
+ logger.AllIndexPropertiesNotMappedToAnyTable(
+ index.DeclaringEntityType, index);
+ }
+ else
+ {
+ logger.IndexPropertiesBothMappedAndNotMappedToTable(
+ index.DeclaringEntityType, index, propertyNotMappedToAnyTable!.Name);
+ }
+ }
+ else if (overlappingTables.Count == 0)
+ {
+ Check.DebugAssert(firstPropertyTables != null);
+ Check.DebugAssert(lastPropertyTables != null);
+
+ logger.IndexPropertiesMappedToNonOverlappingTables(
+ index.DeclaringEntityType,
+ index,
+ firstPropertyTables.Value.Item1,
+ firstPropertyTables.Value.Item2.Select(t => (t.Name, t.Schema)).ToList(),
+ lastPropertyTables.Value.Item1,
+ lastPropertyTables.Value.Item2.Select(t => (t.Name, t.Schema)).ToList());
+ }
}
///
- protected override void ValidateData(IModel model, IDiagnosticsLogger logger)
+ protected override void ValidateData(
+ IEntityType entityType,
+ Dictionary identityMaps,
+ bool sensitiveDataLogged,
+ IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ if (entityType.IsMappedToJson() && entityType.GetSeedData().Any())
{
- if (entityType.IsMappedToJson() && entityType.GetSeedData().Any())
- {
- throw new InvalidOperationException(RelationalStrings.HasDataNotSupportedForEntitiesMappedToJson(entityType.DisplayName()));
- }
+ throw new InvalidOperationException(RelationalStrings.HasDataNotSupportedForEntitiesMappedToJson(entityType.DisplayName()));
+ }
- foreach (var navigation in entityType.GetNavigations()
- .Where(x => x.ForeignKey.IsOwnership && x.TargetEntityType.IsMappedToJson()))
+ foreach (var navigation in entityType.GetNavigations()
+ .Where(x => x.ForeignKey.IsOwnership && x.TargetEntityType.IsMappedToJson()))
+ {
+ if (entityType.GetSeedData().Any(x => x.TryGetValue(navigation.Name, out _)))
{
- if (entityType.GetSeedData().Any(x => x.TryGetValue(navigation.Name, out _)))
- {
- throw new InvalidOperationException(
- RelationalStrings.HasDataNotSupportedForEntitiesMappedToJson(entityType.DisplayName()));
- }
+ throw new InvalidOperationException(
+ RelationalStrings.HasDataNotSupportedForEntitiesMappedToJson(entityType.DisplayName()));
}
}
- base.ValidateData(model, logger);
+ base.ValidateData(entityType, identityMaps, sensitiveDataLogged, logger);
+ }
+
+ ///
+ protected override void ValidateTrigger(
+ ITrigger trigger,
+ IEntityType entityType,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidateTrigger(trigger, entityType, logger);
+
+ ValidateTriggerTableMapping(trigger, entityType, logger);
}
///
- /// Validates that the triggers are unambiguously mapped to exactly one table.
+ /// Validates that a trigger is mapped to the same table as its entity type.
///
- /// The model to validate.
+ /// The trigger to validate.
+ /// The entity type containing the trigger.
/// The logger to use.
- protected override void ValidateTriggers(
- IModel model,
+ protected virtual void ValidateTriggerTableMapping(
+ ITrigger trigger,
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes().Where(e => e.GetDeclaredTriggers().Any()))
+ var tableName = entityType.GetTableName();
+ var tableSchema = entityType.GetSchema();
+
+ if ((trigger.GetTableName() != tableName
+ || trigger.GetTableSchema() != tableSchema)
+ && entityType.GetMappingFragments(StoreObjectType.Table)
+ .All(f => trigger.GetTableName() != f.StoreObject.Name || trigger.GetTableSchema() != f.StoreObject.Schema))
{
- if (entityType.BaseType is not null
- && entityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy)
+ throw new InvalidOperationException(
+ RelationalStrings.TriggerWithMismatchedTable(
+ trigger.ModelName,
+ (trigger.GetTableName()!, trigger.GetTableSchema()).FormatTable(),
+ entityType.DisplayName(),
+ entityType.GetSchemaQualifiedTableName())
+ );
+ }
+ }
+
+ ///
+ /// Validates the container column type configuration for a specific entity type.
+ ///
+ /// The entity type to validate.
+ /// The logger to use.
+ protected virtual void ValidateContainerColumnType(
+ IEntityType entityType,
+ IDiagnosticsLogger logger)
+ {
+ if (entityType[RelationalAnnotationNames.ContainerColumnType] != null)
+ {
+ if (entityType.FindOwnership()?.PrincipalEntityType.IsOwned() == true)
{
- logger.TriggerOnNonRootTphEntity(entityType);
+ throw new InvalidOperationException(RelationalStrings.ContainerTypeOnNestedOwnedEntityType(entityType.DisplayName()));
}
- var tableName = entityType.GetTableName();
- var tableSchema = entityType.GetSchema();
-
- foreach (var trigger in entityType.GetDeclaredTriggers())
+ if (!entityType.IsOwned()
+ || entityType.GetContainerColumnName() == null)
{
- if ((trigger.GetTableName() != tableName
- || trigger.GetTableSchema() != tableSchema)
- && entityType.GetMappingFragments(StoreObjectType.Table)
- .All(f => trigger.GetTableName() != f.StoreObject.Name || trigger.GetTableSchema() != f.StoreObject.Schema))
- {
- throw new InvalidOperationException(
- RelationalStrings.TriggerWithMismatchedTable(
- trigger.ModelName,
- (trigger.GetTableName()!, trigger.GetTableSchema()).FormatTable(),
- entityType.DisplayName(),
- entityType.GetSchemaQualifiedTableName())
- );
- }
+ throw new InvalidOperationException(RelationalStrings.ContainerTypeOnNonContainer(entityType.DisplayName()));
}
}
}
///
- /// Validates the JSON entities.
+ /// Validates the JSON entities mapped to a table.
///
- /// The model to validate.
+ /// The entity types mapped to the table.
+ /// The table identifier.
/// The logger to use.
- protected virtual void ValidateJsonEntities(
- IModel model,
+ protected virtual void ValidateJsonTable(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ var jsonColumnMappings = new Dictionary>();
+ foreach (var entityType in mappedTypes)
{
- if (entityType[RelationalAnnotationNames.ContainerColumnType] != null)
+ if (entityType.FindOwnership() is not { } ownership
+ || !entityType.IsMappedToJson())
{
- if (entityType.FindOwnership()?.PrincipalEntityType.IsOwned() == true)
- {
- throw new InvalidOperationException(RelationalStrings.ContainerTypeOnNestedOwnedEntityType(entityType.DisplayName()));
- }
-
- if (!entityType.IsOwned()
- || entityType.GetContainerColumnName() == null)
- {
- throw new InvalidOperationException(RelationalStrings.ContainerTypeOnNonContainer(entityType.DisplayName()));
- }
+ continue;
}
- }
- var tables = BuildSharedTableEntityMap(model.GetEntityTypes());
- foreach (var (table, mappedTypes) in tables)
- {
- if (mappedTypes.All(x => !x.IsMappedToJson()))
+ ValidateJsonEntityNavigations(table, entityType);
+ ValidateJsonEntityKey(table, entityType);
+ ValidateJsonEntityProperties(table, entityType);
+ ValidateJsonEntitySingularMapping(table, entityType);
+
+ if (ownership.PrincipalEntityType.IsMappedToJson())
{
continue;
}
- foreach (var jsonEntityType in mappedTypes.Where(x => x.IsMappedToJson()))
- {
- var ownership = jsonEntityType.FindOwnership()!;
- var ownerTableOrViewName = ownership.PrincipalEntityType.GetViewName() ?? ownership.PrincipalEntityType.GetTableName();
- if (table.Name != ownerTableOrViewName)
- {
- throw new InvalidOperationException(
- RelationalStrings.JsonEntityMappedToDifferentTableOrViewThanOwner(
- jsonEntityType.DisplayName(), table.Name, ownership.PrincipalEntityType.DisplayName(), ownerTableOrViewName));
- }
+ var columnName = entityType.GetContainerColumnName();
+ Check.DebugAssert(columnName != null);
- var principalContainerColumn = ownership.PrincipalEntityType.GetContainerColumnName();
- if (principalContainerColumn != null
- && principalContainerColumn != jsonEntityType.GetContainerColumnName())
- {
- throw new InvalidOperationException(
- RelationalStrings.JsonEntityMappedToDifferentColumnThanOwner(
- jsonEntityType.DisplayName(), jsonEntityType.GetContainerColumnName(),
- ownership.PrincipalEntityType.DisplayName(), principalContainerColumn));
- }
+ if (!jsonColumnMappings.TryGetValue(columnName, out var sources))
+ {
+ sources = [];
+ jsonColumnMappings[columnName] = sources;
}
- var nonOwnedTypes = mappedTypes.Where(x => !x.IsOwned());
- var nonOwnedTypesCount = nonOwnedTypes.Count();
- if (nonOwnedTypesCount == 0)
- {
- var nonJsonType = mappedTypes.Where(x => !x.IsMappedToJson()).First();
+ sources.Add(entityType);
+ }
+ var nonOwnedTypes = mappedTypes.Where(x => !x.IsOwned());
+ var nonOwnedTypesCount = nonOwnedTypes.Count();
+ if (nonOwnedTypesCount == 0)
+ {
+ var nonJsonType = mappedTypes.FirstOrDefault(x => !x.IsMappedToJson());
+ if (nonJsonType != null)
+ {
// must be an owned collection (mapped to a separate table) that owns a JSON type
// Issue #28441
throw new InvalidOperationException(
RelationalStrings.JsonEntityOwnedByNonJsonOwnedType(
nonJsonType.DisplayName(), table.DisplayName()));
}
+ }
- var distinctRootTypes = nonOwnedTypes.Select(x => x.GetRootType()).Distinct().ToList();
- if (distinctRootTypes.Count > 1)
- {
- // Issue #28442
- throw new InvalidOperationException(
- RelationalStrings.JsonEntityWithTableSplittingIsNotSupported);
- }
-
- var rootType = distinctRootTypes[0];
- var jsonColumnMappings = new Dictionary>();
- foreach (var entityType in mappedTypes)
- {
- if (entityType.FindOwnership() is not { } ownership
- || ownership.PrincipalEntityType.IsMappedToJson())
- {
- continue;
- }
-
- var columnName = entityType.GetContainerColumnName();
- if (columnName != null)
- {
- if (!jsonColumnMappings.TryGetValue(columnName, out var sources))
- {
- sources = [];
- jsonColumnMappings[columnName] = sources;
- }
-
- sources.Add(entityType);
- }
- }
+ var distinctRootTypes = nonOwnedTypes.Select(x => x.GetRootType()).Distinct().ToList();
+ if (distinctRootTypes.Count > 1)
+ {
+ // Issue #28442
+ throw new InvalidOperationException(
+ RelationalStrings.JsonEntityWithTableSplittingIsNotSupported);
+ }
- foreach (var entityType in mappedTypes)
+ var rootType = distinctRootTypes[0];
+ foreach (var entityType in mappedTypes)
+ {
+ if (!entityType.IsMappedToJson())
{
- if (entityType.IsMappedToJson())
- {
- continue;
- }
-
ValidateNestedComplexTypes(jsonColumnMappings, entityType);
}
+ }
- var conflictingColumn = jsonColumnMappings.FirstOrDefault(kvp => kvp.Value.Count > 1);
- if (conflictingColumn.Key != null)
- {
- // TODO: handle JSON columns on views, issue #28584
- throw new InvalidOperationException(
- RelationalStrings.JsonEntityMultipleRootsMappedToTheSameJsonColumn(
- conflictingColumn.Key, table.Name));
- }
-
- ValidateJsonEntityRoot(table, rootType);
-
- foreach (var jsonEntityType in mappedTypes.Where(x => x.IsMappedToJson()))
- {
- ValidateJsonEntityNavigations(table, jsonEntityType);
- ValidateJsonEntityKey(table, jsonEntityType);
- ValidateJsonEntityProperties(table, jsonEntityType);
- }
+ var conflictingColumn = jsonColumnMappings.FirstOrDefault(kvp => kvp.Value.Count > 1);
+ if (conflictingColumn.Key != null)
+ {
+ // TODO: handle JSON columns on views, issue #28584
+ throw new InvalidOperationException(
+ RelationalStrings.JsonEntityMultipleRootsMappedToTheSameJsonColumn(
+ conflictingColumn.Key, table.Name));
}
- // TODO: support this for raw SQL and function mappings in #19970 and #21627 and remove the check
- ValidateJsonEntitiesNotMappedToTableOrView(model.GetEntityTypes());
- ValidateJsonViews(model.GetEntityTypes().Where(t => t.IsMappedToJson()));
+ ValidateJsonEntityRoot(table, rootType);
static void ValidateNestedComplexTypes(Dictionary> jsonColumnMappings, ITypeBase structuralType)
{
@@ -2800,41 +2966,80 @@ static void ValidateNestedComplexTypes(Dictionary> jsonC
}
}
- private void ValidateJsonEntitiesNotMappedToTableOrView(IEnumerable entityTypes)
+ ///
+ /// Validates that an entity type owning a JSON entity is mapped to a table or view.
+ ///
+ /// The entity type to validate.
+ /// The logger to use.
+ protected virtual void ValidateJsonEntityOwnerMappedToTableOrView(
+ IEntityType entityType,
+ IDiagnosticsLogger logger)
{
- var entitiesNotMappedToTableOrView = entityTypes.Where(x => !x.IsMappedToJson()
- && x.GetSchemaQualifiedTableName() == null
- && x.GetSchemaQualifiedViewName() == null);
+ if (entityType.GetTableName() != null
+ || entityType.GetViewName() != null
+ || entityType.IsMappedToJson())
+ {
+ return;
+ }
- foreach (var entityNotMappedToTableOrView in entitiesNotMappedToTableOrView)
+ if (entityType.GetDeclaredNavigations()
+ .Any(x => x.ForeignKey.IsOwnership && x.TargetEntityType.IsMappedToJson()))
{
- if (entityNotMappedToTableOrView.GetDeclaredNavigations()
- .Any(x => x.ForeignKey.IsOwnership && x.TargetEntityType.IsMappedToJson()))
- {
- throw new InvalidOperationException(
- RelationalStrings.JsonEntityWithOwnerNotMappedToTableOrView(
- entityNotMappedToTableOrView.DisplayName()));
- }
+ throw new InvalidOperationException(
+ RelationalStrings.JsonEntityWithOwnerNotMappedToTableOrView(
+ entityType.DisplayName()));
}
}
- private void ValidateJsonViews(IEnumerable entityTypes)
+ ///
+ /// Validates the singular (non-collection) mapping aspects of an entity mapped to a JSON column.
+ ///
+ /// The table identifier.
+ /// The entity type to validate.
+ protected virtual void ValidateJsonEntitySingularMapping(
+ in StoreObjectIdentifier table,
+ IEntityType jsonEntityType)
{
- foreach (var jsonEntityType in entityTypes)
+ var ownership = jsonEntityType.FindOwnership()!;
+ var ownerTableOrViewName = ownership.PrincipalEntityType.GetViewName() ?? ownership.PrincipalEntityType.GetTableName();
+ if (table.Name != ownerTableOrViewName)
{
- var viewName = jsonEntityType.GetViewName();
- if (viewName == null)
- {
- continue;
- }
+ throw new InvalidOperationException(
+ RelationalStrings.JsonEntityMappedToDifferentTableOrViewThanOwner(
+ jsonEntityType.DisplayName(), table.Name, ownership.PrincipalEntityType.DisplayName(), ownerTableOrViewName));
+ }
+ var principalContainerColumn = ownership.PrincipalEntityType.GetContainerColumnName();
+ if (principalContainerColumn != null
+ && principalContainerColumn != jsonEntityType.GetContainerColumnName())
+ {
+ throw new InvalidOperationException(
+ RelationalStrings.JsonEntityMappedToDifferentColumnThanOwner(
+ jsonEntityType.DisplayName(), jsonEntityType.GetContainerColumnName(),
+ ownership.PrincipalEntityType.DisplayName(), principalContainerColumn));
+ }
+ }
+
+ ///
+ /// Validates the JSON entities mapped to a view.
+ ///
+ /// The entity types mapped to the view.
+ /// The view identifier.
+ /// The logger to use.
+ protected virtual void ValidateJsonView(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier view,
+ IDiagnosticsLogger logger)
+ {
+ foreach (var jsonEntityType in mappedTypes.Where(x => x.IsMappedToJson()))
+ {
var ownership = jsonEntityType.FindOwnership()!;
var ownerTableOrViewName = ownership.PrincipalEntityType.GetViewName() ?? ownership.PrincipalEntityType.GetTableName();
- if (viewName != ownerTableOrViewName)
+ if (view.Name != ownerTableOrViewName)
{
throw new InvalidOperationException(
RelationalStrings.JsonEntityMappedToDifferentTableOrViewThanOwner(
- jsonEntityType.DisplayName(), viewName, ownership.PrincipalEntityType.DisplayName(), ownerTableOrViewName));
+ jsonEntityType.DisplayName(), view.Name, ownership.PrincipalEntityType.DisplayName(), ownerTableOrViewName));
}
}
}
@@ -2842,16 +3047,16 @@ private void ValidateJsonViews(IEnumerable entityTypes)
///
/// Validates the root entity mapped to a JSON column.
///
- /// The store object.
+ /// The table identifier.
/// The entity type to validate.
protected virtual void ValidateJsonEntityRoot(
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IEntityType rootType)
{
var mappingStrategy = rootType.GetMappingStrategy();
if (mappingStrategy != null && mappingStrategy != RelationalAnnotationNames.TphMappingStrategy)
{
- // issue #28443
+ // TODO: issue #37445
throw new InvalidOperationException(
RelationalStrings.JsonEntityWithNonTphInheritanceOnOwner(rootType.DisplayName()));
}
@@ -2860,10 +3065,10 @@ protected virtual void ValidateJsonEntityRoot(
///
/// Validates navigations of the entity mapped to a JSON column.
///
- /// The store object.
+ /// The table identifier.
/// The entity type to validate.
protected virtual void ValidateJsonEntityNavigations(
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IEntityType jsonEntityType)
{
var ownership = jsonEntityType.FindOwnership()!;
@@ -2875,7 +3080,7 @@ protected virtual void ValidateJsonEntityNavigations(
throw new InvalidOperationException(
RelationalStrings.JsonEntityOwnedByNonJsonOwnedType(
ownership.PrincipalEntityType.DisplayName(),
- storeObject.DisplayName()));
+ table.DisplayName()));
}
foreach (var navigation in jsonEntityType.GetDeclaredNavigations())
@@ -2892,10 +3097,10 @@ protected virtual void ValidateJsonEntityNavigations(
///
/// Validate the key of entity mapped to a JSON column.
///
- /// The store object.
+ /// The table identifier.
/// The entity type containing the key to validate.
protected virtual void ValidateJsonEntityKey(
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IEntityType jsonEntityType)
{
var primaryKeyProperties = jsonEntityType.FindPrimaryKey()!.Properties;
@@ -2943,13 +3148,13 @@ protected virtual void ValidateJsonEntityKey(
///
/// Validate the properties of entity mapped to a JSON column.
///
- /// The store object.
+ /// The table identifier.
/// The entity type containing the properties to validate.
protected virtual void ValidateJsonEntityProperties(
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IEntityType jsonEntityType)
{
- var jsonPropertyNames = ValidateJsonProperties((IConventionTypeBase)jsonEntityType);
+ var jsonPropertyNames = ValidateJsonProperties(jsonEntityType);
foreach (var navigation in jsonEntityType.GetNavigations())
{
if (!navigation.TargetEntityType.IsMappedToJson()
@@ -2966,7 +3171,7 @@ protected virtual void ValidateJsonEntityProperties(
}
}
- private static Dictionary ValidateJsonProperties(IConventionTypeBase typeBase)
+ private static Dictionary ValidateJsonProperties(ITypeBase typeBase)
{
var jsonPropertyNames = new Dictionary();
foreach (var property in typeBase.GetProperties())
@@ -3045,8 +3250,8 @@ private static void CheckUniqueness(
/// The property.
protected override void ThrowPropertyNotMappedException(
string propertyType,
- IConventionTypeBase typeBase,
- IConventionProperty unmappedProperty)
+ ITypeBase typeBase,
+ IProperty unmappedProperty)
{
var storeType = unmappedProperty.GetColumnType();
if (storeType != null)
diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalIndexExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalIndexExtensions.cs
index 0c3b14c996a..705f8dd2028 100644
--- a/src/EFCore.Relational/Metadata/Internal/RelationalIndexExtensions.cs
+++ b/src/EFCore.Relational/Metadata/Internal/RelationalIndexExtensions.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 System.Text;
-
namespace Microsoft.EntityFrameworkCore.Metadata.Internal;
///
@@ -124,178 +122,4 @@ public static bool AreCompatible(
return 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 static string? GetDatabaseName(
- this IReadOnlyIndex index,
- in StoreObjectIdentifier storeObject,
- IDiagnosticsLogger? logger)
- {
- if (storeObject.StoreObjectType != StoreObjectType.Table)
- {
- return null;
- }
-
- var defaultName = index.GetDefaultDatabaseName(storeObject, logger);
- var annotation = index.FindAnnotation(RelationalAnnotationNames.Name);
- return annotation != null && defaultName != null
- ? (string?)annotation.Value
- : defaultName != null
- ? index.Name ?? defaultName
- : defaultName;
- }
-
- ///
- /// 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 string? GetDefaultDatabaseName(
- this IReadOnlyIndex index,
- in StoreObjectIdentifier storeObject,
- IDiagnosticsLogger? logger)
- {
- if (storeObject.StoreObjectType != StoreObjectType.Table)
- {
- return null;
- }
-
- var columnNames = index.Properties.GetColumnNames(storeObject);
- if (columnNames == null)
- {
- if (logger != null
- && ((IConventionIndex)index).GetConfigurationSource() != ConfigurationSource.Convention)
- {
- IReadOnlyProperty? propertyNotMappedToAnyTable = null;
- (string, List)? firstPropertyTables = null;
- (string, List)? lastPropertyTables = null;
- HashSet? overlappingTables = null;
- foreach (var property in index.Properties)
- {
- var tablesMappedToProperty = property.GetMappedStoreObjects(storeObject.StoreObjectType).ToList();
- if (tablesMappedToProperty.Count == 0)
- {
- propertyNotMappedToAnyTable = property;
- overlappingTables = null;
-
- if (firstPropertyTables != null)
- {
- // Property is not mapped but we already found a property that is mapped.
- break;
- }
-
- continue;
- }
-
- if (firstPropertyTables == null)
- {
- firstPropertyTables = (property.Name, tablesMappedToProperty);
- }
- else
- {
- lastPropertyTables = (property.Name, tablesMappedToProperty);
- }
-
- if (propertyNotMappedToAnyTable != null)
- {
- // Property is mapped but we already found a property that is not mapped.
- overlappingTables = null;
- break;
- }
-
- if (overlappingTables == null)
- {
- overlappingTables = [..tablesMappedToProperty];
- }
- else
- {
- overlappingTables.IntersectWith(tablesMappedToProperty);
- if (overlappingTables.Count == 0)
- {
- break;
- }
- }
- }
-
- if (overlappingTables == null)
- {
- if (firstPropertyTables == null)
- {
- logger.AllIndexPropertiesNotToMappedToAnyTable(
- (IEntityType)index.DeclaringEntityType,
- (IIndex)index);
- }
- else
- {
- logger.IndexPropertiesBothMappedAndNotMappedToTable(
- (IEntityType)index.DeclaringEntityType,
- (IIndex)index,
- propertyNotMappedToAnyTable!.Name);
- }
- }
- else if (overlappingTables.Count == 0)
- {
- Check.DebugAssert(firstPropertyTables != null);
- Check.DebugAssert(lastPropertyTables != null);
-
- logger.IndexPropertiesMappedToNonOverlappingTables(
- (IEntityType)index.DeclaringEntityType,
- (IIndex)index,
- firstPropertyTables.Value.Item1,
- firstPropertyTables.Value.Item2.Select(t => (t.Name, t.Schema)).ToList(),
- lastPropertyTables.Value.Item1,
- lastPropertyTables.Value.Item2.Select(t => (t.Name, t.Schema)).ToList());
- }
- }
-
- return null;
- }
-
- var rootIndex = index;
-
- // Limit traversal to avoid getting stuck in a cycle (validation will throw for these later)
- // Using a hashset is detrimental to the perf when there are no cycles
- for (var i = 0; i < RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++)
- {
- IReadOnlyIndex? linkedIndex = null;
- foreach (var otherIndex in rootIndex.DeclaringEntityType
- .FindRowInternalForeignKeys(storeObject)
- .SelectMany(fk => fk.PrincipalEntityType.GetIndexes()))
- {
- var otherColumnNames = otherIndex.Properties.GetColumnNames(storeObject);
- if ((otherColumnNames != null)
- && otherColumnNames.SequenceEqual(columnNames))
- {
- linkedIndex = otherIndex;
- break;
- }
- }
-
- if (linkedIndex == null)
- {
- break;
- }
-
- rootIndex = linkedIndex;
- }
-
- if (rootIndex != index)
- {
- return rootIndex.GetDatabaseName(storeObject);
- }
-
- var baseName = new StringBuilder()
- .Append("IX_")
- .Append(storeObject.Name)
- .Append('_')
- .AppendJoin(columnNames, "_")
- .ToString();
-
- return Uniquifier.Truncate(baseName, index.DeclaringEntityType.Model.GetMaxIdentifierLength());
- }
}
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
index fd7dfc953ba..0f142263862 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
+++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs
@@ -53,12 +53,6 @@ public static string BadSequenceType
public static string CannotChangeWhenOpen
=> GetString("CannotChangeWhenOpen");
- ///
- /// Join expressions have no aliases; set the alias on the enclosed table expression.
- ///
- public static string CannotSetAliasOnJoin
- => GetString("CannotSetAliasOnJoin");
-
///
/// Cannot compare complex type '{jsonComplexType}', which is mapped to JSON, to complex type '{nonJsonComplexType}', which is not.
///
@@ -68,7 +62,13 @@ public static string CannotCompareJsonComplexTypeToNonJson(object? jsonComplexTy
jsonComplexType, nonJsonComplexType);
///
- /// The query contained a new array expression containing non-constant elements, which could not be translated: '{newArrayExpression}'.
+ /// Aliases cannot be set on join expressions; set the alias on the enclosed table expression.
+ ///
+ public static string CannotSetAliasOnJoin
+ => GetString("CannotSetAliasOnJoin");
+
+ ///
+ /// The query contains a new array expression with non-constant elements that cannot be translated: '{newArrayExpression}'.
///
public static string CannotTranslateNonConstantNewArrayExpression(object? newArrayExpression)
=> string.Format(
@@ -82,7 +82,7 @@ public static string ClientGroupByNotSupported
=> GetString("ClientGroupByNotSupported");
///
- /// The function '{function}' has a custom translation. Compiled model can't be generated, because custom function translations are not supported.
+ /// The function '{function}' has a custom translation. A compiled model cannot be generated because custom function translations are not supported.
///
public static string CompiledModelFunctionTranslation(object? function)
=> string.Format(
@@ -152,7 +152,7 @@ public static string ConflictingAmbientTransaction
=> GetString("ConflictingAmbientTransaction");
///
- /// {conflictingConfiguration} cannot be set for '{property}' at the same time as {existingConfiguration}. Remove one of these configurations.
+ /// '{conflictingConfiguration}' cannot be set for '{property}' at the same time as '{existingConfiguration}'. Remove one of these configurations.
///
public static string ConflictingColumnServerGeneration(object? conflictingConfiguration, object? property, object? existingConfiguration)
=> string.Format(
@@ -278,7 +278,7 @@ public static string DataOperationNoTable(object? table)
table);
///
- /// The provided DbFunction expression '{expression}' is invalid. The expression must be a lambda expression containing a single method call to the target static method. Default values can be provided as arguments if required, e.g. '() => SomeClass.SomeMethod(null, 0)'
+ /// The provided DbFunction expression '{expression}' is invalid. The expression must be a lambda expression containing a single method call to the target static method. Default values can be provided as arguments if required, e.g. '() => SomeClass.SomeMethod(null, 0)'.
///
public static string DbFunctionExpressionIsNotMethodCall(object? expression)
=> string.Format(
@@ -524,7 +524,7 @@ public static string DuplicateColumnNamePrecisionMismatch(object? entityType1, o
entityType1, property1, entityType2, property2, columnName, table, precision1, precision2);
///
- /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use differing provider types ('{type1}' and '{type2}').
+ /// '{entityType1}.{property1}' and '{entityType2}.{property2}' are both mapped to column '{columnName}' in '{table}', but are configured to use different provider types ('{type1}' and '{type2}').
///
public static string DuplicateColumnNameProviderTypeMismatch(object? entityType1, object? property1, object? entityType2, object? property2, object? columnName, object? table, object? type1, object? type2)
=> string.Format(
@@ -812,7 +812,7 @@ public static string ExecuteDeleteOnTableSplitting(object? tableName)
tableName);
///
- /// The operation '{operation}' is being applied on entity type '{entityType}', which uses entity splitting. 'ExecuteDelete'/'ExecuteUpdate' operations on entity types using entity splitting is not supported.
+ /// The operation '{operation}' is being applied on entity type '{entityType}', which uses entity splitting. 'ExecuteDelete'/'ExecuteUpdate' operations on entity types using entity splitting are not supported.
///
public static string ExecuteOperationOnEntitySplitting(object? operation, object? entityType)
=> string.Format(
@@ -844,7 +844,7 @@ public static string ExecuteOperationOnTPC(object? operation, object? entityType
operation, entityType);
///
- /// The operation '{operation}' is being applied on entity type '{entityType}', which is using the TPT mapping strategy. 'ExecuteDelete'/'ExecuteUpdate' operations on hierarchies mapped as TPT is not supported.
+ /// The operation '{operation}' is being applied on entity type '{entityType}', which is using the TPT mapping strategy. 'ExecuteDelete'/'ExecuteUpdate' operations on hierarchies mapped as TPT are not supported.
///
public static string ExecuteOperationOnTPT(object? operation, object? entityType)
=> string.Format(
@@ -859,18 +859,18 @@ public static string ExecuteOperationWithUnsupportedOperatorInSqlGeneration(obje
GetString("ExecuteOperationWithUnsupportedOperatorInSqlGeneration", nameof(operation)),
operation);
- ///
- /// 'ExecuteUpdate' cannot currently set a property in a JSON column to a regular, non-JSON column; see https://github.com/dotnet/efcore/issues/36688.
- ///
- public static string ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn
- => GetString("ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn");
-
///
/// 'ExecuteUpdate' cannot currently set a property in a JSON column to arbitrary expressions; only constants, parameters and other JSON properties are supported; see https://github.com/dotnet/efcore/issues/36688.
///
public static string ExecuteUpdateCannotSetJsonPropertyToArbitraryExpression
=> GetString("ExecuteUpdateCannotSetJsonPropertyToArbitraryExpression");
+ ///
+ /// 'ExecuteUpdate' cannot currently set a property in a JSON column to a regular, non-JSON column; see https://github.com/dotnet/efcore/issues/36688.
+ ///
+ public static string ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn
+ => GetString("ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn");
+
///
/// 'ExecuteUpdate' or 'ExecuteDelete' was called on entity type '{entityType}', but that entity type is not mapped to a table.
///
@@ -896,7 +896,7 @@ public static string ExecuteUpdateSubqueryNotSupportedOverComplexTypes(object? c
complexType);
///
- /// Can't use explicitly named default constraints with TPC inheritance or entity splitting. Constraint name: '{explicitDefaultConstraintName}'.
+ /// Explicitly named default constraints cannot be used with TPC inheritance or entity splitting. Constraint name: '{explicitDefaultConstraintName}'.
///
public static string ExplicitDefaultConstraintNamesNotSupportedForTpc(object? explicitDefaultConstraintName)
=> string.Format(
@@ -926,7 +926,7 @@ public static string FunctionOverrideMismatch(object? propertySpecification, obj
propertySpecification, function);
///
- /// Can't use 'HasData' for entity type '{entity}'. 'HasData' is not supported for entities mapped to JSON.
+ /// 'HasData' cannot be used for entity type '{entity}'. 'HasData' is not supported for entities mapped to JSON.
///
public static string HasDataNotSupportedForEntitiesMappedToJson(object? entity)
=> string.Format(
@@ -934,7 +934,7 @@ public static string HasDataNotSupportedForEntitiesMappedToJson(object? entity)
entity);
///
- /// Named default constraints can't be used with TPC or entity splitting if they result in non-unique constraint name. Constraint name: '{constraintNameCandidate}'.
+ /// Named default constraints cannot be used with TPC or entity splitting if they result in non-unique constraint names. Constraint name: '{constraintNameCandidate}'.
///
public static string ImplicitDefaultNamesNotSupportedForTpcWhenNamesClash(object? constraintNameCandidate)
=> string.Format(
@@ -942,19 +942,19 @@ public static string ImplicitDefaultNamesNotSupportedForTpcWhenNamesClash(object
constraintNameCandidate);
///
- /// The complex types '{complexType1}' and '{complexType2}' are being compared, but the latter is lacking property '{property}' of the former.
+ /// The complex types '{complexType1}' and '{complexType2}' are being assigned, but the latter is lacking property '{property}' of the former.
///
- public static string IncompatibleComplexTypesInComparison(object? complexType1, object? complexType2, object? property)
+ public static string IncompatibleComplexTypesInAssignment(object? complexType1, object? complexType2, object? property)
=> string.Format(
- GetString("IncompatibleComplexTypesInComparison", nameof(complexType1), nameof(complexType2), nameof(property)),
+ GetString("IncompatibleComplexTypesInAssignment", nameof(complexType1), nameof(complexType2), nameof(property)),
complexType1, complexType2, property);
///
- /// The complex types '{complexType1}' and '{complexType2}' are being assigned, but the latter is lacking property '{property}' of the former.
+ /// The complex types '{complexType1}' and '{complexType2}' are being compared, but the latter is lacking property '{property}' of the former.
///
- public static string IncompatibleComplexTypesInAssignment(object? complexType1, object? complexType2, object? property)
+ public static string IncompatibleComplexTypesInComparison(object? complexType1, object? complexType2, object? property)
=> string.Format(
- GetString("IncompatibleComplexTypesInAssignment", nameof(complexType1), nameof(complexType2), nameof(property)),
+ GetString("IncompatibleComplexTypesInComparison", nameof(complexType1), nameof(complexType2), nameof(property)),
complexType1, complexType2, property);
///
@@ -1170,7 +1170,7 @@ public static string InvalidValueInSetProperty(object? valueExpression)
valueExpression);
///
- /// Can't navigate from JSON-mapped entity '{jsonEntity}' to its parent entity '{parentEntity}' using navigation '{navigation}'. Entities mapped to JSON can only navigate to their children.
+ /// Navigation from JSON-mapped entity '{jsonEntity}' to its parent entity '{parentEntity}' using navigation '{navigation}' is not supported. Entities mapped to JSON can only navigate to their children.
///
public static string JsonCantNavigateToParentEntity(object? jsonEntity, object? parentEntity, object? navigation)
=> string.Format(
@@ -1200,7 +1200,7 @@ public static string JsonEntityMappedToDifferentTableOrViewThanOwner(object? jso
jsonType, tableOrViewName, ownerType, ownerTableOrViewName);
///
- /// 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.
+ /// JSON entity '{jsonEntity}' is missing key information. This is not allowed for tracking queries since Entity Framework cannot correctly build identity for this entity object.
///
public static string JsonEntityMissingKeyInformation(object? jsonEntity)
=> string.Format(
@@ -1293,6 +1293,12 @@ public static string JsonErrorExtractingJsonProperty(object? entityType, object?
GetString("JsonErrorExtractingJsonProperty", nameof(entityType), nameof(propertyName)),
entityType, propertyName);
+ ///
+ /// ExecuteUpdate over JSON columns is not supported when the column is mapped as an owned entity. Map the column as a complex type instead.
+ ///
+ public static string JsonExecuteUpdateNotSupportedWithOwnedEntities
+ => GetString("JsonExecuteUpdateNotSupportedWithOwnedEntities");
+
///
/// This node should be handled by provider-specific SQL generator.
///
@@ -1307,6 +1313,12 @@ public static string JsonObjectWithMultiplePropertiesMappedToSameJsonProperty(ob
GetString("JsonObjectWithMultiplePropertiesMappedToSameJsonProperty", nameof(property1), nameof(property2), nameof(type), nameof(jsonPropertyName)),
property1, property2, type, jsonPropertyName);
+ ///
+ /// The provider in use does not support partial updates with ExecuteUpdate within JSON columns.
+ ///
+ public static string JsonPartialExecuteUpdateNotSupportedByProvider
+ => GetString("JsonPartialExecuteUpdateNotSupportedByProvider");
+
///
/// Using a parameter to access the element of a JSON collection '{entityTypeName}' is not supported when using '{asNoTrackingWithIdentityResolution}'. Use a constant, or project the entire JSON entity collection instead.
///
@@ -1453,14 +1465,6 @@ public static string MissingParameterValue(object? parameter)
public static string MissingResultSetWhenSaving
=> GetString("MissingResultSetWhenSaving");
- ///
- /// Entity type '{entityType}' is mapped to multiple columns with name '{columnName}', and one of them is configured as a JSON column. Assign different names to the columns.
- ///
- public static string MultipleColumnsWithSameJsonContainerName(object? entityType, object? columnName)
- => string.Format(
- GetString("MultipleColumnsWithSameJsonContainerName", nameof(entityType), nameof(columnName)),
- entityType, columnName);
-
///
/// Commands cannot be added to a completed 'ModificationCommandBatch'.
///
@@ -1489,6 +1493,14 @@ public static string ModificationCommandInvalidEntityStateSensitive(object? enti
GetString("ModificationCommandInvalidEntityStateSensitive", nameof(entityType), nameof(keyValues), nameof(entityState)),
entityType, keyValues, entityState);
+ ///
+ /// Entity type '{entityType}' is mapped to multiple columns with name '{columnName}', and one of them is configured as a JSON column. Assign different names to the columns.
+ ///
+ public static string MultipleColumnsWithSameJsonContainerName(object? entityType, object? columnName)
+ => string.Format(
+ GetString("MultipleColumnsWithSameJsonContainerName", nameof(entityType), nameof(columnName)),
+ entityType, columnName);
+
///
/// Multiple relational database provider configurations found. A context can only be configured to use a single database provider.
///
@@ -1526,7 +1538,7 @@ public static string NestedCollectionsNotSupported(object? propertyType, object?
propertyType, type, property);
///
- /// Complex property '{complexProperty}' is mapped to JSON but its containing type '{containingType}' is not. Map the root complex type to JSON. See https://github.com/dotnet/efcore/issues/36558
+ /// Complex property '{complexProperty}' is mapped to JSON but its containing type '{containingType}' is not. Map the root complex type to JSON. See https://github.com/dotnet/efcore/issues/36558.
///
public static string NestedComplexPropertyJsonWithTableSharing(object? complexProperty, object? containingType)
=> string.Format(
@@ -1682,7 +1694,7 @@ public static string OnlyConstantsSupportedInInlineCollectionQueryRoots
=> GetString("OnlyConstantsSupportedInInlineCollectionQueryRoots");
///
- /// Entity type '{entityType}' is an optional dependent using table sharing and containing other dependents without any required non shared property to identify whether the entity exists. If all nullable properties contain a null value in database then an object instance won't be created in the query causing nested dependent's values to be lost. Add a required property to create instances with null values for other properties or mark the incoming navigation as required to always create an instance.
+ /// Entity type '{entityType}' is an optional dependent using table sharing and containing other dependents without any required non shared property to identify whether the entity exists. If all nullable properties contain a 'null' value in database then an object instance won't be created in the query causing nested dependent's values to be lost. Add a required property to create instances with 'null' values for other properties or mark the incoming navigation as required to always create an instance.
///
public static string OptionalDependentWithDependentWithoutIdentifyingProperty(object? entityType)
=> string.Format(
@@ -1697,18 +1709,6 @@ public static string ParameterNotObjectArray(object? parameter)
GetString("ParameterNotObjectArray", nameof(parameter)),
parameter);
- ///
- /// The provider in use does not support partial updates with ExecuteUpdate within JSON columns.
- ///
- public static string JsonPartialExecuteUpdateNotSupportedByProvider
- => GetString("JsonPartialExecuteUpdateNotSupportedByProvider");
-
- ///
- /// ExecuteUpdate over JSON columns is not supported when the column is mapped as an owned entity. Map the column as a complex type instead.
- ///
- public static string JsonExecuteUpdateNotSupportedWithOwnedEntities
- => GetString("JsonExecuteUpdateNotSupportedWithOwnedEntities");
-
///
/// This connection was used with an ambient transaction. The original ambient transaction needs to be completed before this connection can be used outside of it.
///
@@ -2449,7 +2449,7 @@ public static EventDefinition LogAcquiringMigrationLock(IDiagnosticsLogger logge
}
///
- /// An ambient transaction has been detected, but the current provider does not support ambient transactions. See https://go.microsoft.com/fwlink/?LinkId=800142
+ /// An ambient transaction has been detected, but the current provider does not support ambient transactions. See https://go.microsoft.com/fwlink/?LinkId=800142.
///
public static EventDefinition LogAmbientTransaction(IDiagnosticsLogger logger)
{
@@ -3249,7 +3249,7 @@ public static EventDefinition LogDisposingTransaction(IDiagnosticsLogger logger)
}
///
- /// The configured column orders for the table '{table}' contains duplicates. Ensure the specified column order values are distinct. Conflicting columns: {columns}
+ /// The configured column orders for the table '{table}' contains duplicates. Ensure the specified column order values are distinct. Conflicting columns: {columns}.
///
public static EventDefinition LogDuplicateColumnOrders(IDiagnosticsLogger logger)
{
@@ -3593,7 +3593,7 @@ public static EventDefinition LogMigrating(IDiagnosticsLogger lo
}
///
- /// A [Migration] attribute isn't specified on the '{class}' class.
+ /// A [Migration] attribute is not specified on the '{class}' class.
///
public static EventDefinition LogMigrationAttributeMissingWarning(IDiagnosticsLogger logger)
{
@@ -3670,23 +3670,23 @@ public static EventDefinition LogMultipleCollectionIncludeWarning(IDiagnosticsLo
///
/// The index named '{indexName}' on the entity type '{entityType}' specifies properties {indexProperties}, but none of these properties are mapped to a column in any table. This index will not be created in the database.
///
- public static EventDefinition LogNamedIndexAllPropertiesNotToMappedToAnyTable(IDiagnosticsLogger logger)
+ public static EventDefinition LogNamedIndexAllPropertiesNotMappedToAnyTable(IDiagnosticsLogger logger)
{
- var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogNamedIndexAllPropertiesNotToMappedToAnyTable;
+ var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogNamedIndexAllPropertiesNotMappedToAnyTable;
if (definition == null)
{
definition = NonCapturingLazyInitializer.EnsureInitialized(
- ref ((RelationalLoggingDefinitions)logger.Definitions).LogNamedIndexAllPropertiesNotToMappedToAnyTable,
+ ref ((RelationalLoggingDefinitions)logger.Definitions).LogNamedIndexAllPropertiesNotMappedToAnyTable,
logger,
static logger => new EventDefinition(
logger.Options,
- RelationalEventId.AllIndexPropertiesNotToMappedToAnyTable,
+ RelationalEventId.AllIndexPropertiesNotMappedToAnyTable,
LogLevel.Warning,
- "RelationalEventId.AllIndexPropertiesNotToMappedToAnyTable",
+ "RelationalEventId.AllIndexPropertiesNotMappedToAnyTable",
level => LoggerMessage.Define(
level,
- RelationalEventId.AllIndexPropertiesNotToMappedToAnyTable,
- _resourceManager.GetString("LogNamedIndexAllPropertiesNotToMappedToAnyTable")!)));
+ RelationalEventId.AllIndexPropertiesNotMappedToAnyTable,
+ _resourceManager.GetString("LogNamedIndexAllPropertiesNotMappedToAnyTable")!)));
}
return (EventDefinition)definition;
@@ -3965,7 +3965,7 @@ public static EventDefinition LogOptionalDependentWithAllNullPro
}
///
- /// The entity type '{entityType}' is an optional dependent using table sharing without any required non shared property that could be used to identify whether the entity exists. If all nullable properties contain a null value in database then an object instance won't be created in the query. Add a required property to create instances with null values for other properties or mark the incoming navigation as required to always create an instance.
+ /// The entity type '{entityType}' is an optional dependent using table sharing without any required non shared column that could be used to identify whether the entity exists. If all nullable properties contain a 'null' value in database then an object instance won't be created in the query. Add a required property to create instances with 'null' values for other properties or mark the incoming navigation as required to always create an instance.
///
public static EventDefinition LogOptionalDependentWithoutIdentifyingProperty(IDiagnosticsLogger logger)
{
@@ -4290,7 +4290,7 @@ public static EventDefinition LogTransactionError(IDiagnosticsLogger logger)
}
///
- /// Can't configure a trigger on entity type '{entityType}', which is in a TPH hierarchy and isn't the root. Configure the trigger on the TPH root entity type '{rootEntityType}' instead.
+ /// A trigger cannot be configured on entity type '{entityType}', which is in a TPH hierarchy and is not the root. Configure the trigger on the TPH root entity type '{rootEntityType}' instead.
///
public static EventDefinition LogTriggerOnNonRootTphEntity(IDiagnosticsLogger logger)
{
@@ -4342,23 +4342,23 @@ public static EventDefinition LogUnexpectedTrailingResultSetWhenSaving(IDiagnost
///
/// The unnamed index on the entity type '{entityType}' specifies properties {indexProperties}, but none of these properties are mapped to a column in any table. This index will not be created in the database.
///
- public static EventDefinition LogUnnamedIndexAllPropertiesNotToMappedToAnyTable(IDiagnosticsLogger logger)
+ public static EventDefinition LogUnnamedIndexAllPropertiesNotMappedToAnyTable(IDiagnosticsLogger logger)
{
- var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogUnnamedIndexAllPropertiesNotToMappedToAnyTable;
+ var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogUnnamedIndexAllPropertiesNotMappedToAnyTable;
if (definition == null)
{
definition = NonCapturingLazyInitializer.EnsureInitialized(
- ref ((RelationalLoggingDefinitions)logger.Definitions).LogUnnamedIndexAllPropertiesNotToMappedToAnyTable,
+ ref ((RelationalLoggingDefinitions)logger.Definitions).LogUnnamedIndexAllPropertiesNotMappedToAnyTable,
logger,
static logger => new EventDefinition(
logger.Options,
- RelationalEventId.AllIndexPropertiesNotToMappedToAnyTable,
+ RelationalEventId.AllIndexPropertiesNotMappedToAnyTable,
LogLevel.Warning,
- "RelationalEventId.AllIndexPropertiesNotToMappedToAnyTable",
+ "RelationalEventId.AllIndexPropertiesNotMappedToAnyTable",
level => LoggerMessage.Define(
level,
- RelationalEventId.AllIndexPropertiesNotToMappedToAnyTable,
- _resourceManager.GetString("LogUnnamedIndexAllPropertiesNotToMappedToAnyTable")!)));
+ RelationalEventId.AllIndexPropertiesNotMappedToAnyTable,
+ _resourceManager.GetString("LogUnnamedIndexAllPropertiesNotMappedToAnyTable")!)));
}
return (EventDefinition)definition;
diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx
index 15b9aec9e02..446c394bd2b 100644
--- a/src/EFCore.Relational/Properties/RelationalStrings.resx
+++ b/src/EFCore.Relational/Properties/RelationalStrings.resx
@@ -130,12 +130,12 @@
The instance of DbConnection is currently in use. The connection can only be changed when the existing connection is not being used.
-
- Aliases cannot be set on join expressions; set the alias on the enclosed table expression.
-
Cannot compare complex type '{jsonComplexType}', which is mapped to JSON, to complex type '{nonJsonComplexType}', which is not.
+
+ Aliases cannot be set on join expressions; set the alias on the enclosed table expression.
+
The query contains a new array expression with non-constant elements that cannot be translated: '{newArrayExpression}'.
@@ -439,12 +439,12 @@
The operation '{operation}' contains a select expression feature that isn't supported in the query SQL generator, but has been declared as supported by provider during translation phase. This is a bug in your EF Core provider, file an issue at https://aka.ms/efcorefeedback.
-
- 'ExecuteUpdate' cannot currently set a property in a JSON column to a regular, non-JSON column; see https://github.com/dotnet/efcore/issues/36688.
-
'ExecuteUpdate' cannot currently set a property in a JSON column to arbitrary expressions; only constants, parameters and other JSON properties are supported; see https://github.com/dotnet/efcore/issues/36688.
+
+ 'ExecuteUpdate' cannot currently set a property in a JSON column to a regular, non-JSON column; see https://github.com/dotnet/efcore/issues/36688.
+
'ExecuteUpdate' or 'ExecuteDelete' was called on entity type '{entityType}', but that entity type is not mapped to a table.
@@ -472,12 +472,12 @@
Named default constraints cannot be used with TPC or entity splitting if they result in non-unique constraint names. Constraint name: '{constraintNameCandidate}'.
-
- The complex types '{complexType1}' and '{complexType2}' are being compared, but the latter is lacking property '{property}' of the former.
-
The complex types '{complexType1}' and '{complexType2}' are being assigned, but the latter is lacking property '{property}' of the former.
+
+ The complex types '{complexType1}' and '{complexType2}' are being compared, but the latter is lacking property '{property}' of the former.
+
The table '{table}' cannot be used for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and the comment '{comment}' does not match the comment '{otherComment}'.
@@ -607,12 +607,18 @@
An error occurred while reading a JSON value for property '{entityType}.{propertyName}'. See the inner exception for more information.
+
+ ExecuteUpdate over JSON columns is not supported when the column is mapped as an owned entity. Map the column as a complex type instead.
+
This node should be handled by provider-specific SQL generator.
Both '{property1}' and '{property2}' on '{type}' are configured to use the same JSON property name '{jsonPropertyName}'. Each property within a JSON-mapped type must have a unique JSON property name.
+
+ The provider in use does not support partial updates with ExecuteUpdate within JSON columns.
+
Using a parameter to access the element of a JSON collection '{entityTypeName}' is not supported when using '{asNoTrackingWithIdentityResolution}'. Use a constant, or project the entire JSON entity collection instead.
@@ -840,9 +846,9 @@
Compiling a query which loads related collections for more than one collection navigation, either via 'Include' or through projection, but no 'QuerySplittingBehavior' has been configured. By default, Entity Framework will use 'QuerySplittingBehavior.SingleQuery', which can potentially result in slow query performance. See https://go.microsoft.com/fwlink/?linkid=2134277 for more information. To identify the query that's triggering this warning call 'ConfigureWarnings(w => w.Throw(RelationalEventId.MultipleCollectionIncludeWarning))'.
Warning RelationalEventId.MultipleCollectionIncludeWarning
-
+
The index named '{indexName}' on the entity type '{entityType}' specifies properties {indexProperties}, but none of these properties are mapped to a column in any table. This index will not be created in the database.
- Warning RelationalEventId.AllIndexPropertiesNotToMappedToAnyTable string? string string
+ Warning RelationalEventId.AllIndexPropertiesNotMappedToAnyTable string? string string
The index named '{indexName}' on the entity type '{entityType}' specifies properties {indexProperties}. Some properties are mapped to a column in a table, but the property '{propertyName}' is not. All of the properties must be mapped for the index to be created in the database.
@@ -948,9 +954,9 @@
An unexpected trailing result set was found when reading the results of a SaveChanges operation; this may indicate that a stored procedure returned a result set without being configured for it in the EF model. Check your stored procedure definitions.
Warning RelationalEventId.UnexpectedTrailingResultSetWhenSaving
-
+
The unnamed index on the entity type '{entityType}' specifies properties {indexProperties}, but none of these properties are mapped to a column in any table. This index will not be created in the database.
- Warning RelationalEventId.AllIndexPropertiesNotToMappedToAnyTable string string
+ Warning RelationalEventId.AllIndexPropertiesNotMappedToAnyTable string string
The unnamed index on the entity type '{entityType}' specifies properties {indexProperties}. Some properties are mapped to a column in a table, but the property '{propertyName}' is not. All of the properties must be mapped for the index to be created in the database.
@@ -997,9 +1003,6 @@
A result set was missing when reading the results of a SaveChanges operation; this may indicate that a stored procedure was configured to return results in the EF model, but did not. Check your stored procedure definitions.
-
- Entity type '{entityType}' is mapped to multiple columns with name '{columnName}', and one of them is configured as a JSON column. Assign different names to the columns.
-
Commands cannot be added to a completed 'ModificationCommandBatch'.
@@ -1012,6 +1015,9 @@
Cannot save changes for an entity of type '{entityType}' with primary key values {keyValues} in state '{entityState}'. This may indicate a bug in Entity Framework, file an issue at https://aka.ms/efcorefeedback.
+
+ Entity type '{entityType}' is mapped to multiple columns with name '{columnName}', and one of them is configured as a JSON column. Assign different names to the columns.
+
Multiple relational database provider configurations found. A context can only be configured to use a single database provider.
@@ -1096,12 +1102,6 @@
The value provided for parameter '{parameter}' cannot be used because it isn't assignable to type 'object[]'.
-
- The provider in use does not support partial updates with ExecuteUpdate within JSON columns.
-
-
- ExecuteUpdate over JSON columns is not supported when the column is mapped as an owned entity. Map the column as a complex type instead.
-
This connection was used with an ambient transaction. The original ambient transaction needs to be completed before this connection can be used outside of it.
diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs
index 89fa381a450..91d2e8b93c8 100644
--- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs
+++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs
@@ -1,8 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using System.Text;
-using Microsoft.Data.SqlTypes;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Extensions.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Internal;
@@ -17,26 +17,31 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
/// 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 class SqlServerModelValidator(ModelValidatorDependencies dependencies, RelationalModelValidatorDependencies relationalDependencies)
+public class SqlServerModelValidator(
+ ModelValidatorDependencies dependencies,
+ RelationalModelValidatorDependencies relationalDependencies)
: RelationalModelValidator(dependencies, relationalDependencies)
{
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public override void Validate(IModel model, IDiagnosticsLogger logger)
+ ///
+ protected override void ValidateEntityType(
+ IEntityType entityType,
+ IDiagnosticsLogger logger)
{
- ValidateIndexIncludeProperties(model, logger);
- ValidateVectorIndexes(model, logger);
+ base.ValidateEntityType(entityType, logger);
+
+ ValidateTemporalTable(entityType, logger);
+ }
- base.Validate(model, logger);
+ ///
+ protected override void ValidateProperty(
+ IProperty property,
+ ITypeBase structuralType,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidateProperty(property, structuralType, logger);
- ValidateDecimalColumns(model, logger);
- ValidateVectorColumns(model, logger);
- ValidateByteIdentityMapping(model, logger);
- ValidateTemporalTables(model, logger);
+ ValidateDecimalColumn(property, logger);
+ ValidateVectorProperty(property, logger);
}
///
@@ -45,38 +50,44 @@ public override void Validate(IModel model, IDiagnosticsLogger
- protected virtual void ValidateDecimalColumns(
- IModel model,
+ protected virtual void ValidateDecimalColumn(
+ IProperty property,
IDiagnosticsLogger logger)
{
- foreach (IConventionProperty property in model.GetEntityTypes()
- .SelectMany(t => t.GetDeclaredProperties())
- .Where(p => p.ClrType.UnwrapNullableType() == typeof(decimal)
- && !p.IsForeignKey()))
- {
- var valueConverterConfigurationSource = property.GetValueConverterConfigurationSource();
- var valueConverterProviderType = property.GetValueConverter()?.ProviderClrType;
- if (!ConfigurationSource.Convention.Overrides(valueConverterConfigurationSource)
- && typeof(decimal) != valueConverterProviderType)
- {
- continue;
- }
+ if (property.DeclaringType.IsMappedToJson())
+ {
+ return;
+ }
- var columnTypeConfigurationSource = property.GetColumnTypeConfigurationSource();
- if (((columnTypeConfigurationSource == null
- && ConfigurationSource.Convention.Overrides(property.GetTypeMappingConfigurationSource()))
- || (columnTypeConfigurationSource != null
- && ConfigurationSource.Convention.Overrides(columnTypeConfigurationSource)))
- && (ConfigurationSource.Convention.Overrides(property.GetPrecisionConfigurationSource())
- || ConfigurationSource.Convention.Overrides(property.GetScaleConfigurationSource())))
- {
- logger.DecimalTypeDefaultWarning((IProperty)property);
- }
+ if (property is not IConventionProperty conventionProperty
+ || conventionProperty.ClrType.UnwrapNullableType() != typeof(decimal)
+ || conventionProperty.IsForeignKey())
+ {
+ return;
+ }
- if (property.IsKey())
- {
- logger.DecimalTypeKeyWarning((IProperty)property);
- }
+ var valueConverterConfigurationSource = conventionProperty.GetValueConverterConfigurationSource();
+ var valueConverterProviderType = conventionProperty.GetValueConverter()?.ProviderClrType;
+ if (!ConfigurationSource.Convention.Overrides(valueConverterConfigurationSource)
+ && typeof(decimal) != valueConverterProviderType)
+ {
+ return;
+ }
+
+ var columnTypeConfigurationSource = conventionProperty.GetColumnTypeConfigurationSource();
+ if (((columnTypeConfigurationSource == null
+ && ConfigurationSource.Convention.Overrides(conventionProperty.GetTypeMappingConfigurationSource()))
+ || (columnTypeConfigurationSource != null
+ && ConfigurationSource.Convention.Overrides(columnTypeConfigurationSource)))
+ && (ConfigurationSource.Convention.Overrides(conventionProperty.GetPrecisionConfigurationSource())
+ || ConfigurationSource.Convention.Overrides(conventionProperty.GetScaleConfigurationSource())))
+ {
+ logger.DecimalTypeDefaultWarning(property);
+ }
+
+ if (conventionProperty.IsKey())
+ {
+ logger.DecimalTypeKeyWarning(property);
}
}
@@ -86,38 +97,22 @@ protected virtual void ValidateDecimalColumns(
/// 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 virtual void ValidateVectorColumns(
- IModel model,
+ protected virtual void ValidateVectorProperty(
+ IProperty property,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
- {
- ValidateVectorColumns(entityType);
- }
-
- void ValidateVectorColumns(ITypeBase typeBase)
+ if (property.FindTypeMapping() is SqlServerVectorTypeMapping vectorTypeMapping)
{
- foreach (var property in typeBase.GetDeclaredProperties())
+ if (property.DeclaringType.IsMappedToJson())
{
- if (property.FindTypeMapping() is SqlServerVectorTypeMapping vectorTypeMapping)
- {
- if (property.DeclaringType.IsMappedToJson())
- {
- throw new InvalidOperationException(
- SqlServerStrings.VectorPropertiesNotSupportedInJson(property.DeclaringType.DisplayName(), property.Name));
- }
-
- if (vectorTypeMapping.Size is null)
- {
- throw new InvalidOperationException(
- SqlServerStrings.VectorDimensionsMissing(property.DeclaringType.DisplayName(), property.Name));
- }
- }
+ throw new InvalidOperationException(
+ SqlServerStrings.VectorPropertiesNotSupportedInJson(property.DeclaringType.DisplayName(), property.Name));
}
- foreach (var complexProperty in typeBase.GetDeclaredComplexProperties())
+ if (vectorTypeMapping.Size is null)
{
- ValidateVectorColumns(complexProperty.ComplexType);
+ throw new InvalidOperationException(
+ SqlServerStrings.VectorDimensionsMissing(property.DeclaringType.DisplayName(), property.Name));
}
}
}
@@ -129,17 +124,35 @@ void ValidateVectorColumns(ITypeBase typeBase)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
protected virtual void ValidateByteIdentityMapping(
- IModel model,
+ IProperty property,
+ in StoreObjectIdentifier table,
+ IDiagnosticsLogger logger)
+ {
+ if (property.DeclaringType.IsMappedToJson())
+ {
+ return;
+ }
+
+ if (property.ClrType.UnwrapNullableType() == typeof(byte)
+ && property.GetValueGenerationStrategy(table) == SqlServerValueGenerationStrategy.IdentityColumn)
+ {
+ logger.ByteIdentityColumnWarning(property);
+ }
+ }
+
+ ///
+ protected override void ValidateTable(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ base.ValidateTable(mappedTypes, table, logger);
+
+ foreach (var entityType in mappedTypes)
{
- // TODO: Validate this per table
- foreach (var property in entityType.GetDeclaredProperties()
- .Where(p => p.ClrType.UnwrapNullableType() == typeof(byte)
- && p.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn))
+ foreach (var property in entityType.GetDeclaredProperties())
{
- logger.ByteIdentityColumnWarning(property);
+ ValidateByteIdentityMapping(property, table, logger);
}
}
}
@@ -151,10 +164,10 @@ protected virtual void ValidateByteIdentityMapping(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
protected override void ValidateValueGeneration(
- IEntityType entityType,
IKey key,
IDiagnosticsLogger logger)
{
+ var entityType = key.DeclaringEntityType;
if (entityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy
&& entityType.BaseType == null)
{
@@ -167,36 +180,44 @@ protected override void ValidateValueGeneration(
}
///
- protected override void ValidateTypeMappings(
- IModel model,
+ protected override void ValidateTypeMapping(
+ IProperty property,
IDiagnosticsLogger logger)
{
- base.ValidateTypeMappings(model, logger);
+ base.ValidateTypeMapping(property, logger);
+
+ var strategy = property.GetValueGenerationStrategy();
+ var propertyType = property.ClrType;
- foreach (var entityType in model.GetEntityTypes())
+ if (strategy == SqlServerValueGenerationStrategy.IdentityColumn
+ && !SqlServerPropertyExtensions.IsCompatibleWithValueGeneration(property))
{
- foreach (var property in entityType.GetFlattenedDeclaredProperties())
- {
- var strategy = property.GetValueGenerationStrategy();
- var propertyType = property.ClrType;
-
- if (strategy == SqlServerValueGenerationStrategy.IdentityColumn
- && !SqlServerPropertyExtensions.IsCompatibleWithValueGeneration(property))
- {
- throw new InvalidOperationException(
- SqlServerStrings.IdentityBadType(
- property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName()));
- }
-
- if (strategy is SqlServerValueGenerationStrategy.SequenceHiLo or SqlServerValueGenerationStrategy.Sequence
- && !SqlServerPropertyExtensions.IsCompatibleWithValueGeneration(property))
- {
- throw new InvalidOperationException(
- SqlServerStrings.SequenceBadType(
- property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName()));
- }
- }
+ throw new InvalidOperationException(
+ SqlServerStrings.IdentityBadType(
+ property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName()));
}
+
+ if (strategy is SqlServerValueGenerationStrategy.SequenceHiLo or SqlServerValueGenerationStrategy.Sequence
+ && !SqlServerPropertyExtensions.IsCompatibleWithValueGeneration(property))
+ {
+ throw new InvalidOperationException(
+ SqlServerStrings.SequenceBadType(
+ property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName()));
+ }
+ }
+
+ ///
+ protected override void ValidateIndex(
+ IIndex index,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidateIndex(index, logger);
+
+ ValidateIndexIncludeProperties(index);
+
+#pragma warning disable EF9105 // Vector indexes are experimental
+ ValidateVectorIndex(index);
+#pragma warning restore EF9105
}
///
@@ -205,53 +226,48 @@ protected override void ValidateTypeMappings(
/// 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 virtual void ValidateIndexIncludeProperties(
- IModel model,
- IDiagnosticsLogger logger)
+ protected virtual void ValidateIndexIncludeProperties(IIndex index)
{
- foreach (var index in model.GetEntityTypes().SelectMany(t => t.GetDeclaredIndexes()))
+ var includeProperties = index.GetIncludeProperties();
+ if (includeProperties?.Count > 0)
{
- var includeProperties = index.GetIncludeProperties();
- if (includeProperties?.Count > 0)
+ var notFound = includeProperties
+ .FirstOrDefault(i => index.DeclaringEntityType.FindProperty(i) == null);
+
+ if (notFound != null)
{
- var notFound = includeProperties
- .FirstOrDefault(i => index.DeclaringEntityType.FindProperty(i) == null);
-
- if (notFound != null)
- {
- throw new InvalidOperationException(
- SqlServerStrings.IncludePropertyNotFound(
- notFound,
- index.DisplayName(),
- index.DeclaringEntityType.DisplayName()));
- }
-
- var duplicateProperty = includeProperties
- .GroupBy(i => i)
- .Where(g => g.Count() > 1)
- .Select(y => y.Key)
- .FirstOrDefault();
-
- if (duplicateProperty != null)
- {
- throw new InvalidOperationException(
- SqlServerStrings.IncludePropertyDuplicated(
- index.DeclaringEntityType.DisplayName(),
- duplicateProperty,
- index.DisplayName()));
- }
-
- var coveredProperty = includeProperties
- .FirstOrDefault(i => index.Properties.Any(p => i == p.Name));
-
- if (coveredProperty != null)
- {
- throw new InvalidOperationException(
- SqlServerStrings.IncludePropertyInIndex(
- index.DeclaringEntityType.DisplayName(),
- coveredProperty,
- index.DisplayName()));
- }
+ throw new InvalidOperationException(
+ SqlServerStrings.IncludePropertyNotFound(
+ notFound,
+ index.DisplayName(),
+ index.DeclaringEntityType.DisplayName()));
+ }
+
+ var duplicateProperty = includeProperties
+ .GroupBy(i => i)
+ .Where(g => g.Count() > 1)
+ .Select(y => y.Key)
+ .FirstOrDefault();
+
+ if (duplicateProperty != null)
+ {
+ throw new InvalidOperationException(
+ SqlServerStrings.IncludePropertyDuplicated(
+ index.DeclaringEntityType.DisplayName(),
+ duplicateProperty,
+ index.DisplayName()));
+ }
+
+ var coveredProperty = includeProperties
+ .FirstOrDefault(i => index.Properties.Any(p => i == p.Name));
+
+ if (coveredProperty != null)
+ {
+ throw new InvalidOperationException(
+ SqlServerStrings.IncludePropertyInIndex(
+ index.DeclaringEntityType.DisplayName(),
+ coveredProperty,
+ index.DisplayName()));
}
}
}
@@ -262,53 +278,47 @@ protected virtual void ValidateIndexIncludeProperties(
/// 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.
///
-#pragma warning disable EF9105 // Vector indexes are experimental
- protected virtual void ValidateVectorIndexes(
- IModel model,
- IDiagnosticsLogger logger)
+ [Experimental(EFDiagnostics.SqlServerVectorSearch)]
+ protected virtual void ValidateVectorIndex(IIndex index)
{
- foreach (var index in model.GetEntityTypes().SelectMany(t => t.GetDeclaredIndexes()))
+ if (index.IsVectorIndex())
{
- if (index.IsVectorIndex())
+ if (index.Properties is not [var property])
+ {
+ throw new InvalidOperationException(
+ SqlServerStrings.VectorIndexRequiresSingleProperty(
+ index.DisplayName(),
+ index.DeclaringEntityType.DisplayName()));
+ }
+
+ if (index.GetVectorMetric() is null or "")
{
- if (index.Properties is not [var property])
- {
- throw new InvalidOperationException(
- SqlServerStrings.VectorIndexRequiresSingleProperty(
- index.DisplayName(),
- index.DeclaringEntityType.DisplayName()));
- }
-
- if (index.GetVectorMetric() is null or "")
- {
- throw new InvalidOperationException(
- SqlServerStrings.VectorIndexRequiresMetric(
- index.DisplayName(),
- index.DeclaringEntityType.DisplayName()));
- }
-
- if (index.GetVectorIndexType() is "")
- {
- throw new InvalidOperationException(
- SqlServerStrings.VectorIndexRequiresType(
- index.DisplayName(),
- index.DeclaringEntityType.DisplayName()));
- }
-
- var typeMapping = property.FindTypeMapping();
-
- if (typeMapping is not SqlServerVectorTypeMapping)
- {
- throw new InvalidOperationException(
- SqlServerStrings.VectorIndexOnNonVectorProperty(
- index.DisplayName(),
- index.DeclaringEntityType.DisplayName(),
- property.Name));
- }
+ throw new InvalidOperationException(
+ SqlServerStrings.VectorIndexRequiresMetric(
+ index.DisplayName(),
+ index.DeclaringEntityType.DisplayName()));
+ }
+
+ if (index.GetVectorIndexType() is "")
+ {
+ throw new InvalidOperationException(
+ SqlServerStrings.VectorIndexRequiresType(
+ index.DisplayName(),
+ index.DeclaringEntityType.DisplayName()));
+ }
+
+ var typeMapping = property.FindTypeMapping();
+
+ if (typeMapping is not SqlServerVectorTypeMapping)
+ {
+ throw new InvalidOperationException(
+ SqlServerStrings.VectorIndexOnNonVectorProperty(
+ index.DisplayName(),
+ index.DeclaringEntityType.DisplayName(),
+ property.Name));
}
}
}
-#pragma warning restore EF9105
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -316,27 +326,28 @@ protected virtual void ValidateVectorIndexes(
/// 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 virtual void ValidateTemporalTables(
- IModel model,
+ protected virtual void ValidateTemporalTable(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- var temporalEntityTypes = model.GetEntityTypes().Where(t => t.IsTemporal()).ToList();
- foreach (var temporalEntityType in temporalEntityTypes)
+ if (!entityType.IsTemporal())
{
- if (temporalEntityType.BaseType != null)
- {
- throw new InvalidOperationException(SqlServerStrings.TemporalOnlyOnRoot(temporalEntityType.DisplayName()));
- }
+ return;
+ }
- ValidateTemporalPeriodProperty(temporalEntityType, periodStart: true);
- ValidateTemporalPeriodProperty(temporalEntityType, periodStart: false);
+ if (entityType.BaseType != null)
+ {
+ throw new InvalidOperationException(SqlServerStrings.TemporalOnlyOnRoot(entityType.DisplayName()));
+ }
- var derivedTableMappings = temporalEntityType.GetDerivedTypes().Select(t => t.GetTableName()).Distinct().ToList();
- if (derivedTableMappings.Count > 0
- && (derivedTableMappings.Count != 1 || derivedTableMappings.First() != temporalEntityType.GetTableName()))
- {
- throw new InvalidOperationException(SqlServerStrings.TemporalOnlySupportedForTPH(temporalEntityType.DisplayName()));
- }
+ ValidateTemporalPeriodProperty(entityType, periodStart: true);
+ ValidateTemporalPeriodProperty(entityType, periodStart: false);
+
+ var derivedTableMappings = entityType.GetDerivedTypes().Select(t => t.GetTableName()).Distinct().ToList();
+ if (derivedTableMappings.Count > 0
+ && (derivedTableMappings.Count != 1 || derivedTableMappings.First() != entityType.GetTableName()))
+ {
+ throw new InvalidOperationException(SqlServerStrings.TemporalOnlySupportedForTPH(entityType.DisplayName()));
}
}
@@ -416,7 +427,25 @@ private static void ValidateTemporalPeriodProperty(IEntityType temporalEntityTyp
///
protected override void ValidateSharedTableCompatibility(
IReadOnlyList mappedTypes,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidateSharedTableCompatibility(mappedTypes, table, logger);
+
+ ValidateMemoryOptimized(mappedTypes, table, logger);
+ ValidateSqlOutputClause(mappedTypes, table, logger);
+ ValidateTemporalTableSplitting(mappedTypes, table, 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 virtual void ValidateMemoryOptimized(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
var firstMappedType = mappedTypes[0];
@@ -427,14 +456,26 @@ protected override void ValidateSharedTableCompatibility(
{
throw new InvalidOperationException(
SqlServerStrings.IncompatibleTableMemoryOptimizedMismatch(
- storeObject.DisplayName(), firstMappedType.DisplayName(), otherMappedType.DisplayName(),
+ table.DisplayName(), firstMappedType.DisplayName(), otherMappedType.DisplayName(),
isMemoryOptimized ? firstMappedType.DisplayName() : otherMappedType.DisplayName(),
!isMemoryOptimized ? firstMappedType.DisplayName() : otherMappedType.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
+ /// 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 virtual void ValidateSqlOutputClause(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier table,
+ IDiagnosticsLogger logger)
+ {
bool? firstSqlOutputSetting = null;
- firstMappedType = null;
+ IEntityType? firstMappedType = null;
foreach (var mappedType in mappedTypes)
{
if (((IConventionEntityType)mappedType).GetUseSqlOutputClauseConfigurationSource() is null)
@@ -450,70 +491,82 @@ protected override void ValidateSharedTableCompatibility(
{
throw new InvalidOperationException(
SqlServerStrings.IncompatibleSqlOutputClauseMismatch(
- storeObject.DisplayName(), firstMappedType!.DisplayName(), mappedType.DisplayName(),
+ table.DisplayName(), firstMappedType!.DisplayName(), mappedType.DisplayName(),
firstSqlOutputSetting.Value ? firstMappedType.DisplayName() : mappedType.DisplayName(),
!firstSqlOutputSetting.Value ? firstMappedType.DisplayName() : mappedType.DisplayName()));
}
}
+ }
- if (mappedTypes.Any(t => t.IsTemporal())
- && mappedTypes.Select(t => t.GetRootType()).Distinct().Count() > 1)
+ ///
+ /// 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 virtual void ValidateTemporalTableSplitting(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier table,
+ IDiagnosticsLogger logger)
+ {
+ if (!mappedTypes.Any(t => t.IsTemporal())
+ || mappedTypes.Select(t => t.GetRootType()).Distinct().Count() <= 1)
{
- // table splitting is only supported when all entities mapped to this table have consistent temporal period mappings also
- var expectedPeriodStartColumnName = default(string);
- var expectedPeriodEndColumnName = default(string);
+ return;
+ }
- foreach (var mappedType in mappedTypes.Where(t => t.BaseType == null))
+ // table splitting is only supported when all entities mapped to this table have consistent temporal period mappings also
+ var expectedPeriodStartColumnName = default(string);
+ var expectedPeriodEndColumnName = default(string);
+
+ foreach (var mappedType in mappedTypes.Where(t => t.BaseType == null))
+ {
+ if (!mappedType.IsTemporal())
{
- if (!mappedType.IsTemporal())
- {
- throw new InvalidOperationException(
- SqlServerStrings.TemporalAllEntitiesMappedToSameTableMustBeTemporal(
- mappedType.DisplayName()));
- }
-
- var periodStartPropertyName = mappedType.GetPeriodStartPropertyName();
- var periodEndPropertyName = mappedType.GetPeriodEndPropertyName();
-
- var periodStartProperty = mappedType.GetProperty(periodStartPropertyName!);
- var periodEndProperty = mappedType.GetProperty(periodEndPropertyName!);
-
- var periodStartColumnName = periodStartProperty.GetColumnName(storeObject);
- var periodEndColumnName = periodEndProperty.GetColumnName(storeObject);
-
- if (expectedPeriodStartColumnName == null)
- {
- expectedPeriodStartColumnName = periodStartColumnName;
- }
- else if (expectedPeriodStartColumnName != periodStartColumnName)
- {
- throw new InvalidOperationException(
- SqlServerStrings.TemporalNotSupportedForTableSplittingWithInconsistentPeriodMapping(
- "start",
- mappedType.DisplayName(),
- periodStartPropertyName,
- periodStartColumnName,
- expectedPeriodStartColumnName));
- }
-
- if (expectedPeriodEndColumnName == null)
- {
- expectedPeriodEndColumnName = periodEndColumnName;
- }
- else if (expectedPeriodEndColumnName != periodEndColumnName)
- {
- throw new InvalidOperationException(
- SqlServerStrings.TemporalNotSupportedForTableSplittingWithInconsistentPeriodMapping(
- "end",
- mappedType.DisplayName(),
- periodEndPropertyName,
- periodEndColumnName,
- expectedPeriodEndColumnName));
- }
+ throw new InvalidOperationException(
+ SqlServerStrings.TemporalAllEntitiesMappedToSameTableMustBeTemporal(
+ mappedType.DisplayName()));
}
- }
- base.ValidateSharedTableCompatibility(mappedTypes, storeObject, logger);
+ var periodStartPropertyName = mappedType.GetPeriodStartPropertyName();
+ var periodEndPropertyName = mappedType.GetPeriodEndPropertyName();
+
+ var periodStartProperty = mappedType.GetProperty(periodStartPropertyName!);
+ var periodEndProperty = mappedType.GetProperty(periodEndPropertyName!);
+
+ var periodStartColumnName = periodStartProperty.GetColumnName(table);
+ var periodEndColumnName = periodEndProperty.GetColumnName(table);
+
+ if (expectedPeriodStartColumnName == null)
+ {
+ expectedPeriodStartColumnName = periodStartColumnName;
+ }
+ else if (expectedPeriodStartColumnName != periodStartColumnName)
+ {
+ throw new InvalidOperationException(
+ SqlServerStrings.TemporalNotSupportedForTableSplittingWithInconsistentPeriodMapping(
+ "start",
+ mappedType.DisplayName(),
+ periodStartPropertyName,
+ periodStartColumnName,
+ expectedPeriodStartColumnName));
+ }
+
+ if (expectedPeriodEndColumnName == null)
+ {
+ expectedPeriodEndColumnName = periodEndColumnName;
+ }
+ else if (expectedPeriodEndColumnName != periodEndColumnName)
+ {
+ throw new InvalidOperationException(
+ SqlServerStrings.TemporalNotSupportedForTableSplittingWithInconsistentPeriodMapping(
+ "end",
+ mappedType.DisplayName(),
+ periodEndPropertyName,
+ periodEndColumnName,
+ expectedPeriodEndColumnName));
+ }
+ }
}
///
@@ -660,12 +713,12 @@ protected override void ValidateCompatible(
IKey key,
IKey duplicateKey,
string keyName,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
- base.ValidateCompatible(key, duplicateKey, keyName, storeObject, logger);
+ base.ValidateCompatible(key, duplicateKey, keyName, table, logger);
- key.AreCompatibleForSqlServer(duplicateKey, storeObject, shouldThrow: true);
+ key.AreCompatibleForSqlServer(duplicateKey, table, shouldThrow: true);
}
///
@@ -673,11 +726,11 @@ protected override void ValidateCompatible(
IIndex index,
IIndex duplicateIndex,
string indexName,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
- base.ValidateCompatible(index, duplicateIndex, indexName, storeObject, logger);
+ base.ValidateCompatible(index, duplicateIndex, indexName, table, logger);
- index.AreCompatibleForSqlServer(duplicateIndex, storeObject, shouldThrow: true);
+ index.AreCompatibleForSqlServer(duplicateIndex, table, shouldThrow: true);
}
}
diff --git a/src/EFCore.Sqlite.Core/Infrastructure/Internal/SqliteModelValidator.cs b/src/EFCore.Sqlite.Core/Infrastructure/Internal/SqliteModelValidator.cs
index 08fe9f7a074..bc20adb264e 100644
--- a/src/EFCore.Sqlite.Core/Infrastructure/Internal/SqliteModelValidator.cs
+++ b/src/EFCore.Sqlite.Core/Infrastructure/Internal/SqliteModelValidator.cs
@@ -11,7 +11,10 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Infrastructure.Internal;
/// 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 class SqliteModelValidator : RelationalModelValidator
+public class SqliteModelValidator(
+ ModelValidatorDependencies dependencies,
+ RelationalModelValidatorDependencies relationalDependencies)
+ : RelationalModelValidator(dependencies, relationalDependencies)
{
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -19,26 +22,19 @@ public class SqliteModelValidator : RelationalModelValidator
/// 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 SqliteModelValidator(
- ModelValidatorDependencies dependencies,
- RelationalModelValidatorDependencies relationalDependencies)
- : base(dependencies, relationalDependencies)
+ public override void Validate(IModel model, IDiagnosticsLogger logger)
{
+ base.Validate(model, 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.
- ///
- public override void Validate(IModel model, IDiagnosticsLogger logger)
+ ///
+ protected override void ValidateEntityType(
+ IEntityType entityType,
+ IDiagnosticsLogger logger)
{
- base.Validate(model, logger);
+ base.ValidateEntityType(entityType, logger);
- ValidateNoSchemas(model, logger);
- ValidateNoSequences(model, logger);
- ValidateNoStoredProcedures(model, logger);
+ ValidateNoSchema(entityType, logger);
}
///
@@ -47,30 +43,25 @@ public override void Validate(IModel model, IDiagnosticsLogger
- protected virtual void ValidateNoSchemas(
- IModel model,
+ protected virtual void ValidateNoSchema(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes().Where(e => e.GetSchema() != null))
+ var schema = entityType.GetSchema();
+ if (schema != null)
{
- logger.SchemaConfiguredWarning(entityType, entityType.GetSchema()!);
+ logger.SchemaConfiguredWarning(entityType, schema);
}
}
- ///
- /// 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 virtual void ValidateNoSequences(
- IModel model,
+ ///
+ protected override void ValidateSequence(
+ ISequence sequence,
IDiagnosticsLogger logger)
{
- foreach (var sequence in model.GetSequences())
- {
- logger.SequenceConfiguredWarning(sequence);
- }
+ base.ValidateSequence(sequence, logger);
+
+ logger.SequenceConfiguredWarning(sequence);
}
///
@@ -79,21 +70,19 @@ protected virtual void ValidateNoSequences(
/// 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 virtual void ValidateNoStoredProcedures(
- IModel model,
+ protected override void ValidateStoredProcedures(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ if (entityType.GetInsertStoredProcedure() is not null
+ || entityType.GetUpdateStoredProcedure() is not null
+ || entityType.GetDeleteStoredProcedure() is not null)
{
- if (entityType.GetInsertStoredProcedure() is not null
- || entityType.GetUpdateStoredProcedure() is not null
- || entityType.GetDeleteStoredProcedure() is not null)
- {
- throw new InvalidOperationException(SqliteStrings.StoredProceduresNotSupported(entityType.DisplayName()));
- }
+ throw new InvalidOperationException(SqliteStrings.StoredProceduresNotSupported(entityType.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
@@ -131,12 +120,12 @@ protected override void ValidateCompatible(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
protected override void ValidateValueGeneration(
- IEntityType entityType,
IKey key,
IDiagnosticsLogger logger)
{
- base.ValidateValueGeneration(entityType, key, logger);
+ base.ValidateValueGeneration(key, logger);
+ var entityType = key.DeclaringEntityType;
var keyProperties = key.Properties;
if (!entityType.IsMappedToJson()
&& key.IsPrimaryKey()
@@ -159,7 +148,23 @@ protected override void ValidateValueGeneration(
///
protected override void ValidateSharedTableCompatibility(
IReadOnlyList mappedTypes,
- in StoreObjectIdentifier storeObject,
+ in StoreObjectIdentifier table,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidateSharedTableCompatibility(mappedTypes, table, logger);
+
+ ValidateSqlReturningClause(mappedTypes, table, 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 virtual void ValidateSqlReturningClause(
+ IReadOnlyList mappedTypes,
+ in StoreObjectIdentifier table,
IDiagnosticsLogger logger)
{
bool? firstSqlOutputSetting = null;
@@ -179,12 +184,10 @@ protected override void ValidateSharedTableCompatibility(
{
throw new InvalidOperationException(
SqliteStrings.IncompatibleSqlReturningClauseMismatch(
- storeObject.DisplayName(), firstMappedType!.DisplayName(), mappedType.DisplayName(),
+ table.DisplayName(), firstMappedType!.DisplayName(), mappedType.DisplayName(),
firstSqlOutputSetting.Value ? firstMappedType.DisplayName() : mappedType.DisplayName(),
!firstSqlOutputSetting.Value ? firstMappedType.DisplayName() : mappedType.DisplayName()));
}
}
-
- base.ValidateSharedTableCompatibility(mappedTypes, storeObject, logger);
}
}
diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs
index 866e1f4b53b..d29918dce95 100644
--- a/src/EFCore/Infrastructure/ModelValidator.cs
+++ b/src/EFCore/Infrastructure/ModelValidator.cs
@@ -12,6 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure;
///
/// The validator that enforces core rules common for all providers.
///
+/// Parameter object containing dependencies for this service.
///
///
/// The service lifetime is . This means a single instance
@@ -23,121 +24,287 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure;
/// for more information and examples.
///
///
-public class ModelValidator : IModelValidator
+public class ModelValidator(ModelValidatorDependencies dependencies) : IModelValidator
{
private static readonly IEnumerable DictionaryProperties =
typeof(IDictionary).GetRuntimeProperties().Select(e => e.Name);
- ///
- /// Creates a new instance of .
- ///
- /// Parameter object containing dependencies for this service.
- public ModelValidator(ModelValidatorDependencies dependencies)
- => Dependencies = dependencies;
-
///
/// Dependencies for this service.
///
- protected virtual ModelValidatorDependencies Dependencies { get; }
+ protected virtual ModelValidatorDependencies Dependencies { get; } = dependencies;
///
public virtual void Validate(IModel model, IDiagnosticsLogger logger)
{
- ValidateIgnoredMembers(model, logger);
- ValidateEntityClrTypes(model, logger);
- ValidatePropertyMapping(model, logger);
- ValidateRelationships(model, logger);
- ValidateOwnership(model, logger);
- ValidateNonNullPrimaryKeys(model, logger);
- ValidateNoShadowKeys(model, logger);
- ValidateNoMutableKeys(model, logger);
- ValidateNoCycles(model, logger);
- ValidateClrInheritance(model, logger);
- ValidateInheritanceMapping(model, logger);
- ValidateChangeTrackingStrategy(model, logger);
- ValidateForeignKeys(model, logger);
- ValidateFieldMapping(model, logger);
- ValidateQueryFilters(model, logger);
- ValidateData(model, logger);
- ValidateTypeMappings(model, logger);
- ValidatePrimitiveCollections(model, logger);
- ValidateTriggers(model, logger);
- LogShadowProperties(model, logger);
- }
+ var validEntityTypes = new HashSet();
+ var identityMaps = new Dictionary();
+ var identifyingFkGraph = new Multigraph();
+ var sensitiveDataLogged = logger.ShouldLogSensitiveData();
- ///
- /// Validates relationships.
- ///
- /// The model.
- /// The logger to use.
- protected virtual void ValidateRelationships(
- IModel model,
- IDiagnosticsLogger logger)
- {
foreach (var entityType in model.GetEntityTypes())
{
- foreach (var foreignKey in entityType.GetDeclaredForeignKeys())
+ ValidateEntityType(entityType, logger);
+ ValidateClrInheritance(entityType, validEntityTypes);
+ ValidateData(entityType, identityMaps, sensitiveDataLogged, logger);
+
+ var primaryKey = entityType.FindPrimaryKey();
+ if (primaryKey == null)
{
- if (foreignKey.IsUnique
- && foreignKey is IConventionForeignKey concreteFk
- && concreteFk.GetPrincipalEndConfigurationSource() == null)
- {
- throw new InvalidOperationException(
- CoreStrings.AmbiguousOneToOneRelationship(
- foreignKey.DeclaringEntityType.DisplayName()
- + (foreignKey.DependentToPrincipal == null
- ? ""
- : "." + foreignKey.DependentToPrincipal.Name),
- foreignKey.PrincipalEntityType.DisplayName()
- + (foreignKey.PrincipalToDependent == null
- ? ""
- : "." + foreignKey.PrincipalToDependent.Name)));
- }
+ continue;
}
- foreach (var skipNavigation in entityType.GetDeclaredSkipNavigations())
+ foreach (var foreignKey in entityType.GetForeignKeys())
{
- if (!skipNavigation.IsCollection)
- {
- throw new InvalidOperationException(
- CoreStrings.SkipNavigationNonCollection(
- skipNavigation.Name, skipNavigation.DeclaringEntityType.DisplayName()));
- }
-
- if (skipNavigation.ForeignKey == null)
+ var principalType = foreignKey.PrincipalEntityType;
+ if (!foreignKey.PrincipalKey.IsPrimaryKey()
+ || !PropertyListComparer.Instance.Equals(foreignKey.Properties, primaryKey.Properties)
+ || foreignKey.PrincipalEntityType.IsAssignableFrom(entityType))
{
- throw new InvalidOperationException(
- CoreStrings.SkipNavigationNoForeignKey(
- skipNavigation.Name, skipNavigation.DeclaringEntityType.DisplayName()));
+ continue;
}
- if (skipNavigation.Inverse == null)
- {
- throw new InvalidOperationException(
- CoreStrings.SkipNavigationNoInverse(
- skipNavigation.Name, skipNavigation.DeclaringEntityType.DisplayName()));
- }
+ identifyingFkGraph.AddVertex(entityType);
+ identifyingFkGraph.AddVertex(principalType);
+ identifyingFkGraph.AddEdge(entityType, principalType, foreignKey);
}
}
+
+ ValidateNoIdentifyingRelationshipCycles(identifyingFkGraph);
+ }
+
+ private static void ValidateNoIdentifyingRelationshipCycles(Multigraph graph)
+ {
+ graph.TopologicalSort(
+ tryBreakEdge: null,
+ formatCycle: c => c.Select(d => d.Item1.DisplayName()).Join(" -> "),
+ CoreStrings.IdentifyingRelationshipCycle);
}
///
- /// Validates property mappings.
+ /// Validates a single entity type.
///
- /// The model.
+ /// The entity type to validate.
/// The logger to use.
- protected virtual void ValidatePropertyMapping(
- IModel model,
+ protected virtual void ValidateEntityType(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- if (model is not IConventionModel conventionModel)
+ ValidateEntityClrType(entityType, logger);
+ ValidateChangeTrackingStrategy(entityType, logger);
+ ValidateIgnoredMembers(entityType, logger);
+ ValidatePropertyMapping(entityType, logger);
+ ValidateOwnership(entityType, logger);
+ ValidateNonNullPrimaryKey(entityType, logger);
+ ValidateInheritanceMapping(entityType, logger);
+ ValidateFieldMapping(entityType, logger);
+ ValidateQueryFilters(entityType, logger);
+
+ foreach (var property in entityType.GetDeclaredProperties())
{
- return;
+ ValidateProperty(property, entityType, logger);
+ }
+
+ foreach (var skipNavigation in entityType.GetDeclaredSkipNavigations())
+ {
+ ValidateSkipNavigation(skipNavigation, logger);
+ }
+
+ foreach (var foreignKey in entityType.GetDeclaredForeignKeys())
+ {
+ ValidateForeignKey(foreignKey, logger);
+ }
+
+ foreach (var key in entityType.GetDeclaredKeys())
+ {
+ ValidateKey(key, logger);
+ }
+
+ foreach (var index in entityType.GetDeclaredIndexes())
+ {
+ ValidateIndex(index, logger);
+ }
+
+ foreach (var trigger in entityType.GetDeclaredTriggers())
+ {
+ ValidateTrigger(trigger, entityType, logger);
+ }
+
+ foreach (var complexProperty in entityType.GetDeclaredComplexProperties())
+ {
+ ValidateComplexProperty(complexProperty, logger);
+ }
+
+ LogShadowProperties(entityType, logger);
+ }
+
+ ///
+ /// Validates inheritance mapping for an entity type.
+ ///
+ /// The entity type to validate.
+ /// The logger to use.
+ protected virtual void ValidateInheritanceMapping(
+ IEntityType entityType,
+ IDiagnosticsLogger logger)
+ {
+ // For root entity types, validate discriminator values
+ if (entityType.BaseType == null)
+ {
+ ValidateDiscriminatorValues(entityType);
+ }
+ }
+
+ ///
+ /// Validates a single property.
+ ///
+ /// The property to validate.
+ /// The structural type containing the property.
+ /// The logger to use.
+ protected virtual void ValidateProperty(
+ IProperty property,
+ ITypeBase structuralType,
+ IDiagnosticsLogger logger)
+ {
+ ValidateTypeMapping(property, logger);
+ ValidatePrimitiveCollection(property, logger);
+ }
+
+ ///
+ /// Validates a single complex property and its nested members.
+ ///
+ /// The complex property to validate.
+ /// The logger to use.
+ protected virtual void ValidateComplexProperty(
+ IComplexProperty complexProperty,
+ IDiagnosticsLogger logger)
+ {
+ var complexType = complexProperty.ComplexType;
+
+ ValidateChangeTrackingStrategy(complexType, logger);
+
+ foreach (var property in complexType.GetDeclaredProperties())
+ {
+ ValidateProperty(property, complexType, logger);
+ }
+
+ foreach (var nestedComplexProperty in complexType.GetDeclaredComplexProperties())
+ {
+ ValidateComplexProperty(nestedComplexProperty, logger);
+ }
+ }
+
+ ///
+ /// Validates a single index.
+ ///
+ /// The index to validate.
+ /// The logger to use.
+ protected virtual void ValidateIndex(
+ IIndex index,
+ IDiagnosticsLogger logger)
+ {
+ }
+
+ ///
+ /// Validates a single key.
+ ///
+ /// The key to validate.
+ /// The logger to use.
+ protected virtual void ValidateKey(
+ IKey key,
+ IDiagnosticsLogger logger)
+ {
+ ValidateShadowKey(key, logger);
+ ValidateMutableKey(key, logger);
+ }
+
+ ///
+ /// Validates that a one-to-one relationship has an unambiguous principal end.
+ ///
+ /// The foreign key to validate.
+ /// The logger to use.
+ protected virtual void ValidateAmbiguousOneToOneRelationship(
+ IForeignKey foreignKey,
+ IDiagnosticsLogger logger)
+ {
+ if (foreignKey.IsUnique
+ && foreignKey is IConventionForeignKey concreteFk
+ && concreteFk.GetPrincipalEndConfigurationSource() == null)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.AmbiguousOneToOneRelationship(
+ foreignKey.DeclaringEntityType.DisplayName()
+ + (foreignKey.DependentToPrincipal == null
+ ? ""
+ : "." + foreignKey.DependentToPrincipal.Name),
+ foreignKey.PrincipalEntityType.DisplayName()
+ + (foreignKey.PrincipalToDependent == null
+ ? ""
+ : "." + foreignKey.PrincipalToDependent.Name)));
+ }
+ }
+
+ ///
+ /// Validates a skip navigation.
+ ///
+ /// The skip navigation to validate.
+ /// The logger to use.
+ protected virtual void ValidateSkipNavigation(
+ ISkipNavigation skipNavigation,
+ IDiagnosticsLogger logger)
+ {
+ ValidateSkipNavigationIsCollection(skipNavigation, logger);
+ ValidateSkipNavigationForeignKey(skipNavigation, logger);
+ ValidateSkipNavigationInverse(skipNavigation, logger);
+ }
+
+ ///
+ /// Validates that a skip navigation is a collection navigation.
+ ///
+ /// The skip navigation to validate.
+ /// The logger to use.
+ protected virtual void ValidateSkipNavigationIsCollection(
+ ISkipNavigation skipNavigation,
+ IDiagnosticsLogger logger)
+ {
+ if (!skipNavigation.IsCollection)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.SkipNavigationNonCollection(
+ skipNavigation.Name, skipNavigation.DeclaringEntityType.DisplayName()));
+ }
+ }
+
+ ///
+ /// Validates that a skip navigation has a foreign key configured.
+ ///
+ /// The skip navigation to validate.
+ /// The logger to use.
+ protected virtual void ValidateSkipNavigationForeignKey(
+ ISkipNavigation skipNavigation,
+ IDiagnosticsLogger logger)
+ {
+ if (skipNavigation.ForeignKey == null)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.SkipNavigationNoForeignKey(
+ skipNavigation.Name, skipNavigation.DeclaringEntityType.DisplayName()));
}
+ }
- foreach (var entityType in conventionModel.GetEntityTypes())
+ ///
+ /// Validates that a skip navigation has an inverse navigation configured.
+ ///
+ /// The skip navigation to validate.
+ /// The logger to use.
+ protected virtual void ValidateSkipNavigationInverse(
+ ISkipNavigation skipNavigation,
+ IDiagnosticsLogger logger)
+ {
+ if (skipNavigation.Inverse == null)
{
- ValidatePropertyMapping(entityType, conventionModel, logger);
+ throw new InvalidOperationException(
+ CoreStrings.SkipNavigationNoInverse(
+ skipNavigation.Name, skipNavigation.DeclaringEntityType.DisplayName()));
}
}
@@ -145,14 +312,15 @@ protected virtual void ValidatePropertyMapping(
/// Validates property mappings for a given type.
///
/// The type base to validate.
- /// The model to validate.
/// The logger to use.
protected virtual void ValidatePropertyMapping(
- IConventionTypeBase structuralType,
- IConventionModel model,
+ ITypeBase structuralType,
IDiagnosticsLogger logger)
{
- var unmappedProperty = structuralType.GetDeclaredProperties().FirstOrDefault(p
+ var conventionTypeBase = (IConventionTypeBase)structuralType;
+ var conventionModel = (IConventionModel)structuralType.Model;
+
+ var unmappedProperty = conventionTypeBase.GetDeclaredProperties().FirstOrDefault(p
=> (!ConfigurationSource.Convention.Overrides(p.GetConfigurationSource())
// Use a better condition for non-persisted properties when issue #14121 is implemented
|| !p.IsImplicitlyCreated())
@@ -163,14 +331,13 @@ protected virtual void ValidatePropertyMapping(
ThrowPropertyNotMappedException(
(unmappedProperty.GetValueConverter()?.ProviderClrType ?? unmappedProperty.ClrType).ShortDisplayName(),
structuralType,
- unmappedProperty);
+ (IProperty)unmappedProperty);
}
- foreach (var complexProperty in structuralType.GetDeclaredComplexProperties())
+ foreach (var complexProperty in conventionTypeBase.GetDeclaredComplexProperties())
{
- ValidatePropertyMapping(complexProperty, logger);
-
- ValidatePropertyMapping(complexProperty.ComplexType, model, logger);
+ ValidatePropertyMapping((IComplexProperty)complexProperty, logger);
+ ValidatePropertyMapping((ITypeBase)complexProperty.ComplexType, logger);
}
if (structuralType.ClrType == Model.DefaultPropertyBagType)
@@ -199,7 +366,7 @@ protected virtual void ValidatePropertyMapping(
foreach (var clrPropertyName in clrProperties)
{
- if (structuralType.FindIgnoredConfigurationSource(clrPropertyName) != null)
+ if (conventionTypeBase.FindIgnoredConfigurationSource(clrPropertyName) != null)
{
continue;
}
@@ -208,30 +375,30 @@ protected virtual void ValidatePropertyMapping(
var propertyType = clrProperty.PropertyType;
var targetSequenceType = propertyType.TryGetSequenceType();
- if (model.FindIgnoredConfigurationSource(propertyType) != null
- || model.IsIgnoredType(propertyType)
+ if (conventionModel.FindIgnoredConfigurationSource(propertyType) != null
+ || conventionModel.IsIgnoredType(propertyType)
|| (targetSequenceType != null
- && (model.FindIgnoredConfigurationSource(targetSequenceType) != null
- || model.IsIgnoredType(targetSequenceType))))
+ && (conventionModel.FindIgnoredConfigurationSource(targetSequenceType) != null
+ || conventionModel.IsIgnoredType(targetSequenceType))))
{
continue;
}
var targetType = Dependencies.MemberClassifier.FindCandidateNavigationPropertyType(
- clrProperty, model, useAttributes: true, out var targetOwned);
+ clrProperty, conventionModel, useAttributes: true, out var targetOwned);
if (targetType == null
&& clrProperty.FindSetterProperty() == null)
{
continue;
}
- var isAdHoc = Equals(model.FindAnnotation(CoreAnnotationNames.AdHocModel)?.Value, true);
+ var isAdHoc = Equals(conventionModel.FindAnnotation(CoreAnnotationNames.AdHocModel)?.Value, true);
if (targetType != null)
{
- var targetShared = model.IsShared(targetType);
- targetOwned ??= IsOwned(targetType, model);
+ var targetShared = conventionModel.IsShared(targetType);
+ targetOwned ??= IsOwned(targetType, structuralType.Model);
- if (structuralType is not IConventionEntityType entityType)
+ if (conventionTypeBase is not IEntityType entityType)
{
if (!((IReadOnlyComplexType)structuralType).IsContainedBy(targetType))
{
@@ -246,7 +413,7 @@ protected virtual void ValidatePropertyMapping(
// ReSharper disable CheckForReferenceEqualityInstead.1
// ReSharper disable CheckForReferenceEqualityInstead.3
if ((isAdHoc
- || !entityType.IsKeyless
+ || !((IConventionEntityType)entityType).IsKeyless
|| targetSequenceType == null)
&& entityType.GetDerivedTypes().All(dt
=> dt.GetDeclaredNavigations().FirstOrDefault(n => n.Name == clrProperty.GetSimpleMemberName())
@@ -306,7 +473,7 @@ protected virtual void ValidatePropertyMapping(
/// The complex property to validate.
/// The logger to use.
protected virtual void ValidatePropertyMapping(
- IConventionComplexProperty complexProperty,
+ IComplexProperty complexProperty,
IDiagnosticsLogger logger)
{
var structuralType = complexProperty.DeclaringType;
@@ -373,8 +540,8 @@ protected virtual void ValidatePropertyMapping(
/// The property.
protected virtual void ThrowPropertyNotMappedException(
string propertyType,
- IConventionTypeBase structuralType,
- IConventionProperty unmappedProperty)
+ ITypeBase structuralType,
+ IProperty unmappedProperty)
=> throw new InvalidOperationException(
CoreStrings.PropertyNotMapped(
propertyType,
@@ -385,229 +552,139 @@ protected virtual void ThrowPropertyNotMappedException(
/// Returns a value indicating whether that target CLR type would correspond to an owned entity type.
///
/// The target CLR type.
- /// The model.
+ /// The model.
/// if the given CLR type corresponds to an owned entity type.
- protected virtual bool IsOwned(Type targetType, IConventionModel conventionModel)
- => conventionModel.FindIsOwnedConfigurationSource(targetType) != null
+ protected virtual bool IsOwned(Type targetType, IModel model)
+ {
+ var conventionModel = (IConventionModel)model;
+ return conventionModel.FindIsOwnedConfigurationSource(targetType) != null
|| conventionModel.FindEntityTypes(targetType).Any(t => t.IsOwned());
+ }
///
- /// Validates that no attempt is made to ignore inherited properties.
+ /// Validates that no attempt is made to ignore inherited properties on an entity type.
///
- /// The model.
+ /// The entity type to validate.
/// The logger to use.
protected virtual void ValidateIgnoredMembers(
- IModel model,
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- if (model is not IConventionModel conventionModel)
+ if (entityType is not IConventionEntityType conventionEntityType)
{
return;
}
- foreach (var entityType in conventionModel.GetEntityTypes())
+ foreach (var ignoredMember in conventionEntityType.GetIgnoredMembers())
{
- foreach (var ignoredMember in entityType.GetIgnoredMembers())
+ if (conventionEntityType.FindIgnoredConfigurationSource(ignoredMember) != ConfigurationSource.Explicit)
{
- if (entityType.FindIgnoredConfigurationSource(ignoredMember) != ConfigurationSource.Explicit)
- {
- continue;
- }
-
- var property = entityType.FindProperty(ignoredMember);
- if (property != null)
- {
- if (property.DeclaringType != entityType)
- {
- throw new InvalidOperationException(
- CoreStrings.InheritedPropertyCannotBeIgnored(
- ignoredMember, entityType.DisplayName(), property.DeclaringType.DisplayName()));
- }
-
- Check.DebugFail("Should never get here...");
- }
-
- var navigation = entityType.FindNavigation(ignoredMember);
- if (navigation != null)
- {
- if (navigation.DeclaringEntityType != entityType)
- {
- throw new InvalidOperationException(
- CoreStrings.InheritedPropertyCannotBeIgnored(
- ignoredMember, entityType.DisplayName(), navigation.DeclaringEntityType.DisplayName()));
- }
-
- Check.DebugFail("Should never get here...");
- }
-
- var skipNavigation = entityType.FindSkipNavigation(ignoredMember);
- if (skipNavigation != null)
- {
- if (skipNavigation.DeclaringEntityType != entityType)
- {
- throw new InvalidOperationException(
- CoreStrings.InheritedPropertyCannotBeIgnored(
- ignoredMember, entityType.DisplayName(), skipNavigation.DeclaringEntityType.DisplayName()));
- }
-
- Check.DebugFail("Should never get here...");
- }
-
- var serviceProperty = entityType.FindServiceProperty(ignoredMember);
- if (serviceProperty != null)
- {
- if (serviceProperty.DeclaringEntityType != entityType)
- {
- throw new InvalidOperationException(
- CoreStrings.InheritedPropertyCannotBeIgnored(
- ignoredMember, entityType.DisplayName(), serviceProperty.DeclaringEntityType.DisplayName()));
- }
+ continue;
+ }
- Check.DebugFail("Should never get here...");
- }
+ var property = entityType.FindProperty(ignoredMember);
+ if (property != null)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.InheritedPropertyCannotBeIgnored(
+ ignoredMember, entityType.DisplayName(), property.DeclaringType.DisplayName()));
}
- }
- }
- ///
- /// Validates the mapping/configuration of shadow keys in the model.
- ///
- /// The model to validate.
- /// The logger to use.
- protected virtual void ValidateNoShadowKeys(
- IModel model,
- IDiagnosticsLogger logger)
- {
- foreach (IConventionEntityType entityType in model.GetEntityTypes())
- {
- foreach (var key in entityType.GetDeclaredKeys())
+ var navigation = entityType.FindNavigation(ignoredMember);
+ if (navigation != null)
{
- if (key.Properties.Any(p => p.IsShadowProperty())
- && ConfigurationSource.Convention.Overrides(key.GetConfigurationSource())
- && !key.IsPrimaryKey())
- {
- var referencingFk = key.GetReferencingForeignKeys().FirstOrDefault();
+ throw new InvalidOperationException(
+ CoreStrings.InheritedPropertyCannotBeIgnored(
+ ignoredMember, entityType.DisplayName(), navigation.DeclaringEntityType.DisplayName()));
+ }
- if (referencingFk != null)
- {
- throw new InvalidOperationException(
- CoreStrings.ReferencedShadowKey(
- referencingFk.DeclaringEntityType.DisplayName()
- + (referencingFk.DependentToPrincipal == null
- ? ""
- : "." + referencingFk.DependentToPrincipal.Name),
- entityType.DisplayName()
- + (referencingFk.PrincipalToDependent == null
- ? ""
- : "." + referencingFk.PrincipalToDependent.Name),
- referencingFk.Properties.Format(includeTypes: true),
- entityType.FindPrimaryKey()!.Properties.Format(includeTypes: true)));
- }
- }
+ var skipNavigation = entityType.FindSkipNavigation(ignoredMember);
+ if (skipNavigation != null)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.InheritedPropertyCannotBeIgnored(
+ ignoredMember, entityType.DisplayName(), skipNavigation.DeclaringEntityType.DisplayName()));
}
- }
- }
- ///
- /// Validates the mapping/configuration of mutable keys in the model.
- ///
- /// The model to validate.
- /// The logger to use.
- protected virtual void ValidateNoMutableKeys(
- IModel model,
- IDiagnosticsLogger logger)
- {
- foreach (var entityType in model.GetEntityTypes())
- {
- foreach (var key in entityType.GetDeclaredKeys())
+ var serviceProperty = entityType.FindServiceProperty(ignoredMember);
+ if (serviceProperty != null)
{
- var mutableProperty = key.Properties.FirstOrDefault(p => p.ValueGenerated.HasFlag(ValueGenerated.OnUpdate));
- if (mutableProperty != null)
- {
- throw new InvalidOperationException(CoreStrings.MutableKeyProperty(mutableProperty.Name));
- }
+ throw new InvalidOperationException(
+ CoreStrings.InheritedPropertyCannotBeIgnored(
+ ignoredMember, entityType.DisplayName(), serviceProperty.DeclaringEntityType.DisplayName()));
}
}
}
///
- /// Validates the mapping/configuration of the model for cycles.
+ /// Validates that a key doesn't have shadow properties inappropriately.
///
- /// The model to validate.
+ /// The key to validate.
/// The logger to use.
- protected virtual void ValidateNoCycles(
- IModel model,
+ protected virtual void ValidateShadowKey(
+ IKey key,
IDiagnosticsLogger logger)
{
- var graph = new Multigraph();
- foreach (var entityType in model.GetEntityTypes())
+ if (key is IConventionKey conventionKey
+ && key.Properties.Any(p => p.IsShadowProperty())
+ && ConfigurationSource.Convention.Overrides(conventionKey.GetConfigurationSource())
+ && !key.IsPrimaryKey())
{
- var primaryKey = entityType.FindPrimaryKey();
- if (primaryKey == null)
- {
- continue;
- }
+ var referencingFk = key.GetReferencingForeignKeys().FirstOrDefault();
- foreach (var foreignKey in entityType.GetForeignKeys())
+ if (referencingFk != null)
{
- var principalType = foreignKey.PrincipalEntityType;
- if (!foreignKey.PrincipalKey.IsPrimaryKey()
- || !PropertyListComparer.Instance.Equals(foreignKey.Properties, primaryKey.Properties)
- || foreignKey.PrincipalEntityType.IsAssignableFrom(entityType))
- {
- continue;
- }
-
- graph.AddVertex(entityType);
- graph.AddVertex(principalType);
- graph.AddEdge(entityType, principalType, foreignKey);
+ throw new InvalidOperationException(
+ CoreStrings.ReferencedShadowKey(
+ referencingFk.DeclaringEntityType.DisplayName()
+ + (referencingFk.DependentToPrincipal == null
+ ? ""
+ : "." + referencingFk.DependentToPrincipal.Name),
+ key.DeclaringEntityType.DisplayName()
+ + (referencingFk.PrincipalToDependent == null
+ ? ""
+ : "." + referencingFk.PrincipalToDependent.Name),
+ referencingFk.Properties.Format(includeTypes: true),
+ key.DeclaringEntityType.FindPrimaryKey()!.Properties.Format(includeTypes: true)));
}
}
-
- graph.TopologicalSort(
- tryBreakEdge: null,
- formatCycle: c => c.Select(d => d.Item1.DisplayName()).Join(" -> "),
- CoreStrings.IdentifyingRelationshipCycle);
}
///
- /// Validates that all trackable entity types have a primary key.
+ /// Validates that a key doesn't have mutable properties.
///
- /// The model to validate.
+ /// The key to validate.
/// The logger to use.
- protected virtual void ValidateNonNullPrimaryKeys(
- IModel model,
+ protected virtual void ValidateMutableKey(
+ IKey key,
IDiagnosticsLogger logger)
{
- var entityTypeWithNullPk
- = model.GetEntityTypes()
- .FirstOrDefault(et => !((IConventionEntityType)et).IsKeyless && et.BaseType == null && et.FindPrimaryKey() == null);
-
- if (entityTypeWithNullPk != null)
+ var mutableProperty = key.Properties.FirstOrDefault(p => p.ValueGenerated.HasFlag(ValueGenerated.OnUpdate));
+ if (mutableProperty != null)
{
- throw new InvalidOperationException(
- CoreStrings.EntityRequiresKey(entityTypeWithNullPk.DisplayName()));
+ throw new InvalidOperationException(CoreStrings.MutableKeyProperty(mutableProperty.Name));
}
}
///
- /// Validates the mapping/configuration of inheritance in the model.
+ /// Validates that a trackable entity type has a primary key.
///
- /// The model to validate.
+ /// The entity type to validate.
/// The logger to use.
- protected virtual void ValidateClrInheritance(
- IModel model,
+ protected virtual void ValidateNonNullPrimaryKey(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- var validEntityTypes = new HashSet();
- foreach (var entityType in model.GetEntityTypes())
+ if (!((IConventionEntityType)entityType).IsKeyless
+ && entityType.BaseType == null
+ && entityType.FindPrimaryKey() == null)
{
- ValidateClrInheritance(model, entityType, validEntityTypes);
+ throw new InvalidOperationException(
+ CoreStrings.EntityRequiresKey(entityType.DisplayName()));
}
}
private static void ValidateClrInheritance(
- IModel model,
IEntityType entityType,
HashSet validEntityTypes)
{
@@ -627,7 +704,7 @@ private static void ValidateClrInheritance(
var baseClrType = entityType.ClrType.BaseType;
while (baseClrType != null)
{
- var baseEntityType = model.FindEntityType(baseClrType);
+ var baseEntityType = entityType.Model.FindEntityType(baseClrType);
if (baseEntityType != null)
{
if (!baseEntityType.IsAssignableFrom(entityType))
@@ -655,22 +732,7 @@ private static void ValidateClrInheritance(
}
///
- /// Validates the mapping of inheritance in the model.
- ///
- /// The model to validate.
- /// The logger to use.
- protected virtual void ValidateInheritanceMapping(
- IModel model,
- IDiagnosticsLogger logger)
- {
- foreach (var rootEntityType in model.GetRootEntityTypes())
- {
- ValidateDiscriminatorValues(rootEntityType);
- }
- }
-
- ///
- /// Validates the discriminator and values for all entity types derived from the given one.
+ /// Validates the discriminator and values for all entity types derived from the given one.
///
/// The entity type to validate.
protected virtual void ValidateDiscriminatorValues(IEntityType rootEntityType)
@@ -804,107 +866,86 @@ protected virtual void ValidateDiscriminatorValues(IComplexType complexType)
discriminatorValues[discriminatorValue] = derivedType;
}
}
-
- ///
- /// Validates the mapping/configuration of change tracking in the model.
- ///
- /// The model to validate.
- /// The logger to use.
- protected virtual void ValidateChangeTrackingStrategy(
- IModel model,
+ private void ValidateChangeTrackingStrategy(
+ ITypeBase structuralType,
IDiagnosticsLogger logger)
{
- var requireFullNotifications = (bool?)model[CoreAnnotationNames.FullChangeTrackingNotificationsRequired] == true;
- foreach (var entityType in model.GetEntityTypes())
- {
- Validate(entityType, requireFullNotifications);
- }
+ var requireFullNotifications =
+ (bool?)structuralType.Model[CoreAnnotationNames.FullChangeTrackingNotificationsRequired] == true;
+ var errorMessage = TypeBase.CheckChangeTrackingStrategy(
+ structuralType, structuralType.GetChangeTrackingStrategy(), requireFullNotifications);
- static void Validate(ITypeBase structuralType, bool requireFullNotifications)
+ if (errorMessage != null)
{
- var errorMessage = TypeBase.CheckChangeTrackingStrategy(
- structuralType, structuralType.GetChangeTrackingStrategy(), requireFullNotifications);
-
- if (errorMessage != null)
- {
- throw new InvalidOperationException(errorMessage);
- }
-
- foreach (var complexProperty in structuralType.GetComplexProperties())
- {
- Validate(complexProperty.ComplexType, requireFullNotifications);
- }
+ throw new InvalidOperationException(errorMessage);
}
}
///
- /// Validates the mapping/configuration of ownership in the model.
+ /// Validates ownership configuration for an entity type.
///
- /// The model to validate.
+ /// The entity type to validate.
/// The logger to use.
protected virtual void ValidateOwnership(
- IModel model,
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ var ownerships = entityType.GetForeignKeys().Where(fk => fk.IsOwnership).ToList();
+ if (ownerships.Count > 1)
{
- var ownerships = entityType.GetForeignKeys().Where(fk => fk.IsOwnership).ToList();
- if (ownerships.Count > 1)
+ throw new InvalidOperationException(
+ CoreStrings.MultipleOwnerships(
+ entityType.DisplayName(),
+ string.Join(
+ ", ",
+ ownerships.Select(o => $"'{o.PrincipalEntityType.DisplayName()}.{o.PrincipalToDependent?.Name}'"))));
+ }
+
+ if (ownerships.Count == 1)
+ {
+ Check.DebugAssert(entityType.IsOwned(), $"Expected the entity type {entityType.DisplayName()} to be marked as owned");
+
+ var ownership = ownerships[0];
+ if (entityType.BaseType != null)
{
- throw new InvalidOperationException(
- CoreStrings.MultipleOwnerships(
- entityType.DisplayName(),
- string.Join(
- ", ",
- ownerships.Select(o => $"'{o.PrincipalEntityType.DisplayName()}.{o.PrincipalToDependent?.Name}'"))));
+ throw new InvalidOperationException(CoreStrings.OwnedDerivedType(entityType.DisplayName()));
}
- if (ownerships.Count == 1)
+ foreach (var referencingFk in entityType.GetReferencingForeignKeys().Where(fk => !fk.IsOwnership
+ && (fk.PrincipalEntityType != fk.DeclaringEntityType
+ || !fk.Properties.SequenceEqual(entityType.FindPrimaryKey()!.Properties))
+ && !Contains(fk.DeclaringEntityType.FindOwnership(), fk)))
{
- Check.DebugAssert(entityType.IsOwned(), $"Expected the entity type {entityType.DisplayName()} to be marked as owned");
-
- var ownership = ownerships[0];
- if (entityType.BaseType != null)
- {
- throw new InvalidOperationException(CoreStrings.OwnedDerivedType(entityType.DisplayName()));
- }
-
- foreach (var referencingFk in entityType.GetReferencingForeignKeys().Where(fk => !fk.IsOwnership
- && (fk.PrincipalEntityType != fk.DeclaringEntityType
- || !fk.Properties.SequenceEqual(entityType.FindPrimaryKey()!.Properties))
- && !Contains(fk.DeclaringEntityType.FindOwnership(), fk)))
- {
- throw new InvalidOperationException(
- CoreStrings.PrincipalOwnedType(
- referencingFk.DeclaringEntityType.DisplayName()
- + (referencingFk.DependentToPrincipal == null
- ? ""
- : "." + referencingFk.DependentToPrincipal.Name),
- referencingFk.PrincipalEntityType.DisplayName()
- + (referencingFk.PrincipalToDependent == null
- ? ""
- : "." + referencingFk.PrincipalToDependent.Name),
- entityType.DisplayName()));
- }
-
- foreach (var fk in entityType.GetDeclaredForeignKeys().Where(fk
- => fk is { IsOwnership: false, PrincipalToDependent: not null }
- && !Contains(fk.DeclaringEntityType.FindOwnership(), fk)))
- {
- throw new InvalidOperationException(
- CoreStrings.InverseToOwnedType(
- fk.PrincipalEntityType.DisplayName(),
- fk.PrincipalToDependent!.Name,
- entityType.DisplayName(),
- ownership.PrincipalEntityType.DisplayName()));
- }
+ throw new InvalidOperationException(
+ CoreStrings.PrincipalOwnedType(
+ referencingFk.DeclaringEntityType.DisplayName()
+ + (referencingFk.DependentToPrincipal == null
+ ? ""
+ : "." + referencingFk.DependentToPrincipal.Name),
+ referencingFk.PrincipalEntityType.DisplayName()
+ + (referencingFk.PrincipalToDependent == null
+ ? ""
+ : "." + referencingFk.PrincipalToDependent.Name),
+ entityType.DisplayName()));
}
- else if (((IConventionModel)model).IsOwned(entityType.ClrType)
- || entityType.IsOwned())
+
+ foreach (var fk in entityType.GetDeclaredForeignKeys().Where(fk
+ => fk is { IsOwnership: false, PrincipalToDependent: not null }
+ && !Contains(fk.DeclaringEntityType.FindOwnership(), fk)))
{
- throw new InvalidOperationException(CoreStrings.OwnerlessOwnedType(entityType.DisplayName()));
+ throw new InvalidOperationException(
+ CoreStrings.InverseToOwnedType(
+ fk.PrincipalEntityType.DisplayName(),
+ fk.PrincipalToDependent!.Name,
+ entityType.DisplayName(),
+ ownership.PrincipalEntityType.DisplayName()));
}
}
+ else if (((IConventionModel)entityType.Model).IsOwned(entityType.ClrType)
+ || entityType.IsOwned())
+ {
+ throw new InvalidOperationException(CoreStrings.OwnerlessOwnedType(entityType.DisplayName()));
+ }
}
private static bool Contains(IForeignKey? inheritedFk, IForeignKey derivedFk)
@@ -913,50 +954,68 @@ private static bool Contains(IForeignKey? inheritedFk, IForeignKey derivedFk)
&& PropertyListComparer.Instance.Equals(inheritedFk.Properties, derivedFk.Properties);
///
- /// Validates the mapping/configuration of foreign keys in the model.
+ /// Validates a foreign key.
///
- /// The model to validate.
+ /// The foreign key to validate.
/// The logger to use.
- protected virtual void ValidateForeignKeys(
- IModel model,
+ protected virtual void ValidateForeignKey(
+ IForeignKey foreignKey,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ ValidateAmbiguousOneToOneRelationship(foreignKey, logger);
+ ValidateRedundantForeignKey(foreignKey, logger);
+ ValidateForeignKeyPropertyInKey(foreignKey, logger);
+ }
+
+ ///
+ /// Validates that a foreign key is not redundant and logs a warning if it is.
+ ///
+ /// The foreign key to validate.
+ /// The logger to use.
+ protected virtual void ValidateRedundantForeignKey(
+ IForeignKey foreignKey,
+ IDiagnosticsLogger logger)
+ {
+ if (IsRedundant(foreignKey))
{
- foreach (var declaredForeignKey in entityType.GetDeclaredForeignKeys())
- {
- if (IsRedundant(declaredForeignKey))
- {
- logger.RedundantForeignKeyWarning(declaredForeignKey);
- }
+ logger.RedundantForeignKeyWarning(foreignKey);
+ }
+ }
- if (entityType.BaseType == null
- || declaredForeignKey.IsBaseLinking())
- {
- continue;
- }
+ ///
+ /// Validates that foreign key properties are not part of an inherited key that would cause issues.
+ ///
+ /// The foreign key to validate.
+ /// The logger to use.
+ protected virtual void ValidateForeignKeyPropertyInKey(
+ IForeignKey foreignKey,
+ IDiagnosticsLogger logger)
+ {
+ if (foreignKey.DeclaringEntityType.BaseType == null
+ || foreignKey.IsBaseLinking())
+ {
+ return;
+ }
- foreach (var generatedProperty in declaredForeignKey.Properties)
- {
- if (!generatedProperty.ValueGenerated.ForAdd())
- {
- continue;
- }
+ foreach (var generatedProperty in foreignKey.Properties)
+ {
+ if (!generatedProperty.ValueGenerated.ForAdd())
+ {
+ continue;
+ }
- foreach (var inheritedKey in generatedProperty.GetContainingKeys())
- {
- if (inheritedKey.DeclaringEntityType != entityType
- && inheritedKey.Properties.All(p => declaredForeignKey.Properties.Contains(p))
- && !ContainedInForeignKeyForAllConcreteTypes(inheritedKey.DeclaringEntityType, generatedProperty))
- {
- throw new InvalidOperationException(
- CoreStrings.ForeignKeyPropertyInKey(
- generatedProperty.Name,
- entityType.DisplayName(),
- inheritedKey.Properties.Format(),
- inheritedKey.DeclaringEntityType.DisplayName()));
- }
- }
+ foreach (var inheritedKey in generatedProperty.GetContainingKeys())
+ {
+ if (inheritedKey.DeclaringEntityType != foreignKey.DeclaringEntityType
+ && inheritedKey.Properties.All(p => foreignKey.Properties.Contains(p))
+ && !ContainedInForeignKeyForAllConcreteTypes(inheritedKey.DeclaringEntityType, generatedProperty))
+ {
+ throw new InvalidOperationException(
+ CoreStrings.ForeignKeyPropertyInKey(
+ generatedProperty.Name,
+ foreignKey.DeclaringEntityType.DisplayName(),
+ inheritedKey.Properties.Format(),
+ inheritedKey.DeclaringEntityType.DisplayName()));
}
}
}
@@ -978,478 +1037,436 @@ protected virtual bool IsRedundant(IForeignKey foreignKey)
&& foreignKey.PrincipalKey.Properties.SequenceEqual(foreignKey.Properties);
///
- /// Validates the mapping/configuration of properties mapped to fields in the model.
+ /// Validates field mapping configuration for a structural type.
///
- /// The model to validate.
+ /// The structural type to validate.
/// The logger to use.
protected virtual void ValidateFieldMapping(
- IModel model,
+ ITypeBase structuralType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
- {
- Validate(entityType);
- }
+ var properties = new HashSet(
+ structuralType
+ .GetDeclaredMembers()
+ .Where(p => !p.IsShadowProperty() && !p.IsIndexerProperty()));
- static void Validate(ITypeBase structuralType)
+ var fieldProperties = new Dictionary();
+ foreach (var propertyBase in properties)
{
- var properties = new HashSet(
- structuralType
- .GetDeclaredMembers()
- .Where(p => !p.IsShadowProperty() && !p.IsIndexerProperty()));
+ var field = propertyBase.FieldInfo;
+ if (field == null)
+ {
+ continue;
+ }
- var fieldProperties = new Dictionary();
- foreach (var propertyBase in properties)
+ if (fieldProperties.TryGetValue(field, out var conflictingProperty))
{
- var field = propertyBase.FieldInfo;
- if (field == null)
- {
- continue;
- }
+ throw new InvalidOperationException(
+ CoreStrings.ConflictingFieldProperty(
+ propertyBase.DeclaringType.DisplayName(),
+ propertyBase.Name,
+ field.Name,
+ conflictingProperty.DeclaringType.DisplayName(),
+ conflictingProperty.Name));
+ }
- if (fieldProperties.TryGetValue(field, out var conflictingProperty))
- {
- throw new InvalidOperationException(
- CoreStrings.ConflictingFieldProperty(
- propertyBase.DeclaringType.DisplayName(),
- propertyBase.Name,
- field.Name,
- conflictingProperty.DeclaringType.DisplayName(),
- conflictingProperty.Name));
- }
+ fieldProperties.Add(field, propertyBase);
+ }
- fieldProperties.Add(field, propertyBase);
+ var constructorBinding = structuralType.ConstructorBinding;
+ if (constructorBinding != null)
+ {
+ foreach (var consumedProperty in constructorBinding.ParameterBindings.SelectMany(p => p.ConsumedProperties))
+ {
+ properties.Remove(consumedProperty);
}
+ }
- var constructorBinding = structuralType.ConstructorBinding;
- if (constructorBinding != null)
+ foreach (var propertyBase in properties)
+ {
+ if (!propertyBase.TryGetMemberInfo(
+ forMaterialization: true,
+ forSet: true,
+ memberInfo: out _,
+ errorMessage: out var errorMessage))
{
- foreach (var consumedProperty in constructorBinding.ParameterBindings.SelectMany(p => p.ConsumedProperties))
- {
- properties.Remove(consumedProperty);
- }
+ throw new InvalidOperationException(errorMessage);
}
- foreach (var propertyBase in properties)
+ if (!propertyBase.TryGetMemberInfo(
+ forMaterialization: false,
+ forSet: true,
+ memberInfo: out _,
+ errorMessage: out errorMessage))
{
- if (!propertyBase.TryGetMemberInfo(
- forMaterialization: true,
- forSet: true,
- memberInfo: out _,
- errorMessage: out var errorMessage))
- {
- throw new InvalidOperationException(errorMessage);
- }
-
- if (!propertyBase.TryGetMemberInfo(
- forMaterialization: false,
- forSet: true,
- memberInfo: out _,
- errorMessage: out errorMessage))
- {
- throw new InvalidOperationException(errorMessage);
- }
-
- if (!propertyBase.TryGetMemberInfo(
- forMaterialization: false,
- forSet: false,
- memberInfo: out _,
- errorMessage: out errorMessage))
- {
- throw new InvalidOperationException(errorMessage);
- }
+ throw new InvalidOperationException(errorMessage);
}
- foreach (var complexProperty in structuralType.GetDeclaredComplexProperties())
+ if (!propertyBase.TryGetMemberInfo(
+ forMaterialization: false,
+ forSet: false,
+ memberInfo: out _,
+ errorMessage: out errorMessage))
{
- if (complexProperty.IsCollection
- && !complexProperty.ClrType.GetGenericTypeImplementations(typeof(IList<>)).Any())
- {
- throw new InvalidOperationException(
- CoreStrings.NonListCollection(
- complexProperty.DeclaringType.DisplayName(),
- complexProperty.Name,
- complexProperty.ClrType.ShortDisplayName(),
- $"IList<{complexProperty.ComplexType.ClrType.ShortDisplayName()}>"));
- }
+ throw new InvalidOperationException(errorMessage);
+ }
+ }
- Validate(complexProperty.ComplexType);
+ foreach (var complexProperty in structuralType.GetDeclaredComplexProperties())
+ {
+ if (complexProperty.IsCollection
+ && !complexProperty.ClrType.GetGenericTypeImplementations(typeof(IList<>)).Any())
+ {
+ throw new InvalidOperationException(
+ CoreStrings.NonListCollection(
+ complexProperty.DeclaringType.DisplayName(),
+ complexProperty.Name,
+ complexProperty.ClrType.ShortDisplayName(),
+ $"IList<{complexProperty.ComplexType.ClrType.ShortDisplayName()}>"));
}
+
+ ValidateFieldMapping(complexProperty.ComplexType, logger);
}
}
///
- /// Validates the type mapping of properties in the model.
+ /// Validates type mapping for a property.
///
- /// The model to validate.
+ /// The property to validate.
/// The logger to use.
- protected virtual void ValidateTypeMappings(
- IModel model,
+ protected virtual void ValidateTypeMapping(
+ IProperty property,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ var converter = property.GetValueConverter();
+ if (converter != null
+ && property[CoreAnnotationNames.ValueComparer] == null)
{
- Validate(entityType, logger);
+ var type = converter.ModelClrType;
+ if (type != typeof(string)
+ && !(type == typeof(byte[]) && property.IsKey()) // Already special-cased elsewhere
+ && !property.IsForeignKey()
+ && type.TryGetSequenceType() != null)
+ {
+ logger.CollectionWithoutComparer(property);
+ }
}
- static void Validate(ITypeBase structuralType, IDiagnosticsLogger logger)
+ if (property.IsKey()
+ || property.IsForeignKey()
+ || property.IsUniqueIndex())
{
- foreach (var property in structuralType.GetDeclaredProperties())
- {
- var converter = property.GetValueConverter();
- if (converter != null
- && property[CoreAnnotationNames.ValueComparer] == null)
- {
- var type = converter.ModelClrType;
- if (type != typeof(string)
- && !(type == typeof(byte[]) && property.IsKey()) // Already special-cased elsewhere
- && !property.IsForeignKey()
- && type.TryGetSequenceType() != null)
- {
- logger.CollectionWithoutComparer(property);
- }
- }
-
- if (property.IsKey()
- || property.IsForeignKey()
- || property.IsUniqueIndex())
- {
- _ = property.GetCurrentValueComparer(); // Will throw if there is no way to compare
- }
+ _ = property.GetCurrentValueComparer(); // Will throw if there is no way to compare
+ }
- var providerComparer = property.GetProviderValueComparer();
- if (providerComparer == null)
- {
- continue;
- }
+ var providerComparer = property.GetProviderValueComparer();
+ if (providerComparer == null)
+ {
+ return;
+ }
- var typeMapping = property.GetTypeMapping();
- var actualProviderClrType = (typeMapping.Converter?.ProviderClrType ?? typeMapping.ClrType).UnwrapNullableType();
+ var typeMapping = property.GetTypeMapping();
+ var actualProviderClrType = (typeMapping.Converter?.ProviderClrType ?? typeMapping.ClrType).UnwrapNullableType();
- if (providerComparer.Type.UnwrapNullableType() != actualProviderClrType)
- {
- throw new InvalidOperationException(
- CoreStrings.ComparerPropertyMismatch(
- providerComparer.Type.ShortDisplayName(),
- property.DeclaringType.DisplayName(),
- property.Name,
- actualProviderClrType.ShortDisplayName()));
- }
- }
-
- foreach (var complexProperty in structuralType.GetDeclaredComplexProperties())
- {
- Validate(complexProperty.ComplexType, logger);
- }
+ if (providerComparer.Type.UnwrapNullableType() != actualProviderClrType)
+ {
+ throw new InvalidOperationException(
+ CoreStrings.ComparerPropertyMismatch(
+ providerComparer.Type.ShortDisplayName(),
+ property.DeclaringType.DisplayName(),
+ property.Name,
+ actualProviderClrType.ShortDisplayName()));
}
}
///
- /// Validates that common CLR types are not mapped accidentally as entity types.
+ /// Validates that an entity type is not accidentally mapped from common CLR types.
///
- /// The model to validate.
+ /// The entity type to validate.
/// The logger to use.
- protected virtual void ValidateEntityClrTypes(
- IModel model,
+ protected virtual void ValidateEntityClrType(
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ if (entityType.ClrType.IsGenericType)
{
- if (entityType.ClrType.IsGenericType)
+ var genericTypeDefinition = entityType.ClrType.GetGenericTypeDefinition();
+ if (genericTypeDefinition == typeof(List<>)
+ || genericTypeDefinition == typeof(HashSet<>)
+ || genericTypeDefinition == typeof(Collection<>)
+ || genericTypeDefinition == typeof(ObservableCollection<>))
{
- var genericTypeDefinition = entityType.ClrType.GetGenericTypeDefinition();
- if (genericTypeDefinition == typeof(List<>)
- || genericTypeDefinition == typeof(HashSet<>)
- || genericTypeDefinition == typeof(Collection<>)
- || genericTypeDefinition == typeof(ObservableCollection<>))
- {
- logger.AccidentalEntityType(entityType);
- }
+ logger.AccidentalEntityType(entityType);
}
}
}
///
- /// Validates the mapping of primitive collection properties in the model.
+ /// Validates a primitive collection property.
///
- /// The model to validate.
+ /// The property to validate.
/// The logger to use.
- protected virtual void ValidatePrimitiveCollections(
- IModel model,
+ protected virtual void ValidatePrimitiveCollection(
+ IProperty property,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
- {
- ValidateType(entityType);
- }
-
- static void ValidateType(ITypeBase structuralType)
+ var elementClrType = property.GetElementType()?.ClrType;
+ if (property is { IsPrimitiveCollection: true, ClrType.IsArray: false })
{
- foreach (var property in structuralType.GetDeclaredProperties())
- {
- var elementClrType = property.GetElementType()?.ClrType;
- if (property is { IsPrimitiveCollection: true, ClrType.IsArray: false })
- {
- if (property.ClrType.IsSealed && property.ClrType.TryGetElementType(typeof(IList<>)) == null)
- {
- throw new InvalidOperationException(
- CoreStrings.BadListType(
- property.ClrType.ShortDisplayName(),
- typeof(IList<>).MakeGenericType(elementClrType!).ShortDisplayName()));
- }
- }
- }
-
- foreach (var complexProperty in structuralType.GetDeclaredComplexProperties())
+ if (property.ClrType.IsSealed && property.ClrType.TryGetElementType(typeof(IList<>)) == null)
{
- ValidateType(complexProperty.ComplexType);
+ throw new InvalidOperationException(
+ CoreStrings.BadListType(
+ property.ClrType.ShortDisplayName(),
+ typeof(IList<>).MakeGenericType(elementClrType!).ShortDisplayName()));
}
}
}
///
- /// Validates the mapping/configuration of query filters in the model.
+ /// Validates query filter configuration for an entity type.
///
- /// The model to validate.
+ /// The entity type to validate.
/// The logger to use.
protected virtual void ValidateQueryFilters(
- IModel model,
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (var entityType in model.GetEntityTypes())
+ var queryFilters = entityType.GetDeclaredQueryFilters();
+ if (queryFilters.Count > 0)
{
- var queryFilters = entityType.GetDeclaredQueryFilters();
- if (queryFilters.Count > 0)
+ if (entityType.BaseType != null)
{
- if (entityType.BaseType != null)
- {
- throw new InvalidOperationException(
- CoreStrings.BadFilterDerivedType(
- queryFilters.First().Expression,
- entityType.DisplayName(),
- entityType.GetRootType().DisplayName()));
- }
+ throw new InvalidOperationException(
+ CoreStrings.BadFilterDerivedType(
+ queryFilters.First().Expression,
+ entityType.DisplayName(),
+ entityType.GetRootType().DisplayName()));
+ }
- if (entityType.IsOwned())
- {
- throw new InvalidOperationException(
- CoreStrings.BadFilterOwnedType(queryFilters.First().Expression, entityType.DisplayName()));
- }
+ if (entityType.IsOwned())
+ {
+ throw new InvalidOperationException(
+ CoreStrings.BadFilterOwnedType(queryFilters.First().Expression, entityType.DisplayName()));
}
+ }
- if (!entityType.IsOwned())
+ if (!entityType.IsOwned())
+ {
+ // Owned type doesn't allow to define query filter
+ // So we don't check navigations there. We assume the owner will propagate filtering
+ var requiredNavigationWithQueryFilter = entityType
+ .GetNavigations()
+ .FirstOrDefault(n => n is { IsCollection: false, ForeignKey.IsRequired: true, IsOnDependent: true }
+ && n.ForeignKey.PrincipalEntityType.GetRootType().GetDeclaredQueryFilters().Count > 0
+ && n.ForeignKey.DeclaringEntityType.GetRootType().GetDeclaredQueryFilters().Count == 0);
+
+ if (requiredNavigationWithQueryFilter != null)
{
- // Owned type doesn't allow to define query filter
- // So we don't check navigations there. We assume the owner will propagate filtering
- var requiredNavigationWithQueryFilter = entityType
- .GetNavigations()
- .FirstOrDefault(n => n is { IsCollection: false, ForeignKey.IsRequired: true, IsOnDependent: true }
- && n.ForeignKey.PrincipalEntityType.GetRootType().GetDeclaredQueryFilters().Count > 0
- && n.ForeignKey.DeclaringEntityType.GetRootType().GetDeclaredQueryFilters().Count == 0);
-
- if (requiredNavigationWithQueryFilter != null)
- {
- logger.PossibleIncorrectRequiredNavigationWithQueryFilterInteractionWarning(
- requiredNavigationWithQueryFilter.ForeignKey);
- }
+ logger.PossibleIncorrectRequiredNavigationWithQueryFilterInteractionWarning(
+ requiredNavigationWithQueryFilter.ForeignKey);
}
}
}
///
- /// Validates the mapping/configuration of data (e.g. seed data) in the model.
+ /// Validates seed data for an entity type.
///
- /// The model to validate.
+ /// The entity type to validate.
+ /// Shared identity maps for detecting duplicates.
+ /// Whether sensitive data should be logged.
/// The logger to use.
- protected virtual void ValidateData(IModel model, IDiagnosticsLogger logger)
+ protected virtual void ValidateData(
+ IEntityType entityType,
+ Dictionary identityMaps,
+ bool sensitiveDataLogged,
+ IDiagnosticsLogger logger)
{
- var identityMaps = new Dictionary();
- var sensitiveDataLogged = logger.ShouldLogSensitiveData();
-
- foreach (var entityType in model.GetEntityTypes())
+ var key = entityType.FindPrimaryKey();
+ if (key == null)
{
- var key = entityType.FindPrimaryKey();
- if (key == null)
+ if (entityType.GetSeedData().Any())
{
- if (entityType.GetSeedData().Any())
- {
- throw new InvalidOperationException(CoreStrings.SeedKeylessEntity(entityType.DisplayName()));
- }
-
- continue;
+ throw new InvalidOperationException(CoreStrings.SeedKeylessEntity(entityType.DisplayName()));
}
- IIdentityMap? identityMap = null;
- foreach (var seedDatum in entityType.GetSeedData())
+ return;
+ }
+
+ IIdentityMap? identityMap = null;
+ foreach (var seedDatum in entityType.GetSeedData())
+ {
+ foreach (var property in entityType.GetProperties())
{
- foreach (var property in entityType.GetProperties())
+ if (!seedDatum.TryGetValue(property.Name, out var value)
+ || value == null)
{
- if (!seedDatum.TryGetValue(property.Name, out var value)
- || value == null)
+ if (!property.IsNullable
+ && ((!property.RequiresValueGenerator()
+ && !property.ValueGenerated.ForAdd())
+ || property.IsPrimaryKey()))
{
- if (!property.IsNullable
- && ((!property.RequiresValueGenerator()
- && !property.ValueGenerated.ForAdd())
- || property.IsPrimaryKey()))
- {
- throw new InvalidOperationException(
- CoreStrings.SeedDatumMissingValue(entityType.DisplayName(), property.Name));
- }
+ throw new InvalidOperationException(
+ CoreStrings.SeedDatumMissingValue(entityType.DisplayName(), property.Name));
}
- else if (property.RequiresValueGenerator()
- && property.IsPrimaryKey()
- && property.ClrType.IsDefaultValue(value))
+ }
+ else if (property.RequiresValueGenerator()
+ && property.IsPrimaryKey()
+ && property.ClrType.IsDefaultValue(value))
+ {
+ if (property.ClrType.IsSignedInteger())
{
- if (property.ClrType.IsSignedInteger())
- {
- throw new InvalidOperationException(
- CoreStrings.SeedDatumSignedNumericValue(entityType.DisplayName(), property.Name));
- }
-
throw new InvalidOperationException(
- CoreStrings.SeedDatumDefaultValue(
- entityType.DisplayName(), property.Name, property.ClrType.GetDefaultValue()));
+ CoreStrings.SeedDatumSignedNumericValue(entityType.DisplayName(), property.Name));
}
- else if (!property.ClrType.IsAssignableFrom(value.GetType().GetTypeInfo()))
- {
- if (sensitiveDataLogged)
- {
- throw new InvalidOperationException(
- CoreStrings.SeedDatumIncompatibleValueSensitive(
- entityType.DisplayName(), value, property.Name, property.ClrType.DisplayName()));
- }
+ throw new InvalidOperationException(
+ CoreStrings.SeedDatumDefaultValue(
+ entityType.DisplayName(), property.Name, property.ClrType.GetDefaultValue()));
+ }
+ else if (!property.ClrType.IsAssignableFrom(value.GetType().GetTypeInfo()))
+ {
+ if (sensitiveDataLogged)
+ {
throw new InvalidOperationException(
- CoreStrings.SeedDatumIncompatibleValue(
- entityType.DisplayName(), property.Name, property.ClrType.DisplayName()));
+ CoreStrings.SeedDatumIncompatibleValueSensitive(
+ entityType.DisplayName(), value, property.Name, property.ClrType.DisplayName()));
}
- }
- var keyValues = new object[key.Properties.Count];
- for (var i = 0; i < key.Properties.Count; i++)
- {
- keyValues[i] = seedDatum[key.Properties[i].Name]!;
+ throw new InvalidOperationException(
+ CoreStrings.SeedDatumIncompatibleValue(
+ entityType.DisplayName(), property.Name, property.ClrType.DisplayName()));
}
+ }
+
+ var keyValues = new object[key.Properties.Count];
+ for (var i = 0; i < key.Properties.Count; i++)
+ {
+ keyValues[i] = seedDatum[key.Properties[i].Name]!;
+ }
- foreach (var navigation in entityType.GetNavigations().Concat(entityType.GetSkipNavigations()))
+ foreach (var navigation in entityType.GetNavigations().Concat(entityType.GetSkipNavigations()))
+ {
+ if (seedDatum.TryGetValue(navigation.Name, out var value)
+ && ((navigation.IsCollection && value is IEnumerable collection && collection.Any())
+ || (!navigation.IsCollection && value != null)))
{
- if (seedDatum.TryGetValue(navigation.Name, out var value)
- && ((navigation.IsCollection && value is IEnumerable collection && collection.Any())
- || (!navigation.IsCollection && value != null)))
+ var foreignKey = navigation is INavigation nav
+ ? nav.ForeignKey
+ : ((ISkipNavigation)navigation).ForeignKey;
+ if (sensitiveDataLogged)
{
- var foreignKey = navigation is INavigation nav
- ? nav.ForeignKey
- : ((ISkipNavigation)navigation).ForeignKey;
- if (sensitiveDataLogged)
- {
- throw new InvalidOperationException(
- CoreStrings.SeedDatumNavigationSensitive(
- entityType.DisplayName(),
- string.Join(", ", key.Properties.Select((p, i) => p.Name + ":" + keyValues[i])),
- navigation.Name,
- foreignKey.DeclaringEntityType.DisplayName(),
- foreignKey.Properties.Format()));
- }
-
throw new InvalidOperationException(
- CoreStrings.SeedDatumNavigation(
+ CoreStrings.SeedDatumNavigationSensitive(
entityType.DisplayName(),
+ string.Join(", ", key.Properties.Select((p, i) => p.Name + ":" + keyValues[i])),
navigation.Name,
foreignKey.DeclaringEntityType.DisplayName(),
foreignKey.Properties.Format()));
}
+
+ throw new InvalidOperationException(
+ CoreStrings.SeedDatumNavigation(
+ entityType.DisplayName(),
+ navigation.Name,
+ foreignKey.DeclaringEntityType.DisplayName(),
+ foreignKey.Properties.Format()));
}
+ }
- foreach (var complexProperty in entityType.GetComplexProperties())
+ foreach (var complexProperty in entityType.GetComplexProperties())
+ {
+ if (seedDatum.TryGetValue(complexProperty.Name, out var value)
+ && ((complexProperty.IsCollection && value is IEnumerable collection && collection.Any())
+ || (!complexProperty.IsCollection && value != complexProperty.Sentinel)))
{
- if (seedDatum.TryGetValue(complexProperty.Name, out var value)
- && ((complexProperty.IsCollection && value is IEnumerable collection && collection.Any())
- || (!complexProperty.IsCollection && value != complexProperty.Sentinel)))
+ if (sensitiveDataLogged)
{
- if (sensitiveDataLogged)
- {
- throw new InvalidOperationException(
- CoreStrings.SeedDatumComplexPropertySensitive(
- entityType.DisplayName(),
- string.Join(", ", key.Properties.Select((p, i) => p.Name + ":" + keyValues[i])),
- complexProperty.Name));
- }
-
throw new InvalidOperationException(
- CoreStrings.SeedDatumComplexProperty(
+ CoreStrings.SeedDatumComplexPropertySensitive(
entityType.DisplayName(),
+ string.Join(", ", key.Properties.Select((p, i) => p.Name + ":" + keyValues[i])),
complexProperty.Name));
}
+
+ throw new InvalidOperationException(
+ CoreStrings.SeedDatumComplexProperty(
+ entityType.DisplayName(),
+ complexProperty.Name));
}
+ }
- if (identityMap == null)
+ if (identityMap == null)
+ {
+ if (!identityMaps.TryGetValue(key, out identityMap))
{
- if (!identityMaps.TryGetValue(key, out identityMap))
- {
- identityMap = ((IRuntimeKey)key).GetIdentityMapFactory()(sensitiveDataLogged);
- identityMaps[key] = identityMap;
- }
+ identityMap = ((IRuntimeKey)key).GetIdentityMapFactory()(sensitiveDataLogged);
+ identityMaps[key] = identityMap;
}
+ }
- var entry = identityMap.TryGetEntry(keyValues);
- if (entry != null)
+ var entry = identityMap.TryGetEntry(keyValues);
+ if (entry != null)
+ {
+ if (sensitiveDataLogged)
{
- if (sensitiveDataLogged)
- {
- throw new InvalidOperationException(
- CoreStrings.SeedDatumDuplicateSensitive(
- entityType.DisplayName(),
- string.Join(", ", key.Properties.Select((p, i) => p.Name + ":" + keyValues[i]))));
- }
-
throw new InvalidOperationException(
- CoreStrings.SeedDatumDuplicate(
- entityType.DisplayName(), key.Properties.Format()));
+ CoreStrings.SeedDatumDuplicateSensitive(
+ entityType.DisplayName(),
+ string.Join(", ", key.Properties.Select((p, i) => p.Name + ":" + keyValues[i]))));
}
- entry = new InternalEntityEntry(null!, entityType, seedDatum);
- identityMap.Add(keyValues, entry);
+ throw new InvalidOperationException(
+ CoreStrings.SeedDatumDuplicate(
+ entityType.DisplayName(), key.Properties.Format()));
}
+
+ entry = new InternalEntityEntry(null!, entityType, seedDatum);
+ identityMap.Add(keyValues, entry);
}
}
///
- /// Validates triggers.
+ /// Validates a single trigger.
///
- /// The model to validate.
+ /// The trigger to validate.
+ /// The entity type that declares the trigger.
/// The logger to use.
- protected virtual void ValidateTriggers(
- IModel model,
+ protected virtual void ValidateTrigger(
+ ITrigger trigger,
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
}
///
- /// Logs all shadow properties that were created because there was no matching CLR member.
+ /// Logs all shadow properties for an entity type.
///
- /// The model to validate.
+ /// The entity type to check.
/// The logger to use.
protected virtual void LogShadowProperties(
- IModel model,
+ IEntityType entityType,
IDiagnosticsLogger logger)
{
- foreach (IConventionEntityType entityType in model.GetEntityTypes())
+ if (entityType is not IConventionEntityType conventionEntityType)
+ {
+ return;
+ }
+
+ foreach (var property in conventionEntityType.GetDeclaredProperties())
{
- foreach (var property in entityType.GetDeclaredProperties())
+ if (property.IsShadowProperty()
+ && property.GetConfigurationSource() == ConfigurationSource.Convention)
{
- if (property.IsShadowProperty()
- && property.GetConfigurationSource() == ConfigurationSource.Convention)
+ var uniquifiedAnnotation = property.FindAnnotation(CoreAnnotationNames.PreUniquificationName);
+ if (uniquifiedAnnotation != null
+ && property.IsForeignKey())
{
- var uniquifiedAnnotation = property.FindAnnotation(CoreAnnotationNames.PreUniquificationName);
- if (uniquifiedAnnotation != null
- && property.IsForeignKey())
- {
- logger.ShadowForeignKeyPropertyCreated((IProperty)property, (string)uniquifiedAnnotation.Value!);
- }
- else
- {
- logger.ShadowPropertyCreated((IProperty)property);
- }
+ logger.ShadowForeignKeyPropertyCreated((IProperty)property, (string)uniquifiedAnnotation.Value!);
+ }
+ else
+ {
+ logger.ShadowPropertyCreated((IProperty)property);
}
}
}
diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs
index 697d768f712..ce0b366410e 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocAdvancedMappingsQueryRelationalTestBase.cs
@@ -161,7 +161,7 @@ public class NestedEntity : EntityBase
public Payment Payment { get; set; } = new(0, 0);
}
- public record Payment(decimal Netto, decimal Brutto);
+ public record Payment(double Netto, double Brutto);
}
protected class Context32911_2(DbContextOptions options) : DbContext(options)
diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
index 489f087ca0a..0eb2ae524f5 100644
--- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
+++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs
@@ -863,7 +863,7 @@ public virtual void Detects_entity_splitting_on_derived_type()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity().ToTable("Animal");
- modelBuilder.Entity().ToTable("Cat").SplitToTable("CatDetails", s => s.Property(a => a.Name));
+ modelBuilder.Entity().ToTable("Cat").SplitToTable("CatDetails", s => s.Property(c => c.Breed));
VerifyError(
RelationalStrings.EntitySplittingHierarchy(nameof(Cat), "CatDetails"),
@@ -874,7 +874,7 @@ public virtual void Detects_entity_splitting_on_derived_type()
public virtual void Detects_entity_splitting_with_unmapped_main()
{
var modelBuilder = CreateConventionModelBuilder();
- modelBuilder.Entity().SplitToView("AnimalDetails", s => s.Property(a => a.Name));
+ modelBuilder.Entity().SplitToView("AnimalDetails", s => s.Property(a => a.Id).HasColumnName("AnimalId"));
VerifyError(
RelationalStrings.EntitySplittingUnmappedMainFragment(nameof(Animal), "AnimalDetails", "View"),
@@ -2880,7 +2880,7 @@ public void Detects_derived_entity_type_mapped_to_a_different_function()
}
[ConditionalFact]
- public void Detects_multiple_entity_types_mapped_to_the_same_stored_procedure()
+ public virtual void Detects_multiple_entity_types_mapped_to_the_same_stored_procedure()
{
var modelBuilder = CreateConventionModelBuilder();
@@ -3515,7 +3515,7 @@ public void Passes_for_unnamed_index_with_all_properties_not_mapped_to_any_table
modelBuilder.Entity().HasIndex(nameof(Animal.Id), nameof(Animal.Name));
var definition = RelationalResources
- .LogUnnamedIndexAllPropertiesNotToMappedToAnyTable(
+ .LogUnnamedIndexAllPropertiesNotMappedToAnyTable(
new TestLogger());
VerifyWarning(
definition.GenerateMessage(
@@ -3536,7 +3536,7 @@ public void Passes_for_named_index_with_all_properties_not_mapped_to_any_table()
"IX_AllPropertiesNotMapped");
var definition = RelationalResources
- .LogNamedIndexAllPropertiesNotToMappedToAnyTable(
+ .LogNamedIndexAllPropertiesNotMappedToAnyTable(
new TestLogger());
VerifyWarning(
definition.GenerateMessage(
diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs
index 259bb4dcee8..8b6e1a6a65c 100644
--- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs
+++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs
@@ -3252,7 +3252,7 @@ protected virtual TestHelpers.TestModelBuilder CreateConventionModelBuilder()
configureContext: b =>
b.ConfigureWarnings(w => w.Default(WarningBehavior.Throw)
.Ignore(RelationalEventId.ForeignKeyTpcPrincipalWarning)
- .Ignore(RelationalEventId.AllIndexPropertiesNotToMappedToAnyTable)));
+ .Ignore(RelationalEventId.AllIndexPropertiesNotMappedToAnyTable)));
public static void AssertEqual(IRelationalModel expectedModel, IRelationalModel actualModel)
=> RelationalModelAsserter.Instance.AssertEqual(expectedModel, actualModel);
diff --git a/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs
index 0f0df7ca8e7..c8523e7e4b3 100644
--- a/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/ComplexTypesTrackingSqlServerTest.cs
@@ -290,7 +290,7 @@ public override void Can_write_original_values_for_properties_of_complex_record_
{
}
- // Fields can't be proxied
+ // Fields can't be proxied
public override Task Can_track_entity_with_complex_field_collections(EntityState state, bool async)
=> Task.CompletedTask;
@@ -358,17 +358,17 @@ protected override IServiceCollection AddServices(IServiceCollection serviceColl
}
}
-public abstract class ComplexTypesTrackingSqlServerTestBase : ComplexTypesTrackingRelationalTestBase
+public abstract class ComplexTypesTrackingSqlServerTestBase(TFixture fixture, ITestOutputHelper testOutputHelper)
+ : ComplexTypesTrackingRelationalTestBase(fixture, testOutputHelper)
where TFixture : ComplexTypesTrackingSqlServerTestBase.SqlServerFixtureBase, new()
{
- protected ComplexTypesTrackingSqlServerTestBase(TFixture fixture, ITestOutputHelper testOutputHelper)
- : base(fixture, testOutputHelper)
- {
- }
-
public abstract class SqlServerFixtureBase : RelationalFixtureBase
{
protected override ITestStoreFactory TestStoreFactory
=> SqlServerTestStoreFactory.Instance;
+
+ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
+ => base.AddOptions(builder)
+ .ConfigureWarnings(c => c.Ignore(SqlServerEventId.DecimalTypeDefaultWarning));
}
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/LazyLoadProxySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/LazyLoadProxySqlServerTest.cs
index 8b0367fd920..0a6facf8e62 100644
--- a/test/EFCore.SqlServer.FunctionalTests/LazyLoadProxySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/LazyLoadProxySqlServerTest.cs
@@ -536,5 +536,9 @@ public TestSqlLoggerFactory TestSqlLoggerFactory
protected override ITestStoreFactory TestStoreFactory
=> SqlServerTestStoreFactory.Instance;
+
+ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
+ => base.AddOptions(builder)
+ .ConfigureWarnings(c => c.Ignore(SqlServerEventId.DecimalTypeDefaultWarning));
}
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/PropertyValuesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/PropertyValuesSqlServerTest.cs
index 5532778ac42..70061bda7c7 100644
--- a/test/EFCore.SqlServer.FunctionalTests/PropertyValuesSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/PropertyValuesSqlServerTest.cs
@@ -17,8 +17,25 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
{
base.OnModelCreating(modelBuilder, context);
- modelBuilder.Entity()
- .Property(b => b.Value).HasColumnType("decimal(18,2)");
+ modelBuilder.Entity(b =>
+ {
+ b.Property(b => b.Value).HasColumnType("decimal(18,2)");
+
+ b.ComplexProperty(b => b.Culture)
+ .ComplexProperty(c => c.License)
+ .Property(l => l.Charge)
+ .HasColumnType("decimal(18,2)");
+
+ b.ComplexProperty(b => b.Milk)
+ .ComplexProperty(c => c.License)
+ .Property(l => l.Charge)
+ .HasColumnType("decimal(18,2)");
+
+ b.ComplexProperty(b => b.OptionalMilk)
+ .ComplexProperty(c => c.License)
+ .Property(l => l.Charge)
+ .HasColumnType("decimal(18,2)");
+ });
modelBuilder.Entity()
.Property(ce => ce.LeaveBalance).HasColumnType("decimal(18,2)");
diff --git a/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteDatabaseCleaner.cs b/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteDatabaseCleaner.cs
index ad0d1273068..35ab1bdd643 100644
--- a/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteDatabaseCleaner.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/TestUtilities/SqliteDatabaseCleaner.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Data;
+using System.Text;
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
using Microsoft.EntityFrameworkCore.Sqlite.Design.Internal;
@@ -21,6 +22,9 @@ protected override IDatabaseModelFactory CreateDatabaseModelFactory(ILoggerFacto
.GetRequiredService();
}
+ protected override bool AcceptTable(DatabaseTable table)
+ => table is not DatabaseView;
+
protected override bool AcceptForeignKey(DatabaseForeignKey foreignKey)
=> false;
@@ -28,7 +32,17 @@ protected override bool AcceptIndex(DatabaseIndex index)
=> false;
protected override string BuildCustomSql(DatabaseModel databaseModel)
- => "PRAGMA foreign_keys=OFF;";
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("PRAGMA foreign_keys=OFF;");
+
+ foreach (var view in databaseModel.Tables.OfType())
+ {
+ sb.AppendLine($"DROP VIEW IF EXISTS \"{view.Name}\";");
+ }
+
+ return sb.ToString();
+ }
protected override string BuildCustomEndingSql(DatabaseModel databaseModel)
=> "PRAGMA foreign_keys=ON;";
diff --git a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs
index 1ae2e320eda..8397788fde1 100644
--- a/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs
+++ b/test/EFCore.Sqlite.Tests/Infrastructure/SqliteModelValidatorTest.cs
@@ -150,6 +150,108 @@ public override void Detects_unmapped_concurrency_token()
Assert.Equal(SqliteStrings.StoredProceduresNotSupported(nameof(Animal)), exception.Message);
}
+ public override void Detects_non_generated_update_stored_procedure_result_columns_in_TPT()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_missing_generated_stored_procedure_parameters_in_TPT()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_missing_generated_stored_procedure_parameters_in_TPC()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_multiple_entity_types_mapped_to_the_same_stored_procedure()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_current_value_parameter_on_delete_stored_procedure()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_InsertUsingStoredProcedure_without_a_name()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_non_key_delete_stored_procedure_params_in_TPH()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_stored_procedure_input_parameter_for_update_non_save_property()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_unmatched_stored_procedure_result_columns_in_TPC()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_rows_affected_with_result_columns()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_original_value_parameter_on_insert_stored_procedure()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_non_generated_insert_stored_procedure_result_columns_in_TPH()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_unmatched_stored_procedure_result_columns_in_TPH()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_generated_properties_mapped_to_result_and_parameter()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_missing_stored_procedure_parameters_for_abstract_properties_in_TPT()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_unmatched_stored_procedure_result_columns_in_TPT()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_delete_stored_procedure_result_columns_in_TPH()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_unmatched_stored_procedure_parameters_in_TPT()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_generated_properties_mapped_to_original_and_current_parameter()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_tableless_entity_type_mapped_to_some_stored_procedures()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_duplicate_parameter()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_missing_generated_stored_procedure_parameters()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_non_generated_insert_stored_procedure_output_parameter_in_TPC()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_unmatched_stored_procedure_parameters_in_TPC()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_unmatched_stored_procedure_parameters_in_TPH()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_keyless_entity_type_mapped_to_a_stored_procedure()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_non_generated_update_stored_procedure_input_output_parameter()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_stored_procedure_input_parameter_for_insert_non_save_property()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_missing_generated_stored_procedure_parameters_in_TPH()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_rows_affected_on_insert_stored_procedure()
+ => VerifyStoredProceduresNotSupported();
+
+ public override void Detects_duplicate_result_column()
+ => VerifyStoredProceduresNotSupported();
+
+ private void VerifyStoredProceduresNotSupported()
+ {
+ var modelBuilder = CreateConventionModelBuilder();
+ modelBuilder.Entity()
+ .InsertUsingStoredProcedure("Insert", s => s.HasParameter(a => a.Id));
+
+ VerifyError(SqliteStrings.StoredProceduresNotSupported(nameof(Animal)), modelBuilder);
+ }
+
[ConditionalFact]
public void Detects_conflicting_autoincrement_and_default_value_sql()
{
diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs
index 17f7b7ba45d..b2f9e39f66b 100644
--- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs
+++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs
@@ -1008,6 +1008,11 @@ public virtual void Detects_skipped_base_type()
var entityF = modelBuilder.Entity();
entityF.HasBaseType();
+ entityA.SetDiscriminatorProperty(entityA.AddProperty("Disc", typeof(string)));
+ entityA.SetDiscriminatorValue("A");
+ entityD.Metadata.SetDiscriminatorValue("D");
+ entityF.Metadata.SetDiscriminatorValue("F");
+
VerifyError(CoreStrings.InconsistentInheritance(nameof(F), nameof(A), nameof(D)), modelBuilder);
}
@@ -1024,6 +1029,9 @@ public virtual void Detects_abstract_leaf_type()
var entityAbstract = model.AddEntityType(typeof(Abstract));
SetBaseType(entityAbstract, entityA);
+ entityA.SetDiscriminatorProperty(entityA.AddProperty("Disc", typeof(string)));
+ entityA.SetDiscriminatorValue("A");
+
VerifyError(CoreStrings.AbstractLeafEntityType(entityAbstract.DisplayName()), modelBuilder);
}
@@ -1040,6 +1048,8 @@ public virtual void Detects_generic_leaf_type()
var entityGeneric = model.AddEntityType(typeof(Generic<>));
SetBaseType(entityGeneric, entityAbstract);
+ entityAbstract.SetDiscriminatorProperty(entityAbstract.AddProperty("Disc", typeof(string)));
+
VerifyError(CoreStrings.AbstractLeafEntityType(entityGeneric.DisplayName()), modelBuilder);
}
@@ -1450,6 +1460,11 @@ public virtual void Detects_derived_owned_entity_type()
Assert.NotNull(ownedTypeBuilder.HasBaseType(typeof(A), ConfigurationSource.DataAnnotation));
+ var entityA = (IMutableEntityType)anotherEntityTypeBuilder.Metadata;
+ entityA.SetDiscriminatorProperty(entityA.AddProperty("Disc", typeof(string)));
+ entityA.SetDiscriminatorValue("A");
+ ((IMutableEntityType)ownedTypeBuilder.Metadata).SetDiscriminatorValue("D");
+
VerifyError(CoreStrings.OwnedDerivedType(nameof(D)), builder);
}
diff --git a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs
index b27a7c1c82f..d6ad38bd6e1 100644
--- a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs
+++ b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs
@@ -1858,7 +1858,11 @@ private void VerifyIgnoreProperty(
ignoredOnType, ignoreConfigurationSource, addConfigurationSource, ignoredFirst, setBaseFirst,
et => et.Metadata.FindProperty(Order.CustomerIdProperty.Name) != null,
et => et.Property(Order.CustomerIdProperty, addConfigurationSource) != null,
- et => et.Property(Order.CustomerIdProperty, ignoreConfigurationSource) != null,
+ modelBuilder => modelBuilder.Entity(typeof(Order), ConfigurationSource.Convention).HasRelationship(
+ modelBuilder.Entity(typeof(Customer), ConfigurationSource.Convention).Metadata,
+ Order.CustomerProperty,
+ Customer.OrdersProperty,
+ ConfigurationSource.Convention),
Order.CustomerIdProperty.Name);
private void VerifyIgnoreMember(
@@ -1869,14 +1873,19 @@ private void VerifyIgnoreMember(
bool setBaseFirst,
Func findMember,
Func addMember,
- Func unignoreMember,
+ Action configureModel,
string memberToIgnore)
{
- var modelBuilder = CreateModelBuilder();
+ var testModelBuilder = CreateConventionlessModelBuilder();
+ var modelBuilder = (InternalModelBuilder)testModelBuilder.GetInfrastructure();
+ configureModel(modelBuilder);
var customerTypeBuilder = modelBuilder.Entity(typeof(Customer), ConfigurationSource.Convention);
customerTypeBuilder.PrimaryKey([Customer.IdProperty], ConfigurationSource.Convention);
var productTypeBuilder = modelBuilder.Entity(typeof(Product), ConfigurationSource.Convention);
productTypeBuilder.PrimaryKey([Product.IdProperty], ConfigurationSource.Convention);
+ customerTypeBuilder.Ignore(Customer.NotCollectionOrdersProperty.Name, ConfigurationSource.Convention);
+ customerTypeBuilder.Ignore(Customer.SpecialOrdersProperty.Name, ConfigurationSource.Convention);
+ customerTypeBuilder.Ignore(Customer.UniqueProperty.Name, ConfigurationSource.Convention);
if (setBaseFirst)
{
@@ -1933,8 +1942,6 @@ private void VerifyIgnoreMember(
ConfigureOrdersHierarchy(modelBuilder);
}
- var modelValidator = InMemoryTestHelpers.Instance.CreateContextServices().GetRequiredService();
-
if (exceptionExpected)
{
Assert.Equal(
@@ -1942,11 +1949,7 @@ private void VerifyIgnoreMember(
memberToIgnore,
typeof(ExtraSpecialOrder).ShortDisplayName(),
typeof(SpecialOrder).ShortDisplayName()),
- Assert.Throws(() => modelValidator.Validate(
- modelBuilder.Metadata,
- new TestLogger())).Message);
-
- Assert.True(unignoreMember(ignoredEntityTypeBuilder));
+ Assert.Throws(() => testModelBuilder.FinalizeModel(designTime: true)).Message);
}
else
{
@@ -2345,12 +2348,7 @@ private void VerifyIgnoreNavigation(
Customer.OrdersProperty,
addConfigurationSource)
!= null,
- et => et.HasRelationship(
- et.ModelBuilder.Entity(typeof(Customer), ConfigurationSource.Explicit).Metadata,
- Order.CustomerProperty,
- Customer.OrdersProperty,
- ignoreConfigurationSource)
- != null,
+ modelBuilder => { },
Order.CustomerProperty.Name);
[ConditionalFact]
@@ -2429,11 +2427,11 @@ private void VerifyIgnoreSkipNavigation(
et.ModelBuilder.Entity(typeof(Product), ConfigurationSource.Explicit).Metadata,
addConfigurationSource)
!= null,
- et => et.HasSkipNavigation(
- MemberIdentity.Create(Order.ProductsProperty),
- et.ModelBuilder.Entity(typeof(Product), ConfigurationSource.Explicit).Metadata,
- ignoreConfigurationSource)
- != null,
+ modelBuilder => modelBuilder.Entity(typeof(Order), ConfigurationSource.Convention).HasRelationship(
+ modelBuilder.Entity(typeof(Customer), ConfigurationSource.Convention).Metadata,
+ Order.CustomerProperty,
+ Customer.OrdersProperty,
+ ConfigurationSource.Convention),
nameof(Order.Products));
[ConditionalTheory, MemberData(
@@ -3400,6 +3398,16 @@ public void Can_replace_named_query_filter_only_with_lower_or_equal_source()
private InternalModelBuilder CreateModelBuilder(Model model = null)
=> new(model ?? new Model());
+ protected virtual TestHelpers.TestModelBuilder CreateConventionlessModelBuilder(
+ Action configure = null,
+ bool sensitiveDataLoggingEnabled = false)
+ => InMemoryTestHelpers.Instance.CreateConventionBuilder(
+ configureConventions: configurationBuilder =>
+ {
+ configure?.Invoke(configurationBuilder);
+ configurationBuilder.RemoveAllConventions();
+ });
+
private InternalModelBuilder CreateConventionalModelBuilder()
=> (InternalModelBuilder)InMemoryTestHelpers.Instance.CreateConventionBuilder().GetInfrastructure();