From 686276f22c44a92f182941931194f46cebcc179e Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Fri, 16 Jun 2023 19:31:19 -0700 Subject: [PATCH 1/4] Add complex types to Metadata Fixes #13947 --- .../Extensions/CosmosPropertyExtensions.cs | 7 +- .../CosmosValueGenerationConvention.cs | 5 +- .../Conventions/StoreKeyConvention.cs | 8 +- .../Internal/CosmosPropertyExtensions.cs | 2 +- ...ionBindingRemovingExpressionVisitorBase.cs | 14 +- .../Internal/EntityProjectionExpression.cs | 4 +- .../IdValueGeneratorFactory.cs | 2 +- .../Internal/CosmosValueGeneratorSelector.cs | 6 +- .../Internal/SnapshotModelProcessor.cs | 2 +- .../CSharpRuntimeModelCodeGenerator.cs | 224 ++- .../RelationalScaffoldingModelFactory.cs | 4 +- .../Internal/EntityProjectionExpression.cs | 21 +- .../Storage/Internal/InMemoryStore.cs | 5 +- .../InMemoryValueGeneratorSelector.cs | 58 +- .../Diagnostics/RelationalLoggerExtensions.cs | 12 +- ...nalComplexTypePropertyBuilderExtensions.cs | 521 +++++ ...s.cs => RelationalDbFunctionExtensions.cs} | 2 +- .../RelationalEntityTypeExtensions.cs | 88 +- .../RelationalPropertyExtensions.cs | 184 +- .../RelationalTypeBaseExtensions.cs | 372 ++++ .../RelationalModelValidator.cs | 109 +- ...ationalPrimitivePropertyBaseExtensions.cs} | 4 +- .../IConventionCheckConstraintBuilder.cs | 39 + .../Builders/IConventionDbFunctionBuilder.cs | 39 + .../IConventionDbFunctionParameterBuilder.cs | 39 + .../Builders/IConventionSequenceBuilder.cs | 39 + .../IConventionStoredProcedureBuilder.cs | 39 + ...nventionStoredProcedureParameterBuilder.cs | 39 + ...ntionStoredProcedureResultColumnBuilder.cs | 39 + .../Builders/StoredProcedureBuilder.cs | 2 +- .../PropertyOverridesConvention.cs | 2 +- .../RelationalColumnAttributeConvention.cs | 8 +- ...ationalColumnCommentAttributeConvention.cs | 8 +- .../RelationalTableAttributeConvention.cs | 2 +- ...lationalTableCommentAttributeConvention.cs | 2 +- .../RelationalValueGenerationConvention.cs | 15 +- .../Conventions/SharedTableConvention.cs | 16 +- .../TableSharingConcurrencyTokenConvention.cs | 24 +- src/EFCore.Relational/Metadata/IColumnBase.cs | 2 +- .../Metadata/IColumnMapping.cs | 2 +- .../InternalCheckConstraintBuilder.cs | 18 + .../Internal/InternalDbFunctionBuilder.cs | 33 + .../InternalDbFunctionParameterBuilder.cs | 18 + .../Internal/InternalSequenceBuilder.cs | 18 + .../InternalStoredProcedureBuilder.cs | 18 + ...InternalStoredProcedureParameterBuilder.cs | 18 + ...ernalStoredProcedureResultColumnBuilder.cs | 18 + .../Metadata/Internal/RelationalModel.cs | 2 +- ...ationalPrimitivePropertyBaseExtensions.cs} | 12 +- .../Internal/RelationalPropertyOverrides.cs | 2 +- .../Internal/StoredProcedureParameter.cs | 2 +- .../Metadata/StoreObjectIdentifier.cs | 26 +- .../Internal/MigrationsModelDiffer.cs | 41 +- .../Query/EntityProjectionExpression.cs | 8 +- .../Query/Internal/CollateTranslator.cs | 2 +- .../Query/JsonQueryExpression.cs | 4 +- .../RelationalEvaluatableExpressionFilter.cs | 2 +- .../Query/SqlExpressions/SelectExpression.cs | 4 +- .../RelationalTypeMappingSourceExtensions.cs | 2 +- .../RelationalConverterMappingHints.cs | 4 +- .../Update/ModificationCommand.cs | 2 +- .../RelationalValueGeneratorSelector.cs | 10 +- .../SqlServerAnnotationCodeGenerator.cs | 4 +- .../Internal/SqlServerLoggerExtensions.cs | 16 +- ...verComplexTypePropertyBuilderExtensions.cs | 260 +++ ...lServerPrimitivePropertyBaseExtensions.cs} | 28 +- .../SqlServerPropertyBuilderExtensions.cs | 10 +- .../Internal/SqlServerModelValidator.cs | 22 +- .../SqlServerValueGenerationConvention.cs | 11 +- ...ServerValueGenerationStrategyConvention.cs | 2 +- .../Internal/SqlServerValueGeneratorCache.cs | 2 +- .../SqlServerValueGeneratorSelector.cs | 10 +- .../Internal/SqliteModelValidator.cs | 4 +- .../Internal/SqliteAnnotationProvider.cs | 3 +- src/EFCore/ChangeTracking/EntityEntry`.cs | 2 +- .../Internal/ArrayPropertyValues.cs | 4 +- .../EmptyShadowValuesFactoryFactory.cs | 4 +- .../Internal/InternalEntityEntry.cs | 34 +- .../ChangeTracking/Internal/KeyPropagator.cs | 12 +- .../ChangeTracking/Internal/OriginalValues.cs | 8 +- .../Internal/OriginalValuesFactoryFactory.cs | 4 +- .../RelationshipSnapshotFactoryFactory.cs | 4 +- .../Internal/ShadowValuesFactoryFactory.cs | 4 +- .../ChangeTracking/Internal/SidecarValues.cs | 2 +- .../Internal/SidecarValuesFactoryFactory.cs | 4 +- .../Internal/SnapshotFactoryFactory.cs | 18 +- .../Internal/SnapshotFactoryFactory`.cs | 8 +- .../ChangeTracking/Internal/StateManager.cs | 5 +- .../Internal/ValueGenerationManager.cs | 2 +- src/EFCore/ChangeTracking/LocalView.cs | 2 +- .../CSharpRuntimeAnnotationCodeGenerator.cs | 38 + ...untimeAnnotationCodeGeneratorParameters.cs | 4 +- .../ICSharpRuntimeAnnotationCodeGenerator.cs | 14 + .../Diagnostics/ComplexPropertyEventData.cs | 34 + src/EFCore/Diagnostics/CoreEventId.cs | 18 + .../Diagnostics/CoreLoggerExtensions.cs | 75 +- src/EFCore/Diagnostics/LoggingDefinitions.cs | 9 + .../Infrastructure/AnnotatableBuilder.cs | 6 +- src/EFCore/Infrastructure/ModelValidator.cs | 9 +- src/EFCore/Internal/EntityFinder.cs | 2 +- .../Builders/CollectionNavigationBuilder.cs | 10 +- .../ComplexPropertiesConfigurationBuilder.cs | 74 + .../ComplexPropertiesConfigurationBuilder`.cs | 31 + .../Builders/ComplexPropertyBuilder.cs | 486 +++++ .../Builders/ComplexPropertyBuilder`.cs | 275 +++ .../Builders/ComplexTypePropertyBuilder.cs | 695 +++++++ .../Builders/ComplexTypePropertyBuilder`.cs | 610 ++++++ .../Metadata/Builders/EntityTypeBuilder.cs | 142 +- .../Metadata/Builders/EntityTypeBuilder`.cs | 94 +- .../IConventionComplexPropertyBuilder.cs | 52 + .../Builders/IConventionComplexTypeBuilder.cs | 137 ++ .../Builders/IConventionEntityTypeBuilder.cs | 260 +-- .../Builders/IConventionForeignKeyBuilder.cs | 39 + .../Builders/IConventionIndexBuilder.cs | 39 + .../Builders/IConventionKeyBuilder.cs | 39 + .../Builders/IConventionModelBuilder.cs | 81 +- .../Builders/IConventionNavigationBuilder.cs | 61 +- .../IConventionPropertyBaseBuilder.cs | 48 +- .../Builders/IConventionPropertyBuilder.cs | 59 +- .../IConventionServicePropertyBuilder.cs | 37 +- .../IConventionSkipNavigationBuilder.cs | 37 +- .../Builders/IConventionTriggerBuilder.cs | 39 + .../Builders/IConventionTypeBaseBuilder.cs | 402 ++++ .../Metadata/Builders/PropertyBuilder.cs | 4 +- .../Metadata/Builders/PropertyBuilder`.cs | 4 +- .../BackingFieldAttributeConvention.cs | 21 +- .../Conventions/BackingFieldConvention.cs | 28 +- .../ConcurrencyCheckAttributeConvention.cs | 8 +- .../ConstructorBindingConvention.cs | 22 + .../Metadata/Conventions/ConventionSet.cs | 179 +- .../DatabaseGeneratedAttributeConvention.cs | 8 +- .../DeleteBehaviorAttributeConvention.cs | 56 +- .../EntityTypeAttributeConventionBase.cs | 71 - ...tyTypeConfigurationAttributeConvention.cs} | 31 +- .../ForeignKeyAttributeConvention.cs | 45 +- .../ForeignKeyPropertyDiscoveryConvention.cs | 9 +- .../IComplexPropertyAddedConvention.cs | 22 + ...plexPropertyAnnotationChangedConvention.cs | 28 + .../IComplexPropertyFieldChangedConvention.cs | 26 + ...lexPropertyNullabilityChangedConvention.cs | 22 + .../IComplexPropertyRemovedConvention.cs | 24 + ...IComplexTypeAnnotationChangedConvention.cs | 28 + .../IComplexTypeMemberIgnoredConvention.cs | 24 + .../IEntityTypeIgnoredConvention.cs | 1 + .../Conventions/IPropertyRemovedConvention.cs | 4 +- .../Conventions/ITypeIgnoredConvention.cs | 26 + .../ProviderConventionSetBuilder.cs | 8 +- .../ConventionDispatcher.ConventionScope.cs | 49 +- ...entionDispatcher.DelayedConventionScope.cs | 239 ++- ...tionDispatcher.ImmediateConventionScope.cs | 296 ++- .../Internal/ConventionDispatcher.cs | 122 +- .../Conventions/KeyAttributeConvention.cs | 62 +- .../Conventions/KeyDiscoveryConvention.cs | 7 +- ...ntion.cs => KeylessAttributeConvention.cs} | 6 +- .../MaxLengthAttributeConvention.cs | 30 +- .../NavigationAttributeConventionBase.cs | 92 +- .../NonNullableReferencePropertyConvention.cs | 51 +- .../NotMappedMemberAttributeConvention.cs | 30 +- ...cs => NotMappedTypeAttributeConvention.cs} | 6 +- .../Conventions/OwnedAttributeConvention.cs | 62 + .../OwnedEntityTypeAttributeConvention.cs | 40 - .../PrecisionAttributeConvention.cs | 28 +- .../PropertyAttributeConventionBase.cs | 93 +- .../PropertyDiscoveryConvention.cs | 36 +- .../RelationshipDiscoveryConvention.cs | 5 +- .../RequiredPropertyAttributeConvention.cs | 20 +- .../Conventions/RuntimeModelConvention.cs | 189 +- .../StringLengthAttributeConvention.cs | 8 +- .../TimestampAttributeConvention.cs | 8 +- .../TypeAttributeConventionBase.cs | 134 ++ .../Conventions/UnicodeAttributeConvention.cs | 8 +- .../Metadata/EntityTypeFullNameComparer.cs | 2 +- src/EFCore/Metadata/ForeignKeyComparer.cs | 8 +- src/EFCore/Metadata/IComplexProperty.cs | 18 + src/EFCore/Metadata/IComplexType.cs | 29 + .../Metadata/IConstructorBindingFactory.cs | 12 + .../Metadata/IConventionComplexProperty.cs | 49 + src/EFCore/Metadata/IConventionComplexType.cs | 30 + src/EFCore/Metadata/IConventionEntityType.cs | 247 +-- src/EFCore/Metadata/IConventionProperty.cs | 55 +- src/EFCore/Metadata/IConventionTypeBase.cs | 391 +++- src/EFCore/Metadata/IEntityType.cs | 98 +- .../Metadata/IMutableComplexProperty.cs | 34 + src/EFCore/Metadata/IMutableComplexType.cs | 27 + src/EFCore/Metadata/IMutableEntityType.cs | 235 +-- src/EFCore/Metadata/IMutableProperty.cs | 26 +- src/EFCore/Metadata/IMutableTypeBase.cs | 347 +++- src/EFCore/Metadata/IProperty.cs | 13 +- .../IPropertyParameterBindingFactory.cs | 12 + .../Metadata/IReadOnlyComplexProperty.cs | 126 ++ src/EFCore/Metadata/IReadOnlyComplexType.cs | 97 + src/EFCore/Metadata/IReadOnlyEntityType.cs | 163 +- src/EFCore/Metadata/IReadOnlyIndex.cs | 2 +- src/EFCore/Metadata/IReadOnlyKey.cs | 2 +- src/EFCore/Metadata/IReadOnlyNavigation.cs | 11 +- src/EFCore/Metadata/IReadOnlyProperty.cs | 748 +++---- .../Metadata/IReadOnlySkipNavigation.cs | 18 +- src/EFCore/Metadata/IReadOnlyTypeBase.cs | 202 +- src/EFCore/Metadata/ITypeBase.cs | 151 ++ src/EFCore/Metadata/IndexComparer.cs | 4 +- src/EFCore/Metadata/Internal/AdHocMapper.cs | 6 +- .../Metadata/Internal/ComplexProperty.cs | 349 ++++ .../Internal/ComplexPropertyConfiguration.cs | 49 + .../Internal/ComplexPropertySnapshot.cs | 183 ++ src/EFCore/Metadata/Internal/ComplexType.cs | 772 ++++++++ .../Internal/ComplexTypeExtensions.cs | 87 + .../Internal/ConstructorBindingFactory.cs | 92 +- src/EFCore/Metadata/Internal/EntityType.cs | 1342 +++---------- .../Metadata/Internal/EntityTypeExtensions.cs | 140 +- src/EFCore/Metadata/Internal/ForeignKey.cs | 2 +- .../Metadata/Internal/IRuntimeComplexType.cs} | 7 +- .../Metadata/Internal/IRuntimeEntityType.cs | 62 +- .../Metadata/Internal/IRuntimeTypeBase.cs | 142 ++ .../InternalComplexPropertyBuilder.cs | 322 +++ .../Internal/InternalComplexTypeBuilder.cs | 613 ++++++ .../Internal/InternalEntityTypeBuilder.cs | 1680 +++++----------- .../Internal/InternalForeignKeyBuilder.cs | 100 +- .../Metadata/Internal/InternalIndexBuilder.cs | 38 +- .../Metadata/Internal/InternalKeyBuilder.cs | 23 +- .../Metadata/Internal/InternalModelBuilder.cs | 366 +++- .../Internal/InternalNavigationBuilder.cs | 84 +- ...der`.cs => InternalPropertyBaseBuilder.cs} | 24 +- .../Internal/InternalPropertyBuilder.cs | 377 ++-- .../InternalServicePropertyBuilder.cs | 67 +- .../Internal/InternalSkipNavigationBuilder.cs | 90 +- .../Internal/InternalTriggerBuilder.cs | 18 + .../Internal/InternalTypeBaseBuilder.cs | 1629 +++++++++++++++ src/EFCore/Metadata/Internal/Key.cs | 2 +- src/EFCore/Metadata/Internal/Model.cs | 109 +- .../Metadata/Internal/ModelConfiguration.cs | 71 +- src/EFCore/Metadata/Internal/Navigation.cs | 2 +- .../Metadata/Internal/PropertiesSnapshot.cs | 9 +- src/EFCore/Metadata/Internal/Property.cs | 397 ++-- .../Internal/PropertyAccessorsFactory.cs | 2 +- src/EFCore/Metadata/Internal/PropertyBase.cs | 68 +- .../Internal/PropertyConfiguration.cs | 9 - .../Metadata/Internal/PropertyCounts.cs | 10 + .../Metadata/Internal/PropertyExtensions.cs | 2 +- .../Metadata/Internal/PropertyNameComparer.cs | 9 +- .../PropertyParameterBindingFactory.cs | 47 +- .../Metadata/Internal/SkipNavigation.cs | 14 +- .../Internal/SkipNavigationComparer.cs | 2 +- src/EFCore/Metadata/Internal/TypeBase.cs | 1761 +++++++++++++++-- .../Metadata/Internal/TypeBaseExtensions.cs | 28 + .../Internal/TypeConfigurationType.cs | 8 + src/EFCore/Metadata/KeyComparer.cs | 4 +- src/EFCore/Metadata/RuntimeComplexProperty.cs | 129 ++ src/EFCore/Metadata/RuntimeComplexType.cs | 178 ++ src/EFCore/Metadata/RuntimeEntityType.cs | 449 +---- src/EFCore/Metadata/RuntimeKey.cs | 2 +- src/EFCore/Metadata/RuntimeModel.cs | 4 +- src/EFCore/Metadata/RuntimeNavigation.cs | 6 +- src/EFCore/Metadata/RuntimeProperty.cs | 65 +- src/EFCore/Metadata/RuntimePropertyBase.cs | 6 +- src/EFCore/Metadata/RuntimeServiceProperty.cs | 6 +- src/EFCore/Metadata/RuntimeSkipNavigation.cs | 6 +- src/EFCore/Metadata/RuntimeTypeBase.cs | 557 ++++++ src/EFCore/Metadata/TypeBaseNameComparer.cs | 73 + src/EFCore/Metadata/TypeBaseTypeComparer.cs | 73 + src/EFCore/ModelConfigurationBuilder.cs | 45 + src/EFCore/Properties/CoreStrings.Designer.cs | 197 +- src/EFCore/Properties/CoreStrings.resx | 122 +- .../ShapedQueryCompilingExpressionVisitor.cs | 2 +- src/EFCore/Storage/CoreTypeMapping.cs | 6 +- .../ValueConversion/ConverterMappingHints.cs | 4 +- .../DiscriminatorValueGeneratorFactory.cs | 2 +- .../ValueGeneration/IValueGeneratorCache.cs | 6 +- .../IValueGeneratorSelector.cs | 8 +- .../TemporaryNumberValueGeneratorFactory.cs | 6 +- .../ValueGeneration/ValueGeneratorCache.cs | 18 +- .../ValueGeneration/ValueGeneratorFactory.cs | 4 +- .../ValueGeneration/ValueGeneratorSelector.cs | 23 +- src/Shared/SharedTypeExtensions.cs | 9 +- .../CosmosApiConsistencyTest.cs | 54 +- .../EndToEndCosmosTest.cs | 19 +- .../TestUtilities/CosmosTestStore.cs | 32 +- ...stomPartitionKeyIdValueGeneratorFactory.cs | 2 +- .../CosmosModelBuilderGenericTest.cs | 205 ++ .../Migrations/ModelSnapshotSqlServerTest.cs | 32 +- .../CSharpRuntimeModelCodeGeneratorTest.cs | 770 ++++++- .../CustomValueGeneratorTest.cs | 8 +- .../InMemoryValueGeneratorSelectorTest.cs | 2 +- .../InMemoryModelBuilderGenericTest.cs | 6 + .../RelationalMetadataExtensionsTest.cs | 2 +- ...lationalPropertyAttributeConventionTest.cs | 2 +- .../RelationalModelBuilderTest.cs | 329 +++ .../RelationalApiConsistencyTest.cs | 155 +- .../ApiConsistencyTestBase.cs | 288 ++- .../JsonTypesTestBase.cs | 39 - .../TestUtilities/TestHelpers.cs | 2 +- .../SqlServerApiConsistencyTest.cs | 88 +- .../SqlServerModelBuilderGenericTest.cs | 8 + .../SqlServerModelBuilderNonGenericTest.cs | 8 + .../SqlServerModelBuilderTestBase.cs | 190 ++ .../Storage/SqlServerTypeMappingSourceTest.cs | 42 +- .../SqliteApiConsistencyTest.cs | 21 +- test/EFCore.Tests/ApiConsistencyTest.cs | 12 +- .../ChangeTracking/ChangeTrackerTest.cs | 2 +- .../ChangeTracking/EntityEntryTest.cs | 28 - .../Infrastructure/CoreEventIdTest.cs | 9 +- .../Conventions/BackingFieldConventionTest.cs | 6 +- .../Conventions/ConventionDispatcherTest.cs | 1148 ++++++++++- .../DeleteBehaviorAttributeConventionTest.cs | 16 +- .../EntityTypeAttributeConventionTest.cs | 6 +- ...reignKeyPropertyDiscoveryConventionTest.cs | 8 +- .../Conventions/KeyDiscoveryConventionTest.cs | 2 +- .../NavigationAttributeConventionTest.cs | 2 + ...NullableReferencePropertyConventionTest.cs | 2 +- .../PropertyAttributeConventionTest.cs | 2 +- .../PropertyDiscoveryConventionTest.cs | 4 +- .../Metadata/EntityTypeExtensionsTest.cs | 2 +- .../Internal/ClrPropertyGetterFactoryTest.cs | 3 +- .../Internal/ClrPropertySetterFactoryTest.cs | 3 +- .../Metadata/Internal/EntityTypeTest.cs | 130 +- .../Internal/InternalEntityTypeBuilderTest.cs | 40 +- .../Internal/InternalPropertyBuilderTest.cs | 8 +- .../Metadata/Internal/PropertyTest.cs | 15 +- .../Metadata/Internal/SkipNavigationTest.cs | 28 +- .../ModelBuilding/ComplexTypeTestBase.cs | 1452 ++++++++++++++ .../ModelBuilding/ManyToOneTestBase.cs | 8 +- .../ModelBuilding/ModelBuilderGenericTest.cs | 289 ++- .../ModelBuilderNonGenericTest.cs | 278 ++- .../ModelBuilding/ModelBuilderTestBase.cs | 131 +- .../ModelBuilding/NonRelationshipTestBase.cs | 61 +- .../ModelBuilding/OneToManyTestBase.cs | 8 +- test/EFCore.Tests/ModelBuilding/TestModel.cs | 43 +- ...emporaryNumberValueGeneratorFactoryTest.cs | 2 +- 327 files changed, 24504 insertions(+), 6968 deletions(-) create mode 100644 src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs rename src/EFCore.Relational/Extensions/{RelationalDbFunctionsExtensions.cs => RelationalDbFunctionExtensions.cs} (97%) create mode 100644 src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs rename src/EFCore.Relational/Infrastructure/{RelationalPropertyExtensions.cs => RelationalPrimitivePropertyBaseExtensions.cs} (92%) rename src/EFCore.Relational/{Extensions/Internal/RelationalPropertyInternalExtensions.cs => Metadata/Internal/RelationalPrimitivePropertyBaseExtensions.cs} (66%) create mode 100644 src/EFCore.SqlServer/Extensions/SqlServerComplexTypePropertyBuilderExtensions.cs rename src/EFCore.SqlServer/Extensions/{SqlServerPropertyExtensions.cs => SqlServerPrimitivePropertyBaseExtensions.cs} (97%) create mode 100644 src/EFCore/Diagnostics/ComplexPropertyEventData.cs create mode 100644 src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder.cs create mode 100644 src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder`.cs create mode 100644 src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs create mode 100644 src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs create mode 100644 src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs create mode 100644 src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs create mode 100644 src/EFCore/Metadata/Builders/IConventionComplexPropertyBuilder.cs create mode 100644 src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs create mode 100644 src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs delete mode 100644 src/EFCore/Metadata/Conventions/EntityTypeAttributeConventionBase.cs rename src/EFCore/Metadata/Conventions/{EntityTypeConfigurationEntityTypeAttributeConvention.cs => EntityTypeConfigurationAttributeConvention.cs} (72%) create mode 100644 src/EFCore/Metadata/Conventions/IComplexPropertyAddedConvention.cs create mode 100644 src/EFCore/Metadata/Conventions/IComplexPropertyAnnotationChangedConvention.cs create mode 100644 src/EFCore/Metadata/Conventions/IComplexPropertyFieldChangedConvention.cs create mode 100644 src/EFCore/Metadata/Conventions/IComplexPropertyNullabilityChangedConvention.cs create mode 100644 src/EFCore/Metadata/Conventions/IComplexPropertyRemovedConvention.cs create mode 100644 src/EFCore/Metadata/Conventions/IComplexTypeAnnotationChangedConvention.cs create mode 100644 src/EFCore/Metadata/Conventions/IComplexTypeMemberIgnoredConvention.cs create mode 100644 src/EFCore/Metadata/Conventions/ITypeIgnoredConvention.cs rename src/EFCore/Metadata/Conventions/{KeylessEntityTypeAttributeConvention.cs => KeylessAttributeConvention.cs} (81%) rename src/EFCore/Metadata/Conventions/{NotMappedEntityTypeAttributeConvention.cs => NotMappedTypeAttributeConvention.cs} (82%) create mode 100644 src/EFCore/Metadata/Conventions/OwnedAttributeConvention.cs delete mode 100644 src/EFCore/Metadata/Conventions/OwnedEntityTypeAttributeConvention.cs create mode 100644 src/EFCore/Metadata/Conventions/TypeAttributeConventionBase.cs create mode 100644 src/EFCore/Metadata/IComplexProperty.cs create mode 100644 src/EFCore/Metadata/IComplexType.cs create mode 100644 src/EFCore/Metadata/IConventionComplexProperty.cs create mode 100644 src/EFCore/Metadata/IConventionComplexType.cs create mode 100644 src/EFCore/Metadata/IMutableComplexProperty.cs create mode 100644 src/EFCore/Metadata/IMutableComplexType.cs create mode 100644 src/EFCore/Metadata/IReadOnlyComplexProperty.cs create mode 100644 src/EFCore/Metadata/IReadOnlyComplexType.cs create mode 100644 src/EFCore/Metadata/Internal/ComplexProperty.cs create mode 100644 src/EFCore/Metadata/Internal/ComplexPropertyConfiguration.cs create mode 100644 src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs create mode 100644 src/EFCore/Metadata/Internal/ComplexType.cs create mode 100644 src/EFCore/Metadata/Internal/ComplexTypeExtensions.cs rename src/{EFCore.Relational/Metadata/Internal/RelationalPropertyExtensions.cs => EFCore/Metadata/Internal/IRuntimeComplexType.cs} (83%) create mode 100644 src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs create mode 100644 src/EFCore/Metadata/Internal/InternalComplexPropertyBuilder.cs create mode 100644 src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs rename src/EFCore/Metadata/Internal/{InternalPropertyBaseBuilder`.cs => InternalPropertyBaseBuilder.cs} (87%) create mode 100644 src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs create mode 100644 src/EFCore/Metadata/RuntimeComplexProperty.cs create mode 100644 src/EFCore/Metadata/RuntimeComplexType.cs create mode 100644 src/EFCore/Metadata/RuntimeTypeBase.cs create mode 100644 src/EFCore/Metadata/TypeBaseNameComparer.cs create mode 100644 src/EFCore/Metadata/TypeBaseTypeComparer.cs create mode 100644 test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs diff --git a/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs index e3a33708622..e1bf8acccc4 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore; @@ -26,11 +27,11 @@ public static string GetJsonPropertyName(this IReadOnlyProperty property) private static string GetDefaultJsonPropertyName(IReadOnlyProperty property) { - var entityType = property.DeclaringEntityType; - var ownership = entityType.FindOwnership(); + var entityType = property.DeclaringType as IEntityType; + var ownership = entityType?.FindOwnership(); if (ownership != null - && !entityType.IsDocumentRoot()) + && !entityType!.IsDocumentRoot()) { var pk = property.FindContainingPrimaryKey(); if (pk != null diff --git a/src/EFCore.Cosmos/Metadata/Conventions/CosmosValueGenerationConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/CosmosValueGenerationConvention.cs index 64828e5aa56..3a7836693c1 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/CosmosValueGenerationConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/CosmosValueGenerationConvention.cs @@ -67,9 +67,10 @@ public virtual void ProcessEntityTypeAnnotationChanged( /// The store value generation strategy to set for the given property. protected override ValueGenerated? GetValueGenerated(IConventionProperty property) { - var entityType = property.DeclaringEntityType; + var entityType = property.DeclaringType as IConventionEntityType; var propertyType = property.ClrType.UnwrapNullableType(); - if (propertyType == typeof(int)) + if (propertyType == typeof(int) + && entityType != null) { var ownership = entityType.FindOwnership(); if (ownership is { IsUnique: false } diff --git a/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs index 7bd0c030bc0..a62df3cf722 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs @@ -295,9 +295,9 @@ public virtual void ProcessPropertyAnnotationChanged( && (string?)annotation?.Value == IdPropertyJsonName && propertyBuilder.Metadata.Name != DefaultIdPropertyName) { - var entityType = propertyBuilder.Metadata.DeclaringEntityType; + var declaringType = propertyBuilder.Metadata.DeclaringType; - var idProperty = entityType.FindProperty(DefaultIdPropertyName); + var idProperty = declaringType.FindProperty(DefaultIdPropertyName); if (idProperty != null) { foreach (var key in idProperty.GetContainingKeys().ToList()) @@ -306,7 +306,9 @@ public virtual void ProcessPropertyAnnotationChanged( } } - ProcessIdProperty(entityType.Builder); + ProcessIdProperty(((declaringType as IConventionEntityType) + ?? ((IConventionComplexType)declaringType).FundamentalEntityType) + .Builder); } } } diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosPropertyExtensions.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosPropertyExtensions.cs index a115200db60..331e414bce5 100644 --- a/src/EFCore.Cosmos/Metadata/Internal/CosmosPropertyExtensions.cs +++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosPropertyExtensions.cs @@ -20,7 +20,7 @@ public static class CosmosPropertyExtensions public static bool IsOrdinalKeyProperty(this IReadOnlyProperty property) { Check.DebugAssert( - property.DeclaringEntityType.IsOwned(), $"Expected {property.DeclaringEntityType.DisplayName()} to be owned."); + (property.DeclaringType as IEntityType)?.IsOwned() == true, $"Expected {property.DeclaringType.DisplayName()} to be owned."); Check.DebugAssert(property.GetJsonPropertyName().Length == 0, $"Expected {property.Name} to be non-persisted."); return property.FindContainingPrimaryKey() is IReadOnlyKey { Properties.Count: > 1 } diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs index 620034dee62..67d7e7da9d1 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs @@ -598,11 +598,13 @@ private Expression CreateGetValueExpression( var storeName = property.GetJsonPropertyName(); if (storeName.Length == 0) { - var entityType = property.DeclaringEntityType; - if (!entityType.IsDocumentRoot()) + var entityType = property.DeclaringType as IEntityType; + if (entityType == null + || !entityType.IsDocumentRoot()) { - var ownership = entityType.FindOwnership(); - if (!ownership.IsUnique + var ownership = entityType?.FindOwnership(); + if (ownership != null + && !ownership.IsUnique && property.IsOrdinalKeyProperty()) { var readExpression = _ordinalParameterBindings[jObjectExpression]; @@ -621,8 +623,8 @@ private Expression CreateGetValueExpression( if (_ownerMappings.TryGetValue(jObjectExpression, out var ownerInfo)) { Check.DebugAssert( - principalProperty.DeclaringEntityType.IsAssignableFrom(ownerInfo.EntityType), - $"{principalProperty.DeclaringEntityType} is not assignable from {ownerInfo.EntityType}"); + principalProperty.DeclaringType.IsAssignableFrom(ownerInfo.EntityType), + $"{principalProperty.DeclaringType} is not assignable from {ownerInfo.EntityType}"); ownerJObjectExpression = ownerInfo.JObjectExpression; } diff --git a/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs b/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs index cc9eb884b41..abcd196ab7d 100644 --- a/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs +++ b/src/EFCore.Cosmos/Query/Internal/EntityProjectionExpression.cs @@ -101,8 +101,8 @@ public virtual Expression Update(Expression accessExpression) /// public virtual Expression BindProperty(IProperty property, bool clientEval) { - if (!EntityType.IsAssignableFrom(property.DeclaringEntityType) - && !property.DeclaringEntityType.IsAssignableFrom(EntityType)) + if (!EntityType.IsAssignableFrom(property.DeclaringType) + && !property.DeclaringType.IsAssignableFrom(EntityType)) { throw new InvalidOperationException( CosmosStrings.UnableToBindMemberToEntityProjection("property", property.Name, EntityType.DisplayName())); diff --git a/src/EFCore.Cosmos/ValueGeneration/IdValueGeneratorFactory.cs b/src/EFCore.Cosmos/ValueGeneration/IdValueGeneratorFactory.cs index bf4207a07e9..5b1236478cf 100644 --- a/src/EFCore.Cosmos/ValueGeneration/IdValueGeneratorFactory.cs +++ b/src/EFCore.Cosmos/ValueGeneration/IdValueGeneratorFactory.cs @@ -15,6 +15,6 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration; public class IdValueGeneratorFactory : ValueGeneratorFactory { /// - public override ValueGenerator Create(IProperty property, IEntityType entityType) + public override ValueGenerator Create(IProperty property, ITypeBase entityType) => new IdValueGenerator(); } diff --git a/src/EFCore.Cosmos/ValueGeneration/Internal/CosmosValueGeneratorSelector.cs b/src/EFCore.Cosmos/ValueGeneration/Internal/CosmosValueGeneratorSelector.cs index d11d7fa3943..afad6d61dc0 100644 --- a/src/EFCore.Cosmos/ValueGeneration/Internal/CosmosValueGeneratorSelector.cs +++ b/src/EFCore.Cosmos/ValueGeneration/Internal/CosmosValueGeneratorSelector.cs @@ -28,14 +28,14 @@ public CosmosValueGeneratorSelector(ValueGeneratorSelectorDependencies dependenc /// 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 ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType) + protected override ValueGenerator? FindForType(IProperty property, ITypeBase typeBase, Type clrType) { if (property.GetJsonPropertyName() == "" && clrType == typeof(int)) { - return new TemporaryNumberValueGeneratorFactory().Create(property, entityType); + return new TemporaryNumberValueGeneratorFactory().Create(property, typeBase); } - return base.FindForType(property, entityType, clrType); + return base.FindForType(property, typeBase, clrType); } } diff --git a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs index edda96f37d2..a53268548e6 100644 --- a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs +++ b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs @@ -185,7 +185,7 @@ private static void UpdateOwnedTypes(IMutableEntityType entityType) if (oldProperty is IConventionProperty conventionProperty && conventionProperty.GetConfigurationSource() == ConfigurationSource.Convention) { - oldProperty.DeclaringEntityType.RemoveProperty(oldProperty); + oldProperty.DeclaringType.RemoveProperty(oldProperty); } } } diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index 1f9f21fe697..36a1bbc2f65 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -4,6 +4,7 @@ using System.Text; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -459,6 +460,11 @@ private string GenerateEntityType(IEntityType entityType, string @namespace, str { CreateEntityType(entityType, mainBuilder, methodBuilder, namespaces, className, nullable); + foreach (var complexProperty in entityType.GetDeclaredComplexProperties()) + { + CreateComplexProperty(complexProperty, mainBuilder, methodBuilder, namespaces, className, nullable); + } + var foreignKeyNumber = 1; foreach (var foreignKey in entityType.GetDeclaredForeignKeys()) { @@ -538,6 +544,17 @@ private void CreateEntityType( Create(property, parameters); } + foreach (var complexProperty in entityType.GetDeclaredComplexProperties()) + { + mainBuilder + .Append(_code.Identifier(complexProperty.Name, capitalize: true)) + .Append("ComplexProperty") + .Append(".Create") + .Append("(") + .Append(entityTypeVariable) + .AppendLine(");"); + } + foreach (var key in entityType.GetDeclaredKeys()) { Create(key, propertyVariables, parameters, nullable); @@ -665,6 +682,25 @@ private void Create( IProperty property, Dictionary propertyVariables, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var variableName = _code.Identifier(property.Name, parameters.ScopeVariables, capitalize: false); + propertyVariables[property] = variableName; + + Create(property, variableName, propertyVariables, parameters); + + CreateAnnotations( + property, + _annotationCodeGenerator.Generate, + parameters with { TargetName = variableName }); + + parameters.MainBuilder.AppendLine(); + } + + private void Create( + IProperty property, + string variableName, + Dictionary propertyVariables, + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var valueGeneratorFactoryType = (Type?)property[CoreAnnotationNames.ValueGeneratorFactoryType]; if (valueGeneratorFactoryType == null @@ -672,7 +708,7 @@ private void Create( { throw new InvalidOperationException( DesignStrings.CompiledModelValueGenerator( - property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasValueGeneratorFactory))); + property.DeclaringType.ShortName(), property.Name, nameof(PropertyBuilder.HasValueGeneratorFactory))); } var valueComparerType = (Type?)property[CoreAnnotationNames.ValueComparerType]; @@ -681,7 +717,7 @@ private void Create( { throw new InvalidOperationException( DesignStrings.CompiledModelValueComparer( - property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); + property.DeclaringType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); } var providerValueComparerType = (Type?)property[CoreAnnotationNames.ProviderValueComparerType]; @@ -690,7 +726,7 @@ private void Create( { throw new InvalidOperationException( DesignStrings.CompiledModelValueComparer( - property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); + property.DeclaringType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); } var valueConverterType = GetValueConverterType(property); @@ -699,7 +735,7 @@ private void Create( { throw new InvalidOperationException( DesignStrings.CompiledModelValueConverter( - property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); + property.DeclaringType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); } if (property is IConventionProperty conventionProperty @@ -707,12 +743,9 @@ private void Create( { throw new InvalidOperationException( DesignStrings.CompiledModelTypeMapping( - property.DeclaringEntityType.ShortName(), property.Name, "Customize()", parameters.ClassName)); + property.DeclaringType.ShortName(), property.Name, "Customize()", parameters.ClassName)); } - var variableName = _code.Identifier(property.Name, parameters.ScopeVariables, capitalize: false); - propertyVariables[property] = variableName; - var mainBuilder = parameters.MainBuilder; mainBuilder .Append("var ").Append(variableName).Append(" = ").Append(parameters.TargetName).AppendLine(".AddProperty(") @@ -870,13 +903,6 @@ private void Create( mainBuilder .AppendLine(");") .DecrementIndent(); - - CreateAnnotations( - property, - _annotationCodeGenerator.Generate, - parameters with { TargetName = variableName }); - - mainBuilder.AppendLine(); } private static Type? GetValueConverterType(IProperty property) @@ -925,9 +951,8 @@ private void Create( } return i == ForeignKey.LongestFkChainAllowedLength - ? throw new InvalidOperationException( - CoreStrings.RelationshipCycle( - property.DeclaringEntityType.DisplayName(), property.Name, "ValueConverterType")) + ? throw new InvalidOperationException(CoreStrings.RelationshipCycle( + property.DeclaringType.DisplayName(), property.Name, "ValueConverterType")) : null; } @@ -1057,9 +1082,10 @@ private void Create( PropertyBaseParameters(property, parameters, skipType: true); + AddNamespace(property.ClrType, parameters.Namespaces); mainBuilder .AppendLine(",") - .Append("serviceType: typeof(" + property.ClrType.DisplayName(fullName: true, compilable: true) + ")"); + .Append("serviceType: typeof(" + _code.Reference(property.ClrType) + ")"); mainBuilder .AppendLine(");") @@ -1148,6 +1174,166 @@ private void Create( mainBuilder.AppendLine(); } + private void CreateComplexProperty( + IComplexProperty complexProperty, + IndentedStringBuilder mainBuilder, + IndentedStringBuilder methodBuilder, + SortedSet namespaces, + string topClassName, + bool nullable) + { + mainBuilder + .AppendLine() + .Append("private static class ") + .Append(_code.Identifier(complexProperty.Name, capitalize: true)) + .AppendLine("ComplexProperty") + .AppendLine("{"); + + var complexType = complexProperty.ComplexType; + using (mainBuilder.Indent()) + { + var declaringTypeVariable = "declaringType"; + mainBuilder + .Append("public static RuntimeComplexProperty Create(") + .Append(complexProperty.DeclaringType is IEntityType ? "RuntimeEntityType " : "RuntimeComplexType ") + .Append(declaringTypeVariable) + .AppendLine(")") + .AppendLine("{"); + + using (mainBuilder.Indent()) + { + const string complexPropertyVariable = "complexProperty"; + const string complexTypeVariable = "complexType"; + var variables = new HashSet + { + declaringTypeVariable, + complexPropertyVariable, + complexTypeVariable + }; + + mainBuilder + .Append("var ").Append(complexPropertyVariable).Append(" = ") + .Append(declaringTypeVariable).Append(".AddComplexProperty(") + .IncrementIndent() + .Append(_code.Literal(complexProperty.Name)) + .AppendLine(",") + .Append(_code.Literal(complexProperty.ClrType)) + .AppendLine(",") + .Append(_code.Literal(complexType.Name)) + .AppendLine(",") + .Append(_code.Literal(complexType.ClrType)); + + AddNamespace(complexProperty.ClrType, namespaces); + AddNamespace(complexType.ClrType, namespaces); + + var parameters = new CSharpRuntimeAnnotationCodeGeneratorParameters( + declaringTypeVariable, + topClassName, + mainBuilder, + methodBuilder, + namespaces, + variables, + nullable); + + PropertyBaseParameters(complexProperty, parameters, skipType: true); + + if (complexProperty.IsNullable) + { + mainBuilder.AppendLine(",") + .Append("nullable: ") + .Append(_code.Literal(true)); + } + + if (complexProperty.IsCollection) + { + mainBuilder.AppendLine(",") + .Append("collection: ") + .Append(_code.Literal(true)); + } + + var changeTrackingStrategy = complexType.GetChangeTrackingStrategy(); + if (changeTrackingStrategy != ChangeTrackingStrategy.Snapshot) + { + namespaces.Add(typeof(ChangeTrackingStrategy).Namespace!); + + mainBuilder.AppendLine(",") + .Append("changeTrackingStrategy: ") + .Append(_code.Literal(changeTrackingStrategy)); + } + + var indexerPropertyInfo = complexType.FindIndexerPropertyInfo(); + if (indexerPropertyInfo != null) + { + mainBuilder.AppendLine(",") + .Append("indexerPropertyInfo: RuntimeEntityType.FindIndexerProperty(") + .Append(_code.Literal(complexType.ClrType)) + .Append(")"); + } + + if (complexType.IsPropertyBag) + { + mainBuilder.AppendLine(",") + .Append("propertyBag: ") + .Append(_code.Literal(true)); + } + + mainBuilder + .AppendLine(");") + .AppendLine() + .DecrementIndent(); + + mainBuilder + .Append("var ").Append(complexTypeVariable).Append(" = ") + .Append(complexPropertyVariable).AppendLine(".ComplexType;"); + + var complexTypeParameters = parameters with { TargetName = complexTypeVariable }; + var propertyVariables = new Dictionary(); + foreach (var property in complexType.GetProperties()) + { + Create(property, propertyVariables, complexTypeParameters); + } + + foreach (var nestedComplexProperty in complexType.GetComplexProperties()) + { + mainBuilder + .Append(_code.Identifier(nestedComplexProperty.Name, capitalize: true)) + .Append("ComplexProperty") + .Append(".Create") + .Append("(") + .Append(complexTypeVariable) + .AppendLine(");"); + } + + CreateAnnotations( + complexType, + _annotationCodeGenerator.Generate, + complexTypeParameters); + + CreateAnnotations( + complexProperty, + _annotationCodeGenerator.Generate, + parameters with { TargetName = complexPropertyVariable }); + + mainBuilder + .Append("return ") + .Append(complexPropertyVariable) + .AppendLine(";"); + } + + mainBuilder.AppendLine("}"); + } + + using (mainBuilder.Indent()) + { + foreach (var nestedComplexProperty in complexType.GetComplexProperties()) + { + CreateComplexProperty(nestedComplexProperty, mainBuilder, methodBuilder, namespaces, topClassName, nullable); + } + } + + mainBuilder.AppendLine("}"); + } + private void CreateForeignKey( IForeignKey foreignKey, int foreignKeyNumber, diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs index 40032248f1c..80143d3243c 100644 --- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs +++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs @@ -714,10 +714,10 @@ protected virtual ModelBuilder VisitForeignKeys( uniquifier: NavigationUniquifier); var leftSkipNavigation = leftEntityType.AddSkipNavigation( - leftNavigationPropertyName, null, rightEntityType, collection: true, onDependent: false); + leftNavigationPropertyName, memberInfo: null, targetEntityType: rightEntityType, collection: true, onDependent: false); leftSkipNavigation.SetForeignKey(fks[0]); var rightSkipNavigation = rightEntityType.AddSkipNavigation( - rightNavigationPropertyName, null, leftEntityType, collection: true, onDependent: false); + rightNavigationPropertyName, memberInfo: null, targetEntityType: leftEntityType, collection: true, onDependent: false); rightSkipNavigation.SetForeignKey(fks[1]); leftSkipNavigation.SetInverse(rightSkipNavigation); rightSkipNavigation.SetInverse(leftSkipNavigation); diff --git a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs index c2d461e8d43..5d3f38a628a 100644 --- a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs @@ -74,8 +74,13 @@ public virtual EntityProjectionExpression UpdateEntityType(IEntityType derivedTy var readExpressionMap = new Dictionary(); foreach (var (property, methodCallExpression) in _readExpressionMap) { - if (derivedType.IsAssignableFrom(property.DeclaringEntityType) - || property.DeclaringEntityType.IsAssignableFrom(derivedType)) + if (property.DeclaringType is not IEntityType entityType) + { + continue; + } + + if (derivedType.IsAssignableFrom(entityType) + || entityType.IsAssignableFrom(derivedType)) { readExpressionMap[property] = methodCallExpression; } @@ -92,8 +97,16 @@ public virtual EntityProjectionExpression UpdateEntityType(IEntityType derivedTy /// public virtual MethodCallExpression BindProperty(IProperty property) { - if (!EntityType.IsAssignableFrom(property.DeclaringEntityType) - && !property.DeclaringEntityType.IsAssignableFrom(EntityType)) + if (property.DeclaringType is not IEntityType entityType) + { + if (EntityType != property.DeclaringType) + { + throw new InvalidOperationException( + InMemoryStrings.UnableToBindMemberToEntityProjection("property", property.Name, EntityType.DisplayName())); + } + } + else if (!EntityType.IsAssignableFrom(entityType) + && !entityType.IsAssignableFrom(EntityType)) { throw new InvalidOperationException( InMemoryStrings.UnableToBindMemberToEntityProjection("property", property.Name, EntityType.DisplayName())); diff --git a/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs b/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs index 42631c8db87..d6d001dc697 100644 --- a/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs +++ b/src/EFCore.InMemory/Storage/Internal/InMemoryStore.cs @@ -40,13 +40,12 @@ public InMemoryStore(IInMemoryTableFactory tableFactory) public virtual InMemoryIntegerValueGenerator GetIntegerValueGenerator( IProperty property) { + var entityType = property.DeclaringType.FundamentalEntityType; lock (_lock) { - var entityType = property.DeclaringEntityType; - return EnsureTable(entityType).GetIntegerValueGenerator( property, - entityType.GetDerivedTypesInclusive().Select(type => EnsureTable(type)).ToArray()); + entityType.GetDerivedTypesInclusive().Select(EnsureTable).ToArray()); } } diff --git a/src/EFCore.InMemory/ValueGeneration/Internal/InMemoryValueGeneratorSelector.cs b/src/EFCore.InMemory/ValueGeneration/Internal/InMemoryValueGeneratorSelector.cs index 8b98f3f12a3..d2147ac2bb5 100644 --- a/src/EFCore.InMemory/ValueGeneration/Internal/InMemoryValueGeneratorSelector.cs +++ b/src/EFCore.InMemory/ValueGeneration/Internal/InMemoryValueGeneratorSelector.cs @@ -35,12 +35,12 @@ public InMemoryValueGeneratorSelector( /// 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 ValueGenerator Select(IProperty property, IEntityType entityType) + public override ValueGenerator Select(IProperty property, ITypeBase typeBase) => property.GetValueGeneratorFactory() == null && property.ClrType.IsInteger() && property.ClrType.UnwrapNullableType() != typeof(char) ? GetOrCreate(property) - : base.Select(property, entityType); + : base.Select(property, typeBase); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -58,73 +58,57 @@ private ValueGenerator GetOrCreate(IProperty property) throw new ArgumentException( CoreStrings.InvalidValueGeneratorFactoryProperty( - "InMemoryIntegerValueGeneratorFactory", property.Name, property.DeclaringEntityType.DisplayName())); + "InMemoryIntegerValueGeneratorFactory", property.Name, property.DeclaringType.DisplayName())); } private bool FindGenerator(IProperty property, Type type, out ValueGenerator? valueGenerator) { if (type == typeof(long)) { - { - valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); - return true; - } + valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); + return true; } if (type == typeof(int)) { - { - valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); - return true; - } + valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); + return true; } if (type == typeof(short)) { - { - valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); - return true; - } + valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); + return true; } if (type == typeof(byte)) { - { - valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); - return true; - } + valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); + return true; } if (type == typeof(ulong)) { - { - valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); - return true; - } + valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); + return true; } if (type == typeof(uint)) { - { - valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); - return true; - } + valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); + return true; } if (type == typeof(ushort)) { - { - valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); - return true; - } + valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); + return true; } if (type == typeof(sbyte)) { - { - valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); - return true; - } + valueGenerator = _inMemoryStore.GetIntegerValueGenerator(property); + return true; } valueGenerator = null; @@ -132,8 +116,8 @@ private bool FindGenerator(IProperty property, Type type, out ValueGenerator? va } /// - protected override ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType) + protected override ValueGenerator? FindForType(IProperty property, ITypeBase typeBase, Type clrType) => property.ValueGenerated != ValueGenerated.Never && FindGenerator(property, clrType, out var valueGenerator) ? valueGenerator! - : base.FindForType(property, entityType, clrType); + : base.FindForType(property, typeBase, clrType); } diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs index e39d8aef907..ac1c95e6b20 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs @@ -2469,7 +2469,7 @@ public static void ModelValidationKeyDefaultValueWarning( if (diagnostics.ShouldLog(definition)) { - definition.Log(diagnostics, property.Name, property.DeclaringEntityType.DisplayName()); + definition.Log(diagnostics, property.Name, property.DeclaringType.DisplayName()); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -2489,7 +2489,7 @@ private static string ModelValidationKeyDefaultValueWarning(EventDefinitionBase var p = (PropertyEventData)payload; return d.GenerateMessage( p.Property.Name, - p.Property.DeclaringEntityType.DisplayName()); + p.Property.DeclaringType.DisplayName()); } /// @@ -2510,7 +2510,7 @@ public static void BoolWithDefaultWarning( diagnostics, property.ClrType.ShortDisplayName(), property.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), defaultValue == null ? "null" : defaultValue.ToString()!, property.ClrType.ShortDisplayName()); } @@ -2534,7 +2534,7 @@ private static string BoolWithDefaultWarning(EventDefinitionBase definition, Eve return d.GenerateMessage( p.Property.ClrType.ShortDisplayName(), p.Property.Name, - p.Property.DeclaringEntityType.DisplayName(), + p.Property.DeclaringType.DisplayName(), defaultValue == null ? "null" : defaultValue.ToString()!, p.Property.ClrType.ShortDisplayName()); } @@ -3077,7 +3077,7 @@ public static void TpcStoreGeneratedIdentityWarning( { definition.Log( diagnostics, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name); } @@ -3097,7 +3097,7 @@ private static string TpcStoreGeneratedIdentity(EventDefinitionBase definition, var d = (EventDefinition)definition; var p = (PropertyEventData)payload; return d.GenerateMessage( - p.Property.DeclaringEntityType.DisplayName(), + p.Property.DeclaringType.DisplayName(), p.Property.Name); } diff --git a/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs new file mode 100644 index 00000000000..e2fe7925290 --- /dev/null +++ b/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs @@ -0,0 +1,521 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore; + +/// +/// Relational database specific extension methods for . +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public static class RelationalComplexTypePropertyBuilderExtensions +{ + /// + /// Configures the column that the property maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasColumnName( + this ComplexTypePropertyBuilder propertyBuilder, + string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + propertyBuilder.Metadata.SetColumnName(name); + + return propertyBuilder; + } + + /// + /// Configures the column that the property maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasColumnName( + this ComplexTypePropertyBuilder propertyBuilder, + string? name) + => (ComplexTypePropertyBuilder)HasColumnName((ComplexTypePropertyBuilder)propertyBuilder, name); + + /// + /// Configures the order of the column the property is mapped to. + /// + /// The builder of the property being configured. + /// The column order. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasColumnOrder(this ComplexTypePropertyBuilder propertyBuilder, int? order) + { + propertyBuilder.Metadata.SetColumnOrder(order); + + return propertyBuilder; + } + + /// + /// Configures the order of the column the property is mapped to. + /// + /// The builder of the property being configured. + /// The column order. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasColumnOrder(this ComplexTypePropertyBuilder propertyBuilder, int? order) + => (ComplexTypePropertyBuilder)HasColumnOrder((ComplexTypePropertyBuilder)propertyBuilder, order); + + /// + /// Configures the data type of the column that the property maps to when targeting a relational database. + /// This should be the complete type name, including precision, scale, length, etc. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the data type of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasColumnType( + this ComplexTypePropertyBuilder propertyBuilder, + string? typeName) + { + Check.NullButNotEmpty(typeName, nameof(typeName)); + + propertyBuilder.Metadata.SetColumnType(typeName); + + return propertyBuilder; + } + + /// + /// Configures the data type of the column that the property maps to when targeting a relational database. + /// This should be the complete type name, including precision, scale, length, etc. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the data type of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasColumnType( + this ComplexTypePropertyBuilder propertyBuilder, + string? typeName) + => (ComplexTypePropertyBuilder)HasColumnType((ComplexTypePropertyBuilder)propertyBuilder, typeName); + + /// + /// Configures the property as capable of storing only fixed-length data, such as strings. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// A value indicating whether the property is constrained to fixed length values. + /// The same builder instance so that multiple configuration calls can be chained. + public static ComplexTypePropertyBuilder IsFixedLength( + this ComplexTypePropertyBuilder propertyBuilder, + bool fixedLength = true) + { + propertyBuilder.Metadata.SetIsFixedLength(fixedLength); + + return propertyBuilder; + } + + /// + /// Configures the property as capable of storing only fixed-length data, such as strings. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// A value indicating whether the property is constrained to fixed length values. + /// The same builder instance so that multiple configuration calls can be chained. + public static ComplexTypePropertyBuilder IsFixedLength( + this ComplexTypePropertyBuilder propertyBuilder, + bool fixedLength = true) + => (ComplexTypePropertyBuilder)IsFixedLength((ComplexTypePropertyBuilder)propertyBuilder, fixedLength); + + /// + /// Configures the default value expression for the column that the property maps to when targeting a + /// relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default value constraint of + /// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an + /// existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasDefaultValueSql(this ComplexTypePropertyBuilder propertyBuilder) + { + propertyBuilder.Metadata.SetDefaultValueSql(string.Empty); + + return propertyBuilder; + } + + /// + /// Configures the default value expression for the column that the property maps to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The SQL expression for the default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasDefaultValueSql( + this ComplexTypePropertyBuilder propertyBuilder, + string? sql) + { + Check.NullButNotEmpty(sql, nameof(sql)); + + propertyBuilder.Metadata.SetDefaultValueSql(sql); + + return propertyBuilder; + } + + /// + /// Configures the default value expression for the column that the property maps to when targeting a + /// relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default value constraint of + /// some sort without needing to specify exactly what it is. This can be useful when mapping EF to an + /// existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasDefaultValueSql( + this ComplexTypePropertyBuilder propertyBuilder) + => (ComplexTypePropertyBuilder)HasDefaultValueSql((ComplexTypePropertyBuilder)propertyBuilder); + + /// + /// Configures the default value expression for the column that the property maps to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The SQL expression for the default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasDefaultValueSql( + this ComplexTypePropertyBuilder propertyBuilder, + string? sql) + => (ComplexTypePropertyBuilder)HasDefaultValueSql((ComplexTypePropertyBuilder)propertyBuilder, sql); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// + /// When called with no arguments, this method tells EF that a column is computed without needing to + /// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing + /// database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasComputedColumnSql(this ComplexTypePropertyBuilder propertyBuilder) + { + propertyBuilder.Metadata.SetComputedColumnSql(string.Empty); + + return propertyBuilder; + } + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasComputedColumnSql( + this ComplexTypePropertyBuilder propertyBuilder, + string? sql) + => HasComputedColumnSql(propertyBuilder, sql, null); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// + /// If , the computed value is calculated on row modification and stored in the database like a regular column. + /// If , the value is computed when the value is read, and does not occupy any actual storage. + /// selects the database provider default. + /// + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasComputedColumnSql( + this ComplexTypePropertyBuilder propertyBuilder, + string? sql, + bool? stored) + { + Check.NullButNotEmpty(sql, nameof(sql)); + + propertyBuilder.Metadata.SetComputedColumnSql(sql); + + if (stored != null) + { + propertyBuilder.Metadata.SetIsStored(stored); + } + + return propertyBuilder; + } + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// + /// When called with no arguments, this method tells EF that a column is computed without needing to + /// specify the actual SQL used to computed it. This can be useful when mapping EF to an existing + /// database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasComputedColumnSql( + this ComplexTypePropertyBuilder propertyBuilder) + => (ComplexTypePropertyBuilder)HasComputedColumnSql((ComplexTypePropertyBuilder)propertyBuilder); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasComputedColumnSql( + this ComplexTypePropertyBuilder propertyBuilder, + string? sql) + => HasComputedColumnSql(propertyBuilder, sql, null); + + /// + /// Configures the property to map to a computed column when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The SQL expression that computes values for the column. + /// + /// If , the computed value is calculated on row modification and stored in the database like a regular column. + /// If , the value is computed when the value is read, and does not occupy any actual storage. + /// selects the database provider default. + /// + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasComputedColumnSql( + this ComplexTypePropertyBuilder propertyBuilder, + string? sql, + bool? stored) + => (ComplexTypePropertyBuilder)HasComputedColumnSql((ComplexTypePropertyBuilder)propertyBuilder, sql, stored); + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default + /// value constraint of some sort without needing to specify exactly what it is. + /// This can be useful when mapping EF to an existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasDefaultValue(this ComplexTypePropertyBuilder propertyBuilder) + { + propertyBuilder.Metadata.SetDefaultValue(DBNull.Value); + + return propertyBuilder; + } + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasDefaultValue( + this ComplexTypePropertyBuilder propertyBuilder, + object? value) + { + propertyBuilder.Metadata.SetDefaultValue(value); + + return propertyBuilder; + } + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// + /// When called with no argument, this method tells EF that a column has a default + /// value constraint of some sort without needing to specify exactly what it is. + /// This can be useful when mapping EF to an existing database. + /// + /// + /// See Database default values for more information and examples. + /// + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasDefaultValue( + this ComplexTypePropertyBuilder propertyBuilder) + => (ComplexTypePropertyBuilder)HasDefaultValue((ComplexTypePropertyBuilder)propertyBuilder); + + /// + /// Configures the default value for the column that the property maps + /// to when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The default value of the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasDefaultValue( + this ComplexTypePropertyBuilder propertyBuilder, + object? value) + => (ComplexTypePropertyBuilder)HasDefaultValue((ComplexTypePropertyBuilder)propertyBuilder, value); + + /// + /// Configures a comment to be applied to the column + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The comment for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasComment( + this ComplexTypePropertyBuilder propertyBuilder, + string? comment) + { + propertyBuilder.Metadata.SetComment(comment); + + return propertyBuilder; + } + + /// + /// Configures a comment to be applied to the column + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The comment for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasComment( + this ComplexTypePropertyBuilder propertyBuilder, + string? comment) + => (ComplexTypePropertyBuilder)HasComment((ComplexTypePropertyBuilder)propertyBuilder, comment); + + /// + /// Configures the property to use the given collation. The database column will be created with the given + /// collation, and it will be used implicitly in all collation-sensitive operations. + /// + /// + /// See Database collations for more information and examples. + /// + /// The builder for the property being configured. + /// The collation for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder UseCollation(this ComplexTypePropertyBuilder propertyBuilder, string? collation) + { + Check.NullButNotEmpty(collation, nameof(collation)); + + propertyBuilder.Metadata.SetCollation(collation); + + return propertyBuilder; + } + + /// + /// Configures the property to use the given collation. The database column will be created with the given + /// collation, and it will be used implicitly in all collation-sensitive operations. + /// + /// + /// See Database collations for more information and examples. + /// + /// The builder for the property being configured. + /// The collation for the column. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder UseCollation( + this ComplexTypePropertyBuilder propertyBuilder, + string? collation) + => (ComplexTypePropertyBuilder)UseCollation((ComplexTypePropertyBuilder)propertyBuilder, collation); + + /// + /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property, + /// rather than using the entity property name. + /// + /// The builder for the property being configured. + /// JSON property name to be used. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasJsonPropertyName( + this ComplexTypePropertyBuilder propertyBuilder, + string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + propertyBuilder.Metadata.SetJsonPropertyName(name); + + return propertyBuilder; + } + + /// + /// Configures the property of an entity mapped to a JSON column, mapping the entity property to a specific JSON property, + /// rather than using the entity property name. + /// + /// The builder for the property being configured. + /// JSON property name to be used. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder HasJsonPropertyName( + this ComplexTypePropertyBuilder propertyBuilder, + string? name) + => (ComplexTypePropertyBuilder)HasJsonPropertyName((ComplexTypePropertyBuilder)propertyBuilder, name); +} diff --git a/src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs b/src/EFCore.Relational/Extensions/RelationalDbFunctionExtensions.cs similarity index 97% rename from src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs rename to src/EFCore.Relational/Extensions/RelationalDbFunctionExtensions.cs index f3a3f86be73..7ce5591e770 100644 --- a/src/EFCore.Relational/Extensions/RelationalDbFunctionsExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalDbFunctionExtensions.cs @@ -12,7 +12,7 @@ namespace Microsoft.EntityFrameworkCore; /// /// See Database functions for more information and examples. /// -public static class RelationalDbFunctionsExtensions +public static class RelationalDbFunctionExtensions { /// /// Explicitly specifies a collation to be used in a LINQ query. Can be used to generate fragments such as diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index f07f43709ef..80205f65e62 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -248,26 +248,6 @@ public static void SetSchema(this IMutableEntityType entityType, string? value) return (string.IsNullOrEmpty(schema) ? "" : schema + ".") + viewName; } - /// - /// Returns the default mappings that the entity type would use. - /// - /// The entity type to get the table mappings for. - /// The tables to which the entity type is mapped. - public static IEnumerable GetDefaultMappings(this IEntityType entityType) - => (IEnumerable?)entityType.FindRuntimeAnnotationValue( - RelationalAnnotationNames.DefaultMappings) - ?? Enumerable.Empty(); - - /// - /// Returns the tables to which the entity type is mapped. - /// - /// The entity type to get the table mappings for. - /// The tables to which the entity type is mapped. - public static IEnumerable GetTableMappings(this IEntityType entityType) - => (IEnumerable?)entityType.FindRuntimeAnnotationValue( - RelationalAnnotationNames.TableMappings) - ?? Enumerable.Empty(); - #endregion Table mapping #region View mapping @@ -418,16 +398,6 @@ public static void SetViewSchema(this IMutableEntityType entityType, string? val => entityType.FindAnnotation(RelationalAnnotationNames.ViewSchema) ?.GetConfigurationSource(); - /// - /// Returns the views to which the entity type is mapped. - /// - /// The entity type to get the view mappings for. - /// The views to which the entity type is mapped. - public static IEnumerable GetViewMappings(this IEntityType entityType) - => (IEnumerable?)entityType.FindRuntimeAnnotationValue( - RelationalAnnotationNames.ViewMappings) - ?? Enumerable.Empty(); - #endregion View mapping #region SQL query mapping @@ -487,16 +457,6 @@ public static void SetSqlQuery(this IMutableEntityType entityType, string? name) => entityType.FindAnnotation(RelationalAnnotationNames.SqlQuery) ?.GetConfigurationSource(); - /// - /// Returns the SQL string mappings. - /// - /// The entity type to get the SQL string mappings for. - /// The SQL string to which the entity type is mapped. - public static IEnumerable GetSqlQueryMappings(this IEntityType entityType) - => (IEnumerable?)entityType.FindRuntimeAnnotationValue( - RelationalAnnotationNames.SqlQueryMappings) - ?? Enumerable.Empty(); - #endregion SQL query mapping #region Function mapping @@ -547,16 +507,6 @@ public static void SetFunctionName(this IMutableEntityType entityType, string? n => entityType.FindAnnotation(RelationalAnnotationNames.FunctionName) ?.GetConfigurationSource(); - /// - /// Returns the functions to which the entity type is mapped. - /// - /// The entity type to get the function mappings for. - /// The functions to which the entity type is mapped. - public static IEnumerable GetFunctionMappings(this IEntityType entityType) - => (IEnumerable?)entityType.FindRuntimeAnnotationValue( - RelationalAnnotationNames.FunctionMappings) - ?? Enumerable.Empty(); - #endregion #region SProc mapping @@ -798,36 +748,6 @@ public static IMutableStoredProcedure SetUpdateStoredProcedure(this IMutableEnti public static ConfigurationSource? GetUpdateStoredProcedureConfigurationSource(this IConventionEntityType entityType) => StoredProcedure.GetStoredProcedureConfigurationSource(entityType, StoreObjectType.UpdateStoredProcedure); - /// - /// Returns the insert stored procedures to which the entity type is mapped. - /// - /// The entity type. - /// The insert stored procedures to which the entity type is mapped. - public static IEnumerable GetInsertStoredProcedureMappings(this IEntityType entityType) - => (IEnumerable?)entityType.FindRuntimeAnnotationValue( - RelationalAnnotationNames.InsertStoredProcedureMappings) - ?? Enumerable.Empty(); - - /// - /// Returns the delete stored procedures to which the entity type is mapped. - /// - /// The entity type. - /// The delete stored procedures to which the entity type is mapped. - public static IEnumerable GetDeleteStoredProcedureMappings(this IEntityType entityType) - => (IEnumerable?)entityType.FindRuntimeAnnotationValue( - RelationalAnnotationNames.DeleteStoredProcedureMappings) - ?? Enumerable.Empty(); - - /// - /// Returns the update stored procedures to which the entity type is mapped. - /// - /// The entity type. - /// The update stored procedures to which the entity type is mapped. - public static IEnumerable GetUpdateStoredProcedureMappings(this IEntityType entityType) - => (IEnumerable?)entityType.FindRuntimeAnnotationValue( - RelationalAnnotationNames.UpdateStoredProcedureMappings) - ?? Enumerable.Empty(); - #endregion #region Check constraint @@ -1572,6 +1492,10 @@ public static void SetIsTableExcludedFromMigrations( in StoreObjectIdentifier storeObject) => entityType.FindMappingFragment(storeObject)?.GetIsTableExcludedFromMigrationsConfigurationSource(); + #endregion IsTableExcludedFromMigrations + + #region Mapping strategy + /// /// Gets the mapping strategy for the derived types. /// @@ -1587,10 +1511,6 @@ public static void SetIsTableExcludedFromMigrations( ? null : RelationalAnnotationNames.TptMappingStrategy); - #endregion IsTableExcludedFromMigrations - - #region Mapping strategy - /// /// Sets the mapping strategy for the derived types. /// diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 5696676dbad..f7e3d5affc4 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -63,13 +63,13 @@ public static string GetColumnName(this IReadOnlyProperty property) if (property.IsPrimaryKey()) { var tableFound = false; - if (property.DeclaringEntityType.FindMappingFragment(storeObject) != null) + if (property.DeclaringType.FindMappingFragment(storeObject) != null) { tableFound = true; } - else + else if(property.DeclaringType is IReadOnlyEntityType declaringEntityType) { - foreach (var containingType in property.DeclaringEntityType.GetDerivedTypesInclusive()) + foreach (var containingType in declaringEntityType.GetDerivedTypesInclusive()) { if (StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType) == storeObject) { @@ -84,53 +84,57 @@ public static string GetColumnName(this IReadOnlyProperty property) return null; } } - else if (property.DeclaringEntityType.GetMappingStrategy() != RelationalAnnotationNames.TpcMappingStrategy) + else { - var declaringStoreObject = StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObject.StoreObjectType); - if (declaringStoreObject == null) + var declaringEntityType = property.DeclaringType.FundamentalEntityType; + if (declaringEntityType.GetMappingStrategy() != RelationalAnnotationNames.TpcMappingStrategy) { - var tableFound = false; - var queue = new Queue(); - queue.Enqueue(property.DeclaringEntityType); - while (queue.Count > 0 && !tableFound) + var declaringStoreObject = StoreObjectIdentifier.Create(property.DeclaringType, storeObject.StoreObjectType); + if (declaringStoreObject == null) { - foreach (var containingType in queue.Dequeue().GetDirectlyDerivedTypes()) + var tableFound = false; + var queue = new Queue(); + queue.Enqueue(declaringEntityType); + while (queue.Count > 0 && !tableFound) { - declaringStoreObject = StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType); - if (declaringStoreObject == null) + foreach (var containingType in queue.Dequeue().GetDirectlyDerivedTypes()) { - queue.Enqueue(containingType); - continue; - } - - if (declaringStoreObject == storeObject) - { - tableFound = true; - break; + declaringStoreObject = StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType); + if (declaringStoreObject == null) + { + queue.Enqueue(containingType); + continue; + } + + if (declaringStoreObject == storeObject) + { + tableFound = true; + break; + } } } - } - if (!tableFound) - { - return null; - } - } - else - { - var fragments = property.DeclaringEntityType.GetMappingFragments(storeObject.StoreObjectType).ToList(); - if (fragments.Count > 0) - { - if (overrides == null - && (declaringStoreObject != storeObject - || fragments.Any(f => property.FindOverrides(f.StoreObject) != null))) + if (!tableFound) { return null; } } - else if (declaringStoreObject != storeObject) + else { - return null; + var fragments = property.DeclaringType.GetMappingFragments(storeObject.StoreObjectType).ToList(); + if (fragments.Count > 0) + { + if (overrides == null + && (declaringStoreObject != storeObject + || fragments.Any(f => property.FindOverrides(f.StoreObject) != null))) + { + return null; + } + } + else if (declaringStoreObject != storeObject) + { + return null; + } } } } @@ -181,7 +185,7 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property) } } - return Uniquifier.Truncate(name, property.DeclaringEntityType.Model.GetMaxIdentifierLength()); + return Uniquifier.Truncate(name, property.DeclaringType.Model.GetMaxIdentifierLength()); } /// @@ -192,7 +196,7 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property) /// The default column name to which the property would be mapped. public static string? GetDefaultColumnName(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { - if (property.DeclaringEntityType.IsMappedToJson()) + if (property.DeclaringType.IsMappedToJson()) { return null; } @@ -209,30 +213,47 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property) return sharedTablePrincipalConcurrencyProperty.GetColumnName(storeObject)!; } - var entityType = property.DeclaringEntityType; + StringBuilder? builder = null; var currentStoreObject = storeObject; - while (true) + if (property.DeclaringType is IReadOnlyEntityType entityType) { - var ownership = entityType.GetForeignKeys().SingleOrDefault(fk => fk.IsOwnership); - if (ownership == null) + while (true) { - break; - } + var ownership = entityType.GetForeignKeys().SingleOrDefault(fk => fk.IsOwnership); + if (ownership == null) + { + break; + } - var ownerType = ownership.PrincipalEntityType; - if (StoreObjectIdentifier.Create(ownerType, currentStoreObject.StoreObjectType) != currentStoreObject - && ownerType.GetMappingFragments(storeObject.StoreObjectType) - .All(f => f.StoreObject != currentStoreObject)) - { - break; - } + var ownerType = ownership.PrincipalEntityType; + if (StoreObjectIdentifier.Create(ownerType, currentStoreObject.StoreObjectType) != currentStoreObject + && ownerType.GetMappingFragments(storeObject.StoreObjectType) + .All(f => f.StoreObject != currentStoreObject)) + { + break; + } + + builder ??= new StringBuilder(); + builder.Insert(0, "_"); + builder.Insert(0, ownership.PrincipalToDependent!.Name); + entityType = ownerType; + } + } + else if (StoreObjectIdentifier.Create(property.DeclaringType, currentStoreObject.StoreObjectType) == currentStoreObject + || property.DeclaringType.GetMappingFragments(storeObject.StoreObjectType) + .Any(f => f.StoreObject == currentStoreObject)) + { + var complexType = (IReadOnlyComplexType)property.DeclaringType; builder ??= new StringBuilder(); + while (complexType != null) + { + builder.Insert(0, "_"); + builder.Insert(0, complexType.ComplexProperty.Name); - builder.Insert(0, "_"); - builder.Insert(0, ownership.PrincipalToDependent!.Name); - entityType = ownerType; + complexType = complexType.ComplexProperty.DeclaringType as IReadOnlyComplexType; + } } var baseName = storeObject.StoreObjectType == StoreObjectType.Table ? property.GetDefaultColumnName() : property.Name; @@ -244,7 +265,7 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property) builder.Append(baseName); baseName = builder.ToString(); - return Uniquifier.Truncate(baseName, property.DeclaringEntityType.Model.GetMaxIdentifierLength()); + return Uniquifier.Truncate(baseName, property.DeclaringType.Model.GetMaxIdentifierLength()); } /// @@ -960,7 +981,7 @@ public static void SetDefaultValue(this IMutableProperty property, object? value { throw new InvalidOperationException( RelationalStrings.IncorrectDefaultValueType( - value, valueType, property.Name, property.ClrType, property.DeclaringEntityType.DisplayName())); + value, valueType, property.Name, property.ClrType, property.DeclaringType.DisplayName())); } } @@ -1123,7 +1144,9 @@ public static void SetIsFixedLength(this IMutableProperty property, bool? fixedL /// if the mapped column is nullable; otherwise. public static bool IsColumnNullable(this IReadOnlyProperty property) => property.IsNullable - || (property.DeclaringEntityType.BaseType != null && property.DeclaringEntityType.FindDiscriminatorProperty() != null); + || (property.DeclaringType is IReadOnlyEntityType entityType + && entityType.BaseType != null + && entityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy); /// /// Checks whether the column mapped to the given property will be nullable @@ -1151,8 +1174,10 @@ public static bool IsColumnNullable(this IReadOnlyProperty property, in StoreObj } return property.IsNullable - || (property.DeclaringEntityType.BaseType != null && property.DeclaringEntityType.FindDiscriminatorProperty() != null) - || IsOptionalSharingDependent(property.DeclaringEntityType, storeObject, 0); + || (property.DeclaringType is IReadOnlyEntityType entityType + && ((entityType.BaseType != null + && entityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy) + || IsOptionalSharingDependent(entityType, storeObject, 0))); } private static bool IsOptionalSharingDependent( @@ -1403,7 +1428,7 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope private static IReadOnlyProperty? FindSharedObjectRootProperty(IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { - if (property.DeclaringEntityType.IsMappedToJson()) + if (property.DeclaringType.IsMappedToJson()) { //JSON-splitting is not supported //issue #28574 @@ -1415,7 +1440,7 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope { throw new InvalidOperationException( RelationalStrings.PropertyNotMappedToTable( - property.Name, property.DeclaringEntityType.DisplayName(), storeObject.DisplayName())); + property.Name, property.DeclaringType.DisplayName(), storeObject.DisplayName())); } var rootProperty = property; @@ -1424,8 +1449,14 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope // Using a hashset is detrimental to the perf when there are no cycles for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++) { + var entityType = rootProperty.DeclaringType as IReadOnlyEntityType; + if (entityType == null) + { + break; + } + IReadOnlyProperty? linkedProperty = null; - foreach (var p in rootProperty.DeclaringEntityType + foreach (var p in entityType .FindRowInternalForeignKeys(storeObject) .SelectMany(fk => fk.PrincipalEntityType.GetProperties())) { @@ -1462,9 +1493,8 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope // Using a hashset is detrimental to the perf when there are no cycles for (var i = 0; i < Metadata.Internal.RelationalEntityTypeExtensions.MaxEntityTypesSharingTable; i++) { - var linkingRelationship = principalProperty.DeclaringEntityType - .FindRowInternalForeignKeys(storeObject).FirstOrDefault(); - + var entityType = principalProperty.DeclaringType as IReadOnlyEntityType; + var linkingRelationship = entityType?.FindRowInternalForeignKeys(storeObject).FirstOrDefault(); if (linkingRelationship == null) { break; @@ -1486,12 +1516,13 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope } var principalProperty = property; + // 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++) { - var linkingRelationship = principalProperty.DeclaringEntityType - .FindRowInternalForeignKeys(storeObject).FirstOrDefault(); + var entityType = principalProperty.DeclaringType as IReadOnlyEntityType; + var linkingRelationship = entityType?.FindRowInternalForeignKeys(storeObject).FirstOrDefault(); if (linkingRelationship == null) { break; @@ -1728,7 +1759,7 @@ public static IEnumerable GetMappedStoreObjects( this IReadOnlyProperty property, StoreObjectType storeObjectType) { - var declaringType = property.DeclaringEntityType; + var declaringType = property.DeclaringType; var declaringStoreObject = StoreObjectIdentifier.Create(declaringType, storeObjectType); if (declaringStoreObject != null && property.GetColumnName(declaringStoreObject.Value) != null) @@ -1754,13 +1785,16 @@ public static IEnumerable GetMappedStoreObjects( yield break; } - foreach (var derivedType in declaringType.GetDerivedTypes()) + if (declaringType is IReadOnlyEntityType entityType) { - var derivedStoreObject = StoreObjectIdentifier.Create(derivedType, storeObjectType); - if (derivedStoreObject != null - && property.GetColumnName(derivedStoreObject.Value) != null) + foreach (var derivedType in entityType.GetDerivedTypes()) { - yield return derivedStoreObject.Value; + var derivedStoreObject = StoreObjectIdentifier.Create(derivedType, storeObjectType); + if (derivedStoreObject != null + && property.GetColumnName(derivedStoreObject.Value) != null) + { + yield return derivedStoreObject.Value; + } } } } @@ -1939,7 +1973,7 @@ private static TValue ThrowReadValueException( /// public static string? GetJsonPropertyName(this IReadOnlyProperty property) => (string?)property.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.Value - ?? (property.IsKey() || !property.DeclaringEntityType.IsMappedToJson() ? null : property.Name); + ?? (property.IsKey() || !property.DeclaringType.IsMappedToJson() ? null : property.Name); /// /// Sets the value of JSON property name used for the given property of an entity mapped to a JSON column. diff --git a/src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs b/src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs new file mode 100644 index 00000000000..43a037a3168 --- /dev/null +++ b/src/EFCore.Relational/Extensions/RelationalTypeBaseExtensions.cs @@ -0,0 +1,372 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore; + +/// +/// Type extension methods for relational database metadata. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public static class RelationalTypeBaseExtensions +{ + #region Table mapping + + /// + /// Returns the name of the table to which the type is mapped + /// or if not mapped to a table. + /// + /// The type to get the table name for. + /// The name of the table to which the type is mapped. + public static string? GetTableName(this IReadOnlyTypeBase typeBase) + => typeBase.FundamentalEntityType.GetTableName(); + + /// + /// Returns the database schema that contains the mapped table. + /// + /// The type to get the schema for. + /// The database schema that contains the mapped table. + public static string? GetSchema(this IReadOnlyTypeBase typeBase) + => typeBase.FundamentalEntityType.GetSchema(); + + /// + /// Returns the default mappings that the type would use. + /// + /// The type to get the table mappings for. + /// The tables to which the type is mapped. + public static IEnumerable GetDefaultMappings(this ITypeBase typeBase) + => (IEnumerable?)typeBase.FindRuntimeAnnotationValue( + RelationalAnnotationNames.DefaultMappings) + ?? Enumerable.Empty(); + + /// + /// Returns the tables to which the type is mapped. + /// + /// The type to get the table mappings for. + /// The tables to which the type is mapped. + public static IEnumerable GetTableMappings(this ITypeBase typeBase) + => (IEnumerable?)typeBase.FindRuntimeAnnotationValue( + RelationalAnnotationNames.TableMappings) + ?? Enumerable.Empty(); + + #endregion Table mapping + + #region View mapping + + /// + /// Returns the name of the view to which the type is mapped or if not mapped to a view. + /// + /// The type to get the view name for. + /// The name of the view to which the type is mapped. + public static string? GetViewName(this IReadOnlyTypeBase typeBase) + => typeBase.FundamentalEntityType.GetViewName(); + + /// + /// Returns the database schema that contains the mapped view. + /// + /// The type to get the view schema for. + /// The database schema that contains the mapped view. + public static string? GetViewSchema(this IReadOnlyTypeBase typeBase) + => typeBase.FundamentalEntityType.GetViewSchema(); + + /// + /// Returns the views to which the type is mapped. + /// + /// The type to get the view mappings for. + /// The views to which the type is mapped. + public static IEnumerable GetViewMappings(this ITypeBase typeBase) + => (IEnumerable?)typeBase.FindRuntimeAnnotationValue( + RelationalAnnotationNames.ViewMappings) + ?? Enumerable.Empty(); + + #endregion View mapping + + #region SQL query mapping + + /// + /// Returns the SQL string used to provide data for the type or if not mapped to a SQL string. + /// + /// The type. + /// The SQL string used to provide data for the type. + public static string? GetSqlQuery(this IReadOnlyTypeBase typeBase) + => typeBase.FundamentalEntityType.GetSqlQuery(); + + /// + /// Returns the SQL string mappings. + /// + /// The type to get the SQL string mappings for. + /// The SQL string to which the type is mapped. + public static IEnumerable GetSqlQueryMappings(this ITypeBase typeBase) + => (IEnumerable?)typeBase.FindRuntimeAnnotationValue( + RelationalAnnotationNames.SqlQueryMappings) + ?? Enumerable.Empty(); + + #endregion SQL query mapping + + #region Function mapping + + /// + /// Returns the name of the function to which the type is mapped or if not mapped to a function. + /// + /// The type to get the function name for. + /// The name of the function to which the type is mapped. + public static string? GetFunctionName(this IReadOnlyTypeBase typeBase) + => typeBase.FundamentalEntityType.GetFunctionName(); + + /// + /// Returns the functions to which the type is mapped. + /// + /// The type to get the function mappings for. + /// The functions to which the type is mapped. + public static IEnumerable GetFunctionMappings(this ITypeBase typeBase) + => (IEnumerable?)typeBase.FindRuntimeAnnotationValue( + RelationalAnnotationNames.FunctionMappings) + ?? Enumerable.Empty(); + + #endregion + + #region SProc mapping + + /// + /// Returns the stored procedure to which the type is mapped for deletes + /// or if not mapped to a stored procedure. + /// + /// The type. + /// The stored procedure to which the type is mapped. + public static IReadOnlyStoredProcedure? GetDeleteStoredProcedure(this IReadOnlyTypeBase typeBase) + => typeBase.FundamentalEntityType.GetDeleteStoredProcedure(); + + /// + /// Returns the stored procedure to which the type is mapped for deletes + /// or if not mapped to a stored procedure. + /// + /// The type. + /// The stored procedure to which the type is mapped. + public static IStoredProcedure? GetDeleteStoredProcedure(this ITypeBase typeBase) + => typeBase.FundamentalEntityType.GetDeleteStoredProcedure(); + + /// + /// Returns the stored procedure to which the type is mapped for inserts + /// or if not mapped to a stored procedure. + /// + /// The type. + /// The stored procedure to which the type is mapped. + public static IReadOnlyStoredProcedure? GetInsertStoredProcedure(this IReadOnlyTypeBase typeBase) + => typeBase.FundamentalEntityType.GetInsertStoredProcedure(); + + /// + /// Returns the stored procedure to which the type is mapped for inserts + /// or if not mapped to a stored procedure. + /// + /// The type. + /// The stored procedure to which the type is mapped. + public static IStoredProcedure? GetInsertStoredProcedure(this ITypeBase typeBase) + => typeBase.FundamentalEntityType.GetInsertStoredProcedure(); + + /// + /// Returns the stored procedure to which the type is mapped for updates + /// or if not mapped to a stored procedure. + /// + /// The type. + /// The stored procedure to which the type is mapped. + public static IReadOnlyStoredProcedure? GetUpdateStoredProcedure(this IReadOnlyTypeBase typeBase) + => typeBase.FundamentalEntityType.GetUpdateStoredProcedure(); + + /// + /// Returns the stored procedure to which the type is mapped for updates + /// or if not mapped to a stored procedure. + /// + /// The type. + /// The stored procedure to which the type is mapped. + public static IStoredProcedure? GetUpdateStoredProcedure(this ITypeBase typeBase) + => typeBase.FundamentalEntityType.GetUpdateStoredProcedure(); + + /// + /// Returns the insert stored procedures to which the type is mapped. + /// + /// The type. + /// The insert stored procedures to which the type is mapped. + public static IEnumerable GetInsertStoredProcedureMappings(this ITypeBase typeBase) + => (IEnumerable?)typeBase.FindRuntimeAnnotationValue( + RelationalAnnotationNames.InsertStoredProcedureMappings) + ?? Enumerable.Empty(); + + /// + /// Returns the delete stored procedures to which the type is mapped. + /// + /// The type. + /// The delete stored procedures to which the type is mapped. + public static IEnumerable GetDeleteStoredProcedureMappings(this ITypeBase typeBase) + => (IEnumerable?)typeBase.FindRuntimeAnnotationValue( + RelationalAnnotationNames.DeleteStoredProcedureMappings) + ?? Enumerable.Empty(); + + /// + /// Returns the update stored procedures to which the type is mapped. + /// + /// The type. + /// The update stored procedures to which the type is mapped. + public static IEnumerable GetUpdateStoredProcedureMappings(this ITypeBase typeBase) + => (IEnumerable?)typeBase.FindRuntimeAnnotationValue( + RelationalAnnotationNames.UpdateStoredProcedureMappings) + ?? Enumerable.Empty(); + + #endregion + + #region Mapping Fragments + + /// + /// + /// Returns all configured type mapping fragments. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The type. + /// The configured type mapping fragments. + public static IEnumerable GetMappingFragments(this IReadOnlyTypeBase typeBase) + => typeBase.FundamentalEntityType.GetMappingFragments(); + + /// + /// + /// Returns all configured type mapping fragments. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The type. + /// The configured type mapping fragments. + public static IEnumerable GetMappingFragments(this ITypeBase typeBase) + => typeBase.FundamentalEntityType.GetMappingFragments(); + + /// + /// + /// Returns all configured type mapping fragments of the given type. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The type. + /// The type of store object to get the mapping fragments for. + /// The configured type mapping fragments. + public static IEnumerable GetMappingFragments( + this IReadOnlyTypeBase typeBase, + StoreObjectType storeObjectType) + => typeBase.FundamentalEntityType.GetMappingFragments(storeObjectType); + + /// + /// + /// Returns all configured type mapping fragments of the given type. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The type. + /// The type of store object to get the mapping fragments for. + /// The configured type mapping fragments. + public static IEnumerable GetMappingFragments( + this ITypeBase typeBase, + StoreObjectType storeObjectType) + => typeBase.FundamentalEntityType.GetMappingFragments(storeObjectType); + + /// + /// + /// Returns the type mapping for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The type. + /// The identifier of a table-like store object. + /// An object that represents an type mapping fragment. + public static IReadOnlyEntityTypeMappingFragment? FindMappingFragment( + this IReadOnlyTypeBase typeBase, + in StoreObjectIdentifier storeObject) + => typeBase.FundamentalEntityType.FindMappingFragment(storeObject); + + /// + /// + /// Returns the type mapping for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The type. + /// The identifier of a table-like store object. + /// An object that represents an type mapping fragment. + public static IEntityTypeMappingFragment? FindMappingFragment( + this ITypeBase typeBase, + in StoreObjectIdentifier storeObject) + => typeBase.FundamentalEntityType.FindMappingFragment(storeObject); + + #endregion + + #region Mapping strategy + + /// + /// Gets the mapping strategy for the derived types. + /// + /// The type. + /// The mapping strategy for the derived types. + public static string? GetMappingStrategy(this IReadOnlyTypeBase typeBase) + => typeBase.FundamentalEntityType.GetMappingStrategy(); + + #endregion Mapping strategy + + #region Json + + /// + /// Gets a value indicating whether the specified entity is mapped to a JSON column. + /// + /// The type. + /// A value indicating whether the associated type is mapped to a JSON column. + public static bool IsMappedToJson(this IReadOnlyTypeBase typeBase) + => !string.IsNullOrEmpty(typeBase.GetContainerColumnName()); + + /// + /// Gets the container column name to which the type is mapped. + /// + /// The type to get the container column name for. + /// The container column name to which the type is mapped. + public static string? GetContainerColumnName(this IReadOnlyTypeBase typeBase) + => typeBase.FindAnnotation(RelationalAnnotationNames.ContainerColumnName)?.Value is string columnName + ? columnName + : typeBase is IReadOnlyEntityType entityType + ? (entityType.FindOwnership()?.PrincipalEntityType.GetContainerColumnName()) + : ((IReadOnlyComplexType)typeBase).ComplexProperty.DeclaringType.GetContainerColumnName(); + + /// + /// Gets the value of JSON property name used for the given entity mapped to a JSON column. + /// + /// + /// Unless configured explicitly, navigation name is used. + /// + /// The type. + /// + /// The value for the JSON property used to store this type. + /// is returned for entities that are not mapped to a JSON column. + /// + public static string? GetJsonPropertyName(this IReadOnlyTypeBase typeBase) + => (string?)typeBase.FindAnnotation(RelationalAnnotationNames.JsonPropertyName)?.Value + ?? (!typeBase.IsMappedToJson() + ? null + : typeBase is IReadOnlyEntityType entityType + ? entityType.FindOwnership()!.GetNavigation(pointsToPrincipal: false)!.Name + : ((IReadOnlyComplexType)typeBase).ComplexProperty.Name); + + #endregion +} diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index a65590104d6..a364761733e 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -680,7 +680,7 @@ protected virtual void ValidateBoolsWithDefaults( && property.ValueGenerated != ValueGenerated.Never && property.FieldInfo?.FieldType.IsNullableType() != true && !((IConventionProperty)property).GetSentinelConfigurationSource().HasValue - && (StoreObjectIdentifier.Create(property.DeclaringEntityType, StoreObjectType.Table) is { } table + && (StoreObjectIdentifier.Create(property.DeclaringType, StoreObjectType.Table) is { } table && (IsNotNullAndNotDefault(property.GetDefaultValue(table)) || property.GetDefaultValueSql(table) != null))) { @@ -1231,14 +1231,14 @@ protected virtual void ValidateSharedColumnsCompatibility( continue; } - if (property.DeclaringEntityType.IsAssignableFrom(duplicateProperty.DeclaringEntityType) - || duplicateProperty.DeclaringEntityType.IsAssignableFrom(property.DeclaringEntityType)) + if (property.DeclaringType.IsAssignableFrom(duplicateProperty.DeclaringType) + || duplicateProperty.DeclaringType.IsAssignableFrom(property.DeclaringType)) { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameSameHierarchy( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); @@ -1298,9 +1298,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameNullabilityMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); @@ -1312,9 +1312,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameMaxLengthMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -1326,9 +1326,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameUnicodenessMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); @@ -1338,9 +1338,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameFixedLengthMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); @@ -1352,9 +1352,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNamePrecisionMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -1368,9 +1368,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameScaleMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -1382,9 +1382,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameConcurrencyTokenMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); @@ -1396,9 +1396,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameDataTypeMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -1422,9 +1422,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameProviderTypeMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -1438,9 +1438,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameComputedSqlMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -1454,9 +1454,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameIsStoredMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -1477,9 +1477,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameDefaultSqlMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -1494,9 +1494,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameDefaultSqlMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -1510,9 +1510,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameCommentMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -1526,9 +1526,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameCollationMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -1542,9 +1542,9 @@ protected virtual void ValidateCompatible( { throw new InvalidOperationException( RelationalStrings.DuplicateColumnNameOrderMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName(), @@ -2335,10 +2335,10 @@ private static IEnumerable GetAllMappedStoreObjects( IReadOnlyProperty property, StoreObjectType storeObjectType) { - var mappingStrategy = property.DeclaringEntityType.GetMappingStrategy(); + var mappingStrategy = property.DeclaringType.GetMappingStrategy(); if (property.IsPrimaryKey()) { - var declaringStoreObject = StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObjectType); + var declaringStoreObject = StoreObjectIdentifier.Create(property.DeclaringType, storeObjectType); if (declaringStoreObject != null) { yield return declaringStoreObject.Value; @@ -2349,28 +2349,32 @@ private static IEnumerable GetAllMappedStoreObjects( yield break; } - foreach (var fragment in property.DeclaringEntityType.GetMappingFragments(storeObjectType)) + foreach (var fragment in property.DeclaringType.GetMappingFragments(storeObjectType)) { yield return fragment.StoreObject; } - foreach (var containingType in property.DeclaringEntityType.GetDerivedTypes()) + if (property.DeclaringType is IReadOnlyEntityType entityType) { - var storeObject = StoreObjectIdentifier.Create(containingType, storeObjectType); - if (storeObject != null) + foreach (var containingType in entityType.GetDerivedTypes()) { - yield return storeObject.Value; - - if (mappingStrategy == RelationalAnnotationNames.TphMappingStrategy) + var storeObject = StoreObjectIdentifier.Create(containingType, storeObjectType); + if (storeObject != null) { - yield break; + yield return storeObject.Value; + + if (mappingStrategy == RelationalAnnotationNames.TphMappingStrategy) + { + yield break; + } } } + yield break; } } else { - var declaringStoreObject = StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObjectType); + var declaringStoreObject = StoreObjectIdentifier.Create(property.DeclaringType, storeObjectType); if (storeObjectType is StoreObjectType.Function or StoreObjectType.SqlQuery) { if (declaringStoreObject != null) @@ -2383,7 +2387,7 @@ private static IEnumerable GetAllMappedStoreObjects( if (declaringStoreObject != null) { - var fragments = property.DeclaringEntityType.GetMappingFragments(storeObjectType).ToList(); + var fragments = property.DeclaringType.GetMappingFragments(storeObjectType).ToList(); if (fragments.Count > 0) { var overrides = RelationalPropertyOverrides.Find(property, declaringStoreObject.Value); @@ -2411,9 +2415,14 @@ private static IEnumerable GetAllMappedStoreObjects( } } + if (property.DeclaringType is not IReadOnlyEntityType entityType) + { + yield break; + } + var tableFound = false; var queue = new Queue(); - queue.Enqueue(property.DeclaringEntityType); + queue.Enqueue(entityType); while (queue.Count > 0 && !tableFound) { foreach (var containingType in queue.Dequeue().GetDirectlyDerivedTypes()) diff --git a/src/EFCore.Relational/Infrastructure/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Infrastructure/RelationalPrimitivePropertyBaseExtensions.cs similarity index 92% rename from src/EFCore.Relational/Infrastructure/RelationalPropertyExtensions.cs rename to src/EFCore.Relational/Infrastructure/RelationalPrimitivePropertyBaseExtensions.cs index 7b0eeb40cbd..4b1bb2f7589 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalPrimitivePropertyBaseExtensions.cs @@ -4,9 +4,9 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure; /// -/// Relational extension methods for . +/// Relational extension methods for . /// -public static class RelationalPropertyExtensions +public static class RelationalPrimitivePropertyBaseExtensions { /// /// Creates a comma-separated list of column names. diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs index a1240ea6ddc..24ff53947f3 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionCheckConstraintBuilder.cs @@ -17,6 +17,45 @@ public interface IConventionCheckConstraintBuilder : IConventionAnnotatableBuild /// new IConventionCheckConstraint Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionCheckConstraintBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionCheckConstraintBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionCheckConstraintBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the database name of the check constraint. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs index dd7f26dd28b..57a68e49323 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionBuilder.cs @@ -18,6 +18,45 @@ public interface IConventionDbFunctionBuilder : IConventionAnnotatableBuilder /// new IConventionDbFunction Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionDbFunctionBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionDbFunctionBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionDbFunctionBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the name of the database function. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionParameterBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionParameterBuilder.cs index 0f12ad0fa8c..0b4a924be72 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionDbFunctionParameterBuilder.cs @@ -16,6 +16,45 @@ public interface IConventionDbFunctionParameterBuilder : IConventionAnnotatableB /// new IConventionDbFunctionParameter Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionDbFunctionParameterBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionDbFunctionParameterBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionDbFunctionParameterBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the store type of the function parameter in the database. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionSequenceBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionSequenceBuilder.cs index 2cfb880b98c..4a96d08ac73 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionSequenceBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionSequenceBuilder.cs @@ -16,6 +16,45 @@ public interface IConventionSequenceBuilder : IConventionAnnotatableBuilder /// new IConventionSequence Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionSequenceBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionSequenceBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionSequenceBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the type of values returned by the sequence. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs index 5e33363857a..9b6b114d65b 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureBuilder.cs @@ -16,6 +16,45 @@ public interface IConventionStoredProcedureBuilder : IConventionAnnotatableBuild /// new IConventionStoredProcedure Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionStoredProcedureBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the name of the stored procedure. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs index 3daf64fe4c8..671ec1ddf15 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureParameterBuilder.cs @@ -18,6 +18,45 @@ public interface IConventionStoredProcedureParameterBuilder : IConventionAnnotat /// new IConventionStoredProcedureParameter Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureParameterBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionStoredProcedureParameterBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureParameterBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Configures the parameter name. /// diff --git a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs index b8723de8784..8fe535803e3 100644 --- a/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/IConventionStoredProcedureResultColumnBuilder.cs @@ -16,6 +16,45 @@ public interface IConventionStoredProcedureResultColumnBuilder : IConventionAnno /// new IConventionStoredProcedureResultColumn Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureResultColumnBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionStoredProcedureResultColumnBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionStoredProcedureResultColumnBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Configures the result column name. /// diff --git a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs index e7a7a204c49..59be88ce498 100644 --- a/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/StoredProcedureBuilder.cs @@ -226,7 +226,7 @@ protected virtual PropertyBuilder CreatePropertyBuilder(string propertyName) #pragma warning disable EF1001 // Internal EF Core API usage. return new ModelBuilder(entityType.Model) #pragma warning restore EF1001 // Internal EF Core API usage. - .Entity(property.DeclaringEntityType.Name) + .Entity(property.DeclaringType.Name) .Property(property.ClrType, propertyName); } diff --git a/src/EFCore.Relational/Metadata/Conventions/PropertyOverridesConvention.cs b/src/EFCore.Relational/Metadata/Conventions/PropertyOverridesConvention.cs index 5a9c7479b3e..a45fc972989 100644 --- a/src/EFCore.Relational/Metadata/Conventions/PropertyOverridesConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/PropertyOverridesConvention.cs @@ -43,7 +43,7 @@ public virtual void ProcessPropertyAdded( IConventionContext context) { var property = propertyBuilder.Metadata; - if (!property.DeclaringEntityType.HasSharedClrType) + if (!property.DeclaringType.HasSharedClrType) { return; } diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalColumnAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalColumnAttributeConvention.cs index cdfed28f535..8669ae16b76 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalColumnAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalColumnAttributeConvention.cs @@ -31,13 +31,7 @@ public RelationalColumnAttributeConvention( /// protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, ColumnAttribute attribute, diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalColumnCommentAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalColumnCommentAttributeConvention.cs index daebc058db8..4b4277cdc19 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalColumnCommentAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalColumnCommentAttributeConvention.cs @@ -29,13 +29,7 @@ public RelationalColumnCommentAttributeConvention( /// protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, CommentAttribute attribute, diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalTableAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalTableAttributeConvention.cs index 9b154b2ded5..3226db5a8dd 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalTableAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalTableAttributeConvention.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class RelationalTableAttributeConvention : EntityTypeAttributeConventionBase +public class RelationalTableAttributeConvention : TypeAttributeConventionBase { /// /// Creates a new instance of . diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalTableCommentAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalTableCommentAttributeConvention.cs index 45dd1764914..ba202eb015e 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalTableCommentAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalTableCommentAttributeConvention.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class RelationalTableCommentAttributeConvention : EntityTypeAttributeConventionBase +public class RelationalTableCommentAttributeConvention : TypeAttributeConventionBase { /// /// Creates a new instance of . diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs index b480b83dab7..d0bd5b633cf 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs @@ -37,14 +37,7 @@ public RelationalValueGenerationConvention( /// protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; } - /// - /// Called after an annotation is changed on a property. - /// - /// The builder for the property. - /// The annotation name. - /// The new annotation. - /// The old annotation. - /// Additional information associated with convention execution. + /// public virtual void ProcessPropertyAnnotationChanged( IConventionPropertyBuilder propertyBuilder, string name, @@ -193,13 +186,13 @@ private void ProcessTableChanged( protected override ValueGenerated? GetValueGenerated(IConventionProperty property) { var table = property.GetMappedStoreObjects(StoreObjectType.Table).FirstOrDefault(); - return !MappingStrategyAllowsValueGeneration(property, property.DeclaringEntityType.GetMappingStrategy()) + return !MappingStrategyAllowsValueGeneration(property, property.DeclaringType.GetMappingStrategy()) ? null : table.Name != null ? GetValueGenerated(property, table) - : property.DeclaringEntityType.IsMappedToJson() - && !property.DeclaringEntityType.FindOwnership()!.IsUnique + : property.DeclaringType.IsMappedToJson() && property.IsOrdinalKeyProperty() + && (property.DeclaringType as IReadOnlyEntityType)?.FindOwnership()!.IsUnique == false ? ValueGenerated.OnAddOrUpdate : property.GetMappedStoreObjects(StoreObjectType.InsertStoredProcedure).Any() ? GetValueGenerated((IReadOnlyProperty)property) diff --git a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs index 7c8475c9401..5692e85e53d 100644 --- a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs @@ -250,11 +250,11 @@ private static void TryUniquifyColumnNames( continue; } - var usePrefix = property.DeclaringEntityType != otherProperty.DeclaringEntityType; + var usePrefix = property.DeclaringType != otherProperty.DeclaringType; if (!usePrefix - || (!property.DeclaringEntityType.IsStrictlyDerivedFrom(otherProperty.DeclaringEntityType) - && !otherProperty.DeclaringEntityType.IsStrictlyDerivedFrom(property.DeclaringEntityType)) - || property.DeclaringEntityType.FindRowInternalForeignKeys(storeObject).Any()) + || (!property.DeclaringType.IsStrictlyDerivedFrom(otherProperty.DeclaringType) + && !otherProperty.DeclaringType.IsStrictlyDerivedFrom(property.DeclaringType)) + || (property.DeclaringType as IConventionEntityType)?.FindRowInternalForeignKeys(storeObject).Any() == true) { var newColumnName = TryUniquify(property, columnName, properties, storeObject, usePrefix, maxLength); if (newColumnName != null) @@ -265,9 +265,9 @@ private static void TryUniquifyColumnNames( } if (!usePrefix - || (!property.DeclaringEntityType.IsStrictlyDerivedFrom(otherProperty.DeclaringEntityType) - && !otherProperty.DeclaringEntityType.IsStrictlyDerivedFrom(property.DeclaringEntityType)) - || otherProperty.DeclaringEntityType.FindRowInternalForeignKeys(storeObject).Any()) + || (!property.DeclaringType.IsStrictlyDerivedFrom(otherProperty.DeclaringType) + && !otherProperty.DeclaringType.IsStrictlyDerivedFrom(property.DeclaringType)) + || (otherProperty.DeclaringType as IConventionEntityType)?.FindRowInternalForeignKeys(storeObject).Any() == true) { var newOtherColumnName = TryUniquify(otherProperty, columnName, properties, storeObject, usePrefix, maxLength); if (newOtherColumnName != null) @@ -292,7 +292,7 @@ private static void TryUniquifyColumnNames( { if (usePrefix) { - var prefix = property.DeclaringEntityType.ShortName(); + var prefix = property.DeclaringType.ShortName(); if (!columnName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { columnName = prefix + "_" + columnName; diff --git a/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs b/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs index 6d09e1af470..9dd98b18a63 100644 --- a/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs @@ -1,6 +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 Microsoft.EntityFrameworkCore.Metadata.Internal; + namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// @@ -216,22 +218,24 @@ public static bool IsConcurrencyTokenMissing( var propertyMissing = true; foreach (var mappedProperty in propertiesMappedToConcurrencyColumn) { - var declaringEntityType = mappedProperty.DeclaringEntityType; - if (declaringEntityType.IsAssignableFrom(entityType) - || entityType.IsAssignableFrom(declaringEntityType) - || declaringEntityType.IsInOwnershipPath(entityType) - || entityType.IsInOwnershipPath(declaringEntityType)) + var declaringType = mappedProperty.DeclaringType; + var declaringEntityType = declaringType as IEntityType; + if (declaringType.IsAssignableFrom(entityType) + || entityType.IsAssignableFrom(declaringType) + || declaringEntityType != null + && (declaringEntityType.IsInOwnershipPath(entityType) + || entityType.IsInOwnershipPath(declaringEntityType))) { // The concurrency token is on the base type, derived type or in the same aggregate propertyMissing = false; continue; } - var linkingFks = declaringEntityType.FindForeignKeys(declaringEntityType.FindPrimaryKey()!.Properties) - .Where( - fk => fk.PrincipalKey.IsPrimaryKey() + var linkingFks = declaringEntityType?.FindForeignKeys(declaringEntityType.FindPrimaryKey()!.Properties) + .Where(fk => fk.PrincipalKey.IsPrimaryKey() && mappedTypes.Contains(fk.PrincipalEntityType)).ToList(); - if (linkingFks.Count > 0 + if (linkingFks != null + && linkingFks.Count > 0 && linkingFks.All(fk => fk.PrincipalEntityType != entityType) && linkingFks.Any( fk => fk.PrincipalEntityType.IsAssignableFrom(entityType) @@ -266,7 +270,7 @@ private static void RemoveDerivedEntityTypes( } if (!removed - && entityType.IsAssignableFrom(property.DeclaringEntityType)) + && entityType.IsAssignableFrom(property.DeclaringType)) { entityTypeDictionary.Remove(entityType); } diff --git a/src/EFCore.Relational/Metadata/IColumnBase.cs b/src/EFCore.Relational/Metadata/IColumnBase.cs index 238099e2c73..e82ac0bd7a7 100644 --- a/src/EFCore.Relational/Metadata/IColumnBase.cs +++ b/src/EFCore.Relational/Metadata/IColumnBase.cs @@ -65,7 +65,7 @@ ValueComparer ProviderValueComparer for (var i = 0; i < PropertyMappings.Count; i++) { var mapping = PropertyMappings[i]; - if (mapping.Property.DeclaringEntityType.IsAssignableFrom(entityType)) + if (mapping.Property.DeclaringType.IsAssignableFrom(entityType)) { return mapping; } diff --git a/src/EFCore.Relational/Metadata/IColumnMapping.cs b/src/EFCore.Relational/Metadata/IColumnMapping.cs index a7f4796de37..49a9f07d9e8 100644 --- a/src/EFCore.Relational/Metadata/IColumnMapping.cs +++ b/src/EFCore.Relational/Metadata/IColumnMapping.cs @@ -49,7 +49,7 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt } builder - .Append(Property.DeclaringEntityType.DisplayName()) + .Append(Property.DeclaringType.DisplayName()) .Append('.') .Append(Property.Name) .Append(" - "); diff --git a/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs index 5ef202fa24f..cddb8f80c31 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs @@ -174,6 +174,24 @@ IConventionCheckConstraint IConventionCheckConstraintBuilder.Metadata get => Metadata; } + /// + [DebuggerStepThrough] + IConventionCheckConstraintBuilder? IConventionCheckConstraintBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionCheckConstraintBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionCheckConstraintBuilder? IConventionCheckConstraintBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionCheckConstraintBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionCheckConstraintBuilder? IConventionCheckConstraintBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionCheckConstraintBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionCheckConstraintBuilder? IConventionCheckConstraintBuilder.HasName(string? name, bool fromDataAnnotation) diff --git a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs index ea8b52edbc7..5ccc41eb578 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionBuilder.cs @@ -246,6 +246,39 @@ IConventionDbFunction IConventionDbFunctionBuilder.Metadata get => Metadata; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionDbFunctionBuilder? IConventionDbFunctionBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionDbFunctionBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionDbFunctionBuilder? IConventionDbFunctionBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionDbFunctionBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionDbFunctionBuilder? IConventionDbFunctionBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionDbFunctionBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionDbFunctionBuilder? IConventionDbFunctionBuilder.HasName(string? name, bool fromDataAnnotation) diff --git a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs index f455fc063c3..3d1bacc7501 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalDbFunctionParameterBuilder.cs @@ -127,6 +127,24 @@ IConventionDbFunctionParameter IConventionDbFunctionParameterBuilder.Metadata get => Metadata; } + /// + [DebuggerStepThrough] + IConventionDbFunctionParameterBuilder? IConventionDbFunctionParameterBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionDbFunctionParameterBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionDbFunctionParameterBuilder? IConventionDbFunctionParameterBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionDbFunctionParameterBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionDbFunctionParameterBuilder? IConventionDbFunctionParameterBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionDbFunctionParameterBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionDbFunctionParameterBuilder? IConventionDbFunctionParameterBuilder.HasStoreType( diff --git a/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs index 9b3a1b6372a..1424a93c02e 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalSequenceBuilder.cs @@ -195,6 +195,24 @@ IConventionSequence IConventionSequenceBuilder.Metadata get => Metadata; } + /// + [DebuggerStepThrough] + IConventionSequenceBuilder? IConventionSequenceBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionSequenceBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionSequenceBuilder? IConventionSequenceBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionSequenceBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionSequenceBuilder? IConventionSequenceBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionSequenceBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionSequenceBuilder? IConventionSequenceBuilder.HasType(Type? type, bool fromDataAnnotation) diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs index f437eab0f70..df64a52ece3 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs @@ -420,6 +420,24 @@ IConventionStoredProcedure IConventionStoredProcedureBuilder.Metadata get => Metadata; } + /// + [DebuggerStepThrough] + IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionStoredProcedureBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionStoredProcedureBuilder? IConventionStoredProcedureBuilder.HasName(string? name, bool fromDataAnnotation) diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs index d9ac813a5cf..69579f077bf 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureParameterBuilder.cs @@ -100,6 +100,24 @@ IConventionStoredProcedureParameter IConventionStoredProcedureParameterBuilder.M get => Metadata; } + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureParameterBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureParameterBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionStoredProcedureParameterBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionStoredProcedureParameterBuilder? IConventionStoredProcedureParameterBuilder.HasName(string name, bool fromDataAnnotation) diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs index a42e64c867e..82bcd8afc3f 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureResultColumnBuilder.cs @@ -65,6 +65,24 @@ IConventionStoredProcedureResultColumn IConventionStoredProcedureResultColumnBui get => Metadata; } + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureResultColumnBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionStoredProcedureResultColumnBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionStoredProcedureResultColumnBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionStoredProcedureResultColumnBuilder? IConventionStoredProcedureResultColumnBuilder.HasName( diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index da1b1879f4c..a4fee3746cc 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -342,7 +342,7 @@ private static void AddDefaultMappings( { foreach (var property in entityType.GetProperties()) { - var columnName = property.IsPrimaryKey() || isTpc || isTph || property.DeclaringEntityType == mappedType + var columnName = property.IsPrimaryKey() || isTpc || isTph || property.DeclaringType == mappedType ? property.GetColumnName() : null; if (columnName == null) diff --git a/src/EFCore.Relational/Extensions/Internal/RelationalPropertyInternalExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalPrimitivePropertyBaseExtensions.cs similarity index 66% rename from src/EFCore.Relational/Extensions/Internal/RelationalPropertyInternalExtensions.cs rename to src/EFCore.Relational/Metadata/Internal/RelationalPrimitivePropertyBaseExtensions.cs index f1e3e035d6b..06c92487b28 100644 --- a/src/EFCore.Relational/Extensions/Internal/RelationalPropertyInternalExtensions.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalPrimitivePropertyBaseExtensions.cs @@ -9,8 +9,18 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 static class RelationalPropertyInternalExtensions +public static class RelationalPrimitivePropertyBaseExtensions { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public static string? GetConfiguredColumnType(this IReadOnlyProperty property) + => (string?)property[RelationalAnnotationNames.ColumnType]; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs index 45b56c7cf8f..9dde0563fdf 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs @@ -36,7 +36,7 @@ public RelationalPropertyOverrides( StoreObject = storeObject; _configurationSource = configurationSource; _builder = new InternalRelationalPropertyOverridesBuilder( - this, ((IConventionModel)property.DeclaringEntityType.Model).Builder); + this, ((IConventionModel)property.DeclaringType.Model).Builder); } /// diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameter.cs index 00f38a9abab..24a701fd9f6 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameter.cs @@ -152,7 +152,7 @@ public virtual string Name var baseName = GetProperty().GetDefaultColumnName( ((IReadOnlyStoredProcedure)StoredProcedure).GetStoreIdentifier()!.Value)!; return ForOriginalValue ?? false - ? Uniquifier.Truncate(baseName + "_Original", GetProperty().DeclaringEntityType.Model.GetMaxIdentifierLength()) + ? Uniquifier.Truncate(baseName + "_Original", GetProperty().DeclaringType.Model.GetMaxIdentifierLength()) : baseName; } set => SetName(value, ConfigurationSource.Explicit); diff --git a/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs b/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs index 070aa49b81c..b58223a541b 100644 --- a/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs +++ b/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs @@ -21,39 +21,39 @@ private StoreObjectIdentifier(StoreObjectType storeObjectType, string name, stri /// /// Creates an id for the store object that the given entity type is mapped to. /// - /// The entity type. + /// The entity type. /// The store object type. /// The store object id. - public static StoreObjectIdentifier? Create(IReadOnlyEntityType entityType, StoreObjectType type) + public static StoreObjectIdentifier? Create(IReadOnlyTypeBase typeBase, StoreObjectType type) { - Check.NotNull(entityType, nameof(entityType)); + Check.NotNull(typeBase, nameof(typeBase)); switch (type) { case StoreObjectType.Table: - var tableName = entityType.GetTableName(); - return tableName == null ? null : Table(tableName, entityType.GetSchema()); + var tableName = typeBase.GetTableName(); + return tableName == null ? null : Table(tableName, typeBase.GetSchema()); case StoreObjectType.View: - var viewName = entityType.GetViewName(); - return viewName == null ? null : View(viewName, entityType.GetViewSchema()); + var viewName = typeBase.GetViewName(); + return viewName == null ? null : View(viewName, typeBase.GetViewSchema()); case StoreObjectType.SqlQuery: - var query = entityType.GetSqlQuery(); - return query == null ? null : SqlQuery(entityType); + var query = typeBase.GetSqlQuery(); + return query == null ? null : SqlQuery(typeBase.FundamentalEntityType); case StoreObjectType.Function: - var functionName = entityType.GetFunctionName(); + var functionName = typeBase.GetFunctionName(); return functionName == null ? null : DbFunction(functionName); case StoreObjectType.InsertStoredProcedure: - var insertStoredProcedure = entityType.GetInsertStoredProcedure(); + var insertStoredProcedure = typeBase.GetInsertStoredProcedure(); return insertStoredProcedure == null || insertStoredProcedure.Name == null ? null : InsertStoredProcedure(insertStoredProcedure.Name, insertStoredProcedure.Schema); case StoreObjectType.DeleteStoredProcedure: - var deleteStoredProcedure = entityType.GetDeleteStoredProcedure(); + var deleteStoredProcedure = typeBase.GetDeleteStoredProcedure(); return deleteStoredProcedure == null || deleteStoredProcedure.Name == null ? null : DeleteStoredProcedure(deleteStoredProcedure.Name, deleteStoredProcedure.Schema); case StoreObjectType.UpdateStoredProcedure: - var updateStoredProcedure = entityType.GetUpdateStoredProcedure(); + var updateStoredProcedure = typeBase.GetUpdateStoredProcedure(); return updateStoredProcedure == null || updateStoredProcedure.Name == null ? null : UpdateStoredProcedure(updateStoredProcedure.Name, updateStoredProcedure.Schema); diff --git a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs index bb3adbeb9ea..aed2c547cac 100644 --- a/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs +++ b/src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs @@ -890,7 +890,7 @@ protected virtual IEnumerable Diff( t.PropertyMappings.Any( tm => string.Equals(sm.Property.Name, tm.Property.Name, StringComparison.OrdinalIgnoreCase) - && EntityTypePathEquals(sm.Property.DeclaringEntityType, tm.Property.DeclaringEntityType, c))), + && EntityTypePathEquals(sm.Property.DeclaringType, tm.Property.DeclaringType, c))), (s, t, _) => s.PropertyMappings.Any( sm => t.PropertyMappings.Any( @@ -902,7 +902,7 @@ protected virtual IEnumerable Diff( t.PropertyMappings.Any( tm => string.Equals(sm.Property.Name, tm.Property.Name, StringComparison.OrdinalIgnoreCase) - && EntityTypePathEquals(sm.Property.DeclaringEntityType, tm.Property.DeclaringEntityType, c))), + && EntityTypePathEquals(sm.Property.DeclaringType, tm.Property.DeclaringType, c))), (s, t, _) => ColumnStructureEquals(s, t)); private static bool ColumnStructureEquals(IColumn source, IColumn target) @@ -933,7 +933,7 @@ private static bool ColumnStructureEquals(IColumn source, IColumn target) && source.DefaultValueSql == target.DefaultValueSql; } - private static bool EntityTypePathEquals(IEntityType source, IEntityType target, DiffContext diffContext) + private static bool EntityTypePathEquals(ITypeBase source, ITypeBase target, DiffContext diffContext) { var sourceTable = diffContext.FindTable(source); var targetTable = diffContext.FindTable(target); @@ -951,12 +951,29 @@ private static bool EntityTypePathEquals(IEntityType source, IEntityType target, return false; } - var nextSource = sourceTable?.GetRowInternalForeignKeys(source).FirstOrDefault()?.PrincipalEntityType; - var nextTarget = targetTable?.GetRowInternalForeignKeys(target).FirstOrDefault()?.PrincipalEntityType; - return (nextSource == null && nextTarget == null) - || (nextSource != null - && nextTarget != null - && EntityTypePathEquals(nextSource, nextTarget, diffContext)); + if (source is IEntityType sourceEntityType + && target is IEntityType targetEntityType) + { + var nextSource = sourceTable?.GetRowInternalForeignKeys(sourceEntityType).FirstOrDefault()?.PrincipalEntityType; + var nextTarget = targetTable?.GetRowInternalForeignKeys(targetEntityType).FirstOrDefault()?.PrincipalEntityType; + return (nextSource == null && nextTarget == null) + || (nextSource != null + && nextTarget != null + && EntityTypePathEquals(nextSource, nextTarget, diffContext)); + } + + if (source is IComplexType sourceComplexType + && target is IComplexType targetComplexType) + { + var nextSource = sourceComplexType.ComplexProperty.DeclaringType; + var nextTarget = targetComplexType.ComplexProperty.DeclaringType; + return (nextSource == null && nextTarget == null) + || (nextSource != null + && nextTarget != null + && EntityTypePathEquals(nextSource, nextTarget, diffContext)); + } + + return false; } /// @@ -1861,7 +1878,7 @@ private void AddSeedData(IEntityType entityType, Dictionary - public virtual ITable? FindTable(IEntityType entityType) - => entityType.GetTableMappings().FirstOrDefault()?.Table; + public virtual ITable? FindTable(ITypeBase typeBase) + => typeBase.GetTableMappings().FirstOrDefault()?.Table; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Query/EntityProjectionExpression.cs b/src/EFCore.Relational/Query/EntityProjectionExpression.cs index de75b1589d0..eddac5e66aa 100644 --- a/src/EFCore.Relational/Query/EntityProjectionExpression.cs +++ b/src/EFCore.Relational/Query/EntityProjectionExpression.cs @@ -154,8 +154,8 @@ public virtual EntityProjectionExpression UpdateEntityType(IEntityType derivedTy var propertyExpressionMap = new Dictionary(); foreach (var (property, columnExpression) in _propertyExpressionMap) { - if (derivedType.IsAssignableFrom(property.DeclaringEntityType) - || property.DeclaringEntityType.IsAssignableFrom(derivedType)) + if (derivedType.IsAssignableFrom(property.DeclaringType) + || property.DeclaringType.IsAssignableFrom(derivedType)) { propertyExpressionMap[property] = columnExpression; } @@ -193,8 +193,8 @@ public virtual EntityProjectionExpression UpdateEntityType(IEntityType derivedTy /// A column which is a SQL representation of the property. public virtual ColumnExpression BindProperty(IProperty property) { - if (!EntityType.IsAssignableFrom(property.DeclaringEntityType) - && !property.DeclaringEntityType.IsAssignableFrom(EntityType)) + if (!EntityType.IsAssignableFrom(property.DeclaringType) + && !property.DeclaringType.IsAssignableFrom(EntityType)) { throw new InvalidOperationException( RelationalStrings.UnableToBindMemberToEntityProjection("property", property.Name, EntityType.DisplayName())); diff --git a/src/EFCore.Relational/Query/Internal/CollateTranslator.cs b/src/EFCore.Relational/Query/Internal/CollateTranslator.cs index faebaa9af3e..10c8183a506 100644 --- a/src/EFCore.Relational/Query/Internal/CollateTranslator.cs +++ b/src/EFCore.Relational/Query/Internal/CollateTranslator.cs @@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal; public class CollateTranslator : IMethodCallTranslator { private static readonly MethodInfo MethodInfo - = typeof(RelationalDbFunctionsExtensions).GetMethod(nameof(RelationalDbFunctionsExtensions.Collate))!; + = typeof(RelationalDbFunctionExtensions).GetMethod(nameof(RelationalDbFunctionExtensions.Collate))!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Query/JsonQueryExpression.cs b/src/EFCore.Relational/Query/JsonQueryExpression.cs index 2e22dccab20..1d5d78bb9f6 100644 --- a/src/EFCore.Relational/Query/JsonQueryExpression.cs +++ b/src/EFCore.Relational/Query/JsonQueryExpression.cs @@ -100,8 +100,8 @@ public override ExpressionType NodeType /// public virtual SqlExpression BindProperty(IProperty property) { - if (!EntityType.IsAssignableFrom(property.DeclaringEntityType) - && !property.DeclaringEntityType.IsAssignableFrom(EntityType)) + if (!EntityType.IsAssignableFrom(property.DeclaringType) + && !property.DeclaringType.IsAssignableFrom(EntityType)) { throw new InvalidOperationException( RelationalStrings.UnableToBindMemberToEntityProjection("property", property.Name, EntityType.DisplayName())); diff --git a/src/EFCore.Relational/Query/RelationalEvaluatableExpressionFilter.cs b/src/EFCore.Relational/Query/RelationalEvaluatableExpressionFilter.cs index 29df9899207..6ceab3daa0d 100644 --- a/src/EFCore.Relational/Query/RelationalEvaluatableExpressionFilter.cs +++ b/src/EFCore.Relational/Query/RelationalEvaluatableExpressionFilter.cs @@ -45,7 +45,7 @@ public override bool IsEvaluatableExpression(Expression expression, IModel model return false; } - if (method.DeclaringType == typeof(RelationalDbFunctionsExtensions)) + if (method.DeclaringType == typeof(RelationalDbFunctionExtensions)) { return false; } diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 36c3df4a2eb..4aaf4725137 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -309,9 +309,9 @@ internal SelectExpression(IEntityType entityType, ISqlExpressionFactory sqlExpre for (var j = 0; j < properties.Length; j++) { var property = properties[j]; - var projection = property.DeclaringEntityType.IsAssignableFrom(et) + var projection = property.DeclaringType.IsAssignableFrom(et) ? CreateColumnExpression( - property, table, tableReferenceExpression, property.DeclaringEntityType != entityType) + property, table, tableReferenceExpression, property.DeclaringType != entityType) : (SqlExpression)sqlExpressionFactory.Constant( null, property.ClrType.MakeNullable(), property.GetRelationalTypeMapping()); selectExpression._projection.Add(new ProjectionExpression(projection, propertyNames[j])); diff --git a/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs b/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs index e283a05b2fe..90ec0760337 100644 --- a/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs +++ b/src/EFCore.Relational/Storage/RelationalTypeMappingSourceExtensions.cs @@ -63,7 +63,7 @@ public static RelationalTypeMapping GetMapping( throw new InvalidOperationException( RelationalStrings.UnsupportedPropertyType( - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, property.ClrType.ShortDisplayName())); } diff --git a/src/EFCore.Relational/Storage/ValueConversion/RelationalConverterMappingHints.cs b/src/EFCore.Relational/Storage/ValueConversion/RelationalConverterMappingHints.cs index 1b94d84e0ec..70bb0489ff5 100644 --- a/src/EFCore.Relational/Storage/ValueConversion/RelationalConverterMappingHints.cs +++ b/src/EFCore.Relational/Storage/ValueConversion/RelationalConverterMappingHints.cs @@ -30,7 +30,7 @@ public RelationalConverterMappingHints( int? scale = null, bool? unicode = null, bool? fixedLength = null, - Func? valueGeneratorFactory = null, + Func? valueGeneratorFactory = null, DbType? dbType = null) : base(size, precision, scale, unicode, valueGeneratorFactory) { @@ -55,7 +55,7 @@ public RelationalConverterMappingHints( int? scale, bool? unicode, bool? fixedLength, - Func? valueGeneratorFactory) + Func? valueGeneratorFactory) : base(size, precision, scale, unicode, valueGeneratorFactory) { IsFixedLength = fixedLength; diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs index 631310a3727..5ef656d7d1b 100644 --- a/src/EFCore.Relational/Update/ModificationCommand.cs +++ b/src/EFCore.Relational/Update/ModificationCommand.cs @@ -830,7 +830,7 @@ private static void InitializeSharedColumns( { foreach (var columnMapping in tableMapping.ColumnMappings) { - if (columnMapping.Property.DeclaringEntityType.IsMappedToJson()) + if (columnMapping.Property.DeclaringType.IsMappedToJson()) { continue; } diff --git a/src/EFCore.Relational/ValueGeneration/RelationalValueGeneratorSelector.cs b/src/EFCore.Relational/ValueGeneration/RelationalValueGeneratorSelector.cs index fa93bf5f5af..9e8d9e2844f 100644 --- a/src/EFCore.Relational/ValueGeneration/RelationalValueGeneratorSelector.cs +++ b/src/EFCore.Relational/ValueGeneration/RelationalValueGeneratorSelector.cs @@ -42,11 +42,11 @@ public RelationalValueGeneratorSelector(ValueGeneratorSelectorDependencies depen } /// - protected override ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType) + protected override ValueGenerator? FindForType(IProperty property, ITypeBase typeBase, Type clrType) { - if (entityType.IsMappedToJson() && property.IsOrdinalKeyProperty()) + if (typeBase.IsMappedToJson() && property.IsOrdinalKeyProperty()) { - return _numberFactory.Create(property, entityType); + return _numberFactory.Create(property, typeBase); } if (property.ValueGenerated != ValueGenerated.Never) @@ -56,7 +56,7 @@ public RelationalValueGeneratorSelector(ValueGeneratorSelectorDependencies depen || clrType == typeof(float) || clrType == typeof(double)) { - return _numberFactory.Create(property, entityType); + return _numberFactory.Create(property, typeBase); } if (clrType == typeof(DateTime)) @@ -88,6 +88,6 @@ public RelationalValueGeneratorSelector(ValueGeneratorSelectorDependencies depen } } - return base.FindForType(property, entityType, clrType); + return base.FindForType(property, typeBase, clrType); } } diff --git a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs index 90783024415..bb46a3e6be7 100644 --- a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs +++ b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs @@ -167,7 +167,7 @@ public override IReadOnlyList GenerateFluentApiCalls( { var fragments = new List(base.GenerateFluentApiCalls(property, annotations)); - if (GenerateValueGenerationStrategy(annotations, property.DeclaringEntityType.Model, onModel: false) is MethodCallCodeFragment + if (GenerateValueGenerationStrategy(annotations, property.DeclaringType.Model, onModel: false) is MethodCallCodeFragment valueGenerationStrategy) { fragments.Add(valueGenerationStrategy); @@ -307,7 +307,7 @@ protected override bool IsHandledByConvention(IProperty property, IAnnotation an { if (annotation.Name == SqlServerAnnotationNames.ValueGenerationStrategy) { - return (SqlServerValueGenerationStrategy)annotation.Value! == property.DeclaringEntityType.Model.GetValueGenerationStrategy(); + return (SqlServerValueGenerationStrategy)annotation.Value! == property.DeclaringType.Model.GetValueGenerationStrategy(); } return base.IsHandledByConvention(property, annotation); diff --git a/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs b/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs index 9da54e957a7..64642e4a680 100644 --- a/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/Internal/SqlServerLoggerExtensions.cs @@ -27,7 +27,7 @@ public static void DecimalTypeKeyWarning( if (diagnostics.ShouldLog(definition)) { - definition.Log(diagnostics, property.Name, property.DeclaringEntityType.DisplayName()); + definition.Log(diagnostics, property.Name, property.DeclaringType.DisplayName()); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -47,7 +47,7 @@ private static string DecimalTypeKeyWarning(EventDefinitionBase definition, Even var p = (PropertyEventData)payload; return d.GenerateMessage( p.Property.Name, - p.Property.DeclaringEntityType.DisplayName()); + p.Property.DeclaringType.DisplayName()); } /// @@ -64,7 +64,7 @@ public static void DecimalTypeDefaultWarning( if (diagnostics.ShouldLog(definition)) { - definition.Log(diagnostics, property.Name, property.DeclaringEntityType.DisplayName()); + definition.Log(diagnostics, property.Name, property.DeclaringType.DisplayName()); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -84,7 +84,7 @@ private static string DecimalTypeDefaultWarning(EventDefinitionBase definition, var p = (PropertyEventData)payload; return d.GenerateMessage( p.Property.Name, - p.Property.DeclaringEntityType.DisplayName()); + p.Property.DeclaringType.DisplayName()); } /// @@ -101,7 +101,7 @@ public static void ByteIdentityColumnWarning( if (diagnostics.ShouldLog(definition)) { - definition.Log(diagnostics, property.Name, property.DeclaringEntityType.DisplayName()); + definition.Log(diagnostics, property.Name, property.DeclaringType.DisplayName()); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -121,7 +121,7 @@ private static string ByteIdentityColumnWarning(EventDefinitionBase definition, var p = (PropertyEventData)payload; return d.GenerateMessage( p.Property.Name, - p.Property.DeclaringEntityType.DisplayName()); + p.Property.DeclaringType.DisplayName()); } /// @@ -142,7 +142,7 @@ public static void ConflictingValueGenerationStrategiesWarning( { definition.Log( diagnostics, sqlServerValueGenerationStrategy.ToString(), otherValueGenerationStrategy, - property.Name, property.DeclaringEntityType.DisplayName()); + property.Name, property.DeclaringType.DisplayName()); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -166,7 +166,7 @@ private static string ConflictingValueGenerationStrategiesWarning(EventDefinitio p.SqlServerValueGenerationStrategy.ToString(), p.OtherValueGenerationStrategy, p.Property.Name, - p.Property.DeclaringEntityType.DisplayName()); + p.Property.DeclaringType.DisplayName()); } /// diff --git a/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePropertyBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePropertyBuilderExtensions.cs new file mode 100644 index 00000000000..14c4dc1e768 --- /dev/null +++ b/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePropertyBuilderExtensions.cs @@ -0,0 +1,260 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore; + +/// +/// SQL Server specific extension methods for . +/// +/// +/// See Modeling entity types and relationships, and +/// Accessing SQL Server and SQL Azure databases with EF Core +/// for more information and examples. +/// +public static class SqlServerComplexTypePropertyBuilderExtensions +{ + /// + /// Configures the key property to use a sequence-based hi-lo pattern to generate values for new entities, + /// when targeting SQL Server. This method sets the property to be . + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the sequence. + /// The schema of the sequence. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder UseHiLo( + this ComplexTypePropertyBuilder propertyBuilder, + string? name = null, + string? schema = null) + { + Check.NullButNotEmpty(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + var property = propertyBuilder.Metadata; + + name ??= SqlServerModelExtensions.DefaultHiLoSequenceName; + + var model = property.DeclaringType.Model; + + if (model.FindSequence(name, schema) == null) + { + model.AddSequence(name, schema).IncrementBy = 10; + } + + property.SetValueGenerationStrategy(SqlServerValueGenerationStrategy.SequenceHiLo); + property.SetHiLoSequenceName(name); + property.SetHiLoSequenceSchema(schema); + property.SetIdentitySeed(null); + property.SetIdentityIncrement(null); + + return propertyBuilder; + } + + /// + /// Configures the key property to use a sequence-based hi-lo pattern to generate values for new entities, + /// when targeting SQL Server. This method sets the property to be . + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the sequence. + /// The schema of the sequence. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder UseHiLo( + this ComplexTypePropertyBuilder propertyBuilder, + string? name = null, + string? schema = null) + => (ComplexTypePropertyBuilder)UseHiLo((ComplexTypePropertyBuilder)propertyBuilder, name, schema); + + /// + /// Configures the key property to use a sequence-based key value generation pattern to generate values for new entities, + /// when targeting SQL Server. This method sets the property to be . + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the sequence. + /// The schema of the sequence. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder UseSequence( + this ComplexTypePropertyBuilder propertyBuilder, + string? name = null, + string? schema = null) + { + Check.NullButNotEmpty(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + var property = propertyBuilder.Metadata; + + property.SetValueGenerationStrategy(SqlServerValueGenerationStrategy.Sequence); + property.SetSequenceName(name); + property.SetSequenceSchema(schema); + property.SetHiLoSequenceName(null); + property.SetHiLoSequenceSchema(null); + property.SetIdentitySeed(null); + property.SetIdentityIncrement(null); + + return propertyBuilder; + } + + /// + /// Configures the key property to use a sequence-based key value generation pattern to generate values for new entities, + /// when targeting SQL Server. This method sets the property to be . + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The name of the sequence. + /// The schema of the sequence. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder UseSequence( + this ComplexTypePropertyBuilder propertyBuilder, + string? name = null, + string? schema = null) + => (ComplexTypePropertyBuilder)UseSequence((ComplexTypePropertyBuilder)propertyBuilder, name, schema); + + /// + /// Configures the key property to use the SQL Server IDENTITY feature to generate values for new entities, + /// when targeting SQL Server. This method sets the property to be . + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the property being configured. + /// The value that is used for the very first row loaded into the table. + /// The incremental value that is added to the identity value of the previous row that was loaded. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder UseIdentityColumn( + this ComplexTypePropertyBuilder propertyBuilder, + long seed = 1, + int increment = 1) + { + var property = propertyBuilder.Metadata; + property.SetValueGenerationStrategy(SqlServerValueGenerationStrategy.IdentityColumn); + property.SetIdentitySeed(seed); + property.SetIdentityIncrement(increment); + property.SetHiLoSequenceName(null); + property.SetHiLoSequenceSchema(null); + property.SetSequenceName(null); + property.SetSequenceSchema(null); + + return propertyBuilder; + } + + /// + /// Configures the key property to use the SQL Server IDENTITY feature to generate values for new entities, + /// when targeting SQL Server. This method sets the property to be . + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The builder for the property being configured. + /// The value that is used for the very first row loaded into the table. + /// The incremental value that is added to the identity value of the previous row that was loaded. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder UseIdentityColumn( + this ComplexTypePropertyBuilder propertyBuilder, + int seed, + int increment = 1) + => propertyBuilder.UseIdentityColumn((long)seed, increment); + + /// + /// Configures the key property to use the SQL Server IDENTITY feature to generate values for new entities, + /// when targeting SQL Server. This method sets the property to be . + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The value that is used for the very first row loaded into the table. + /// The incremental value that is added to the identity value of the previous row that was loaded. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder UseIdentityColumn( + this ComplexTypePropertyBuilder propertyBuilder, + long seed = 1, + int increment = 1) + => (ComplexTypePropertyBuilder)UseIdentityColumn((ComplexTypePropertyBuilder)propertyBuilder, seed, increment); + + /// + /// Configures the key property to use the SQL Server IDENTITY feature to generate values for new entities, + /// when targeting SQL Server. This method sets the property to be . + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. + /// + /// The type of the property being configured. + /// The builder for the property being configured. + /// The value that is used for the very first row loaded into the table. + /// The incremental value that is added to the identity value of the previous row that was loaded. + /// The same builder instance so that multiple calls can be chained. + public static ComplexTypePropertyBuilder UseIdentityColumn( + this ComplexTypePropertyBuilder propertyBuilder, + int seed, + int increment = 1) + => (ComplexTypePropertyBuilder)UseIdentityColumn((ComplexTypePropertyBuilder)propertyBuilder, (long)seed, increment); + + /// + /// Configures whether the property's column is created as sparse when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. Also see + /// Sparse columns for + /// general information on SQL Server sparse columns. + /// + /// The builder for the property being configured. + /// A value indicating whether the property's column is created as sparse. + /// A builder to further configure the property. + public static ComplexTypePropertyBuilder IsSparse(this ComplexTypePropertyBuilder propertyBuilder, bool sparse = true) + { + propertyBuilder.Metadata.SetIsSparse(sparse); + + return propertyBuilder; + } + + /// + /// Configures whether the property's column is created as sparse when targeting SQL Server. + /// + /// + /// See Modeling entity types and relationships, and + /// Accessing SQL Server and SQL Azure databases with EF Core + /// for more information and examples. Also see + /// Sparse columns for + /// general information on SQL Server sparse columns. + /// + /// The builder for the property being configured. + /// A value indicating whether the property's column is created as sparse. + /// A builder to further configure the property. + public static ComplexTypePropertyBuilder IsSparse( + this ComplexTypePropertyBuilder propertyBuilder, + bool sparse = true) + => (ComplexTypePropertyBuilder)IsSparse((ComplexTypePropertyBuilder)propertyBuilder, sparse); +} diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPrimitivePropertyBaseExtensions.cs similarity index 97% rename from src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs rename to src/EFCore.SqlServer/Extensions/SqlServerPrimitivePropertyBaseExtensions.cs index 890c57a1005..43d2017a3a2 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerPrimitivePropertyBaseExtensions.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore; /// Accessing SQL Server and SQL Azure databases with EF Core /// for more information and examples. /// -public static class SqlServerPropertyExtensions +public static class SqlServerPrimitivePropertyBaseExtensions { /// /// Returns the name to use for the hi-lo sequence. @@ -142,7 +142,7 @@ public static void SetHiLoSequenceSchema(this IMutableProperty property, string? /// The sequence to use, or if no sequence exists in the model. public static IReadOnlySequence? FindHiLoSequence(this IReadOnlyProperty property) { - var model = property.DeclaringEntityType.Model; + var model = property.DeclaringType.Model; var sequenceName = property.GetHiLoSequenceName() ?? model.GetHiLoSequenceName(); @@ -161,7 +161,7 @@ public static void SetHiLoSequenceSchema(this IMutableProperty property, string? /// The sequence to use, or if no sequence exists in the model. public static IReadOnlySequence? FindHiLoSequence(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { - var model = property.DeclaringEntityType.Model; + var model = property.DeclaringType.Model; var sequenceName = property.GetHiLoSequenceName(storeObject) ?? model.GetHiLoSequenceName(); @@ -314,7 +314,7 @@ public static void SetSequenceSchema(this IMutableProperty property, string? sch /// The sequence to use, or if no sequence exists in the model. public static IReadOnlySequence? FindSequence(this IReadOnlyProperty property) { - var model = property.DeclaringEntityType.Model; + var model = property.DeclaringType.Model; var sequenceName = property.GetSequenceName() ?? model.GetSequenceNameSuffix(); @@ -333,7 +333,7 @@ public static void SetSequenceSchema(this IMutableProperty property, string? sch /// The sequence to use, or if no sequence exists in the model. public static IReadOnlySequence? FindSequence(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { - var model = property.DeclaringEntityType.Model; + var model = property.DeclaringType.Model; var sequenceName = property.GetSequenceName(storeObject) ?? model.GetSequenceNameSuffix(); @@ -412,7 +412,7 @@ public static void SetSequenceSchema(this IMutableProperty property, string? sch var sharedProperty = property.FindSharedStoreObjectRootProperty(storeObject); return sharedProperty == null - ? property.DeclaringEntityType.Model.GetIdentitySeed() + ? property.DeclaringType.Model.GetIdentitySeed() : sharedProperty.GetIdentitySeed(storeObject); } @@ -539,7 +539,7 @@ public static void SetIdentitySeed(this IMutableRelationalPropertyOverrides over => (property is RuntimeProperty) ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData) : (int?)property[SqlServerAnnotationNames.IdentityIncrement] - ?? property.DeclaringEntityType.Model.GetIdentityIncrement(); + ?? property.DeclaringType.Model.GetIdentityIncrement(); /// /// Returns the identity increment. @@ -568,7 +568,7 @@ public static void SetIdentitySeed(this IMutableRelationalPropertyOverrides over var sharedProperty = property.FindSharedStoreObjectRootProperty(storeObject); return sharedProperty == null - ? property.DeclaringEntityType.Model.GetIdentityIncrement() + ? property.DeclaringType.Model.GetIdentityIncrement() : sharedProperty.GetIdentityIncrement(storeObject); } @@ -744,7 +744,7 @@ internal static SqlServerValueGenerationStrategy GetValueGenerationStrategy( var annotation = property.FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy); if (annotation?.Value != null - && StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObject.StoreObjectType) == storeObject) + && StoreObjectIdentifier.Create(property.DeclaringType, storeObject.StoreObjectType) == storeObject) { return (SqlServerValueGenerationStrategy)annotation.Value; } @@ -809,7 +809,7 @@ is StoreObjectIdentifier principal private static SqlServerValueGenerationStrategy GetDefaultValueGenerationStrategy(IReadOnlyProperty property) { - var modelStrategy = property.DeclaringEntityType.Model.GetValueGenerationStrategy(); + var modelStrategy = property.DeclaringType.Model.GetValueGenerationStrategy(); if (modelStrategy is SqlServerValueGenerationStrategy.SequenceHiLo or SqlServerValueGenerationStrategy.Sequence && IsCompatibleWithValueGeneration(property)) @@ -828,7 +828,7 @@ private static SqlServerValueGenerationStrategy GetDefaultValueGenerationStrateg in StoreObjectIdentifier storeObject, ITypeMappingSource? typeMappingSource) { - var modelStrategy = property.DeclaringEntityType.Model.GetValueGenerationStrategy(); + var modelStrategy = property.DeclaringType.Model.GetValueGenerationStrategy(); if (modelStrategy is SqlServerValueGenerationStrategy.SequenceHiLo or SqlServerValueGenerationStrategy.Sequence && IsCompatibleWithValueGeneration(property, storeObject, typeMappingSource)) @@ -838,7 +838,7 @@ private static SqlServerValueGenerationStrategy GetDefaultValueGenerationStrateg return modelStrategy == SqlServerValueGenerationStrategy.IdentityColumn && IsCompatibleWithValueGeneration(property, storeObject, typeMappingSource) - ? property.DeclaringEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy + ? property.DeclaringType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy ? SqlServerValueGenerationStrategy.Sequence : SqlServerValueGenerationStrategy.IdentityColumn : SqlServerValueGenerationStrategy.None; @@ -945,7 +945,7 @@ public static void SetValueGenerationStrategy( { throw new ArgumentException( SqlServerStrings.IdentityBadType( - property.Name, property.DeclaringEntityType.DisplayName(), propertyType.ShortDisplayName())); + property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName())); } if (value is SqlServerValueGenerationStrategy.SequenceHiLo or SqlServerValueGenerationStrategy.Sequence @@ -953,7 +953,7 @@ public static void SetValueGenerationStrategy( { throw new ArgumentException( SqlServerStrings.SequenceBadType( - property.Name, property.DeclaringEntityType.DisplayName(), propertyType.ShortDisplayName())); + property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName())); } return value; diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs index 45d9696492f..f2aa1a664ca 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs @@ -41,7 +41,7 @@ public static PropertyBuilder UseHiLo( name ??= SqlServerModelExtensions.DefaultHiLoSequenceName; - var model = property.DeclaringEntityType.Model; + var model = property.DeclaringType.Model; if (model.FindSequence(name, schema) == null) { @@ -107,7 +107,7 @@ public static PropertyBuilder UseHiLo( return name == null ? null - : propertyBuilder.Metadata.DeclaringEntityType.Model.Builder.HasSequence(name, schema, fromDataAnnotation); + : propertyBuilder.Metadata.DeclaringType.Model.Builder.HasSequence(name, schema, fromDataAnnotation); } /// @@ -220,7 +220,7 @@ public static PropertyBuilder UseSequence( return name == null ? null - : propertyBuilder.Metadata.DeclaringEntityType.Model.Builder.HasSequence(name, schema, fromDataAnnotation); + : propertyBuilder.Metadata.DeclaringType.Model.Builder.HasSequence(name, schema, fromDataAnnotation); } /// @@ -696,7 +696,7 @@ public static bool CanSetValueGenerationStrategy( SqlServerValueGenerationStrategy? valueGenerationStrategy, bool fromDataAnnotation = false) => (valueGenerationStrategy == null - || SqlServerPropertyExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) + || SqlServerPrimitivePropertyBaseExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) && propertyBuilder.CanSetAnnotation( SqlServerAnnotationNames.ValueGenerationStrategy, valueGenerationStrategy, fromDataAnnotation); @@ -719,7 +719,7 @@ public static bool CanSetValueGenerationStrategy( in StoreObjectIdentifier storeObject, bool fromDataAnnotation = false) => (valueGenerationStrategy == null - || SqlServerPropertyExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) + || SqlServerPrimitivePropertyBaseExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) && (propertyBuilder.Metadata.FindOverrides(storeObject)?.Builder .CanSetAnnotation( SqlServerAnnotationNames.ValueGenerationStrategy, diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs index 580ab776269..b6f8370227c 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs @@ -430,7 +430,7 @@ protected override void ValidateSharedColumnsCompatibility( if (identityColumns.Count > 1) { var sb = new StringBuilder() - .AppendJoin(identityColumns.Values.Select(p => "'" + p.DeclaringEntityType.DisplayName() + "." + p.Name + "'")); + .AppendJoin(identityColumns.Values.Select(p => "'" + p.DeclaringType.DisplayName() + "." + p.Name + "'")); throw new InvalidOperationException(SqlServerStrings.MultipleIdentityColumns(sb, storeObject.DisplayName())); } } @@ -464,9 +464,9 @@ protected override void ValidateCompatible( { throw new InvalidOperationException( SqlServerStrings.DuplicateColumnNameValueGenerationStrategyMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); @@ -483,9 +483,9 @@ protected override void ValidateCompatible( { throw new InvalidOperationException( SqlServerStrings.DuplicateColumnIdentityIncrementMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); @@ -497,9 +497,9 @@ protected override void ValidateCompatible( { throw new InvalidOperationException( SqlServerStrings.DuplicateColumnIdentitySeedMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); @@ -512,9 +512,9 @@ protected override void ValidateCompatible( { throw new InvalidOperationException( SqlServerStrings.DuplicateColumnSequenceMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); @@ -528,9 +528,9 @@ protected override void ValidateCompatible( { throw new InvalidOperationException( SqlServerStrings.DuplicateColumnSparsenessMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs index cb6cfc9761d..338cd8c1fd4 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs @@ -97,11 +97,11 @@ public override void ProcessEntityTypeAnnotationChanged( protected override ValueGenerated? GetValueGenerated(IConventionProperty property) { // TODO: move to relational? - if (property.DeclaringEntityType.IsMappedToJson() - && !property.DeclaringEntityType.FindOwnership()!.IsUnique + if (property.DeclaringType.IsMappedToJson() #pragma warning disable EF1001 // Internal EF Core API usage. - && property.IsOrdinalKeyProperty()) + && property.IsOrdinalKeyProperty() #pragma warning restore EF1001 // Internal EF Core API usage. + && (property.DeclaringType as IReadOnlyEntityType)?.FindOwnership()!.IsUnique == false) { return ValueGenerated.OnAdd; } @@ -141,8 +141,9 @@ public override void ProcessEntityTypeAnnotationChanged( private static ValueGenerated? GetTemporalValueGenerated(IReadOnlyProperty property) { - var entityType = property.DeclaringEntityType; - return entityType.IsTemporal() + var entityType = property.DeclaringType as IReadOnlyEntityType; + return entityType != null + && entityType.IsTemporal() && (entityType.GetPeriodStartPropertyName() == property.Name || entityType.GetPeriodEndPropertyName() == property.Name) ? ValueGenerated.OnAddOrUpdate diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs index 89d1f09b18f..2e58ef8dde1 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs @@ -111,7 +111,7 @@ bool IsStrategyNoneNeeded(IReadOnlyProperty property, StoreObjectIdentifier stor && !property.TryGetDefaultValue(storeObject, out _) && property.GetDefaultValueSql(storeObject) == null && property.GetComputedColumnSql(storeObject) == null - && property.DeclaringEntityType.Model.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn) + && property.DeclaringType.Model.GetValueGenerationStrategy() == SqlServerValueGenerationStrategy.IdentityColumn) { var providerClrType = (property.GetValueConverter() ?? (property.FindRelationalTypeMapping(storeObject) diff --git a/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorCache.cs b/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorCache.cs index 3312428503b..8278993c131 100644 --- a/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorCache.cs +++ b/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorCache.cs @@ -34,7 +34,7 @@ public virtual SqlServerSequenceValueGeneratorState GetOrAddSequenceState( IProperty property, IRelationalConnection connection) { - var tableIdentifier = StoreObjectIdentifier.Create(property.DeclaringEntityType, StoreObjectType.Table); + var tableIdentifier = StoreObjectIdentifier.Create(property.DeclaringType, StoreObjectType.Table); var sequence = tableIdentifier != null ? property.FindHiLoSequence(tableIdentifier.Value) : property.FindHiLoSequence(); diff --git a/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorSelector.cs b/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorSelector.cs index 5a8d83676d6..05acf3560ca 100644 --- a/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorSelector.cs +++ b/src/EFCore.SqlServer/ValueGeneration/Internal/SqlServerValueGeneratorSelector.cs @@ -53,12 +53,12 @@ public SqlServerValueGeneratorSelector( /// 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 ValueGenerator Select(IProperty property, IEntityType entityType) + public override ValueGenerator Select(IProperty property, ITypeBase typeBase) { if (property.GetValueGeneratorFactory() != null || property.GetValueGenerationStrategy() != SqlServerValueGenerationStrategy.SequenceHiLo) { - return base.Select(property, entityType); + return base.Select(property, typeBase); } var propertyType = property.ClrType.UnwrapNullableType().UnwrapEnumType(); @@ -96,7 +96,7 @@ public override ValueGenerator Select(IProperty property, IEntityType entityType throw new ArgumentException( CoreStrings.InvalidValueGeneratorFactoryProperty( - nameof(SqlServerSequenceValueGeneratorFactory), property.Name, property.DeclaringEntityType.DisplayName())); + nameof(SqlServerSequenceValueGeneratorFactory), property.Name, property.DeclaringType.DisplayName())); } /// @@ -105,10 +105,10 @@ public override ValueGenerator Select(IProperty property, IEntityType entityType /// 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 ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType) + protected override ValueGenerator? FindForType(IProperty property, ITypeBase typeBase, Type clrType) => property.ClrType.UnwrapNullableType() == typeof(Guid) ? property.ValueGenerated == ValueGenerated.Never || property.GetDefaultValueSql() != null ? new TemporaryGuidValueGenerator() : new SequentialGuidValueGenerator() - : base.FindForType(property, entityType, clrType); + : base.FindForType(property, typeBase, clrType); } diff --git a/src/EFCore.Sqlite.Core/Infrastructure/Internal/SqliteModelValidator.cs b/src/EFCore.Sqlite.Core/Infrastructure/Internal/SqliteModelValidator.cs index e2c12d98d82..d601b461a1f 100644 --- a/src/EFCore.Sqlite.Core/Infrastructure/Internal/SqliteModelValidator.cs +++ b/src/EFCore.Sqlite.Core/Infrastructure/Internal/SqliteModelValidator.cs @@ -115,9 +115,9 @@ protected override void ValidateCompatible( { throw new InvalidOperationException( SqliteStrings.DuplicateColumnNameSridMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.DeclaringType.DisplayName(), duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, columnName, storeObject.DisplayName())); diff --git a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs index fdb63ec6830..b0878379a99 100644 --- a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs +++ b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs @@ -67,7 +67,8 @@ public override IEnumerable For(IColumn column, bool designTime) // Model validation ensures that these facets are the same on all mapped properties var property = column.PropertyMappings.First().Property; // Only return auto increment for integer single column primary key - var primaryKey = property.DeclaringEntityType.FindPrimaryKey(); + var primaryKey = ((property.DeclaringType as IEntityType) ?? ((IComplexType)property.DeclaringType).FundamentalEntityType) + .FindPrimaryKey(); if (primaryKey is { Properties.Count: 1 } && primaryKey.Properties[0] == property && property.ValueGenerated == ValueGenerated.OnAdd diff --git a/src/EFCore/ChangeTracking/EntityEntry`.cs b/src/EFCore/ChangeTracking/EntityEntry`.cs index 050be2e5439..8901fd76d78 100644 --- a/src/EFCore/ChangeTracking/EntityEntry`.cs +++ b/src/EFCore/ChangeTracking/EntityEntry`.cs @@ -237,7 +237,7 @@ private static void ValidateType(IProperty? property) throw new ArgumentException( CoreStrings.WrongGenericPropertyType( property.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.ClrType.ShortDisplayName(), typeof(TProperty).ShortDisplayName())); } diff --git a/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs b/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs index 56cfd4917bd..c8c54fcc2d6 100644 --- a/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs +++ b/src/EFCore/ChangeTracking/Internal/ArrayPropertyValues.cs @@ -165,7 +165,7 @@ private void SetValue(int index, object? value) throw new InvalidCastException( CoreStrings.InvalidType( property.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), value.GetType().DisplayName(), property.ClrType.DisplayName())); } @@ -177,7 +177,7 @@ private void SetValue(int index, object? value) throw new InvalidOperationException( CoreStrings.ValueCannotBeNull( property.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.ClrType.DisplayName())); } } diff --git a/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs index 4aabe0eac85..d5b61954ce0 100644 --- a/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs @@ -28,8 +28,8 @@ protected override int GetPropertyIndex(IPropertyBase propertyBase) /// 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 int GetPropertyCount(IEntityType entityType) - => entityType.ShadowPropertyCount(); + protected override int GetPropertyCount(IRuntimeTypeBase typeBase) + => typeBase.ShadowPropertyCount; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index dbd8d4f3d04..568df9bde78 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -19,7 +19,6 @@ namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// public sealed partial class InternalEntityEntry : IUpdateEntry { - // ReSharper disable once FieldCanBeMadeReadOnly.Local private readonly StateData _stateData; private OriginalValues _originalValues; private RelationshipsSnapshot _relationshipsSnapshot; @@ -39,10 +38,10 @@ public InternalEntityEntry( object entity) { StateManager = stateManager; - EntityType = entityType; + EntityType = (IRuntimeEntityType)entityType; Entity = entity; - _shadowValues = entityType.GetEmptyShadowValuesFactory()(); - _stateData = new StateData(entityType.PropertyCount(), entityType.NavigationCount()); + _shadowValues = EntityType.EmptyShadowValuesFactory(); + _stateData = new StateData(EntityType.PropertyCount, EntityType.NavigationCount); MarkShadowPropertiesNotSet(entityType); } @@ -60,10 +59,10 @@ public InternalEntityEntry( in ValueBuffer valueBuffer) { StateManager = stateManager; - EntityType = entityType; + EntityType = (IRuntimeEntityType)entityType; Entity = entity; - _shadowValues = ((IRuntimeEntityType)entityType).ShadowValuesFactory(valueBuffer); - _stateData = new StateData(entityType.PropertyCount(), entityType.NavigationCount()); + _shadowValues = EntityType.ShadowValuesFactory(valueBuffer); + _stateData = new StateData(EntityType.PropertyCount, EntityType.NavigationCount); } /// @@ -107,7 +106,7 @@ void IUpdateEntry.SetPropertyModified(IProperty property) /// 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 IEntityType EntityType { [DebuggerStepThrough] get; } + public IRuntimeEntityType EntityType { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -271,7 +270,7 @@ private bool PrepareForAdd(EntityState newState) if (EntityState == EntityState.Modified) { _stateData.FlagAllProperties( - EntityType.PropertyCount(), PropertyFlag.Modified, + EntityType.PropertyCount, PropertyFlag.Modified, flagged: false); } @@ -309,7 +308,7 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc if (newState == EntityState.Modified && modifyProperties) { - _stateData.FlagAllProperties(entityType.PropertyCount(), PropertyFlag.Modified, flagged: true); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.Modified, flagged: true); // Hot path; do not use LINQ foreach (var property in entityType.GetProperties()) @@ -329,7 +328,7 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc if (newState == EntityState.Unchanged) { _stateData.FlagAllProperties( - entityType.PropertyCount(), PropertyFlag.Modified, + EntityType.PropertyCount, PropertyFlag.Modified, flagged: false); } @@ -371,7 +370,7 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc if (newState is EntityState.Deleted or EntityState.Detached && HasConceptualNull) { - _stateData.FlagAllProperties(entityType.PropertyCount(), PropertyFlag.Null, flagged: false); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.Null, flagged: false); } if (oldState is EntityState.Detached or EntityState.Unchanged) @@ -1464,9 +1463,9 @@ public void AcceptChanges() _temporaryValues = new SidecarValues(); } - _stateData.FlagAllProperties(EntityType.PropertyCount(), PropertyFlag.IsStoreGenerated, false); - _stateData.FlagAllProperties(EntityType.PropertyCount(), PropertyFlag.IsTemporary, false); - _stateData.FlagAllProperties(EntityType.PropertyCount(), PropertyFlag.Unknown, false); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.IsStoreGenerated, false); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.IsTemporary, false); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.Unknown, false); var currentState = EntityState; switch (currentState) @@ -1684,7 +1683,7 @@ public void DiscardStoreGeneratedValues() if (!_storeGeneratedValues.IsEmpty) { _storeGeneratedValues = new SidecarValues(); - _stateData.FlagAllProperties(EntityType.PropertyCount(), PropertyFlag.IsStoreGenerated, false); + _stateData.FlagAllProperties(EntityType.PropertyCount, PropertyFlag.IsStoreGenerated, false); } } @@ -1991,6 +1990,9 @@ public DebugView DebugView IUpdateEntry? IUpdateEntry.SharedIdentityEntry => SharedIdentityEntry; + IEntityType IUpdateEntry.EntityType + => EntityType; + private enum CurrentValueType { Normal, diff --git a/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs b/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs index 5069c7bc6e5..e4df407b783 100644 --- a/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs +++ b/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs @@ -37,7 +37,7 @@ public KeyPropagator( { Check.DebugAssert(property.IsForeignKey(), $"property {property} is not part of an FK"); - var generationProperty = property.FindGenerationProperty(); + var generationProperty = (IProperty?)property.FindGenerationProperty(); var principalEntry = TryPropagateValue(entry, property, generationProperty); if (principalEntry == null @@ -48,7 +48,7 @@ public KeyPropagator( generationProperty, generationProperty == property ? entry.EntityType - : generationProperty?.DeclaringEntityType); + : generationProperty?.DeclaringType); if (valueGenerator != null) { @@ -72,7 +72,7 @@ public KeyPropagator( { Check.DebugAssert(property.IsForeignKey(), $"property {property} is not part of an FK"); - var generationProperty = property.FindGenerationProperty(); + var generationProperty = (IProperty?)property.FindGenerationProperty(); var principalEntry = TryPropagateValue(entry, property, generationProperty); if (principalEntry == null @@ -83,7 +83,7 @@ public KeyPropagator( generationProperty, generationProperty == property ? entry.EntityType - : generationProperty?.DeclaringEntityType); + : generationProperty?.DeclaringType); if (valueGenerator != null) { @@ -166,8 +166,8 @@ private static void SetValue(InternalEntityEntry entry, IProperty property, Valu return null; } - private ValueGenerator? TryGetValueGenerator(IProperty? generationProperty, IEntityType? entityType) + private ValueGenerator? TryGetValueGenerator(IProperty? generationProperty, ITypeBase? typeBase) => generationProperty != null - ? _valueGeneratorSelector.Select(generationProperty, entityType!) + ? _valueGeneratorSelector.Select(generationProperty, typeBase!) : null; } diff --git a/src/EFCore/ChangeTracking/Internal/OriginalValues.cs b/src/EFCore/ChangeTracking/Internal/OriginalValues.cs index fe5a99a54b9..b25b6c49bd1 100644 --- a/src/EFCore/ChangeTracking/Internal/OriginalValues.cs +++ b/src/EFCore/ChangeTracking/Internal/OriginalValues.cs @@ -22,7 +22,7 @@ public OriginalValues(InternalEntityEntry entry) if (index == -1) { throw new InvalidOperationException( - CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringEntityType.DisplayName())); + CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringType.DisplayName())); } return IsEmpty ? entry[property] : _values[index]; @@ -33,7 +33,7 @@ public T GetValue(InternalEntityEntry entry, IProperty property, int index) if (index == -1) { throw new InvalidOperationException( - CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringEntityType.DisplayName())); + CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringType.DisplayName())); } return IsEmpty ? entry.GetCurrentValue(property) : _values.GetValue(index); @@ -50,7 +50,7 @@ public void SetValue(IProperty property, object? value, int index) if (index == -1) { throw new InvalidOperationException( - CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringEntityType.DisplayName())); + CoreStrings.OriginalValueNotTracked(property.Name, property.DeclaringType.DisplayName())); } } @@ -59,7 +59,7 @@ public void SetValue(IProperty property, object? value, int index) { throw new InvalidOperationException( CoreStrings.ValueCannotBeNull( - property.Name, property.DeclaringEntityType.DisplayName(), property.ClrType.DisplayName())); + property.Name, property.DeclaringType.DisplayName(), property.ClrType.DisplayName())); } _values[index] = SnapshotValue(property, value); diff --git a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs index bb617e971c2..d2fa8816177 100644 --- a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs @@ -28,8 +28,8 @@ protected override int GetPropertyIndex(IPropertyBase propertyBase) /// 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 int GetPropertyCount(IEntityType entityType) - => entityType.OriginalValueCount(); + protected override int GetPropertyCount(IRuntimeTypeBase typeBase) + => typeBase.OriginalValueCount; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs index 72bcd6319a4..8c4bb0e69f8 100644 --- a/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/RelationshipSnapshotFactoryFactory.cs @@ -28,8 +28,8 @@ protected override int GetPropertyIndex(IPropertyBase propertyBase) /// 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 int GetPropertyCount(IEntityType entityType) - => entityType.RelationshipPropertyCount(); + protected override int GetPropertyCount(IRuntimeTypeBase typeBase) + => typeBase.RelationshipPropertyCount; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs index cf6b4ee81d9..02a03f14ae4 100644 --- a/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs @@ -28,8 +28,8 @@ protected override int GetPropertyIndex(IPropertyBase propertyBase) /// 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 int GetPropertyCount(IEntityType entityType) - => entityType.ShadowPropertyCount(); + protected override int GetPropertyCount(IRuntimeTypeBase typeBase) + => typeBase.ShadowPropertyCount; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/SidecarValues.cs b/src/EFCore/ChangeTracking/Internal/SidecarValues.cs index 6c2cfc47676..88d531092ca 100644 --- a/src/EFCore/ChangeTracking/Internal/SidecarValues.cs +++ b/src/EFCore/ChangeTracking/Internal/SidecarValues.cs @@ -41,7 +41,7 @@ public void SetValue(IProperty property, object? value, int index) { throw new InvalidOperationException( CoreStrings.ValueCannotBeNull( - property.Name, property.DeclaringEntityType.DisplayName(), property.ClrType.DisplayName())); + property.Name, property.DeclaringType.DisplayName(), property.ClrType.DisplayName())); } _values[index] = SnapshotValue(property, value); diff --git a/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs index d8030894f67..40fcd76ae1b 100644 --- a/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SidecarValuesFactoryFactory.cs @@ -28,8 +28,8 @@ protected override int GetPropertyIndex(IPropertyBase propertyBase) /// 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 int GetPropertyCount(IEntityType entityType) - => entityType.StoreGeneratedCount(); + protected override int GetPropertyCount(IRuntimeTypeBase typeBase) + => typeBase.StoreGeneratedCount; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs index 04499ea5858..c69e316928a 100644 --- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs @@ -21,11 +21,11 @@ public abstract class SnapshotFactoryFactory /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual Func CreateEmpty(IEntityType entityType) - => GetPropertyCount(entityType) == 0 + public virtual Func CreateEmpty(IRuntimeTypeBase typeBase) + => GetPropertyCount(typeBase) == 0 ? (() => Snapshot.Empty) : Expression.Lambda>( - CreateConstructorExpression(entityType, null!)) + CreateConstructorExpression(typeBase, null!)) .Compile(); /// @@ -35,15 +35,15 @@ public virtual Func CreateEmpty(IEntityType entityType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// protected virtual Expression CreateConstructorExpression( - IEntityType entityType, + IRuntimeTypeBase typeBase, ParameterExpression? parameter) { - var count = GetPropertyCount(entityType); + var count = GetPropertyCount(typeBase); var types = new Type[count]; var propertyBases = new IPropertyBase?[count]; - foreach (var propertyBase in entityType.GetPropertiesAndNavigations()) + foreach (var propertyBase in typeBase.GetSnapshottableMembers()) { var index = GetPropertyIndex(propertyBase); if (index >= 0) @@ -62,7 +62,7 @@ protected virtual Expression CreateConstructorExpression( { snapshotExpressions.Add( CreateSnapshotExpression( - entityType.ClrType, + typeBase.ClrType, parameter, types.Skip(i).Take(Snapshot.MaxGenericTypes).ToArray(), propertyBases.Skip(i).Take(Snapshot.MaxGenericTypes).ToList())); @@ -77,7 +77,7 @@ protected virtual Expression CreateConstructorExpression( } else { - constructorExpression = CreateSnapshotExpression(entityType.ClrType, parameter, types, propertyBases); + constructorExpression = CreateSnapshotExpression(typeBase.ClrType, parameter, types, propertyBases); } return constructorExpression; @@ -257,7 +257,7 @@ protected virtual Expression CreateReadValueExpression( /// 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 abstract int GetPropertyCount(IEntityType entityType); + protected abstract int GetPropertyCount(IRuntimeTypeBase typeBase); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.cs index 756a5a70d31..66f8fc1c536 100644 --- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.cs +++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory`.cs @@ -1,6 +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 Microsoft.EntityFrameworkCore.Metadata.Internal; + namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; /// @@ -17,9 +19,9 @@ public abstract class SnapshotFactoryFactory : SnapshotFactoryFactory /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual Func Create(IEntityType entityType) + public virtual Func Create(IRuntimeTypeBase typeBase) { - if (GetPropertyCount(entityType) == 0) + if (GetPropertyCount(typeBase) == 0) { return _ => Snapshot.Empty; } @@ -27,7 +29,7 @@ public virtual Func Create(IEntityType entityType) var parameter = Expression.Parameter(typeof(TInput), "source"); return Expression.Lambda>( - CreateConstructorExpression(entityType, parameter), + CreateConstructorExpression(typeBase, parameter), parameter) .Compile(); } diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index c6cb87aab01..e98815ec490 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -275,8 +275,9 @@ public virtual InternalEntityEntry GetOrCreateEntry(object entity, IEntityType? public virtual InternalEntityEntry CreateEntry(IDictionary values, IEntityType entityType) { var i = 0; - var valuesArray = new object?[entityType.PropertyCount()]; - var shadowPropertyValuesArray = new object?[entityType.ShadowPropertyCount()]; + var runtimeEntityType = (IRuntimeEntityType)entityType; + var valuesArray = new object?[runtimeEntityType.PropertyCount]; + var shadowPropertyValuesArray = new object?[runtimeEntityType.ShadowPropertyCount]; foreach (var property in entityType.GetProperties()) { valuesArray[i++] = values.TryGetValue(property.Name, out var value) diff --git a/src/EFCore/ChangeTracking/Internal/ValueGenerationManager.cs b/src/EFCore/ChangeTracking/Internal/ValueGenerationManager.cs index 2cb4d4a577e..1686a1f695f 100644 --- a/src/EFCore/ChangeTracking/Internal/ValueGenerationManager.cs +++ b/src/EFCore/ChangeTracking/Internal/ValueGenerationManager.cs @@ -189,7 +189,7 @@ public virtual async Task GenerateAsync( } private ValueGenerator GetValueGenerator(IProperty property) - => _valueGeneratorSelector.Select(property, property.DeclaringEntityType); + => _valueGeneratorSelector.Select(property, property.DeclaringType); private static void SetGeneratedValue(InternalEntityEntry entry, IProperty property, object? generatedValue, bool isTemporary) { diff --git a/src/EFCore/ChangeTracking/LocalView.cs b/src/EFCore/ChangeTracking/LocalView.cs index 6ab659f170a..03c56b2cc17 100644 --- a/src/EFCore/ChangeTracking/LocalView.cs +++ b/src/EFCore/ChangeTracking/LocalView.cs @@ -845,7 +845,7 @@ private IProperty FindAndValidateProperty(string propertyName) throw new ArgumentException( CoreStrings.WrongGenericPropertyType( property.Name, - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.ClrType.ShortDisplayName(), typeof(TProperty).ShortDisplayName())); } diff --git a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs index 7f97dffbe13..152e28af957 100644 --- a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGenerator.cs @@ -68,6 +68,44 @@ public virtual void Generate(IEntityType entityType, CSharpRuntimeAnnotationCode GenerateSimpleAnnotations(parameters); } + /// + public virtual void Generate(IComplexProperty complexProperty, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var annotations = parameters.Annotations; + if (!parameters.IsRuntime) + { + foreach (var (key, _) in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(key) + && key != CoreAnnotationNames.DiscriminatorMappingComplete) + { + annotations.Remove(key); + } + } + } + + GenerateSimpleAnnotations(parameters); + } + + /// + public virtual void Generate(IComplexType complexType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var annotations = parameters.Annotations; + if (!parameters.IsRuntime) + { + foreach (var (key, _) in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(key) + && key != CoreAnnotationNames.DiscriminatorMappingComplete) + { + annotations.Remove(key); + } + } + } + + GenerateSimpleAnnotations(parameters); + } + /// public virtual void Generate(IProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { diff --git a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs index a13bd7f5f25..3cbce4374ec 100644 --- a/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs +++ b/src/EFCore/Design/Internal/CSharpRuntimeAnnotationCodeGeneratorParameters.cs @@ -82,8 +82,8 @@ public CSharpRuntimeAnnotationCodeGeneratorParameters( public bool IsRuntime { get; init; } /// - /// Gets or sets a value indicating whther nullable reference types are enabled. + /// Gets or sets a value indicating whether nullable reference types are enabled. /// - /// A value indicating whther nullable reference types are enabled. + /// A value indicating whether nullable reference types are enabled. public bool UseNullableReferenceTypes { get; init; } } diff --git a/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs index 6d960da09f7..f5a8145037f 100644 --- a/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore/Design/Internal/ICSharpRuntimeAnnotationCodeGenerator.cs @@ -27,6 +27,20 @@ public interface ICSharpRuntimeAnnotationCodeGenerator /// Additional parameters used during code generation. void Generate(IEntityType entityType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); + /// + /// Generates code to create the given annotations. + /// + /// The entity type to which the annotations are applied. + /// Additional parameters used during code generation. + void Generate(IComplexProperty complexProperty, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); + + /// + /// Generates code to create the given annotations. + /// + /// The entity type to which the annotations are applied. + /// Additional parameters used during code generation. + void Generate(IComplexType complexType, CSharpRuntimeAnnotationCodeGeneratorParameters parameters); + /// /// Generates code to create the given annotations. /// diff --git a/src/EFCore/Diagnostics/ComplexPropertyEventData.cs b/src/EFCore/Diagnostics/ComplexPropertyEventData.cs new file mode 100644 index 00000000000..c38393b9860 --- /dev/null +++ b/src/EFCore/Diagnostics/ComplexPropertyEventData.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Diagnostics; + +/// +/// A event payload class for events that have +/// a property. +/// +/// +/// See Logging, events, and diagnostics for more information and examples. +/// +public class ComplexPropertyEventData : EventData +{ + /// + /// Constructs the event payload. + /// + /// The event definition. + /// A delegate that generates a log message for this event. + /// The property. + public ComplexPropertyEventData( + EventDefinitionBase eventDefinition, + Func messageGenerator, + IReadOnlyComplexProperty property) + : base(eventDefinition, messageGenerator) + { + Property = property; + } + + /// + /// The property. + /// + public virtual IReadOnlyComplexProperty Property { get; } +} diff --git a/src/EFCore/Diagnostics/CoreEventId.cs b/src/EFCore/Diagnostics/CoreEventId.cs index 2d4415d1c1a..edef6150287 100644 --- a/src/EFCore/Diagnostics/CoreEventId.cs +++ b/src/EFCore/Diagnostics/CoreEventId.cs @@ -120,6 +120,7 @@ private enum Id MappedEntityTypeIgnoredWarning, MappedNavigationIgnoredWarning, MappedPropertyIgnoredWarning, + MappedComplexPropertyIgnoredWarning, // ChangeTracking events DetectChangesStarting = CoreBaseId + 800, @@ -566,6 +567,23 @@ private static EventId MakeModelValidationId(Id id) /// public static readonly EventId MappedPropertyIgnoredWarning = MakeModelId(Id.MappedPropertyIgnoredWarning); + /// + /// A property was first mapped explicitly and then ignored. + /// + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + /// See Modeling entity types and relationships for more information and + /// examples. + /// + /// + public static readonly EventId MappedComplexPropertyIgnoredWarning = MakeModelId(Id.MappedComplexPropertyIgnoredWarning); + /// /// An index was not created as the properties are already covered. /// diff --git a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs index 59cbf53d00b..ba669747919 100644 --- a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs +++ b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs @@ -1261,7 +1261,7 @@ public static void ShadowForeignKeyPropertyCreated( if (diagnostics.ShouldLog(definition)) { - definition.Log(diagnostics, property.DeclaringEntityType.DisplayName(), property.Name, basePropertyName); + definition.Log(diagnostics, property.DeclaringType.DisplayName(), property.Name, basePropertyName); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -1280,7 +1280,7 @@ private static string ShadowForeignKeyPropertyCreated(EventDefinitionBase defini { var d = (EventDefinition)definition; var p = (UniquifiedPropertyEventData)payload; - return d.GenerateMessage(p.Property.DeclaringEntityType.DisplayName(), p.Property.Name, p.BasePropertyName); + return d.GenerateMessage(p.Property.DeclaringType.DisplayName(), p.Property.Name, p.BasePropertyName); } /// @@ -1296,7 +1296,7 @@ public static void ShadowPropertyCreated( if (diagnostics.ShouldLog(definition)) { - definition.Log(diagnostics, property.DeclaringEntityType.DisplayName(), property.Name); + definition.Log(diagnostics, property.DeclaringType.DisplayName(), property.Name); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -1314,7 +1314,7 @@ private static string ShadowPropertyCreated(EventDefinitionBase definition, Even { var d = (EventDefinition)definition; var p = (PropertyEventData)payload; - return d.GenerateMessage(p.Property.DeclaringEntityType.DisplayName(), p.Property.Name); + return d.GenerateMessage(p.Property.DeclaringType.DisplayName(), p.Property.Name); } /// @@ -1330,7 +1330,7 @@ public static void CollectionWithoutComparer( if (diagnostics.ShouldLog(definition)) { - definition.Log(diagnostics, property.DeclaringEntityType.DisplayName(), property.Name); + definition.Log(diagnostics, property.DeclaringType.DisplayName(), property.Name); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -1348,7 +1348,7 @@ private static string CollectionWithoutComparer(EventDefinitionBase definition, { var d = (EventDefinition)definition; var p = (PropertyEventData)payload; - return d.GenerateMessage(p.Property.DeclaringEntityType.DisplayName(), p.Property.Name); + return d.GenerateMessage(p.Property.DeclaringType.DisplayName(), p.Property.Name); } /// @@ -1660,7 +1660,7 @@ public static void MultiplePrimaryKeyCandidates( diagnostics, firstProperty.Name, secondProperty.Name, - firstProperty.DeclaringEntityType.DisplayName()); + firstProperty.DeclaringType.DisplayName()); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -2092,7 +2092,7 @@ public static void PropertyChangeDetected( if (diagnostics.ShouldLog(definition)) { - definition.Log(diagnostics, property.DeclaringEntityType.ShortName(), property.Name); + definition.Log(diagnostics, property.DeclaringType.ShortName(), property.Name); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -2114,7 +2114,7 @@ private static string PropertyChangeDetected(EventDefinitionBase definition, Eve var d = (EventDefinition)definition; var p = (PropertyChangedEventData)payload; return d.GenerateMessage( - p.Property.DeclaringEntityType.ShortName(), + p.Property.DeclaringType.ShortName(), p.Property.Name); } @@ -2139,11 +2139,11 @@ public static void PropertyChangeDetectedSensitive( { definition.Log( diagnostics, - property.DeclaringEntityType.ShortName(), + property.DeclaringType.ShortName(), property.Name, oldValue, newValue, - internalEntityEntry.BuildCurrentValuesString(property.DeclaringEntityType.FindPrimaryKey()!.Properties)); + internalEntityEntry.BuildCurrentValuesString(((IEntityType)property.DeclaringType).FindPrimaryKey()!.Properties)); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -2165,11 +2165,12 @@ private static string PropertyChangeDetectedSensitive(EventDefinitionBase defini var d = (EventDefinition)definition; var p = (PropertyChangedEventData)payload; return d.GenerateMessage( - p.Property.DeclaringEntityType.ShortName(), + p.Property.DeclaringType.ShortName(), p.Property.Name, p.OldValue, p.NewValue, - p.EntityEntry.GetInfrastructure().BuildCurrentValuesString(p.Property.DeclaringEntityType.FindPrimaryKey()!.Properties)); + p.EntityEntry.GetInfrastructure().BuildCurrentValuesString( + ((IEntityType)p.Property.DeclaringType).FindPrimaryKey()!.Properties)); } /// @@ -2193,7 +2194,7 @@ public static void ForeignKeyChangeDetected( { definition.Log( diagnostics, - property.DeclaringEntityType.ShortName(), + property.DeclaringType.ShortName(), property.Name); } @@ -2216,7 +2217,7 @@ private static string ForeignKeyChangeDetected(EventDefinitionBase definition, E var d = (EventDefinition)definition; var p = (PropertyChangedEventData)payload; return d.GenerateMessage( - p.Property.DeclaringEntityType.ShortName(), + p.Property.DeclaringType.ShortName(), p.Property.Name); } @@ -2241,11 +2242,11 @@ public static void ForeignKeyChangeDetectedSensitive( { definition.Log( diagnostics, - property.DeclaringEntityType.ShortName(), + property.DeclaringType.ShortName(), property.Name, oldValue, newValue, - internalEntityEntry.BuildCurrentValuesString(property.DeclaringEntityType.FindPrimaryKey()!.Properties)); + internalEntityEntry.BuildCurrentValuesString(((IEntityType)property.DeclaringType).FindPrimaryKey()!.Properties)); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -2267,11 +2268,12 @@ private static string ForeignKeyChangeDetectedSensitive(EventDefinitionBase defi var d = (EventDefinition)definition; var p = (PropertyChangedEventData)payload; return d.GenerateMessage( - p.Property.DeclaringEntityType.ShortName(), + p.Property.DeclaringType.ShortName(), p.Property.Name, p.OldValue, p.NewValue, - p.EntityEntry.GetInfrastructure().BuildCurrentValuesString(p.Property.DeclaringEntityType.FindPrimaryKey()!.Properties)); + p.EntityEntry.GetInfrastructure().BuildCurrentValuesString( + ((IEntityType)p.Property.DeclaringType).FindPrimaryKey()!.Properties)); } /// @@ -3073,6 +3075,37 @@ private static string MappedPropertyIgnoredWarning(EventDefinitionBase definitio return d.GenerateMessage(p.Property.DeclaringType.ShortName(), p.Property.Name); } + /// + /// Logs for the event. + /// + /// The diagnostics logger to use. + /// The property. + public static void MappedComplexPropertyIgnoredWarning( + this IDiagnosticsLogger diagnostics, + IComplexProperty property) + { + var definition = CoreResources.LogMappedComplexPropertyIgnored(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + definition.Log(diagnostics, property.DeclaringType.ShortName(), property.Name); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new ComplexPropertyEventData(definition, MappedComplexPropertyIgnoredWarning, property); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string MappedComplexPropertyIgnoredWarning(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (ComplexPropertyEventData)payload; + return d.GenerateMessage(p.Property.DeclaringType.ShortName(), p.Property.Name); + } + /// /// Logs for the event. /// @@ -3378,7 +3411,7 @@ public static void ConflictingKeylessAndKeyAttributesWarning( if (diagnostics.ShouldLog(definition)) { - definition.Log(diagnostics, property.Name, property.DeclaringEntityType.DisplayName()); + definition.Log(diagnostics, property.Name, property.DeclaringType.DisplayName()); } if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) @@ -3398,7 +3431,7 @@ private static string ConflictingKeylessAndKeyAttributesWarning(EventDefinitionB var p = (PropertyEventData)payload; return d.GenerateMessage( p.Property.Name, - p.Property.DeclaringEntityType.DisplayName()); + p.Property.DeclaringType.DisplayName()); } /// diff --git a/src/EFCore/Diagnostics/LoggingDefinitions.cs b/src/EFCore/Diagnostics/LoggingDefinitions.cs index 389f76a963b..cbe8c5523ff 100644 --- a/src/EFCore/Diagnostics/LoggingDefinitions.cs +++ b/src/EFCore/Diagnostics/LoggingDefinitions.cs @@ -70,6 +70,15 @@ public abstract class LoggingDefinitions [EntityFrameworkInternal] public EventDefinitionBase? LogMappedPropertyIgnored; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public EventDefinitionBase? LogMappedComplexPropertyIgnored; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Infrastructure/AnnotatableBuilder.cs b/src/EFCore/Infrastructure/AnnotatableBuilder.cs index 7bb11fdfdaf..b2e74bf565e 100644 --- a/src/EFCore/Infrastructure/AnnotatableBuilder.cs +++ b/src/EFCore/Infrastructure/AnnotatableBuilder.cs @@ -103,7 +103,7 @@ protected AnnotatableBuilder(TMetadata metadata, TModelBuilder modelBuilder) object? value, ConfigurationSource configurationSource) => value == null - ? RemoveAnnotation(name, configurationSource) + ? HasNoAnnotation(name, configurationSource) : HasAnnotation(name, value, configurationSource, canOverrideSameSource: true); /// @@ -143,7 +143,7 @@ private static bool CanSetAnnotationValue( /// The name of the annotation to remove. /// The configuration source of the annotation to be set. /// The same builder so that multiple calls can be chained. - public virtual AnnotatableBuilder? RemoveAnnotation( + public virtual AnnotatableBuilder? HasNoAnnotation( string name, ConfigurationSource configurationSource) { @@ -238,7 +238,7 @@ bool IConventionAnnotatableBuilder.CanSetAnnotation(string name, object? value, /// [DebuggerStepThrough] IConventionAnnotatableBuilder? IConventionAnnotatableBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) - => RemoveAnnotation(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + => HasNoAnnotation(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index c014af1e32c..1598b9c3486 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -164,6 +164,7 @@ protected virtual void ValidatePropertyMapping( clrProperties.ExceptWith( ((IEnumerable)entityType.GetProperties()) + .Concat(entityType.GetComplexProperties()) .Concat(entityType.GetNavigations()) .Concat(entityType.GetSkipNavigations()) .Concat(entityType.GetServiceProperties()).Select(p => p.Name)); @@ -322,11 +323,11 @@ protected virtual void ValidateIgnoredMembers( var property = entityType.FindProperty(ignoredMember); if (property != null) { - if (property.DeclaringEntityType != entityType) + if (property.DeclaringType != entityType) { throw new InvalidOperationException( CoreStrings.InheritedPropertyCannotBeIgnored( - ignoredMember, entityType.DisplayName(), property.DeclaringEntityType.DisplayName())); + ignoredMember, entityType.DisplayName(), property.DeclaringType.DisplayName())); } Check.DebugFail("Should never get here..."); @@ -476,7 +477,7 @@ protected virtual void ValidateNoCycles( } /// - /// Validates the mapping/configuration of primary key nullability in the model. + /// Validates that all trackable entity types have a primary key. /// /// The model to validate. /// The logger to use. @@ -898,7 +899,7 @@ protected virtual void ValidateTypeMappings( throw new InvalidOperationException( CoreStrings.ComparerPropertyMismatch( providerComparer.Type.ShortDisplayName(), - property.DeclaringEntityType.DisplayName(), + property.DeclaringType.DisplayName(), property.Name, actualProviderClrType.ShortDisplayName())); } diff --git a/src/EFCore/Internal/EntityFinder.cs b/src/EFCore/Internal/EntityFinder.cs index 9eff76e904b..2e06583056c 100644 --- a/src/EFCore/Internal/EntityFinder.cs +++ b/src/EFCore/Internal/EntityFinder.cs @@ -94,7 +94,7 @@ public EntityFinder( throw new ArgumentException( CoreStrings.WrongGenericPropertyType( _primaryKey.Properties[0].Name, - _primaryKey.Properties[0].DeclaringEntityType.DisplayName(), + _primaryKey.Properties[0].DeclaringType.DisplayName(), _primaryKeyType.ShortDisplayName(), typeof(TKey).ShortDisplayName())); } diff --git a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs index 0f30401cf12..06067bc1fc7 100644 --- a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs @@ -268,7 +268,10 @@ protected virtual IMutableSkipNavigation WithLeftManyNavigation(string? inverseN return ((EntityType)DeclaringEntityType).Builder.HasSkipNavigation( navigationMember, (EntityType)RelatedEntityType, - ConfigurationSource.Explicit)!.Metadata; + foreignKey.PrincipalToDependent?.ClrType, + ConfigurationSource.Explicit, + collection: true, + onDependent: false)!.Metadata; } } @@ -320,6 +323,7 @@ private IMutableSkipNavigation WithRightManyNavigation(MemberIdentity navigation var skipNavigation = RelatedEntityType.FindSkipNavigation(navigationName); if (skipNavigation != null) { + ((SkipNavigation)skipNavigation).UpdateConfigurationSource(ConfigurationSource.Explicit); return skipNavigation; } } @@ -328,7 +332,9 @@ private IMutableSkipNavigation WithRightManyNavigation(MemberIdentity navigation return ((EntityType)RelatedEntityType).Builder.HasSkipNavigation( navigationMember, (EntityType)DeclaringEntityType, - ConfigurationSource.Explicit)!.Metadata; + ConfigurationSource.Explicit, + collection: true, + onDependent: false)!.Metadata; } } diff --git a/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder.cs b/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder.cs new file mode 100644 index 00000000000..5c14c9f6aa2 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API surface for setting property defaults before conventions run. +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class ComplexPropertiesConfigurationBuilder +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexPropertiesConfigurationBuilder(ComplexPropertyConfiguration property) + { + Check.NotNull(property, nameof(property)); + + Configuration = property; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual ComplexPropertyConfiguration Configuration { get; } + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder`.cs new file mode 100644 index 00000000000..21024c36705 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexPropertiesConfigurationBuilder`.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API surface for setting property defaults before conventions run. +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class ComplexPropertiesConfigurationBuilder : ComplexPropertiesConfigurationBuilder +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexPropertiesConfigurationBuilder(ComplexPropertyConfiguration property) + : base(property) + { + } +} diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs new file mode 100644 index 00000000000..9fbaf953795 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder.cs @@ -0,0 +1,486 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring an . +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class ComplexPropertyBuilder : + IInfrastructure, IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexPropertyBuilder(IMutableComplexProperty complexProperty) + { + PropertyBuilder = ((ComplexProperty)complexProperty).Builder; + TypeBuilder = ((ComplexProperty)complexProperty).ComplexType.Builder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual InternalComplexPropertyBuilder PropertyBuilder { [DebuggerStepThrough] get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual InternalComplexTypeBuilder TypeBuilder { [DebuggerStepThrough] get; } + + /// + /// Gets the internal builder being used to configure the complex property. + /// + IConventionComplexPropertyBuilder IInfrastructure.Instance + => PropertyBuilder; + + /// + /// Gets the internal builder being used to configure the complex type. + /// + IConventionComplexTypeBuilder IInfrastructure.Instance + => TypeBuilder; + + /// + /// The complex property being configured. + /// + public virtual IMutableComplexProperty Metadata + => PropertyBuilder.Metadata; + + /// + /// Adds or updates an annotation on the complex property. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + PropertyBuilder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Adds or updates an annotation on the complex type. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder HasTypeAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + TypeBuilder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether this property must have a value assigned or is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether the property is required. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder IsRequired(bool required = true) + { + PropertyBuilder.IsRequired(required, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Sets the backing field to use for this property. + /// + /// + /// + /// Backing fields are normally found by convention. + /// This method is useful for setting backing fields explicitly in cases where the + /// correct field is not found by convention. + /// + /// + /// By default, the backing field, if one is found or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. This can be changed by calling + /// . + /// + /// + /// See Backing fields for more information and examples. + /// + /// + /// The field name. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder HasField(string fieldName) + { + Check.NotEmpty(fieldName, nameof(fieldName)); + + PropertyBuilder.HasField(fieldName, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Returns an object that can be used to configure a property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state property. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder Property(string propertyName) + => new( + TypeBuilder.Property( + Check.NotEmpty(propertyName, nameof(propertyName)), + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the entity class + /// then it will be added to the model. If no property exists in the entity class, then + /// a new shadow state property will be added. A shadow state property is one that does not have a + /// corresponding property in the entity class. The current value for the property is stored in + /// the rather than being stored in instances of the entity class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder Property(string propertyName) + => new( + TypeBuilder.Property( + typeof(TProperty), + Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the entity class + /// then it will be added to the model. If no property exists in the entity class, then + /// a new shadow state property will be added. A shadow state property is one that does not have a + /// corresponding property in the entity class. The current value for the property is stored in + /// the rather than being stored in instances of the entity class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder Property(Type propertyType, string propertyName) + => new( + TypeBuilder.Property( + Check.NotNull(propertyType, nameof(propertyType)), + Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// Indexer properties are stored in the entity using + /// an indexer + /// supplying the provided property name. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder IndexerProperty + <[DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] TProperty>(string propertyName) + => new( + TypeBuilder.IndexerProperty( + typeof(TProperty), + Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// Indexer properties are stored in the entity using + /// an indexer + /// supplying the provided property name. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder IndexerProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + string propertyName) + { + Check.NotNull(propertyType, nameof(propertyType)); + + return new( + TypeBuilder.IndexerProperty( + propertyType, + Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata); + } + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) + => new( + TypeBuilder.ComplexProperty( + propertyType: null, + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) + => new( + TypeBuilder.ComplexProperty( + typeof(TProperty), + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string propertyName) + => new( + TypeBuilder.ComplexProperty( + Check.NotNull(propertyType, nameof(propertyType)), + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder ComplexProperty(string propertyName, Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyName)); + + return this; + } + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyName)); + + return this; + } + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string propertyName, Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyType, propertyName)); + + return this; + } + + /// + /// Excludes the given property from the complex type. This method is typically used to remove properties + /// and navigations from the complex type that were added by convention. + /// + /// The name of the property to be removed from the complex type. + public virtual ComplexPropertyBuilder Ignore(string propertyName) + { + Check.NotEmpty(propertyName, nameof(propertyName)); + + TypeBuilder.Ignore(propertyName, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the to be used for this entity type. + /// This strategy indicates how the context detects changes to properties for an instance of the entity type. + /// + /// The change tracking strategy to be used. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy) + { + TypeBuilder.HasChangeTrackingStrategy(changeTrackingStrategy, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Sets the to use for this property. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for this property as described in the enum. + /// + /// + /// Calling this method overrides for this property any access mode that was set on the + /// entity type or model. + /// + /// + /// The to use for this property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + { + PropertyBuilder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Sets the to use for all properties of this complex type. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for all properties of this complex type as described in the enum. + /// + /// + /// Calling this method overrides for all properties of this complex type any access mode that was + /// set on the model. + /// + /// + /// The to use for properties of this complex type. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) + { + TypeBuilder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit); + + return this; + } + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs new file mode 100644 index 00000000000..868b5645c87 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexPropertyBuilder`.cs @@ -0,0 +1,275 @@ +// 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 Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring an . +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +/// The complex type being configured. +public class ComplexPropertyBuilder<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TComplex> + : ComplexPropertyBuilder +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexPropertyBuilder(IMutableComplexProperty complexProperty) + : base(complexProperty) + { + } + + /// + /// Adds or updates an annotation on the entity type. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same typeBuilder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value) + => (ComplexPropertyBuilder)base.HasPropertyAnnotation(annotation, value); + + /// + /// Adds or updates an annotation on the entity type. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same typeBuilder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder HasTypeAnnotation(string annotation, object? value) + => (ComplexPropertyBuilder)base.HasTypeAnnotation(annotation, value); + + /// + /// Configures whether this property must have a value assigned or is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether the property is required. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder IsRequired(bool required = true) + => (ComplexPropertyBuilder)base.IsRequired(required); + + /// + /// Sets the backing field to use for this property. + /// + /// + /// + /// Backing fields are normally found by convention. + /// This method is useful for setting backing fields explicitly in cases where the + /// correct field is not found by convention. + /// + /// + /// By default, the backing field, if one is found or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. This can be changed by calling + /// . + /// + /// + /// See Backing fields for more information and examples. + /// + /// + /// The field name. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder HasField(string fieldName) + => (ComplexPropertyBuilder)base.HasField(fieldName); + + /// + /// Returns an object that can be used to configure a property of the entity type. + /// If the specified property is not already part of the model, it will be added. + /// + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ComplexTypePropertyBuilder Property(Expression> propertyExpression) + => new(TypeBuilder.Property( + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), ConfigurationSource.Explicit)! + .Metadata); + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder ComplexProperty(string propertyName, Action buildAction) + => (ComplexPropertyBuilder)base.ComplexProperty(propertyName, buildAction); + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder ComplexProperty( + string propertyName, Action> buildAction) + => (ComplexPropertyBuilder)base.ComplexProperty(propertyName, buildAction); + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder ComplexProperty( + Type propertyType, string propertyName, Action buildAction) + => (ComplexPropertyBuilder)base.ComplexProperty(propertyType, propertyName, buildAction); + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(Expression> propertyExpression) + => new(TypeBuilder.ComplexProperty( + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Configures a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyExpression)); + + return this; + } + + /// + /// Excludes the given property from the entity type. This method is typically used to remove properties + /// or navigations from the entity type that were added by convention. + /// + /// + /// A lambda expression representing the property to be ignored + /// (blog => blog.Url). + /// + public virtual ComplexPropertyBuilder Ignore(Expression> propertyExpression) + => (ComplexPropertyBuilder)base.Ignore( + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess().GetSimpleMemberName()); + + /// + /// Excludes the given property from the entity type. This method is typically used to remove properties + /// or navigations from the entity type that were added by convention. + /// + /// The name of the property to be removed from the entity type. + public new virtual ComplexPropertyBuilder Ignore(string propertyName) + => (ComplexPropertyBuilder)base.Ignore(propertyName); + + /// + /// Configures the to be used for this entity type. + /// This strategy indicates how the context detects changes to properties for an instance of the entity type. + /// + /// The change tracking strategy to be used. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy) + => (ComplexPropertyBuilder)base.HasChangeTrackingStrategy(changeTrackingStrategy); + + /// + /// Sets the to use for this property. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for this property as described in the enum. + /// + /// + /// Calling this method overrides for this property any access mode that was set on the + /// entity type or model. + /// + /// + /// The to use for this property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => (ComplexPropertyBuilder)base.UsePropertyAccessMode(propertyAccessMode); + + /// + /// Sets the to use for all properties of this entity type. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for all properties of this entity type as described in the enum. + /// + /// + /// Calling this method overrides for all properties of this entity type any access mode that was + /// set on the model. + /// + /// + /// The to use for properties of this entity type. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) + => (ComplexPropertyBuilder)base.UseDefaultPropertyAccessMode(propertyAccessMode); +} diff --git a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs new file mode 100644 index 00000000000..b6c57e0effb --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder.cs @@ -0,0 +1,695 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring a . +/// +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling complex types and relationships for more information and +/// examples. +/// +/// +public class ComplexTypePropertyBuilder : IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexTypePropertyBuilder(IMutableProperty property) + { + Check.NotNull(property, nameof(property)); + + Builder = ((Property)property).Builder; + } + + /// + /// The internal builder being used to configure the property. + /// + IConventionPropertyBuilder IInfrastructure.Instance + => Builder; + + private InternalPropertyBuilder Builder { get; } + + /// + /// The property being configured. + /// + public virtual IMutableProperty Metadata + => Builder.Metadata; + + /// + /// Adds or updates an annotation on the property. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether this property must have a value assigned or is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether the property is required. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder IsRequired(bool required = true) + { + Builder.IsRequired(required, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the maximum length of data that can be stored in this property. + /// Maximum length can only be set on array properties (including properties). + /// + /// + /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasMaxLength(int maxLength) + { + Builder.HasMaxLength(maxLength, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the + /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of + /// the property. + /// + /// The sentinel value. + /// The same builder instance if the configuration was applied, otherwise. + public virtual ComplexTypePropertyBuilder HasSentinel(object? sentinel) + { + Builder.HasSentinel(sentinel, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the precision and scale of the property. + /// + /// The precision of the property. + /// The scale of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasPrecision(int precision, int scale) + { + Builder.HasPrecision(precision, ConfigurationSource.Explicit); + Builder.HasScale(scale, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the precision of the property. + /// + /// The precision of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasPrecision(int precision) + { + Builder.HasPrecision(precision, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether the property as capable of persisting unicode characters. + /// Can only be set on properties. + /// + /// A value indicating whether the property can contain unicode characters. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder IsUnicode(bool unicode = true) + { + Builder.IsUnicode(unicode, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the property as and + /// . + /// + /// + /// Database providers can choose to interpret this in different way, but it is commonly used + /// to indicate some form of automatic row-versioning as used for optimistic concurrency detection. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder IsRowVersion() + { + Builder.ValueGenerated(ValueGenerated.OnAddOrUpdate, ConfigurationSource.Explicit); + Builder.IsConcurrencyToken(true, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasValueGenerator + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TGenerator>() + where TGenerator : ValueGenerator + { + Builder.HasValueGenerator(typeof(TGenerator), ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasValueGenerator( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType) + { + Builder.HasValueGenerator(valueGeneratorType, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a factory for creating a to use to generate values + /// for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// This factory will be invoked once to create a single instance of the value generator, and + /// this will be used to generate values for this property in all instances of the complex type. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// A delegate that will be used to create value generator instances. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasValueGenerator(Func factory) + { + Check.NotNull(factory, nameof(factory)); + + Builder.HasValueGenerator(factory, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasValueGeneratorFactory + <[DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] TFactory>() + where TFactory : ValueGeneratorFactory + => HasValueGeneratorFactory(typeof(TFactory)); + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType) + { + Builder.HasValueGeneratorFactory(valueGeneratorFactoryType, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures whether this property should be used as a concurrency token. When a property is configured + /// as a concurrency token the value in the database will be checked when an instance of this complex type + /// is updated or deleted during to ensure it has not changed since + /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the + /// changes will not be applied to the database. + /// + /// A value indicating whether this property is a concurrency token. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder IsConcurrencyToken(bool concurrencyToken = true) + { + Builder.IsConcurrencyToken(concurrencyToken, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a property to never have a value generated by the database when an instance of this + /// complex type is saved. + /// + /// The same builder instance so that multiple configuration calls can be chained. + /// + /// Note that values may still be generated by a client-side value generator, if one is set explicitly or by a convention. + /// + public virtual ComplexTypePropertyBuilder ValueGeneratedNever() + { + Builder.ValueGenerated(ValueGenerated.Never, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a property to have a value generated only when saving a new entity, unless a non-null, + /// non-temporary value has been set, in which case the set value will be saved instead. The value + /// may be generated by a client-side value generator or may be generated by the database as part + /// of saving the entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder ValueGeneratedOnAdd() + { + Builder.ValueGenerated(ValueGenerated.OnAdd, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a property to have a value generated when saving a new or existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder ValueGeneratedOnAddOrUpdate() + { + Builder.ValueGenerated(ValueGenerated.OnAddOrUpdate, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a property to have a value generated when saving an existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder ValueGeneratedOnUpdate() + { + Builder.ValueGenerated(ValueGenerated.OnUpdate, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures a property to have a value generated under certain conditions when saving an existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder ValueGeneratedOnUpdateSometimes() + { + Builder.ValueGenerated(ValueGenerated.OnUpdateSometimes, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Sets the backing field to use for this property. + /// + /// + /// + /// Backing fields are normally found by convention. + /// This method is useful for setting backing fields explicitly in cases where the + /// correct field is not found by convention. + /// + /// + /// By default, the backing field, if one is found or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. This can be changed by calling + /// . + /// + /// + /// See Backing fields for more information and examples. + /// + /// + /// The field name. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasField(string fieldName) + { + Check.NotEmpty(fieldName, nameof(fieldName)); + + Builder.HasField(fieldName, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Sets the to use for this property. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for this property as described in the enum. + /// + /// + /// Calling this method overrides for this property any access mode that was set on the + /// complex type or model. + /// + /// + /// The to use for this property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + { + Builder.UsePropertyAccessMode(propertyAccessMode, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>() + => HasConversion(typeof(TConversion)); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? conversionType) + { + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.HasConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.HasConversion(conversionType, ConfigurationSource.Explicit); + } + + return this; + } + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion(ValueConverter? converter) + => HasConversion(converter, null, null); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The comparer to use for values before conversion. + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( + ValueComparer? valueComparer) + => HasConversion(typeof(TConversion), valueComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => HasConversion(typeof(TConversion), valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + ValueComparer? valueComparer) + => HasConversion(conversionType, valueComparer, null); + + // DynamicallyAccessedMemberTypes.PublicParameterlessConstructor + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + { + Check.NotNull(conversionType, nameof(conversionType)); + + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.HasConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.HasConversion(conversionType, ConfigurationSource.Explicit); + } + + Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit); + Builder.HasProviderValueComparer(providerComparer, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer) + => HasConversion(converter, valueComparer, null); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer, ValueComparer? providerComparer) + { + Builder.HasConversion(converter, ConfigurationSource.Explicit); + Builder.HasValueComparer(valueComparer, ConfigurationSource.Explicit); + Builder.HasProviderValueComparer(providerComparer, ConfigurationSource.Explicit); + + return this; + } + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer>() + where TComparer : ValueComparer + => HasConversion(typeof(TConversion), typeof(TComparer)); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TProviderComparer>() + where TComparer : ValueComparer + where TProviderComparer : ValueComparer + => HasConversion(typeof(TConversion), typeof(TComparer), typeof(TProviderComparer)); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType) + => HasConversion(conversionType, comparerType, null); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? providerComparerType) + { + Check.NotNull(conversionType, nameof(conversionType)); + + if (typeof(ValueConverter).IsAssignableFrom(conversionType)) + { + Builder.HasConverter(conversionType, ConfigurationSource.Explicit); + } + else + { + Builder.HasConversion(conversionType, ConfigurationSource.Explicit); + } + + Builder.HasValueComparer(comparerType, ConfigurationSource.Explicit); + Builder.HasProviderValueComparer(providerComparerType, ConfigurationSource.Explicit); + + return this; + } + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectEqualsIsObjectEquals + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + // ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs new file mode 100644 index 00000000000..7f0e24503b5 --- /dev/null +++ b/src/EFCore/Metadata/Builders/ComplexTypePropertyBuilder`.cs @@ -0,0 +1,610 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring a . +/// +/// +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// +/// See Modeling complex types and relationships for more information and +/// examples. +/// +/// +public class ComplexTypePropertyBuilder : ComplexTypePropertyBuilder +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ComplexTypePropertyBuilder(IMutableProperty property) + : base(property) + { + } + + /// + /// Adds or updates an annotation on the property. If an annotation with the key specified in + /// already exists its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasAnnotation(string annotation, object? value) + => (ComplexTypePropertyBuilder)base.HasAnnotation(annotation, value); + + /// + /// Configures whether this property must have a value assigned or whether null is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// A value indicating whether the property is required. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder IsRequired(bool required = true) + => (ComplexTypePropertyBuilder)base.IsRequired(required); + + /// + /// Configures the maximum length of data that can be stored in this property. + /// Maximum length can only be set on array properties (including properties). + /// + /// + /// The maximum length of data allowed in the property. A value of -1 indicates that the property has no maximum length. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasMaxLength(int maxLength) + => (ComplexTypePropertyBuilder)base.HasMaxLength(maxLength); + + /// + /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the + /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of + /// the property. + /// + /// The sentinel value. + /// The same builder instance if the configuration was applied, otherwise. + public new virtual ComplexTypePropertyBuilder HasSentinel(object? sentinel) + => (ComplexTypePropertyBuilder)base.HasSentinel(sentinel); + + /// + /// Configures the precision and scale of the property. + /// + /// The precision of the property. + /// The scale of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasPrecision(int precision, int scale) + => (ComplexTypePropertyBuilder)base.HasPrecision(precision, scale); + + /// + /// Configures the precision of the property. + /// + /// The precision of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasPrecision(int precision) + => (ComplexTypePropertyBuilder)base.HasPrecision(precision); + + /// + /// Configures the property as capable of persisting unicode characters. + /// Can only be set on properties. + /// + /// A value indicating whether the property can contain unicode characters. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder IsUnicode(bool unicode = true) + => (ComplexTypePropertyBuilder)base.IsUnicode(unicode); + + /// + /// Configures the property as and + /// . + /// + /// + /// Database providers can choose to interpret this in different way, but it is commonly used + /// to indicate some form of automatic row-versioning as used for optimistic concurrency detection. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder IsRowVersion() + => (ComplexTypePropertyBuilder)base.IsRowVersion(); + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasValueGenerator + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TGenerator>() + where TGenerator : ValueGenerator + => (ComplexTypePropertyBuilder)base.HasValueGenerator(); + + /// + /// Configures the that will generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting null does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasValueGenerator( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? valueGeneratorType) + => (ComplexTypePropertyBuilder)base.HasValueGenerator(valueGeneratorType); + + /// + /// Configures a factory for creating a to use to generate values + /// for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// This factory will be invoked once to create a single instance of the value generator, and + /// this will be used to generate values for this property in all instances of the complex type. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// A delegate that will be used to create value generator instances. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasValueGenerator(Func factory) + => (ComplexTypePropertyBuilder)base.HasValueGenerator(factory); + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasValueGeneratorFactory + <[DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] TFactory>() + where TFactory : ValueGeneratorFactory + => (ComplexTypePropertyBuilder)base.HasValueGeneratorFactory(); + + /// + /// Configures the for creating a + /// to use to generate values for this property. + /// + /// + /// + /// Values are generated when the entity is added to the context using, for example, + /// . Values are generated only when the property is assigned + /// the CLR default value ( for string, 0 for int, + /// Guid.Empty for Guid, etc.). + /// + /// + /// A single instance of this type will be created and used to generate values for this property in all + /// instances of the complex type. The type must be instantiable and have a parameterless constructor. + /// + /// + /// This method is intended for use with custom value generation. Value generation for common cases is + /// usually handled automatically by the database provider. + /// + /// + /// Setting does not disable value generation for this property, it just clears any generator explicitly + /// configured for this property. The database provider may still have a value generator for the property type. + /// + /// + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasValueGeneratorFactory( + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactoryType) + => (ComplexTypePropertyBuilder)base.HasValueGeneratorFactory(valueGeneratorFactoryType); + + /// + /// Configures whether this property should be used as a concurrency token. When a property is configured + /// as a concurrency token the value in the database will be checked when an instance of this complex type + /// is updated or deleted during to ensure it has not changed since + /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the + /// changes will not be applied to the database. + /// + /// A value indicating whether this property is a concurrency token. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder IsConcurrencyToken(bool concurrencyToken = true) + => (ComplexTypePropertyBuilder)base.IsConcurrencyToken(concurrencyToken); + + /// + /// Configures a property to never have a value generated when an instance of this + /// complex type is saved. + /// + /// The same builder instance so that multiple configuration calls can be chained. + /// + /// Note that temporary values may still be generated for use internally before a + /// new entity is saved. + /// + public new virtual ComplexTypePropertyBuilder ValueGeneratedNever() + => (ComplexTypePropertyBuilder)base.ValueGeneratedNever(); + + /// + /// Configures a property to have a value generated only when saving a new entity, unless a non-null, + /// non-temporary value has been set, in which case the set value will be saved instead. The value + /// may be generated by a client-side value generator or may be generated by the database as part + /// of saving the entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder ValueGeneratedOnAdd() + => (ComplexTypePropertyBuilder)base.ValueGeneratedOnAdd(); + + /// + /// Configures a property to have a value generated when saving a new or existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder ValueGeneratedOnAddOrUpdate() + => (ComplexTypePropertyBuilder)base.ValueGeneratedOnAddOrUpdate(); + + /// + /// Configures a property to have a value generated when saving an existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder ValueGeneratedOnUpdate() + => (ComplexTypePropertyBuilder)base.ValueGeneratedOnUpdate(); + + /// + /// Configures a property to have a value generated under certain conditions when saving an existing entity. + /// + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder ValueGeneratedOnUpdateSometimes() + => (ComplexTypePropertyBuilder)base.ValueGeneratedOnUpdateSometimes(); + + /// + /// Sets the backing field to use for this property. + /// + /// + /// + /// Backing fields are normally found by convention. + /// This method is useful for setting backing fields explicitly in cases where the + /// correct field is not found by convention. + /// + /// + /// By default, the backing field, if one is found or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. This can be changed by calling + /// . + /// + /// + /// See Backing fields for more information and examples. + /// + /// + /// The field name. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasField(string fieldName) + => (ComplexTypePropertyBuilder)base.HasField(fieldName); + + /// + /// Sets the to use for this property. + /// + /// + /// + /// By default, the backing field, if one is found by convention or has been specified, is used when + /// new objects are constructed, typically when entities are queried from the database. + /// Properties are used for all other accesses. Calling this method will change that behavior + /// for this property as described in the enum. + /// + /// + /// Calling this method overrides for this property any access mode that was set on the + /// complex type or model. + /// + /// + /// The to use for this property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => (ComplexTypePropertyBuilder)base.UsePropertyAccessMode(propertyAccessMode); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>() + => (ComplexTypePropertyBuilder)base.HasConversion(); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? providerClrType) + => (ComplexTypePropertyBuilder)base.HasConversion(providerClrType); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given conversion expressions. + /// + /// The store type generated by the conversions. + /// An expression to convert objects when writing data to the store. + /// An expression to convert objects when reading data from the store. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression) + => HasConversion( + new ValueConverter( + Check.NotNull(convertToProviderExpression, nameof(convertToProviderExpression)), + Check.NotNull(convertFromProviderExpression, nameof(convertFromProviderExpression)))); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The store type generated by the converter. + /// The converter to use. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion(ValueConverter? converter) + => HasConversion((ValueConverter?)converter); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion(ValueConverter? converter) + => (ComplexTypePropertyBuilder)base.HasConversion(converter); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( + ValueComparer? valueComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(valueComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion + <[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion>( + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + ValueComparer? valueComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(conversionType, valueComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(conversionType, valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given conversion expressions. + /// + /// The store type generated by the conversions. + /// An expression to convert objects when writing data to the store. + /// An expression to convert objects when reading data from the store. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer) + => HasConversion( + new ValueConverter( + Check.NotNull(convertToProviderExpression, nameof(convertToProviderExpression)), + Check.NotNull(convertFromProviderExpression, nameof(convertFromProviderExpression))), + valueComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given conversion expressions. + /// + /// The store type generated by the conversions. + /// An expression to convert objects when writing data to the store. + /// An expression to convert objects when reading data from the store. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => HasConversion( + new ValueConverter( + Check.NotNull(convertToProviderExpression, nameof(convertToProviderExpression)), + Check.NotNull(convertFromProviderExpression, nameof(convertFromProviderExpression))), + valueComparer, + providerComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The store type generated by the converter. + /// The converter to use. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer) + => HasConversion((ValueConverter?)converter, valueComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The store type generated by the converter. + /// The converter to use. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual ComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => HasConversion((ValueConverter?)converter, valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(converter, valueComparer); + + /// + /// Configures the property so that the property value is converted to and from the database + /// using the given . + /// + /// The converter to use. + /// The comparer to use for values before conversion. + /// The comparer to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparer) + => (ComplexTypePropertyBuilder)base.HasConversion(converter, valueComparer, providerComparer); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer>() + where TComparer : ValueComparer + => (ComplexTypePropertyBuilder)base.HasConversion(); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TConversion, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TComparer, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TProviderComparer>() + where TComparer : ValueComparer + where TProviderComparer : ValueComparer + => (ComplexTypePropertyBuilder)base.HasConversion(); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType) + => (ComplexTypePropertyBuilder)base.HasConversion(conversionType, comparerType); + + /// + /// Configures the property so that the property value is converted before + /// writing to the database and converted back when reading from the database. + /// + /// The type to convert to and from or a type that inherits from . + /// A type that inherits from . + /// A type that inherits from to use for the provider values. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual ComplexTypePropertyBuilder HasConversion( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type conversionType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? providerComparerType) + => (ComplexTypePropertyBuilder)base.HasConversion(conversionType, comparerType, providerComparerType); +} diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs index 4937b1b67ee..65299bcc607 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder.cs @@ -31,7 +31,14 @@ public EntityTypeBuilder(IMutableEntityType entityType) Builder = ((EntityType)entityType).Builder; } - private InternalEntityTypeBuilder Builder { [DebuggerStepThrough] get; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual InternalEntityTypeBuilder Builder { [DebuggerStepThrough] get; } /// /// Gets the internal builder being used to configure the entity type. @@ -205,6 +212,139 @@ public virtual PropertyBuilder IndexerProperty( Check.NotEmpty(propertyName, nameof(propertyName)), ConfigurationSource.Explicit)!.Metadata); } + /// + /// Returns an object that can be used to configure a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the entity type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) + => new( + Builder.ComplexProperty( + propertyType: null, + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Returns an object that can be used to configure a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the entity class + /// then it will be added to the model. If no property exists in the entity class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the entity class. The current value for the property is stored in + /// the rather than being stored in instances of the entity class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(string propertyName) + => new( + Builder.ComplexProperty( + typeof(TProperty), + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the entity class + /// then it will be added to the model. If no property exists in the entity class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the entity class. The current value for the property is stored in + /// the rather than being stored in instances of the entity class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertyBuilder ComplexProperty(Type propertyType, string propertyName) + => new( + Builder.ComplexProperty( + Check.NotNull(propertyType, nameof(propertyType)), + Check.NotEmpty(propertyName, nameof(propertyName)), + collection: false, + ConfigurationSource.Explicit)!.Metadata); + + /// + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual EntityTypeBuilder ComplexProperty(string propertyName, Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyName)); + + return this; + } + + /// + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual EntityTypeBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyName)); + + return this; + } + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual EntityTypeBuilder ComplexProperty(Type propertyType, string propertyName, Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyType, propertyName)); + + return this; + } + /// /// Returns an object that can be used to configure an existing navigation property of the entity type. /// It is an error for the navigation property not to exist. diff --git a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs index 1f2c5ae82d2..5e4061ab41a 100644 --- a/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs +++ b/src/EFCore/Metadata/Builders/EntityTypeBuilder`.cs @@ -152,6 +152,97 @@ public virtual PropertyBuilder Property(Expression + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property with this overload the property name must match the + /// name of a CLR property or field on the complex type. This overload cannot be used to + /// add a new shadow state complex property. + /// + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual EntityTypeBuilder ComplexProperty(string propertyName, Action buildAction) + => (EntityTypeBuilder)base.ComplexProperty(propertyName, buildAction); + + /// + /// Configures a complex property of the entity type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual EntityTypeBuilder ComplexProperty( + string propertyName, Action> buildAction) + => (EntityTypeBuilder)base.ComplexProperty(propertyName, buildAction); + + /// + /// Returns an object that can be used to configure a complex property of the complex type. + /// If no property with the given name exists, then a new property will be added. + /// + /// + /// When adding a new complex property, if a property with the same name exists in the complex class + /// then it will be added to the model. If no property exists in the complex class, then + /// a new shadow state complex property will be added. A shadow state property is one that does not have a + /// corresponding property in the complex class. The current value for the property is stored in + /// the rather than being stored in instances of the complex class. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual EntityTypeBuilder ComplexProperty( + Type propertyType, string propertyName, Action buildAction) + => (EntityTypeBuilder)base.ComplexProperty(propertyType, propertyName, buildAction); + + /// + /// Returns an object that can be used to configure a complex property of the entity type. + /// If the specified property is not already part of the model, it will be added. + /// + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// An object that can be used to configure the complex property. + public virtual ComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression) + => new( + Builder.ComplexProperty( + Check.NotNull(propertyExpression, nameof(propertyExpression)).GetMemberAccess(), + collection: false, + ConfigurationSource.Explicit)! + .Metadata); + + /// + /// Configures a complex property of the entity type. + /// If the specified property is not already part of the model, it will be added. + /// + /// + /// A lambda expression representing the property to be configured ( + /// blog => blog.Url). + /// + /// An action that performs configuration of the property. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual EntityTypeBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(ComplexProperty(propertyExpression)); + + return this; + } + /// /// Returns an object that can be used to configure an existing navigation property of the entity type. /// It is an error for the navigation property not to exist. @@ -1397,7 +1488,4 @@ public virtual DiscriminatorBuilder HasDiscriminatorThe same builder instance so that multiple configuration calls can be chained. public new virtual EntityTypeBuilder HasNoDiscriminator() => (EntityTypeBuilder)base.HasNoDiscriminator(); - - private InternalEntityTypeBuilder Builder - => (InternalEntityTypeBuilder)this.GetInfrastructure(); } diff --git a/src/EFCore/Metadata/Builders/IConventionComplexPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionComplexPropertyBuilder.cs new file mode 100644 index 00000000000..f389efee52c --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionComplexPropertyBuilder.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// +/// Provides a simple API surface for configuring an from conventions. +/// +/// +/// This interface is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionComplexPropertyBuilder : IConventionPropertyBaseBuilder +{ + /// + /// Gets the property being configured. + /// + new IConventionComplexProperty Metadata { get; } + + /// + /// Configures whether this property must have a value assigned or is a valid value. + /// A property can only be configured as non-required if it is based on a CLR type that can be + /// assigned . + /// + /// + /// A value indicating whether the property is required. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the requiredness was configured, + /// otherwise. + /// + IConventionComplexPropertyBuilder? IsRequired(bool? required, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether this property requiredness can be configured + /// from the current configuration source. + /// + /// + /// A value indicating whether the property is required. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the property requiredness can be configured. + bool CanSetIsRequired(bool? required, bool fromDataAnnotation = false); +} diff --git a/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs new file mode 100644 index 00000000000..f2558141ad4 --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs @@ -0,0 +1,137 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// +/// Provides a simple API surface for configuring an from conventions. +/// +/// +/// This interface is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionComplexTypeBuilder : IConventionTypeBaseBuilder +{ + /// + /// Gets the property being configured. + /// + new IConventionComplexType Metadata { get; } + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionComplexTypeBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionComplexTypeBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionComplexTypeBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + + /// + /// Removes properties in the given list if they are not part of any metadata object. + /// + /// The properties to remove. + new IConventionComplexTypeBuilder RemoveUnusedImplicitProperties(IReadOnlyList properties); + + /// + /// Removes a property from this complex type. + /// + /// The property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the property was removed, + /// otherwise. + /// + new IConventionComplexTypeBuilder? HasNoProperty(IConventionProperty property, bool fromDataAnnotation = false); + + /// + /// Removes a complex property from this complex type. + /// + /// The complex property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the complex property was removed, + /// otherwise. + /// + new IConventionComplexTypeBuilder? HasNoComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation = false); + + /// + /// Excludes the given property from the complex type and prevents conventions from adding a matching property + /// or navigation to the type. + /// + /// The name of the member to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance so that additional configuration calls can be chained + /// if the given member was ignored, otherwise. + /// + new IConventionComplexTypeBuilder? Ignore(string memberName, bool fromDataAnnotation = false); + + /// + /// Configures the to be used for this complex type. + /// This strategy indicates how the context detects changes to properties for an instance of the complex type. + /// + /// + /// The change tracking strategy to be used. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the was set, + /// otherwise. + /// + new IConventionComplexTypeBuilder? HasChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + bool fromDataAnnotation = false); + + /// + /// Sets the to use for all properties of this complex type. + /// + /// + /// The to use for properties of this complex type. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder instance so that multiple configuration calls can be chained. + /// + /// The same builder instance if the was set, + /// otherwise. + /// + new IConventionComplexTypeBuilder? UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + bool fromDataAnnotation = false); +} diff --git a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs index da8fa4ea64a..97310be44a7 100644 --- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionEntityTypeBuilder : IConventionAnnotatableBuilder +public interface IConventionEntityTypeBuilder : IConventionTypeBaseBuilder { /// /// Gets the entity type being configured. @@ -23,141 +23,92 @@ public interface IConventionEntityTypeBuilder : IConventionAnnotatableBuilder new IConventionEntityType Metadata { get; } /// - /// Sets the base type of this entity type in an inheritance hierarchy. + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . /// - /// The base entity type or to indicate no base type. + /// The name of the annotation to be set. + /// The value to be stored in the annotation. /// Indicates whether the configuration was specified using a data annotation. /// - /// The same builder instance if the base type was configured, - /// otherwise. + /// An to continue configuration if the annotation was set, otherwise. /// - IConventionEntityTypeBuilder? HasBaseType( - IConventionEntityType? baseEntityType, - bool fromDataAnnotation = false); + new IConventionEntityTypeBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); /// - /// Returns a value indicating whether the given type can be set as the base type of this entity type. + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. /// - /// The base entity type or to indicate no base type. + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. /// Indicates whether the configuration was specified using a data annotation. - /// if the given type can be set as the base type of this entity type. - bool CanSetBaseType(IConventionEntityType? baseEntityType, bool fromDataAnnotation = false); + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionEntityTypeBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); /// - /// Returns an object that can be used to configure the property with the given name. - /// If no matching property exists, then a new property will be added. + /// Removes the annotation with the given name from this object. /// - /// The type of value the property will hold. - /// The name of the property to be configured. - /// Indicates whether the type configuration source should be set. + /// The name of the annotation to remove. /// Indicates whether the configuration was specified using a data annotation. /// - /// An object that can be used to configure the property if it exists on the entity type, - /// otherwise. + /// An to continue configuration if the annotation was set, otherwise. /// - IConventionPropertyBuilder? Property( - Type propertyType, - string propertyName, - bool setTypeConfigurationSource = true, - bool fromDataAnnotation = false); + new IConventionEntityTypeBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); /// - /// Returns an object that can be used to configure the property with the given member info. - /// If no matching property exists, then a new property will be added. + /// Sets the base type of this entity type in an inheritance hierarchy. /// - /// The or of the property. + /// The base entity type or to indicate no base type. /// Indicates whether the configuration was specified using a data annotation. /// - /// An object that can be used to configure the property if it exists on the entity type, + /// The same builder instance if the base type was configured, /// otherwise. /// - IConventionPropertyBuilder? Property(MemberInfo memberInfo, bool fromDataAnnotation = false); + IConventionEntityTypeBuilder? HasBaseType( + IConventionEntityType? baseEntityType, + bool fromDataAnnotation = false); /// - /// Returns a value indicating whether the given property can be added to this entity type. + /// Returns a value indicating whether the given type can be set as the base type of this entity type. /// - /// The type of value the property will hold. - /// The name of the property to be configured. + /// The base entity type or to indicate no base type. /// Indicates whether the configuration was specified using a data annotation. - /// if the property can be added. - bool CanHaveProperty( - Type? propertyType, - string propertyName, - bool fromDataAnnotation = false); + /// if the given type can be set as the base type of this entity type. + bool CanSetBaseType(IConventionEntityType? baseEntityType, bool fromDataAnnotation = false); /// - /// Returns a value indicating whether the given property can be added to this entity type. + /// Removes properties in the given list if they are not part of any metadata object. /// - /// The or of the property. - /// Indicates whether the configuration was specified using a data annotation. - /// if the property can be added. - bool CanHaveProperty(MemberInfo memberInfo, bool fromDataAnnotation = false); + /// The properties to remove. + new IConventionEntityTypeBuilder RemoveUnusedImplicitProperties(IReadOnlyList properties); /// - /// Returns an object that can be used to configure the indexer property with the given name. - /// If no matching property exists, then a new property will be added. + /// Removes a property from this entity type. /// - /// The type of value the property will hold. - /// The name of the property to be configured. + /// The property to be removed. /// Indicates whether the configuration was specified using a data annotation. /// - /// An object that can be used to configure the property if it exists on the entity type, + /// The same builder instance if the property was removed, /// otherwise. /// - IConventionPropertyBuilder? IndexerProperty( - Type propertyType, - string propertyName, - bool fromDataAnnotation = false); + new IConventionEntityTypeBuilder? HasNoProperty(IConventionProperty property, bool fromDataAnnotation = false); /// - /// Returns a value indicating whether the given indexer property can be added to this entity type. + /// Removes a complex property from this entity type. /// - /// The type of value the property will hold. - /// The name of the property to be configured. + /// The complex property to be removed. /// Indicates whether the configuration was specified using a data annotation. - /// if the property can be added. - bool CanHaveIndexerProperty( - Type propertyType, - string propertyName, - bool fromDataAnnotation = false); - - /// - /// Creates a property with a name that's different from any existing properties. - /// - /// The desired property name. - /// The type of value the property will hold. - /// A value indicating whether the property is required. /// - /// An object that can be used to configure the property if it exists on the entity type, + /// The same builder instance if the complex property was removed, /// otherwise. /// - IConventionPropertyBuilder? CreateUniqueProperty(Type propertyType, string basePropertyName, bool required); - - /// - /// Returns the existing properties with the given names or creates them if matching CLR members are found. - /// - /// The names of the properties. - /// Indicates whether the configuration was specified using a data annotation. - /// A list of properties if they exist on the entity type, otherwise. - IReadOnlyList? GetOrCreateProperties( - IReadOnlyList? propertyNames, - bool fromDataAnnotation = false); - - /// - /// Returns the existing properties matching the given members or creates them. - /// - /// The type members. - /// Indicates whether the configuration was specified using a data annotation. - /// A list of properties if they exist on the entity type, otherwise. - IReadOnlyList? GetOrCreateProperties( - IEnumerable? memberInfos, - bool fromDataAnnotation = false); - - /// - /// Removes properties in the given list if they are not part of any metadata object. - /// - /// The properties to remove. - IConventionEntityTypeBuilder RemoveUnusedImplicitProperties(IReadOnlyList properties); + new IConventionEntityTypeBuilder? HasNoComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation = false); /// /// Returns an object that can be used to configure the service property with the given member info. @@ -198,41 +149,35 @@ bool CanHaveIndexerProperty( bool CanHaveServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation = false); /// - /// Indicates whether the given member name is ignored for the given configuration source. + /// Removes a service property from this entity type. /// - /// The name of the member that might be ignored. + /// The service property to be removed. /// Indicates whether the configuration was specified using a data annotation. /// - /// if the entity type contains a member with the given name, - /// the given member name hasn't been ignored or it was ignored using a lower configuration source; - /// otherwise. + /// The same builder instance if the service property was removed, + /// otherwise. /// - bool IsIgnored(string memberName, bool fromDataAnnotation = false); + IConventionEntityTypeBuilder? HasNoServiceProperty(IConventionServiceProperty serviceProperty, bool fromDataAnnotation = false); /// - /// Excludes the given property from the entity type and prevents conventions from adding a matching property - /// or navigation to the type. + /// Returns a value indicating whether the service property can be removed from this entity type. /// - /// The name of the member to be removed. + /// The service property to be removed. /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same instance so that additional configuration calls can be chained - /// if the given member was ignored, otherwise. - /// - IConventionEntityTypeBuilder? Ignore(string memberName, bool fromDataAnnotation = false); + /// if the service property can be removed from this entity type. + bool CanRemoveServiceProperty(IConventionServiceProperty serviceProperty, bool fromDataAnnotation = false); /// - /// Returns a value indicating whether the given member name can be ignored from the given configuration source. + /// Excludes the given property from the entity type and prevents conventions from adding a matching property + /// or navigation to the type. /// - /// The member name to be removed from the entity type. + /// The name of the member to be removed. /// Indicates whether the configuration was specified using a data annotation. - /// if the given member name can be ignored. /// - /// if the entity type contains a member with the given name - /// that was configured using a higher configuration source; - /// otherwise. + /// The same builder instance so that additional configuration calls can be chained + /// if the given member was ignored, otherwise. /// - bool CanIgnore(string memberName, bool fromDataAnnotation = false); + new IConventionEntityTypeBuilder? Ignore(string memberName, bool fromDataAnnotation = false); /// /// Sets the properties that make up the primary key for this entity type. @@ -743,25 +688,6 @@ bool CanHaveIndexerProperty( /// if the foreign key can be removed from this entity type. bool CanRemoveRelationship(IConventionForeignKey foreignKey, bool fromDataAnnotation = false); - /// - /// Removes a skip navigation from this entity type. - /// - /// The skip navigation to be removed. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the skip navigation was removed, - /// otherwise. - /// - IConventionEntityTypeBuilder? HasNoSkipNavigation(IConventionSkipNavigation skipNavigation, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the skip navigation can be removed from this entity type. - /// - /// The skip navigation to be removed. - /// Indicates whether the configuration was specified using a data annotation. - /// if the skip navigation can be removed from this entity type. - bool CanRemoveSkipNavigation(IConventionSkipNavigation skipNavigation, bool fromDataAnnotation = false); - /// /// Returns a value indicating whether the given navigation can be added to this entity type. /// @@ -780,6 +706,25 @@ bool CanHaveIndexerProperty( bool CanHaveNavigation(MemberInfo navigation, bool fromDataAnnotation = false) => CanHaveNavigation(navigation.Name, navigation.GetMemberType(), fromDataAnnotation); + /// + /// Removes a navigation from this entity type. + /// + /// The navigation to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the navigation was removed, + /// otherwise. + /// + IConventionEntityTypeBuilder? HasNoNavigation(IConventionNavigation navigation, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the navigation can be removed from this entity type. + /// + /// The navigation to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the navigation can be removed from this entity type. + bool CanRemoveNavigation(IConventionNavigation navigation, bool fromDataAnnotation = false); + /// /// Returns a value indicating whether the given skip navigation can be added to this entity type. /// @@ -850,6 +795,7 @@ bool CanHaveSkipNavigation(MemberInfo navigation, bool fromDataAnnotation = fals /// /// The navigation property name. /// The entity type that the navigation targets. + /// The navigation type. /// Whether the navigation property is a collection property. /// /// Whether the navigation property is defined on the dependent side of the underlying foreign key. @@ -862,10 +808,30 @@ bool CanHaveSkipNavigation(MemberInfo navigation, bool fromDataAnnotation = fals IConventionSkipNavigationBuilder? HasSkipNavigation( string navigationName, IConventionEntityType targetEntityType, + Type? navigationType = null, bool? collection = null, bool? onDependent = null, bool fromDataAnnotation = false); + /// + /// Removes a skip navigation from this entity type. + /// + /// The skip navigation to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the skip navigation was removed, + /// otherwise. + /// + IConventionEntityTypeBuilder? HasNoSkipNavigation(IConventionSkipNavigation skipNavigation, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the skip navigation can be removed from this entity type. + /// + /// The skip navigation to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the skip navigation can be removed from this entity type. + bool CanRemoveSkipNavigation(IConventionSkipNavigation skipNavigation, bool fromDataAnnotation = false); + /// /// Configures a database trigger when targeting a relational database. /// @@ -945,21 +911,10 @@ bool CanHaveTrigger( /// The same builder instance if the was set, /// otherwise. /// - IConventionEntityTypeBuilder? HasChangeTrackingStrategy( + new IConventionEntityTypeBuilder? HasChangeTrackingStrategy( ChangeTrackingStrategy? changeTrackingStrategy, bool fromDataAnnotation = false); - /// - /// Returns a value indicating whether the given change tracking strategy can be set from the current configuration source. - /// - /// - /// The change tracking strategy to be used. - /// to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// if the given change tracking strategy can be set. - bool CanSetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy, bool fromDataAnnotation = false); - /// /// Sets the to use for all properties of this entity type. /// @@ -973,21 +928,10 @@ bool CanHaveTrigger( /// The same builder instance if the was set, /// otherwise. /// - IConventionEntityTypeBuilder? UsePropertyAccessMode( + new IConventionEntityTypeBuilder? UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); - /// - /// Returns a value indicating whether the given can be set from the current configuration source. - /// - /// - /// The to use for properties of this model. - /// to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// if the given can be set. - bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); - /// /// Configures the discriminator property used to identify which entity type each row in a table represents /// when an inheritance hierarchy is mapped to a single table in a relational database. diff --git a/src/EFCore/Metadata/Builders/IConventionForeignKeyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionForeignKeyBuilder.cs index 0e5778ba2ed..b3c2a2747b8 100644 --- a/src/EFCore/Metadata/Builders/IConventionForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionForeignKeyBuilder.cs @@ -22,6 +22,45 @@ public interface IConventionForeignKeyBuilder : IConventionAnnotatableBuilder /// new IConventionForeignKey Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionForeignKeyBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionForeignKeyBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionForeignKeyBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Configures which entity types participate in this relationship. /// By calling this method the principal and dependent types can be switched or the relationship could diff --git a/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs b/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs index 93d7dafa6f8..142d6d69774 100644 --- a/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionIndexBuilder.cs @@ -22,6 +22,45 @@ public interface IConventionIndexBuilder : IConventionAnnotatableBuilder /// new IConventionIndex Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionIndexBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionIndexBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionIndexBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Configures whether this index is unique (i.e. each set of values must be unique). /// diff --git a/src/EFCore/Metadata/Builders/IConventionKeyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionKeyBuilder.cs index eef66af6481..104d3997d90 100644 --- a/src/EFCore/Metadata/Builders/IConventionKeyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionKeyBuilder.cs @@ -21,4 +21,43 @@ public interface IConventionKeyBuilder : IConventionAnnotatableBuilder /// Gets the key being configured. /// new IConventionKey Metadata { get; } + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionKeyBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionKeyBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionKeyBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs b/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs index a8fcea58cb0..e0076e164fb 100644 --- a/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionModelBuilder.cs @@ -24,6 +24,45 @@ public interface IConventionModelBuilder : IConventionAnnotatableBuilder /// new IConventionModel Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionModelBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionModelBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionModelBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Returns an object that can be used to configure a given entity type in the model. /// If an entity type with the provided name is not already part of the model, @@ -182,6 +221,38 @@ public interface IConventionModelBuilder : IConventionAnnotatableBuilder /// IConventionModelBuilder? Ignore(string typeName, bool fromDataAnnotation = false); + /// + /// Returns a value indicating whether the given entity type can be added to the model. + /// + /// The name of the entity type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the entity type can be added. + bool CanHaveEntity( + string name, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given entity type can be added to the model. + /// + /// The type of the entity type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the entity type can be added. + bool CanHaveEntity( + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given entity type can be added to the model. + /// + /// The name of the entity type. + /// The type of the entity type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the entity type can be added. + bool CanHaveSharedTypeEntity( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type? type, + bool fromDataAnnotation = false); + /// /// Removes the given entity type from the model. /// @@ -193,7 +264,15 @@ public interface IConventionModelBuilder : IConventionAnnotatableBuilder IConventionModelBuilder? HasNoEntityType(IConventionEntityType entityType, bool fromDataAnnotation = false); /// - /// Returns a value indicating whether the given entity type can be ignored from the current configuration source + /// Returns a value indicating whether the entity type can be removed from the model. + /// + /// The entity type to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the navigation can be removed from this entity type. + bool CanRemoveEntity(IConventionEntityType entityType, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given entity type can be ignored from the current configuration source. /// /// The entity type to be removed from the model. /// Indicates whether the configuration was specified using a data annotation. diff --git a/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs b/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs index cdcb4fe0fad..82c073afebc 100644 --- a/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionNavigationBuilder.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder +public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder { /// /// Gets the navigation being configured. @@ -23,37 +23,15 @@ public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder new IConventionNavigation Metadata { get; } /// - /// Sets the backing field to use for this navigation. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionNavigationBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); - - /// - /// Sets the backing field to use for this navigation. - /// - /// The field. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionNavigationBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); - - /// - /// Sets the to use for this navigation. + /// Configures this navigation to be automatically included in a query. /// - /// The to use for this navigation. + /// A value indicating whether the navigation should be automatically included. /// Indicates whether the configuration was specified using a data annotation. /// /// The same builder instance if the configuration was applied, /// otherwise. /// - new IConventionNavigationBuilder? UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + IConventionNavigationBuilder? AutoInclude(bool? autoInclude, bool fromDataAnnotation = false); /// /// Returns a value indicating whether this navigation can be configured to be automatically included in a query @@ -65,15 +43,15 @@ public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder bool CanSetAutoInclude(bool? autoInclude, bool fromDataAnnotation = false); /// - /// Configures this navigation to be automatically included in a query. + /// Configures this navigation to be enabled for lazy-loading. /// - /// A value indicating whether the navigation should be automatically included. + /// A value indicating whether the navigation should be enabled for lazy-loading. /// Indicates whether the configuration was specified using a data annotation. /// /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionNavigationBuilder? AutoInclude(bool? autoInclude, bool fromDataAnnotation = false); + IConventionNavigationBuilder? EnableLazyLoading(bool? lazyLoadingEnabled, bool fromDataAnnotation = false); /// /// Returns a value indicating whether this navigation can be configured to enable lazy-loading @@ -85,15 +63,17 @@ public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder bool CanSetLazyLoadingEnabled(bool? lazyLoadingEnabled, bool fromDataAnnotation = false); /// - /// Configures this navigation to be enabled for lazy-loading. + /// Configures whether this navigation is required. /// - /// A value indicating whether the navigation should be enabled for lazy-loading. + /// + /// A value indicating whether this is a required navigation. + /// to reset to default. + /// /// Indicates whether the configuration was specified using a data annotation. /// - /// The same builder instance if the configuration was applied, - /// otherwise. + /// The same builder instance if the requiredness was configured, otherwise. /// - IConventionNavigationBuilder? EnableLazyLoading(bool? lazyLoadingEnabled, bool fromDataAnnotation = false); + IConventionNavigationBuilder? IsRequired(bool? required, bool fromDataAnnotation = false); /// /// Returns a value indicating whether this navigation requiredness can be configured @@ -103,17 +83,4 @@ public interface IConventionNavigationBuilder : IConventionPropertyBaseBuilder /// Indicates whether the configuration was specified using a data annotation. /// if requiredness can be set for this navigation. bool CanSetIsRequired(bool? required, bool fromDataAnnotation = false); - - /// - /// Configures whether this navigation is required. - /// - /// - /// A value indicating whether this is a required navigation. - /// to reset to default. - /// - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the requiredness was configured, otherwise. - /// - IConventionNavigationBuilder? IsRequired(bool? required, bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs index ebff66d4ea3..2bdd732ab94 100644 --- a/src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionPropertyBaseBuilder.cs @@ -15,13 +15,53 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionPropertyBaseBuilder : IConventionAnnotatableBuilder +public interface IConventionPropertyBaseBuilder : IConventionAnnotatableBuilder + where TBuilder : IConventionPropertyBaseBuilder { /// /// Gets the property-like object being configured. /// new IConventionPropertyBase Metadata { get; } + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder to continue configuration if the annotation was set, otherwise. + /// + new TBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new TBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder to continue configuration if the annotation was set, otherwise. + /// + new TBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the backing field to use for this property-like object. /// @@ -31,7 +71,7 @@ public interface IConventionPropertyBaseBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionPropertyBaseBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); + TBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); /// /// Sets the backing field to use for this property-like object. @@ -42,7 +82,7 @@ public interface IConventionPropertyBaseBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionPropertyBaseBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); + TBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); /// /// Returns a value indicating whether the backing field can be set for this property-like object @@ -71,7 +111,7 @@ public interface IConventionPropertyBaseBuilder : IConventionAnnotatableBuilder /// The same builder instance if the configuration was applied, /// otherwise. /// - IConventionPropertyBaseBuilder? UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + TBuilder? UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); /// /// Returns a value indicating whether the can be set for this property-like object diff --git a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs index 8868fe52f08..7254760366e 100644 --- a/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionPropertyBuilder.cs @@ -17,7 +17,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionPropertyBuilder : IConventionPropertyBaseBuilder +public interface IConventionPropertyBuilder : IConventionPropertyBaseBuilder { /// /// Gets the property being configured. @@ -107,37 +107,22 @@ public interface IConventionPropertyBuilder : IConventionPropertyBaseBuilder bool CanSetIsConcurrencyToken(bool? concurrencyToken, bool fromDataAnnotation = false); /// - /// Sets the backing field to use for this property. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionPropertyBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); - - /// - /// Sets the backing field to use for this property. + /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the + /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of + /// the property. /// - /// The field. + /// The sentinel value. /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionPropertyBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); + /// The same builder instance if the configuration was applied, otherwise. + IConventionPropertyBuilder? HasSentinel(object? sentinel, bool fromDataAnnotation = false); /// - /// Sets the to use for this property. + /// Returns a value indicating whether the sentinel can be set for this property from the current configuration source. /// - /// The to use for this property. + /// The sentinel value. /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionPropertyBuilder? UsePropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); + /// if the sentinel can be set for this property. + bool CanSetSentinel(object? sentinel, bool fromDataAnnotation = false); /// /// Configures the maximum length of data that can be stored in this property. @@ -161,24 +146,6 @@ public interface IConventionPropertyBuilder : IConventionPropertyBaseBuilder /// if the maximum length of data allowed can be set for this property. bool CanSetMaxLength(int? maxLength, bool fromDataAnnotation = false); - /// - /// Configures the value that will be used to determine if the property has been set or not. If the property is set to the - /// sentinel value, then it is considered not set. By default, the sentinel value is the CLR default value for the type of - /// the property. - /// - /// The sentinel value. - /// Indicates whether the configuration was specified using a data annotation. - /// The same builder instance if the configuration was applied, otherwise. - IConventionPropertyBuilder? HasSentinel(object? sentinel, bool fromDataAnnotation = false); - - /// - /// Returns a value indicating whether the sentinel can be set for this property from the current configuration source. - /// - /// The sentinel value. - /// Indicates whether the configuration was specified using a data annotation. - /// if the sentinel can be set for this property. - bool CanSetSentinel(object? sentinel, bool fromDataAnnotation = false); - /// /// Configures whether the property as capable of persisting unicode characters. /// @@ -318,7 +285,7 @@ public interface IConventionPropertyBuilder : IConventionPropertyBaseBuilder /// otherwise. /// IConventionPropertyBuilder? HasValueGenerator( - Func? factory, + Func? factory, bool fromDataAnnotation = false); /// @@ -349,7 +316,7 @@ public interface IConventionPropertyBuilder : IConventionPropertyBaseBuilder /// if the can be configured for this property. /// bool CanSetValueGenerator( - Func? factory, + Func? factory, bool fromDataAnnotation = false); /// diff --git a/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs b/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs index 957949626d1..2fbf8a4284d 100644 --- a/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionServicePropertyBuilder.cs @@ -15,48 +15,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionServicePropertyBuilder : IConventionPropertyBaseBuilder +public interface IConventionServicePropertyBuilder : IConventionPropertyBaseBuilder { /// /// Gets the service property being configured. /// new IConventionServiceProperty Metadata { get; } - /// - /// Sets the backing field to use for this property. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionServicePropertyBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); - - /// - /// Sets the backing field to use for this property. - /// - /// The field. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionServicePropertyBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); - - /// - /// Sets the to use for this property. - /// - /// The to use for this property. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionServicePropertyBuilder? UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation = false); - /// /// Sets the for this property. /// diff --git a/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs b/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs index b1a1f8d818b..1c189bc815f 100644 --- a/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionSkipNavigationBuilder.cs @@ -15,48 +15,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Model building conventions for more information and examples. /// -public interface IConventionSkipNavigationBuilder : IConventionPropertyBaseBuilder +public interface IConventionSkipNavigationBuilder : IConventionPropertyBaseBuilder { /// /// Gets the navigation property being configured. /// new IConventionSkipNavigation Metadata { get; } - /// - /// Sets the backing field to use for this navigation. - /// - /// The field name. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionSkipNavigationBuilder? HasField(string? fieldName, bool fromDataAnnotation = false); - - /// - /// Sets the backing field to use for this navigation. - /// - /// The field. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionSkipNavigationBuilder? HasField(FieldInfo? fieldInfo, bool fromDataAnnotation = false); - - /// - /// Sets the to use for this navigation. - /// - /// The to use for this navigation. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - new IConventionSkipNavigationBuilder? UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation = false); - /// /// Sets the foreign key. /// diff --git a/src/EFCore/Metadata/Builders/IConventionTriggerBuilder.cs b/src/EFCore/Metadata/Builders/IConventionTriggerBuilder.cs index 49d79bf56f6..aeea9cbfe1d 100644 --- a/src/EFCore/Metadata/Builders/IConventionTriggerBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionTriggerBuilder.cs @@ -15,4 +15,43 @@ public interface IConventionTriggerBuilder : IConventionAnnotatableBuilder /// The trigger being configured. /// new IConventionTrigger Metadata { get; } + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionTriggerBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionTriggerBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionTriggerBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); } diff --git a/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs b/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs new file mode 100644 index 00000000000..26ce5a70949 --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs @@ -0,0 +1,402 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// +/// Provides a simple API surface for configuring an from conventions. +/// +/// +/// This interface is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IConventionTypeBaseBuilder : IConventionAnnotatableBuilder +{ + /// + /// Gets the type-like object being configured. + /// + new IConventionTypeBase Metadata { get; } + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionTypeBaseBuilder? HasAnnotation(string name, object? value, bool fromDataAnnotation = false); + + /// + /// Sets the annotation stored under the given name. Overwrites the existing annotation if an + /// annotation with the specified name already exists with same or lower . + /// Removes the annotation if value is specified. + /// + /// The name of the annotation to be set. + /// The value to be stored in the annotation. to remove the annotations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set or removed, + /// otherwise. + /// + new IConventionTypeBaseBuilder? HasNonNullAnnotation( + string name, + object? value, + bool fromDataAnnotation = false); + + /// + /// Removes the annotation with the given name from this object. + /// + /// The name of the annotation to remove. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An to continue configuration if the annotation was set, otherwise. + /// + new IConventionTypeBaseBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the property with the given name. + /// If no matching property exists, then a new property will be added. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// Indicates whether the type configuration source should be set. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the type, + /// otherwise. + /// + IConventionPropertyBuilder? Property( + Type propertyType, + string propertyName, + bool setTypeConfigurationSource = true, + bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the property with the given member info. + /// If no matching property exists, then a new property will be added. + /// + /// The or of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the type, + /// otherwise. + /// + IConventionPropertyBuilder? Property(MemberInfo memberInfo, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given property can be added to this type. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveProperty( + Type? propertyType, + string propertyName, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given property can be added to this type. + /// + /// The or of the property. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveProperty(MemberInfo memberInfo, bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the indexer property with the given name. + /// If no matching property exists, then a new property will be added. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the type, + /// otherwise. + /// + IConventionPropertyBuilder? IndexerProperty( + Type propertyType, + string propertyName, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given indexer property can be added to this type. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveIndexerProperty( + Type propertyType, + string propertyName, + bool fromDataAnnotation = false); + + /// + /// Creates a property with a name that's different from any existing properties. + /// + /// The desired property name. + /// The type of value the property will hold. + /// A value indicating whether the property is required. + /// + /// An object that can be used to configure the property if it exists on the type, + /// otherwise. + /// + IConventionPropertyBuilder? CreateUniqueProperty(Type propertyType, string basePropertyName, bool required); + + /// + /// Returns the existing properties with the given names or creates them if matching CLR members are found. + /// + /// The names of the properties. + /// Indicates whether the configuration was specified using a data annotation. + /// A list of properties if they exist on the type, otherwise. + IReadOnlyList? GetOrCreateProperties( + IReadOnlyList? propertyNames, + bool fromDataAnnotation = false); + + /// + /// Returns the existing properties matching the given members or creates them. + /// + /// The type members. + /// Indicates whether the configuration was specified using a data annotation. + /// A list of properties if they exist on the type, otherwise. + IReadOnlyList? GetOrCreateProperties( + IEnumerable? memberInfos, + bool fromDataAnnotation = false); + + /// + /// Removes properties in the given list if they are not part of any metadata object. + /// + /// The properties to remove. + IConventionTypeBaseBuilder RemoveUnusedImplicitProperties(IReadOnlyList properties); + + /// + /// Removes a property from this type. + /// + /// The property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the property was removed, + /// otherwise. + /// + IConventionTypeBaseBuilder? HasNoProperty(IConventionProperty property, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the property can be removed from this type. + /// + /// The property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be removed from this type. + bool CanRemoveProperty(IConventionProperty property, bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the complex property with the given name. + /// If no matching property exists, then a new property will be added. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the type, + /// otherwise. + /// + IConventionComplexPropertyBuilder? ComplexProperty( + Type propertyType, + string propertyName, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the complex property with the given member info. + /// If no matching property exists, then a new property will be added. + /// + /// The or of the property. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the type, + /// otherwise. + /// + IConventionComplexPropertyBuilder? ComplexProperty( + MemberInfo memberInfo, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given complex property can be added to this type. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveComplexProperty( + Type? propertyType, + string propertyName, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given complex property can be added to this type. + /// + /// The or of the property. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveComplexProperty( + MemberInfo memberInfo, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns an object that can be used to configure the complex indexer property with the given name. + /// If no matching property exists, then a new property will be added. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// An object that can be used to configure the property if it exists on the type, + /// otherwise. + /// + IConventionComplexPropertyBuilder? ComplexIndexerProperty( + Type propertyType, + string propertyName, + Type? complexType, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given complex indexer property can be added to this type. + /// + /// The type of value the property will hold. + /// The name of the property to be configured. + /// The target complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be added. + bool CanHaveComplexIndexerProperty( + Type propertyType, + string propertyName, + Type? complexType, + bool fromDataAnnotation = false); + + /// + /// Removes a complex property from this type. + /// + /// The complex property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the complex property was removed, + /// otherwise. + /// + IConventionTypeBaseBuilder? HasNoComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the complex property can be removed from this type. + /// + /// The complex property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the complex property can be removed from this type. + bool CanRemoveComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation = false); + + /// + /// Indicates whether the given member name is ignored for the given configuration source. + /// + /// The name of the member that might be ignored. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// if the complex type contains a member with the given name, + /// the given member name hasn't been ignored or it was ignored using a lower configuration source; + /// otherwise. + /// + bool IsIgnored(string memberName, bool fromDataAnnotation = false); + + /// + /// Excludes the given property from the complex type and prevents conventions from adding a matching property + /// or navigation to the type. + /// + /// The name of the member to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance so that additional configuration calls can be chained + /// if the given member was ignored, otherwise. + /// + IConventionTypeBaseBuilder? Ignore(string memberName, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given member name can be ignored from the given configuration source. + /// + /// The member name to be removed from the complex type. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given member name can be ignored. + /// + /// if the complex type contains a member with the given name + /// that was configured using a higher configuration source; + /// otherwise. + /// + bool CanIgnore(string memberName, bool fromDataAnnotation = false); + + /// + /// Configures the to be used for this type. + /// This strategy indicates how the context detects changes to properties for an instance of the type. + /// + /// + /// The change tracking strategy to be used. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the was set, + /// otherwise. + /// + IConventionTypeBaseBuilder? HasChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given change tracking strategy can be set from the current configuration source. + /// + /// + /// The change tracking strategy to be used. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the given change tracking strategy can be set. + bool CanSetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy, bool fromDataAnnotation = false); + + /// + /// Sets the to use for all properties of this type. + /// + /// + /// The to use for properties of this type. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder instance so that multiple configuration calls can be chained. + /// + /// The same builder instance if the was set, + /// otherwise. + /// + IConventionTypeBaseBuilder? UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given can be set from the current configuration source. + /// + /// + /// The to use for properties of this model. + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the given can be set. + bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false); +} diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder.cs b/src/EFCore/Metadata/Builders/PropertyBuilder.cs index 4f9155f3c9d..1e8604eda01 100644 --- a/src/EFCore/Metadata/Builders/PropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/PropertyBuilder.cs @@ -250,7 +250,7 @@ public virtual PropertyBuilder HasValueGenerator( /// /// A delegate that will be used to create value generator instances. /// The same builder instance so that multiple configuration calls can be chained. - public virtual PropertyBuilder HasValueGenerator(Func factory) + public virtual PropertyBuilder HasValueGenerator(Func factory) { Check.NotNull(factory, nameof(factory)); @@ -439,7 +439,7 @@ public virtual PropertyBuilder HasField(string fieldName) /// /// By default, the backing field, if one is found by convention or has been specified, is used when /// new objects are constructed, typically when entities are queried from the database. - /// Properties are used for all other accesses. Calling this method will change that behavior + /// Properties are used for all other accesses. Calling this method will change that behavior /// for this property as described in the enum. /// /// diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder`.cs b/src/EFCore/Metadata/Builders/PropertyBuilder`.cs index a4264488fc0..19d1e0fbf6a 100644 --- a/src/EFCore/Metadata/Builders/PropertyBuilder`.cs +++ b/src/EFCore/Metadata/Builders/PropertyBuilder`.cs @@ -188,7 +188,7 @@ public PropertyBuilder(IMutableProperty property) /// /// A delegate that will be used to create value generator instances. /// The same builder instance so that multiple configuration calls can be chained. - public new virtual PropertyBuilder HasValueGenerator(Func factory) + public new virtual PropertyBuilder HasValueGenerator(Func factory) => (PropertyBuilder)base.HasValueGenerator(factory); /// @@ -338,7 +338,7 @@ public PropertyBuilder(IMutableProperty property) /// /// By default, the backing field, if one is found by convention or has been specified, is used when /// new objects are constructed, typically when entities are queried from the database. - /// Properties are used for all other accesses. Calling this method will change that behavior + /// Properties are used for all other accesses. Calling this method will change that behavior /// for this property as described in the enum. /// /// diff --git a/src/EFCore/Metadata/Conventions/BackingFieldAttributeConvention.cs b/src/EFCore/Metadata/Conventions/BackingFieldAttributeConvention.cs index 3a8f4f3adb3..05116e12357 100644 --- a/src/EFCore/Metadata/Conventions/BackingFieldAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/BackingFieldAttributeConvention.cs @@ -10,7 +10,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class BackingFieldAttributeConvention : PropertyAttributeConventionBase +public class BackingFieldAttributeConvention : + PropertyAttributeConventionBase, + IComplexPropertyAddedConvention, + IComplexPropertyFieldChangedConvention { /// /// Creates a new instance of . @@ -21,17 +24,19 @@ public BackingFieldAttributeConvention(ProviderConventionSetBuilderDependencies { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, BackingFieldAttribute attribute, MemberInfo clrMember, IConventionContext context) => propertyBuilder.HasField(attribute.Name, fromDataAnnotation: true); + + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + BackingFieldAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + => propertyBuilder.HasField(attribute.Name, fromDataAnnotation: true); } diff --git a/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs b/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs index 3042da396a8..f3f3e47e602 100644 --- a/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs +++ b/src/EFCore/Metadata/Conventions/BackingFieldConvention.cs @@ -27,6 +27,7 @@ public class BackingFieldConvention : IPropertyAddedConvention, INavigationAddedConvention, ISkipNavigationAddedConvention, + IComplexPropertyAddedConvention, IModelFinalizingConvention { /// @@ -43,11 +44,7 @@ public BackingFieldConvention(ProviderConventionSetBuilderDependencies dependenc /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// - /// Called after a property is added to the entity type. - /// - /// The builder for the property. - /// Additional information associated with convention execution. + /// public virtual void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) @@ -65,6 +62,12 @@ public virtual void ProcessSkipNavigationAdded( IConventionContext context) => DiscoverField(skipNavigationBuilder); + /// + public virtual void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + => DiscoverField(propertyBuilder); + /// public virtual void ProcessModelFinalizing( IConventionModelBuilder modelBuilder, @@ -88,7 +91,8 @@ public virtual void ProcessModelFinalizing( } } - private static void DiscoverField(IConventionPropertyBaseBuilder conventionPropertyBaseBuilder) + private static void DiscoverField(IConventionPropertyBaseBuilder conventionPropertyBaseBuilder) + where TBuilder : IConventionPropertyBaseBuilder { if (ConfigurationSource.Convention.Overrides(conventionPropertyBaseBuilder.Metadata.GetFieldInfoConfigurationSource())) { @@ -110,12 +114,12 @@ private static void DiscoverField(IConventionPropertyBaseBuilder conventionPrope return null; } - var entityType = (IConventionEntityType)propertyBase.DeclaringType; - var type = entityType.ClrType; - var baseTypes = entityType.GetAllBaseTypes().ToArray(); + var typeBase = propertyBase.DeclaringType; + var type = typeBase.ClrType; + var baseTypes = (typeBase as IConventionEntityType)?.GetAllBaseTypes().ToArray(); while (type != null) { - var fieldInfo = TryMatchFieldName(propertyBase, entityType, type); + var fieldInfo = TryMatchFieldName(propertyBase, typeBase, type); if (fieldInfo != null && (propertyBase.PropertyInfo != null || propertyBase.Name == fieldInfo.GetSimpleMemberName())) { @@ -123,7 +127,7 @@ private static void DiscoverField(IConventionPropertyBaseBuilder conventionPrope } type = type.BaseType; - entityType = baseTypes.FirstOrDefault(et => et.ClrType == type); + typeBase = baseTypes?.FirstOrDefault(et => et.ClrType == type); } return null; @@ -131,7 +135,7 @@ private static void DiscoverField(IConventionPropertyBaseBuilder conventionPrope private static FieldInfo? TryMatchFieldName( IConventionPropertyBase propertyBase, - IConventionEntityType? entityType, + IConventionTypeBase? entityType, Type entityClrType) { var propertyName = propertyBase.Name; diff --git a/src/EFCore/Metadata/Conventions/ConcurrencyCheckAttributeConvention.cs b/src/EFCore/Metadata/Conventions/ConcurrencyCheckAttributeConvention.cs index a8e7e0bd148..20d27e72e70 100644 --- a/src/EFCore/Metadata/Conventions/ConcurrencyCheckAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/ConcurrencyCheckAttributeConvention.cs @@ -22,13 +22,7 @@ public ConcurrencyCheckAttributeConvention(ProviderConventionSetBuilderDependenc { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, ConcurrencyCheckAttribute attribute, diff --git a/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs b/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs index cc7d88810b2..2b053ce46ce 100644 --- a/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs +++ b/src/EFCore/Metadata/Conventions/ConstructorBindingConvention.cs @@ -49,6 +49,28 @@ public virtual void ProcessModelFinalizing( entityType.Builder.HasConstructorBinding(constructorBinding, ConfigurationSource.Convention); entityType.Builder.HasServiceOnlyConstructorBinding(serviceOnlyBinding, ConfigurationSource.Convention); } + + foreach (var complexProperty in entityType.GetDeclaredComplexProperties()) + { + Process(complexProperty.ComplexType); + } + } + } + + private void Process(ComplexType complexType) + { + if (!complexType.ClrType.IsAbstract + && ConfigurationSource.Convention.Overrides(complexType.GetConstructorBindingConfigurationSource())) + { + Dependencies.ConstructorBindingFactory.GetBindings( + complexType, out var constructorBinding, out var serviceOnlyBinding); + complexType.Builder.HasConstructorBinding(constructorBinding, ConfigurationSource.Convention); + complexType.Builder.HasServiceOnlyConstructorBinding(serviceOnlyBinding, ConfigurationSource.Convention); + } + + foreach (var complexProperty in complexType.GetDeclaredComplexProperties()) + { + Process(complexProperty.ComplexType); } } } diff --git a/src/EFCore/Metadata/Conventions/ConventionSet.cs b/src/EFCore/Metadata/Conventions/ConventionSet.cs index 3c4c0c5e96c..05716c93bfc 100644 --- a/src/EFCore/Metadata/Conventions/ConventionSet.cs +++ b/src/EFCore/Metadata/Conventions/ConventionSet.cs @@ -32,14 +32,14 @@ public class ConventionSet public virtual List ModelAnnotationChangedConventions { get; } = new(); /// - /// Conventions to run when an entity type is added to the model. + /// Conventions to run when a type is ignored. /// - public virtual List EntityTypeAddedConventions { get; } = new(); + public virtual List TypeIgnoredConventions { get; } = new(); /// - /// Conventions to run when an entity type is ignored. + /// Conventions to run when an entity type is added to the model. /// - public virtual List EntityTypeIgnoredConventions { get; } = new(); + public virtual List EntityTypeAddedConventions { get; } = new(); /// /// Conventions to run when an entity type is removed. @@ -66,6 +66,41 @@ public class ConventionSet /// public virtual List EntityTypeAnnotationChangedConventions { get; } = new(); + /// + /// Conventions to run when a property is ignored. + /// + public virtual List ComplexTypeMemberIgnoredConventions { get; } = new(); + + /// + /// Conventions to run when an annotation is set or removed on a complex type. + /// + public virtual List ComplexTypeAnnotationChangedConventions { get; } = new(); + + /// + /// Conventions to run when an entity type is added to the model. + /// + public virtual List ComplexPropertyAddedConventions { get; } = new(); + + /// + /// Conventions to run when an entity type is removed. + /// + public virtual List ComplexPropertyRemovedConventions { get; } = new(); + + /// + /// Conventions to run when the nullability of a property is changed. + /// + public virtual List ComplexPropertyNullabilityChangedConventions { get; } = new(); + + /// + /// Conventions to run when the field of a property is changed. + /// + public virtual List ComplexPropertyFieldChangedConventions { get; } = new(); + + /// + /// Conventions to run when an annotation is set or removed on a complex property. + /// + public virtual List ComplexPropertyAnnotationChangedConventions { get; } = new(); + /// /// Conventions to run when a foreign key is added. /// @@ -266,16 +301,16 @@ public virtual void Replace(TImplementation newConvention) ModelAnnotationChangedConventions.Add(modelAnnotationChangedConvention); } - if (newConvention is IEntityTypeAddedConvention entityTypeAddedConvention - && !Replace(EntityTypeAddedConventions, entityTypeAddedConvention, oldConvetionType)) + if (newConvention is ITypeIgnoredConvention typeIgnoredConvention + && !Replace(TypeIgnoredConventions, typeIgnoredConvention, oldConvetionType)) { - EntityTypeAddedConventions.Add(entityTypeAddedConvention); + TypeIgnoredConventions.Add(typeIgnoredConvention); } - if (newConvention is IEntityTypeIgnoredConvention entityTypeIgnoredConvention - && !Replace(EntityTypeIgnoredConventions, entityTypeIgnoredConvention, oldConvetionType)) + if (newConvention is IEntityTypeAddedConvention entityTypeAddedConvention + && !Replace(EntityTypeAddedConventions, entityTypeAddedConvention, oldConvetionType)) { - EntityTypeIgnoredConventions.Add(entityTypeIgnoredConvention); + EntityTypeAddedConventions.Add(entityTypeAddedConvention); } if (newConvention is IEntityTypeRemovedConvention entityTypeRemovedConvention @@ -308,6 +343,42 @@ public virtual void Replace(TImplementation newConvention) EntityTypeAnnotationChangedConventions.Add(entityTypeAnnotationChangedConvention); } + if (newConvention is IComplexPropertyAddedConvention complexPropertyAddedConvention + && !Replace(ComplexPropertyAddedConventions, complexPropertyAddedConvention, oldConvetionType)) + { + ComplexPropertyAddedConventions.Add(complexPropertyAddedConvention); + } + + if (newConvention is IComplexPropertyRemovedConvention complexPropertyRemovedConvention + && !Replace(ComplexPropertyRemovedConventions, complexPropertyRemovedConvention, oldConvetionType)) + { + ComplexPropertyRemovedConventions.Add(complexPropertyRemovedConvention); + } + + if (newConvention is IComplexTypeMemberIgnoredConvention complexPropertyMemberIgnoredConvention + && !Replace(ComplexTypeMemberIgnoredConventions, complexPropertyMemberIgnoredConvention, oldConvetionType)) + { + ComplexTypeMemberIgnoredConventions.Add(complexPropertyMemberIgnoredConvention); + } + + if (newConvention is IComplexPropertyNullabilityChangedConvention complexPropertyNullabilityChangedConvention + && !Replace(ComplexPropertyNullabilityChangedConventions, complexPropertyNullabilityChangedConvention, oldConvetionType)) + { + ComplexPropertyNullabilityChangedConventions.Add(complexPropertyNullabilityChangedConvention); + } + + if (newConvention is IComplexPropertyFieldChangedConvention complexPropertyFieldChangedConvention + && !Replace(ComplexPropertyFieldChangedConventions, complexPropertyFieldChangedConvention, oldConvetionType)) + { + ComplexPropertyFieldChangedConventions.Add(complexPropertyFieldChangedConvention); + } + + if (newConvention is IComplexPropertyAnnotationChangedConvention complexPropertyAnnotationChangedConvention + && !Replace(ComplexPropertyAnnotationChangedConventions, complexPropertyAnnotationChangedConvention, oldConvetionType)) + { + ComplexPropertyAnnotationChangedConventions.Add(complexPropertyAnnotationChangedConvention); + } + if (newConvention is IForeignKeyAddedConvention foreignKeyAddedConvention && !Replace(ForeignKeyAddedConventions, foreignKeyAddedConvention, oldConvetionType)) { @@ -429,6 +500,18 @@ public virtual void Replace(TImplementation newConvention) KeyRemovedConventions.Add(keyRemovedConvention); } + if (newConvention is ITriggerAddedConvention triggerAddedConvention + && !Replace(TriggerAddedConventions, triggerAddedConvention, oldConvetionType)) + { + TriggerAddedConventions.Add(triggerAddedConvention); + } + + if (newConvention is ITriggerRemovedConvention triggerRemovedConvention + && !Replace(TriggerRemovedConventions, triggerRemovedConvention, oldConvetionType)) + { + TriggerRemovedConventions.Add(triggerRemovedConvention); + } + if (newConvention is IKeyAnnotationChangedConvention keyAnnotationChangedConvention && !Replace(KeyAnnotationChangedConventions, keyAnnotationChangedConvention, oldConvetionType)) { @@ -562,14 +645,14 @@ public virtual void Add(IConvention convention) ModelAnnotationChangedConventions.Add(modelAnnotationChangedConvention); } - if (convention is IEntityTypeAddedConvention entityTypeAddedConvention) + if (convention is ITypeIgnoredConvention typeIgnoredConvention) { - EntityTypeAddedConventions.Add(entityTypeAddedConvention); + TypeIgnoredConventions.Add(typeIgnoredConvention); } - if (convention is IEntityTypeIgnoredConvention entityTypeIgnoredConvention) + if (convention is IEntityTypeAddedConvention entityTypeAddedConvention) { - EntityTypeIgnoredConventions.Add(entityTypeIgnoredConvention); + EntityTypeAddedConventions.Add(entityTypeAddedConvention); } if (convention is IEntityTypeRemovedConvention entityTypeRemovedConvention) @@ -597,6 +680,36 @@ public virtual void Add(IConvention convention) EntityTypeAnnotationChangedConventions.Add(entityTypeAnnotationChangedConvention); } + if (convention is IComplexPropertyAddedConvention complexPropertyAddedConvention) + { + ComplexPropertyAddedConventions.Add(complexPropertyAddedConvention); + } + + if (convention is IComplexPropertyRemovedConvention complexPropertyRemovedConvention) + { + ComplexPropertyRemovedConventions.Add(complexPropertyRemovedConvention); + } + + if (convention is IComplexTypeMemberIgnoredConvention complexPropertyMemberIgnoredConvention) + { + ComplexTypeMemberIgnoredConventions.Add(complexPropertyMemberIgnoredConvention); + } + + if (convention is IComplexPropertyNullabilityChangedConvention complexPropertyNullabilityChangedConvention) + { + ComplexPropertyNullabilityChangedConventions.Add(complexPropertyNullabilityChangedConvention); + } + + if (convention is IComplexPropertyFieldChangedConvention complexPropertyFieldChangedConvention) + { + ComplexPropertyFieldChangedConventions.Add(complexPropertyFieldChangedConvention); + } + + if (convention is IComplexPropertyAnnotationChangedConvention complexPropertyAnnotationChangedConvention) + { + ComplexPropertyAnnotationChangedConventions.Add(complexPropertyAnnotationChangedConvention); + } + if (convention is IForeignKeyAddedConvention foreignKeyAddedConvention) { ForeignKeyAddedConventions.Add(foreignKeyAddedConvention); @@ -845,14 +958,14 @@ public virtual void Remove(Type conventionType) Remove(ModelAnnotationChangedConventions, conventionType); } - if (typeof(IEntityTypeAddedConvention).IsAssignableFrom(conventionType)) + if (typeof(ITypeIgnoredConvention).IsAssignableFrom(conventionType)) { - Remove(EntityTypeAddedConventions, conventionType); + Remove(TypeIgnoredConventions, conventionType); } - if (typeof(IEntityTypeIgnoredConvention).IsAssignableFrom(conventionType)) + if (typeof(IEntityTypeAddedConvention).IsAssignableFrom(conventionType)) { - Remove(EntityTypeIgnoredConventions, conventionType); + Remove(EntityTypeAddedConventions, conventionType); } if (typeof(IEntityTypeRemovedConvention).IsAssignableFrom(conventionType)) @@ -880,6 +993,36 @@ public virtual void Remove(Type conventionType) Remove(EntityTypeAnnotationChangedConventions, conventionType); } + if (typeof(IComplexPropertyAddedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexPropertyAddedConventions, conventionType); + } + + if (typeof(IComplexPropertyRemovedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexPropertyRemovedConventions, conventionType); + } + + if (typeof(IComplexTypeMemberIgnoredConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexTypeMemberIgnoredConventions, conventionType); + } + + if (typeof(IComplexPropertyNullabilityChangedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexPropertyNullabilityChangedConventions, conventionType); + } + + if (typeof(IComplexPropertyFieldChangedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexPropertyFieldChangedConventions, conventionType); + } + + if (typeof(IComplexPropertyAnnotationChangedConvention).IsAssignableFrom(conventionType)) + { + Remove(ComplexPropertyAnnotationChangedConventions, conventionType); + } + if (typeof(IForeignKeyAddedConvention).IsAssignableFrom(conventionType)) { Remove(ForeignKeyAddedConventions, conventionType); diff --git a/src/EFCore/Metadata/Conventions/DatabaseGeneratedAttributeConvention.cs b/src/EFCore/Metadata/Conventions/DatabaseGeneratedAttributeConvention.cs index e0925d1d76b..568b6e526a7 100644 --- a/src/EFCore/Metadata/Conventions/DatabaseGeneratedAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/DatabaseGeneratedAttributeConvention.cs @@ -25,13 +25,7 @@ public DatabaseGeneratedAttributeConvention(ProviderConventionSetBuilderDependen { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, DatabaseGeneratedAttribute attribute, diff --git a/src/EFCore/Metadata/Conventions/DeleteBehaviorAttributeConvention.cs b/src/EFCore/Metadata/Conventions/DeleteBehaviorAttributeConvention.cs index b8605be4a5b..1069a26810e 100644 --- a/src/EFCore/Metadata/Conventions/DeleteBehaviorAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/DeleteBehaviorAttributeConvention.cs @@ -12,6 +12,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; public class DeleteBehaviorAttributeConvention : PropertyAttributeConventionBase, INavigationAddedConvention, IForeignKeyPrincipalEndChangedConvention, + IComplexPropertyAddedConvention, IModelFinalizingConvention { /// @@ -23,11 +24,7 @@ public DeleteBehaviorAttributeConvention(ProviderConventionSetBuilderDependencie { } - /// - /// Called after a navigation is added to the entity type. - /// - /// The builder for the navigation. - /// Additional information associated with convention execution. + /// public virtual void ProcessNavigationAdded( IConventionNavigationBuilder navigationBuilder, IConventionContext context) @@ -47,11 +44,7 @@ public virtual void ProcessNavigationAdded( foreignKey.Builder.OnDelete(navAttribute.Behavior, fromDataAnnotation: true); } - /// - /// Called after the principal end of a foreign key is changed. - /// - /// The builder for the foreign key. - /// Additional information associated with convention execution. + /// public virtual void ProcessForeignKeyPrincipalEndChanged( IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) @@ -71,42 +64,49 @@ public virtual void ProcessForeignKeyPrincipalEndChanged( relationshipBuilder.OnDelete(navAttribute.Behavior, fromDataAnnotation: true); } - /// - /// Called when a model is being finalized. - /// - /// The builder for the model. - /// Additional information associated with convention execution. + /// public virtual void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext context) { foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) { - foreach (var navigation in entityType.GetNavigations()) + foreach (var navigation in entityType.GetDeclaredNavigations()) { - var navAttribute = navigation.PropertyInfo?.GetCustomAttribute(); - if (navAttribute == null) + if (navigation.IsOnDependent) { return; } - if (!navigation.IsOnDependent) + var navAttribute = navigation.PropertyInfo?.GetCustomAttribute(); + if (navAttribute != null) { - throw new InvalidOperationException(CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty); + throw new InvalidOperationException(CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty( + navigation.DeclaringEntityType.DisplayName(), navigation.Name)); } } } } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, DeleteBehaviorAttribute attribute, MemberInfo clrMember, IConventionContext context) - => throw new InvalidOperationException(CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty); + { + var property = propertyBuilder.Metadata; + throw new InvalidOperationException(CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty( + property.DeclaringType.DisplayName(), property.Name)); + } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + DeleteBehaviorAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + throw new InvalidOperationException(CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty( + property.DeclaringType.DisplayName(), property.Name)); + } } diff --git a/src/EFCore/Metadata/Conventions/EntityTypeAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/EntityTypeAttributeConventionBase.cs deleted file mode 100644 index 3de9280bfaa..00000000000 --- a/src/EFCore/Metadata/Conventions/EntityTypeAttributeConventionBase.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; - -namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; - -/// -/// A base type for conventions that perform configuration based on an attribute specified on an entity type. -/// -/// -/// See Model building conventions for more information and examples. -/// -/// The attribute type to look for. -public abstract class EntityTypeAttributeConventionBase : IEntityTypeAddedConvention - where TAttribute : Attribute -{ - /// - /// Creates a new instance of . - /// - /// Parameter object containing dependencies for this convention. - protected EntityTypeAttributeConventionBase(ProviderConventionSetBuilderDependencies dependencies) - { - Dependencies = dependencies; - } - - /// - /// Dependencies for this service. - /// - protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - - /// - /// Called after an entity type is added to the model. - /// - /// The builder for the entity type. - /// Additional information associated with convention execution. - public virtual void ProcessEntityTypeAdded( - IConventionEntityTypeBuilder entityTypeBuilder, - IConventionContext context) - { - Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); - - var type = entityTypeBuilder.Metadata.ClrType; - if (!Attribute.IsDefined(type, typeof(TAttribute), inherit: true)) - { - return; - } - - var attributes = type.GetCustomAttributes(true); - - foreach (var attribute in attributes) - { - ProcessEntityTypeAdded(entityTypeBuilder, attribute, context); - if (((IReadableConventionContext)context).ShouldStopProcessing()) - { - return; - } - } - } - - /// - /// Called after an entity type is added to the model if it has an attribute. - /// - /// The builder for the entity type. - /// The attribute. - /// Additional information associated with convention execution. - protected abstract void ProcessEntityTypeAdded( - IConventionEntityTypeBuilder entityTypeBuilder, - TAttribute attribute, - IConventionContext context); -} diff --git a/src/EFCore/Metadata/Conventions/EntityTypeConfigurationEntityTypeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/EntityTypeConfigurationAttributeConvention.cs similarity index 72% rename from src/EFCore/Metadata/Conventions/EntityTypeConfigurationEntityTypeAttributeConvention.cs rename to src/EFCore/Metadata/Conventions/EntityTypeConfigurationAttributeConvention.cs index 37a8334f093..2fc6432627c 100644 --- a/src/EFCore/Metadata/Conventions/EntityTypeConfigurationEntityTypeAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/EntityTypeConfigurationAttributeConvention.cs @@ -9,26 +9,22 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class EntityTypeConfigurationEntityTypeAttributeConvention : EntityTypeAttributeConventionBase +public class EntityTypeConfigurationAttributeConvention : TypeAttributeConventionBase, + IComplexPropertyAddedConvention { private static readonly MethodInfo ConfigureMethod - = typeof(EntityTypeConfigurationEntityTypeAttributeConvention).GetTypeInfo().GetDeclaredMethod(nameof(Configure))!; + = typeof(EntityTypeConfigurationAttributeConvention).GetTypeInfo().GetDeclaredMethod(nameof(Configure))!; /// - /// Creates a new instance of . + /// Creates a new instance of . /// /// Parameter object containing dependencies for this convention. - public EntityTypeConfigurationEntityTypeAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) + public EntityTypeConfigurationAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) : base(dependencies) { } - /// - /// Called after an entity type is added to the model if it has an attribute. - /// - /// The builder for the entity type. - /// The attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, EntityTypeConfigurationAttribute attribute, @@ -37,8 +33,7 @@ protected override void ProcessEntityTypeAdded( var entityTypeConfigurationType = attribute.EntityTypeConfigurationType; if (!entityTypeConfigurationType.GetInterfaces().Any( - x => - x.IsGenericType + x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>) && x.GenericTypeArguments[0] == entityTypeBuilder.Metadata.ClrType)) { @@ -51,6 +46,18 @@ protected override void ProcessEntityTypeAdded( .Invoke(null, new object[] { entityTypeBuilder.Metadata, entityTypeConfigurationType }); } + /// + protected override void ProcessComplexTypeAdded( + IConventionComplexTypeBuilder complexTypeBuilder, + EntityTypeConfigurationAttribute attribute, + IConventionContext context) + { + if (ReplaceWithEntityType(complexTypeBuilder) != null) + { + context.StopProcessing(); + } + } + private static void Configure(IConventionEntityType entityType, Type entityTypeConfigurationType) where TEntity : class { diff --git a/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs index 357d13266dd..d98ebbb31fd 100644 --- a/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/ForeignKeyAttributeConvention.cs @@ -24,6 +24,8 @@ public class ForeignKeyAttributeConvention : IForeignKeyAddedConvention, INavigationAddedConvention, ISkipNavigationForeignKeyChangedConvention, + IPropertyAddedConvention, + IComplexPropertyAddedConvention, IModelFinalizingConvention { /// @@ -71,7 +73,7 @@ public virtual void ProcessEntityTypeAdded( continue; } - if (GetAttribute(navigation) == null) + if (!Attribute.IsDefined(navigation, typeof(ForeignKeyAttribute), inherit: true)) { if (FindForeignKeyAttributeOnProperty(entityType, navigation) == null) { @@ -384,15 +386,7 @@ var fkPropertiesOnDependentToPrincipal private static TAttribute? GetAttribute(MemberInfo? memberInfo) where TAttribute : Attribute - { - if (memberInfo == null - || !Attribute.IsDefined(memberInfo, typeof(TAttribute), inherit: true)) - { - return null; - } - - return memberInfo.GetCustomAttribute(inherit: true); - } + => memberInfo == null ? null : memberInfo.GetCustomAttribute(inherit: true); [ContractAnnotation("navigation:null => null")] private MemberInfo? FindForeignKeyAttributeOnProperty(IConventionEntityType entityType, MemberInfo? navigation) @@ -535,6 +529,37 @@ public virtual void ProcessSkipNavigationForeignKeyChanged( return properties; } + /// + public virtual void ProcessPropertyAdded( + IConventionPropertyBuilder propertyBuilder, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true) + && property.DeclaringType is IConventionComplexType) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "ForeignKey", property.DeclaringType.DisplayName(), property.Name)); + } + } + + /// + public virtual void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true)) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "ForeignKey", property.DeclaringType.DisplayName(), property.Name)); + } + } + /// public virtual void ProcessModelFinalizing( IConventionModelBuilder modelBuilder, diff --git a/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs index 3b183980950..5ed94cc39b1 100644 --- a/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/ForeignKeyPropertyDiscoveryConvention.cs @@ -145,7 +145,7 @@ private IConventionForeignKeyBuilder ProcessForeignKey( var newType = fkProperty.ClrType.MakeNullable(!foreignKey.IsRequired); if (fkProperty.ClrType != newType) { - fkProperty.DeclaringEntityType.Builder.Property( + fkProperty.DeclaringType.Builder.Property( newType, fkProperty.Name, fkProperty.GetConfigurationSource() == ConfigurationSource.DataAnnotation); @@ -570,14 +570,13 @@ public virtual void ProcessPropertyAdded( private void Process(IConventionPropertyBuilder propertyBuilder, IConventionContext context) { var property = propertyBuilder.Metadata; - if (property.IsImplicitlyCreated() - && ConfigurationSource.Convention.Overrides(property.GetConfigurationSource())) + if ((property.IsImplicitlyCreated() + && ConfigurationSource.Convention.Overrides(property.GetConfigurationSource())) + || propertyBuilder.Metadata.DeclaringType is not IConventionEntityType entityType) { return; } - var entityType = propertyBuilder.Metadata.DeclaringEntityType; - Process(entityType, context); } diff --git a/src/EFCore/Metadata/Conventions/IComplexPropertyAddedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexPropertyAddedConvention.cs new file mode 100644 index 00000000000..6a9cb1d53be --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexPropertyAddedConvention.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when a complex property is added to an type-like object. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexPropertyAddedConvention : IConvention +{ + /// + /// Called after a complex property is added to a type-like object. + /// + /// The builder for the complex property. + /// Additional information associated with convention execution. + void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexPropertyAnnotationChangedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexPropertyAnnotationChangedConvention.cs new file mode 100644 index 00000000000..3aa1469d52b --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexPropertyAnnotationChangedConvention.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when an annotation is changed on a complex property. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexPropertyAnnotationChangedConvention : IConvention +{ + /// + /// Called after an annotation is changed on a complex property. + /// + /// The builder for the complex property. + /// The annotation name. + /// The new annotation. + /// The old annotation. + /// Additional information associated with convention execution. + void ProcessComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexPropertyFieldChangedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexPropertyFieldChangedConvention.cs new file mode 100644 index 00000000000..6ee35b101e6 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexPropertyFieldChangedConvention.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when the backing field for a complex property is changed. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexPropertyFieldChangedConvention : IConvention +{ + /// + /// Called after the backing field for a complex property is changed. + /// + /// The builder for the property. + /// The new field. + /// The old field. + /// Additional information associated with convention execution. + void ProcessComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexPropertyNullabilityChangedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexPropertyNullabilityChangedConvention.cs new file mode 100644 index 00000000000..72e20ef3ab6 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexPropertyNullabilityChangedConvention.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when the nullability for a complex property is changed. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexPropertyNullabilityChangedConvention : IConvention +{ + /// + /// Called after the nullability for a complex property is changed. + /// + /// The builder for the property. + /// Additional information associated with convention execution. + void ProcessComplexPropertyNullabilityChanged( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexPropertyRemovedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexPropertyRemovedConvention.cs new file mode 100644 index 00000000000..28d603f8c0f --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexPropertyRemovedConvention.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when a complex property is removed from a type-like object. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexPropertyRemovedConvention : IConvention +{ + /// + /// Called after a complex property is removed from a type-like object. + /// + /// The builder for the type-like object. + /// The removed complex property. + /// Additional information associated with convention execution. + void ProcessComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexTypeAnnotationChangedConvention.cs b/src/EFCore/Metadata/Conventions/IComplexTypeAnnotationChangedConvention.cs new file mode 100644 index 00000000000..9d1b68e107b --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexTypeAnnotationChangedConvention.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when an annotation is changed on a complex type. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexTypeAnnotationChangedConvention : IConvention +{ + /// + /// Called after an annotation is changed on a complex type. + /// + /// The builder for the complex type. + /// The annotation name. + /// The new annotation. + /// The old annotation. + /// Additional information associated with convention execution. + void ProcessComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder complexTypeBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IComplexTypeMemberIgnoredConvention.cs b/src/EFCore/Metadata/Conventions/IComplexTypeMemberIgnoredConvention.cs new file mode 100644 index 00000000000..928a09fdac1 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/IComplexTypeMemberIgnoredConvention.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when a complex type member is ignored. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface IComplexTypeMemberIgnoredConvention : IConvention +{ + /// + /// Called after a complex type member is ignored. + /// + /// The builder for the complex type. + /// The name of the ignored member. + /// Additional information associated with convention execution. + void ProcessComplexTypeMemberIgnored( + IConventionComplexTypeBuilder complexTypeBuilder, + string name, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/IEntityTypeIgnoredConvention.cs b/src/EFCore/Metadata/Conventions/IEntityTypeIgnoredConvention.cs index f973a5680a0..fb11c4139c0 100644 --- a/src/EFCore/Metadata/Conventions/IEntityTypeIgnoredConvention.cs +++ b/src/EFCore/Metadata/Conventions/IEntityTypeIgnoredConvention.cs @@ -9,6 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// +[Obsolete("Use ITypeIgnoredConvention instead")] public interface IEntityTypeIgnoredConvention : IConvention { /// diff --git a/src/EFCore/Metadata/Conventions/IPropertyRemovedConvention.cs b/src/EFCore/Metadata/Conventions/IPropertyRemovedConvention.cs index b699f532450..b79aad15aa4 100644 --- a/src/EFCore/Metadata/Conventions/IPropertyRemovedConvention.cs +++ b/src/EFCore/Metadata/Conventions/IPropertyRemovedConvention.cs @@ -14,11 +14,11 @@ public interface IPropertyRemovedConvention : IConvention /// /// Called after a property is removed from the entity type. /// - /// The builder for the entity type that contained the property. + /// The builder for the entity type that contained the property. /// The removed property. /// Additional information associated with convention execution. void ProcessPropertyRemoved( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property, IConventionContext context); } diff --git a/src/EFCore/Metadata/Conventions/ITypeIgnoredConvention.cs b/src/EFCore/Metadata/Conventions/ITypeIgnoredConvention.cs new file mode 100644 index 00000000000..86a7b8a5804 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/ITypeIgnoredConvention.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// Represents an operation that should be performed when a type is ignored. +/// +/// +/// See Model building conventions for more information and examples. +/// +public interface ITypeIgnoredConvention : IConvention +{ + /// + /// Called after an entity type is ignored. + /// + /// The builder for the model. + /// The name of the ignored type. + /// The ignored type. + /// Additional information associated with convention execution. + void ProcessTypeIgnored( + IConventionModelBuilder modelBuilder, + string name, + Type? type, + IConventionContext context); +} diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index dc89fc504d8..7f383cd0067 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -55,10 +55,10 @@ public virtual ConventionSet CreateConventionSet() var conventionSet = new ConventionSet(); conventionSet.Add(new ModelCleanupConvention(Dependencies)); - conventionSet.Add(new NotMappedEntityTypeAttributeConvention(Dependencies)); - conventionSet.Add(new OwnedEntityTypeAttributeConvention(Dependencies)); - conventionSet.Add(new KeylessEntityTypeAttributeConvention(Dependencies)); - conventionSet.Add(new EntityTypeConfigurationEntityTypeAttributeConvention(Dependencies)); + conventionSet.Add(new NotMappedTypeAttributeConvention(Dependencies)); + conventionSet.Add(new OwnedAttributeConvention(Dependencies)); + conventionSet.Add(new KeylessAttributeConvention(Dependencies)); + conventionSet.Add(new EntityTypeConfigurationAttributeConvention(Dependencies)); conventionSet.Add(new NotMappedMemberAttributeConvention(Dependencies)); conventionSet.Add(new BackingFieldAttributeConvention(Dependencies)); conventionSet.Add(new ConcurrencyCheckAttributeConvention(Dependencies)); diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs index 853b40898cb..ca87a070c5a 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ConventionScope.cs @@ -47,6 +47,11 @@ public int GetLeafCount() return leafCount; } + public abstract string? OnTypeIgnored( + IConventionModelBuilder modelBuilder, + string name, + Type? type); + public abstract IConventionEntityTypeBuilder? OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder); public abstract IConventionAnnotation? OnEntityTypeAnnotationChanged( @@ -60,11 +65,6 @@ public int GetLeafCount() IConventionEntityType? newBaseType, IConventionEntityType? previousBaseType); - public abstract string? OnEntityTypeIgnored( - IConventionModelBuilder modelBuilder, - string name, - Type? type); - public abstract string? OnEntityTypeMemberIgnored( IConventionEntityTypeBuilder entityTypeBuilder, string name); @@ -78,6 +78,37 @@ public int GetLeafCount() IConventionModelBuilder modelBuilder, IConventionEntityType entityType); + public abstract string? OnComplexTypeMemberIgnored( + IConventionComplexTypeBuilder propertyBuilder, + string name); + + public abstract IConventionAnnotation? OnComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation); + + public abstract IConventionComplexPropertyBuilder? OnComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder); + + public abstract IConventionComplexProperty? OnComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property); + + public abstract FieldInfo? OnComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo); + + public abstract bool? OnComplexPropertyNullabilityChanged( + IConventionComplexPropertyBuilder propertyBuilder); + + public abstract IConventionAnnotation? OnComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation); + public abstract IConventionForeignKeyBuilder? OnForeignKeyAdded(IConventionForeignKeyBuilder relationshipBuilder); public abstract IConventionAnnotation? OnForeignKeyAnnotationChanged( @@ -185,7 +216,8 @@ public int GetLeafCount() IConventionEntityTypeBuilder entityTypeBuilder, IConventionSkipNavigation navigation); - public abstract IConventionPropertyBuilder? OnPropertyAdded(IConventionPropertyBuilder propertyBuilder); + public abstract IConventionPropertyBuilder? OnPropertyAdded( + IConventionPropertyBuilder propertyBuilder); public abstract IConventionAnnotation? OnPropertyAnnotationChanged( IConventionPropertyBuilder propertyBuilder, @@ -198,10 +230,11 @@ public int GetLeafCount() FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo); - public abstract bool? OnPropertyNullabilityChanged(IConventionPropertyBuilder propertyBuilder); + public abstract bool? OnPropertyNullabilityChanged( + IConventionPropertyBuilder propertyBuilder); public abstract IConventionProperty? OnPropertyRemoved( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property); public abstract IConventionTriggerBuilder? OnTriggerAdded(IConventionTriggerBuilder triggerBuilder); diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs index cadd461ed35..8057398c93c 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.DelayedConventionScope.cs @@ -43,18 +43,28 @@ public override void Run(ConventionDispatcher dispatcher) } } - public override IConventionEntityTypeBuilder OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) + public override IConventionAnnotation? OnModelAnnotationChanged( + IConventionModelBuilder modelBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) { - Add(new OnEntityTypeAddedNode(entityTypeBuilder)); - return entityTypeBuilder; + Add(new OnModelAnnotationChangedNode(modelBuilder, name, annotation, oldAnnotation)); + return annotation; } - public override string OnEntityTypeIgnored(IConventionModelBuilder modelBuilder, string name, Type? type) + public override string OnTypeIgnored(IConventionModelBuilder modelBuilder, string name, Type? type) { - Add(new OnEntityTypeIgnoredNode(modelBuilder, name, type)); + Add(new OnTypeIgnoredNode(modelBuilder, name, type)); return name; } + public override IConventionEntityTypeBuilder OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) + { + Add(new OnEntityTypeAddedNode(entityTypeBuilder)); + return entityTypeBuilder; + } + public override IConventionEntityType OnEntityTypeRemoved( IConventionModelBuilder modelBuilder, IConventionEntityType entityType) @@ -88,13 +98,58 @@ public override string OnEntityTypeMemberIgnored(IConventionEntityTypeBuilder en return annotation; } - public override IConventionAnnotation? OnModelAnnotationChanged( - IConventionModelBuilder modelBuilder, + public override string OnComplexTypeMemberIgnored(IConventionComplexTypeBuilder complexTypeBuilder, string name) + { + Add(new OnComplexTypeMemberIgnoredNode(complexTypeBuilder, name)); + return name; + } + + public override IConventionAnnotation? OnComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder complexTypeBuilder, string name, IConventionAnnotation? annotation, IConventionAnnotation? oldAnnotation) { - Add(new OnModelAnnotationChangedNode(modelBuilder, name, annotation, oldAnnotation)); + Add(new OnComplexTypeAnnotationChangedNode(complexTypeBuilder, name, annotation, oldAnnotation)); + return annotation; + } + + public override IConventionComplexPropertyBuilder OnComplexPropertyAdded(IConventionComplexPropertyBuilder propertyBuilder) + { + Add(new OnComplexPropertyAddedNode(propertyBuilder)); + return propertyBuilder; + } + + public override IConventionComplexProperty OnComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property) + { + Add(new OnComplexPropertyRemovedNode(typeBaseBuilder, property)); + return property; + } + + public override bool? OnComplexPropertyNullabilityChanged(IConventionComplexPropertyBuilder propertyBuilder) + { + Add(new OnComplexPropertyNullabilityChangedNode(propertyBuilder)); + return propertyBuilder.Metadata.IsNullable; + } + + public override FieldInfo? OnComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo) + { + Add(new OnComplexPropertyFieldChangedNode(propertyBuilder, newFieldInfo, oldFieldInfo)); + return newFieldInfo; + } + + public override IConventionAnnotation? OnComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + Add(new OnComplexPropertyAnnotationChangedNode(propertyBuilder, name, annotation, oldAnnotation)); return annotation; } @@ -336,7 +391,7 @@ public override IConventionPropertyBuilder OnPropertyAdded(IConventionPropertyBu public override bool? OnPropertyNullabilityChanged(IConventionPropertyBuilder propertyBuilder) { - Add(new OnPropertyNullableChangedNode(propertyBuilder)); + Add(new OnPropertyNullabilityChangedNode(propertyBuilder)); return propertyBuilder.Metadata.IsNullable; } @@ -360,10 +415,10 @@ public override IConventionPropertyBuilder OnPropertyAdded(IConventionPropertyBu } public override IConventionProperty OnPropertyRemoved( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property) { - Add(new OnPropertyRemovedNode(entityTypeBuilder, property)); + Add(new OnPropertyRemovedNode(typeBaseBuilder, property)); return property; } } @@ -392,34 +447,34 @@ public override void Run(ConventionDispatcher dispatcher) ModelBuilder, Name, Annotation, OldAnnotation); } - private sealed class OnEntityTypeAddedNode : ConventionNode + private sealed class OnTypeIgnoredNode : ConventionNode { - public OnEntityTypeAddedNode(IConventionEntityTypeBuilder entityTypeBuilder) + public OnTypeIgnoredNode(IConventionModelBuilder modelBuilder, string name, Type? type) { - EntityTypeBuilder = entityTypeBuilder; + ModelBuilder = modelBuilder; + Name = name; + Type = type; } - public IConventionEntityTypeBuilder EntityTypeBuilder { get; } + public IConventionModelBuilder ModelBuilder { get; } + public string Name { get; } + public Type? Type { get; } public override void Run(ConventionDispatcher dispatcher) - => dispatcher._immediateConventionScope.OnEntityTypeAdded(EntityTypeBuilder); + => dispatcher._immediateConventionScope.OnTypeIgnored(ModelBuilder, Name, Type); } - private sealed class OnEntityTypeIgnoredNode : ConventionNode + private sealed class OnEntityTypeAddedNode : ConventionNode { - public OnEntityTypeIgnoredNode(IConventionModelBuilder modelBuilder, string name, Type? type) + public OnEntityTypeAddedNode(IConventionEntityTypeBuilder entityTypeBuilder) { - ModelBuilder = modelBuilder; - Name = name; - Type = type; + EntityTypeBuilder = entityTypeBuilder; } - public IConventionModelBuilder ModelBuilder { get; } - public string Name { get; } - public Type? Type { get; } + public IConventionEntityTypeBuilder EntityTypeBuilder { get; } public override void Run(ConventionDispatcher dispatcher) - => dispatcher._immediateConventionScope.OnEntityTypeIgnored(ModelBuilder, Name, Type); + => dispatcher._immediateConventionScope.OnEntityTypeAdded(EntityTypeBuilder); } private sealed class OnEntityTypeRemovedNode : ConventionNode @@ -497,6 +552,128 @@ public override void Run(ConventionDispatcher dispatcher) EntityTypeBuilder, Name, Annotation, OldAnnotation); } + private sealed class OnComplexTypeMemberIgnoredNode : ConventionNode + { + public OnComplexTypeMemberIgnoredNode(IConventionComplexTypeBuilder complexTypeBuilder, string name) + { + ComplexTypeBuilder = complexTypeBuilder; + Name = name; + } + + public IConventionComplexTypeBuilder ComplexTypeBuilder { get; } + public string Name { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexTypeMemberIgnored(ComplexTypeBuilder, Name); + } + + private sealed class OnComplexTypeAnnotationChangedNode : ConventionNode + { + public OnComplexTypeAnnotationChangedNode( + IConventionComplexTypeBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + ComplexTypeBuilder = propertyBuilder; + Name = name; + Annotation = annotation; + OldAnnotation = oldAnnotation; + } + + public IConventionComplexTypeBuilder ComplexTypeBuilder { get; } + public string Name { get; } + public IConventionAnnotation? Annotation { get; } + public IConventionAnnotation? OldAnnotation { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexTypeAnnotationChanged( + ComplexTypeBuilder, Name, Annotation, OldAnnotation); + } + + private sealed class OnComplexPropertyAddedNode : ConventionNode + { + public OnComplexPropertyAddedNode(IConventionComplexPropertyBuilder propertyBuilder) + { + PropertyBuilder = propertyBuilder; + } + + public IConventionComplexPropertyBuilder PropertyBuilder { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexPropertyAdded(PropertyBuilder); + } + + private sealed class OnComplexPropertyRemovedNode : ConventionNode + { + public OnComplexPropertyRemovedNode(IConventionTypeBaseBuilder modelBuilder, IConventionComplexProperty entityType) + { + TypeBaseBuilder = modelBuilder; + ComplexProperty = entityType; + } + + public IConventionTypeBaseBuilder TypeBaseBuilder { get; } + public IConventionComplexProperty ComplexProperty { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexPropertyRemoved(TypeBaseBuilder, ComplexProperty); + } + + private sealed class OnComplexPropertyNullabilityChangedNode : ConventionNode + { + public OnComplexPropertyNullabilityChangedNode(IConventionComplexPropertyBuilder propertyBuilder) + { + PropertyBuilder = propertyBuilder; + } + + public IConventionComplexPropertyBuilder PropertyBuilder { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexPropertyNullabilityChanged(PropertyBuilder); + } + + private sealed class OnComplexPropertyFieldChangedNode : ConventionNode + { + public OnComplexPropertyFieldChangedNode( + IConventionComplexPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) + { + PropertyBuilder = propertyBuilder; + NewFieldInfo = newFieldInfo; + OldFieldInfo = oldFieldInfo; + } + + public IConventionComplexPropertyBuilder PropertyBuilder { get; } + public FieldInfo? NewFieldInfo { get; } + public FieldInfo? OldFieldInfo { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexPropertyFieldChanged(PropertyBuilder, NewFieldInfo, OldFieldInfo); + } + + private sealed class OnComplexPropertyAnnotationChangedNode : ConventionNode + { + public OnComplexPropertyAnnotationChangedNode( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + PropertyBuilder = propertyBuilder; + Name = name; + Annotation = annotation; + OldAnnotation = oldAnnotation; + } + + public IConventionComplexPropertyBuilder PropertyBuilder { get; } + public string Name { get; } + public IConventionAnnotation? Annotation { get; } + public IConventionAnnotation? OldAnnotation { get; } + + public override void Run(ConventionDispatcher dispatcher) + => dispatcher._immediateConventionScope.OnComplexPropertyAnnotationChanged( + PropertyBuilder, Name, Annotation, OldAnnotation); + } + private sealed class OnForeignKeyAddedNode : ConventionNode { public OnForeignKeyAddedNode(IConventionForeignKeyBuilder relationshipBuilder) @@ -1002,9 +1179,9 @@ public override void Run(ConventionDispatcher dispatcher) => dispatcher._immediateConventionScope.OnPropertyAdded(PropertyBuilder); } - private sealed class OnPropertyNullableChangedNode : ConventionNode + private sealed class OnPropertyNullabilityChangedNode : ConventionNode { - public OnPropertyNullableChangedNode(IConventionPropertyBuilder propertyBuilder) + public OnPropertyNullabilityChangedNode(IConventionPropertyBuilder propertyBuilder) { PropertyBuilder = propertyBuilder; } @@ -1059,17 +1236,17 @@ public override void Run(ConventionDispatcher dispatcher) private sealed class OnPropertyRemovedNode : ConventionNode { public OnPropertyRemovedNode( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property) { - EntityTypeBuilder = entityTypeBuilder; + TypeBaseBuilder = typeBaseBuilder; Property = property; } - public IConventionEntityTypeBuilder EntityTypeBuilder { get; } + public IConventionTypeBaseBuilder TypeBaseBuilder { get; } public IConventionProperty Property { get; } public override void Run(ConventionDispatcher dispatcher) - => dispatcher._immediateConventionScope.OnPropertyRemoved(EntityTypeBuilder, Property); + => dispatcher._immediateConventionScope.OnPropertyRemoved(TypeBaseBuilder, Property); } } diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs index 59e76f1ddd7..2b2fa6e1c2c 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.ImmediateConventionScope.cs @@ -11,6 +11,8 @@ private sealed class ImmediateConventionScope : ConventionScope private readonly ConventionDispatcher _dispatcher; private readonly ConventionContext _entityTypeBuilderConventionContext; private readonly ConventionContext _entityTypeConventionContext; + private readonly ConventionContext _complexPropertyBuilderConventionContext; + private readonly ConventionContext _complexPropertyConventionContext; private readonly ConventionContext _relationshipBuilderConventionContext; private readonly ConventionContext _foreignKeyConventionContext; private readonly ConventionContext _skipNavigationBuilderConventionContext; @@ -23,6 +25,8 @@ private sealed class ImmediateConventionScope : ConventionScope private readonly ConventionContext _keyConventionContext; private readonly ConventionContext _propertyBuilderConventionContext; private readonly ConventionContext _propertyConventionContext; + private readonly ConventionContext _complexTypePropertyBuilderConventionContext; + private readonly ConventionContext _complexTypePropertyConventionContext; private readonly ConventionContext _modelBuilderConventionContext; private readonly ConventionContext _triggerBuilderConventionContext; private readonly ConventionContext _triggerConventionContext; @@ -39,6 +43,8 @@ public ImmediateConventionScope(ConventionSet conventionSet, ConventionDispatche _dispatcher = dispatcher; _entityTypeBuilderConventionContext = new ConventionContext(dispatcher); _entityTypeConventionContext = new ConventionContext(dispatcher); + _complexPropertyBuilderConventionContext = new ConventionContext(dispatcher); + _complexPropertyConventionContext = new ConventionContext(dispatcher); _relationshipBuilderConventionContext = new ConventionContext(dispatcher); _foreignKeyConventionContext = new ConventionContext(dispatcher); _skipNavigationBuilderConventionContext = new ConventionContext(dispatcher); @@ -51,6 +57,8 @@ public ImmediateConventionScope(ConventionSet conventionSet, ConventionDispatche _keyConventionContext = new ConventionContext(dispatcher); _propertyBuilderConventionContext = new ConventionContext(dispatcher); _propertyConventionContext = new ConventionContext(dispatcher); + _complexTypePropertyBuilderConventionContext = new ConventionContext(dispatcher); + _complexTypePropertyConventionContext = new ConventionContext(dispatcher); _modelBuilderConventionContext = new ConventionContext(dispatcher); _triggerBuilderConventionContext = new ConventionContext(dispatcher); _triggerConventionContext = new ConventionContext(dispatcher); @@ -134,59 +142,59 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return annotation; } - public override IConventionEntityTypeBuilder? OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) + public override string? OnTypeIgnored(IConventionModelBuilder modelBuilder, string name, Type? type) { using (_dispatcher.DelayConventions()) { - _entityTypeBuilderConventionContext.ResetState(entityTypeBuilder); + _stringConventionContext.ResetState(name); #if DEBUG - var initialValue = entityTypeBuilder.Metadata.IsInModel; + var initialValue = modelBuilder.Metadata.IsIgnored(name); #endif - foreach (var entityTypeConvention in _conventionSet.EntityTypeAddedConventions) + foreach (var entityTypeConvention in _conventionSet.TypeIgnoredConventions) { - if (!entityTypeBuilder.Metadata.IsInModel) - { - return null; - } - - entityTypeConvention.ProcessEntityTypeAdded(entityTypeBuilder, _entityTypeBuilderConventionContext); - if (_entityTypeBuilderConventionContext.ShouldStopProcessing()) + entityTypeConvention.ProcessTypeIgnored(modelBuilder, name, type, _stringConventionContext); + if (_stringConventionContext.ShouldStopProcessing()) { - return _entityTypeBuilderConventionContext.Result; + return _stringConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == entityTypeBuilder.Metadata.IsInModel, + Check.DebugAssert(initialValue == modelBuilder.Metadata.IsIgnored(name), $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif } } - return !entityTypeBuilder.Metadata.IsInModel ? null : entityTypeBuilder; + return !modelBuilder.Metadata.IsIgnored(name) ? null : name; } - public override string? OnEntityTypeIgnored(IConventionModelBuilder modelBuilder, string name, Type? type) + public override IConventionEntityTypeBuilder? OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) { using (_dispatcher.DelayConventions()) { - _stringConventionContext.ResetState(name); + _entityTypeBuilderConventionContext.ResetState(entityTypeBuilder); #if DEBUG - var initialValue = modelBuilder.Metadata.IsIgnored(name); + var initialValue = entityTypeBuilder.Metadata.IsInModel; #endif - foreach (var entityTypeConvention in _conventionSet.EntityTypeIgnoredConventions) + foreach (var entityTypeConvention in _conventionSet.EntityTypeAddedConventions) { - entityTypeConvention.ProcessEntityTypeIgnored(modelBuilder, name, type, _stringConventionContext); - if (_stringConventionContext.ShouldStopProcessing()) + if (!entityTypeBuilder.Metadata.IsInModel) { - return _stringConventionContext.Result; + return null; + } + + entityTypeConvention.ProcessEntityTypeAdded(entityTypeBuilder, _entityTypeBuilderConventionContext); + if (_entityTypeBuilderConventionContext.ShouldStopProcessing()) + { + return _entityTypeBuilderConventionContext.Result; } #if DEBUG - Check.DebugAssert(initialValue == modelBuilder.Metadata.IsIgnored(name), + Check.DebugAssert(initialValue == entityTypeBuilder.Metadata.IsInModel, $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); #endif } } - return !modelBuilder.Metadata.IsIgnored(name) ? null : name; + return !entityTypeBuilder.Metadata.IsInModel ? null : entityTypeBuilder; } public override IConventionEntityType? OnEntityTypeRemoved( @@ -353,6 +361,232 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return !entityTypeBuilder.Metadata.IsInModel ? null : annotation; } + public override string? OnComplexTypeMemberIgnored( + IConventionComplexTypeBuilder propertyBuilder, + string name) + { + if (!propertyBuilder.Metadata.IsInModel) + { + return null; + } + +#if DEBUG + var initialValue = propertyBuilder.Metadata.IsIgnored(name); +#endif + using (_dispatcher.DelayConventions()) + { + _stringConventionContext.ResetState(name); + foreach (var entityTypeConvention in _conventionSet.ComplexTypeMemberIgnoredConventions) + { + if (!propertyBuilder.Metadata.IsIgnored(name)) + { + return null; + } + + entityTypeConvention.ProcessComplexTypeMemberIgnored(propertyBuilder, name, _stringConventionContext); + if (_stringConventionContext.ShouldStopProcessing()) + { + return _stringConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(initialValue == propertyBuilder.Metadata.IsIgnored(name), + $"Convention {entityTypeConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !propertyBuilder.Metadata.IsIgnored(name) ? null : name; + } + + public override IConventionAnnotation? OnComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder complexTypeBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + if (!complexTypeBuilder.Metadata.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = complexTypeBuilder.Metadata[name]; +#endif + using (_dispatcher.DelayConventions()) + { + _annotationConventionContext.ResetState(annotation); + foreach (var complexTypeConvention in _conventionSet.ComplexTypeAnnotationChangedConventions) + { + + complexTypeConvention.ProcessComplexTypeAnnotationChanged( + complexTypeBuilder, name, annotation, oldAnnotation, _annotationConventionContext); + if (_annotationConventionContext.ShouldStopProcessing()) + { + return _annotationConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(complexTypeBuilder.Metadata.IsInModel + && initialValue == complexTypeBuilder.Metadata[name], + $"Convention {complexTypeConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !complexTypeBuilder.Metadata.IsInModel ? null : annotation; + } + + public override IConventionComplexPropertyBuilder? OnComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder) + { + using (_dispatcher.DelayConventions()) + { + _complexPropertyBuilderConventionContext.ResetState(propertyBuilder); +#if DEBUG + var initialValue = propertyBuilder.Metadata.IsInModel; +#endif + foreach (var complexPropertyConvention in _conventionSet.ComplexPropertyAddedConventions) + { + if (!propertyBuilder.Metadata.IsInModel) + { + return null; + } + + complexPropertyConvention.ProcessComplexPropertyAdded(propertyBuilder, _complexPropertyBuilderConventionContext); + if (_complexPropertyBuilderConventionContext.ShouldStopProcessing()) + { + return _complexPropertyBuilderConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(initialValue == propertyBuilder.Metadata.IsInModel, + $"Convention {complexPropertyConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !propertyBuilder.Metadata.IsInModel ? null : propertyBuilder; + } + + public override IConventionComplexProperty? OnComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property) + { + using (_dispatcher.DelayConventions()) + { + _complexPropertyConventionContext.ResetState(property); + foreach (var complexPropertyConvention in _conventionSet.ComplexPropertyRemovedConventions) + { + complexPropertyConvention.ProcessComplexPropertyRemoved(typeBaseBuilder, property, _complexPropertyConventionContext); + if (_complexPropertyConventionContext.ShouldStopProcessing()) + { + return _complexPropertyConventionContext.Result; + } + } + } + + return property; + } + + public override bool? OnComplexPropertyNullabilityChanged( + IConventionComplexPropertyBuilder propertyBuilder) + { + if (!propertyBuilder.Metadata.DeclaringType.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = propertyBuilder.Metadata.IsNullable; +#endif + using (_dispatcher.DelayConventions()) + { + _boolConventionContext.ResetState(propertyBuilder.Metadata.IsNullable); + foreach (var propertyConvention in _conventionSet.ComplexPropertyNullabilityChangedConventions) + { + if (!propertyBuilder.Metadata.IsInModel) + { + return null; + } + + propertyConvention.ProcessComplexPropertyNullabilityChanged(propertyBuilder, _boolConventionContext); + if (_boolConventionContext.ShouldStopProcessing()) + { + return _boolConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(initialValue == propertyBuilder.Metadata.IsNullable, + $"Convention {propertyConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !propertyBuilder.Metadata.IsInModel ? null : _boolConventionContext.Result; + } + + public override FieldInfo? OnComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo) + { + if (!propertyBuilder.Metadata.IsInModel + || !propertyBuilder.Metadata.DeclaringType.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = propertyBuilder.Metadata.FieldInfo; +#endif + _fieldInfoConventionContext.ResetState(newFieldInfo); + foreach (var propertyConvention in _conventionSet.ComplexPropertyFieldChangedConventions) + { + propertyConvention.ProcessComplexPropertyFieldChanged( + propertyBuilder, newFieldInfo, oldFieldInfo, _fieldInfoConventionContext); + if (_fieldInfoConventionContext.ShouldStopProcessing()) + { + return _fieldInfoConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(initialValue == propertyBuilder.Metadata.FieldInfo, + $"Convention {propertyConvention.GetType().Name} changed value without terminating"); +#endif + } + + return _fieldInfoConventionContext.Result; + } + + public override IConventionAnnotation? OnComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + if (!propertyBuilder.Metadata.IsInModel) + { + return null; + } +#if DEBUG + var initialValue = propertyBuilder.Metadata[name]; +#endif + using (_dispatcher.DelayConventions()) + { + _annotationConventionContext.ResetState(annotation); + foreach (var propertyConvention in _conventionSet.ComplexPropertyAnnotationChangedConventions) + { + + propertyConvention.ProcessComplexPropertyAnnotationChanged( + propertyBuilder, name, annotation, oldAnnotation, _annotationConventionContext); + if (_annotationConventionContext.ShouldStopProcessing()) + { + return _annotationConventionContext.Result; + } +#if DEBUG + Check.DebugAssert(propertyBuilder.Metadata.IsInModel + && initialValue == propertyBuilder.Metadata[name], + $"Convention {propertyConvention.GetType().Name} changed value without terminating"); +#endif + } + } + + return !propertyBuilder.Metadata.IsInModel ? null : annotation; + } + public override IConventionForeignKeyBuilder? OnForeignKeyAdded(IConventionForeignKeyBuilder relationshipBuilder) { if (!relationshipBuilder.Metadata.DeclaringEntityType.IsInModel @@ -1213,7 +1447,7 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB public override IConventionPropertyBuilder? OnPropertyAdded(IConventionPropertyBuilder propertyBuilder) { - if (!propertyBuilder.Metadata.DeclaringEntityType.IsInModel) + if (!propertyBuilder.Metadata.DeclaringType.IsInModel) { return null; } @@ -1245,7 +1479,7 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB public override bool? OnPropertyNullabilityChanged(IConventionPropertyBuilder propertyBuilder) { - if (!propertyBuilder.Metadata.DeclaringEntityType.IsInModel) + if (!propertyBuilder.Metadata.DeclaringType.IsInModel) { return null; } @@ -1283,7 +1517,7 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB FieldInfo? oldFieldInfo) { if (!propertyBuilder.Metadata.IsInModel - || !propertyBuilder.Metadata.DeclaringEntityType.IsInModel) + || !propertyBuilder.Metadata.DeclaringType.IsInModel) { return null; } @@ -1315,7 +1549,7 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB IConventionAnnotation? oldAnnotation) { if (!propertyBuilder.Metadata.IsInModel - || !propertyBuilder.Metadata.DeclaringEntityType.IsInModel) + || !propertyBuilder.Metadata.DeclaringType.IsInModel) { return null; } @@ -1335,18 +1569,18 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB return _annotationConventionContext.Result; } #if DEBUG - Check.DebugAssert(propertyBuilder.Metadata is { IsInModel: true, DeclaringEntityType.IsInModel: true } + Check.DebugAssert(propertyBuilder.Metadata is { IsInModel: true, DeclaringType.IsInModel: true } && initialValue == propertyBuilder.Metadata[name], $"Convention {propertyConvention.GetType().Name} changed value without terminating"); #endif } } - return annotation; + return !propertyBuilder.Metadata.IsInModel ? null : annotation; } public override IConventionProperty? OnPropertyRemoved( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property) { using (_dispatcher.DelayConventions()) @@ -1354,7 +1588,7 @@ public IConventionModelBuilder OnModelInitialized(IConventionModelBuilder modelB _propertyConventionContext.ResetState(property); foreach (var propertyConvention in _conventionSet.PropertyRemovedConventions) { - propertyConvention.ProcessPropertyRemoved(entityTypeBuilder, property, _propertyConventionContext); + propertyConvention.ProcessPropertyRemoved(typeBaseBuilder, property, _propertyConventionContext); if (_propertyConventionContext.ShouldStopProcessing()) { return _propertyConventionContext.Result; diff --git a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs index 8eef98c96b8..6d860e316d1 100644 --- a/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs +++ b/src/EFCore/Metadata/Conventions/Internal/ConventionDispatcher.cs @@ -86,8 +86,11 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IConventionEntityTypeBuilder? OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) - => _scope.OnEntityTypeAdded(entityTypeBuilder); + public virtual string? OnTypeIgnored( + IConventionModelBuilder modelBuilder, + string name, + Type? type) + => _scope.OnTypeIgnored(modelBuilder, name, type); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -95,11 +98,8 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual string? OnEntityTypeIgnored( - IConventionModelBuilder modelBuilder, - string name, - Type? type) - => _scope.OnEntityTypeIgnored(modelBuilder, name, type); + public virtual IConventionEntityTypeBuilder? OnEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder) + => _scope.OnEntityTypeAdded(entityTypeBuilder); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -159,6 +159,108 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder oldAnnotation); } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? OnComplexTypeMemberIgnored( + IConventionComplexTypeBuilder propertyBuilder, + string name) + => _scope.OnComplexTypeMemberIgnored(propertyBuilder, 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. + /// + public virtual IConventionComplexPropertyBuilder? OnComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder) + => _scope.OnComplexPropertyAdded(propertyBuilder); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionAnnotation? OnComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder complexTypeBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + if (CoreAnnotationNames.AllNames.Contains(name)) + { + return annotation; + } + + return _scope.OnComplexTypeAnnotationChanged( + complexTypeBuilder, + name, + annotation, + oldAnnotation); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionComplexProperty? OnComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property) + => _scope.OnComplexPropertyRemoved(typeBaseBuilder, property); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual FieldInfo? OnComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo) + => _scope.OnComplexPropertyFieldChanged(propertyBuilder, newFieldInfo, oldFieldInfo); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? OnComplexPropertyNullabilityChanged( + IConventionComplexPropertyBuilder propertyBuilder) + => _scope.OnComplexPropertyNullabilityChanged(propertyBuilder); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IConventionAnnotation? OnComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + { + if (CoreAnnotationNames.AllNames.Contains(name)) + { + return annotation; + } + + return _scope.OnComplexPropertyAnnotationChanged( + propertyBuilder, + name, + annotation, + oldAnnotation); + } + /// /// 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 @@ -553,9 +655,9 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IConventionProperty? OnPropertyRemoved( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property) - => _scope.OnPropertyRemoved(entityTypeBuilder, property); + => _scope.OnPropertyRemoved(typeBaseBuilder, property); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -563,7 +665,7 @@ public virtual IConventionModelBuilder OnModelFinalizing(IConventionModelBuilder /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool? OnPropertyNullableChanged(IConventionPropertyBuilder propertyBuilder) + public virtual bool? OnPropertyNullabilityChanged(IConventionPropertyBuilder propertyBuilder) => _scope.OnPropertyNullabilityChanged(propertyBuilder); /// diff --git a/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs index ced69fe8985..ded35eddd49 100644 --- a/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/KeyAttributeConvention.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -17,7 +18,8 @@ public class KeyAttributeConvention : PropertyAttributeConventionBase, IModelFinalizingConvention, IEntityTypeAddedConvention, - IEntityTypeBaseTypeChangedConvention + IEntityTypeBaseTypeChangedConvention, + IComplexPropertyAddedConvention { /// /// Creates a new instance of . @@ -56,25 +58,38 @@ protected override void ProcessPropertyAdded( MemberInfo clrMember, IConventionContext context) { - var entityType = propertyBuilder.Metadata.DeclaringEntityType; - if (entityType.IsKeyless) + if (propertyBuilder.Metadata.DeclaringType is EntityType entityType) { - switch (entityType.GetIsKeylessConfigurationSource()) + if (entityType.IsKeyless) { - case ConfigurationSource.DataAnnotation: - Dependencies.Logger.ConflictingKeylessAndKeyAttributesWarning(propertyBuilder.Metadata); - return; + switch (entityType.GetIsKeylessConfigurationSource()) + { + case ConfigurationSource.DataAnnotation: + Dependencies.Logger.ConflictingKeylessAndKeyAttributesWarning(propertyBuilder.Metadata); + return; - case ConfigurationSource.Explicit: - // fluent API overrides the attribute - no warning - return; + case ConfigurationSource.Explicit: + // fluent API overrides the attribute - no warning + return; + } } - } - CheckAttributesAndEnsurePrimaryKey( - (EntityType)propertyBuilder.Metadata.DeclaringEntityType, - propertyBuilder, - shouldThrow: false); + CheckAttributesAndEnsurePrimaryKey( + entityType, + propertyBuilder, + shouldThrow: false); + } + else + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true)) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "Key", property.DeclaringType.DisplayName(), property.Name)); + } + } } private bool CheckAttributesAndEnsurePrimaryKey( @@ -116,6 +131,23 @@ private bool CheckAttributesAndEnsurePrimaryKey( return primaryKeyAttributeExists; } + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + KeyAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true)) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "Key", property.DeclaringType.DisplayName(), property.Name)); + } + } + /// public virtual void ProcessModelFinalizing( IConventionModelBuilder modelBuilder, diff --git a/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs index cbb088a4e62..26c67fe3234 100644 --- a/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs @@ -231,7 +231,12 @@ public virtual void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) { - TryConfigurePrimaryKey(propertyBuilder.Metadata.DeclaringEntityType.Builder); + if (propertyBuilder.Metadata.DeclaringType is not IConventionEntityType entityType) + { + return; + } + + TryConfigurePrimaryKey(entityType.Builder); if (!propertyBuilder.Metadata.IsInModel) { context.StopProcessing(); diff --git a/src/EFCore/Metadata/Conventions/KeylessEntityTypeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/KeylessAttributeConvention.cs similarity index 81% rename from src/EFCore/Metadata/Conventions/KeylessEntityTypeAttributeConvention.cs rename to src/EFCore/Metadata/Conventions/KeylessAttributeConvention.cs index 082481129e2..aa9ac94f40b 100644 --- a/src/EFCore/Metadata/Conventions/KeylessEntityTypeAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/KeylessAttributeConvention.cs @@ -9,13 +9,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class KeylessEntityTypeAttributeConvention : EntityTypeAttributeConventionBase +public class KeylessAttributeConvention : TypeAttributeConventionBase { /// - /// Creates a new instance of . + /// Creates a new instance of . /// /// Parameter object containing dependencies for this convention. - public KeylessEntityTypeAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) + public KeylessAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) : base(dependencies) { } diff --git a/src/EFCore/Metadata/Conventions/MaxLengthAttributeConvention.cs b/src/EFCore/Metadata/Conventions/MaxLengthAttributeConvention.cs index ae1e289157f..2030da98a69 100644 --- a/src/EFCore/Metadata/Conventions/MaxLengthAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/MaxLengthAttributeConvention.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using System.ComponentModel.DataAnnotations.Schema; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -11,7 +13,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class MaxLengthAttributeConvention : PropertyAttributeConventionBase +public class MaxLengthAttributeConvention : PropertyAttributeConventionBase, + IComplexPropertyAddedConvention { /// /// Creates a new instance of . @@ -22,13 +25,7 @@ public MaxLengthAttributeConvention(ProviderConventionSetBuilderDependencies dep { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, MaxLengthAttribute attribute, @@ -40,4 +37,21 @@ protected override void ProcessPropertyAdded( propertyBuilder.HasMaxLength(attribute.Length, fromDataAnnotation: true); } } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + MaxLengthAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true)) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "MaxLength", property.DeclaringType.DisplayName(), property.Name)); + } + } } diff --git a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs index a0cdb322fbe..a083c858f16 100644 --- a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs +++ b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs @@ -30,36 +30,6 @@ protected NavigationAttributeConventionBase(ProviderConventionSetBuilderDependen /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// - /// Called after an entity type is added to the model. - /// - /// The builder for the entity type. - /// Additional information associated with convention execution. - public virtual void ProcessEntityTypeAdded( - IConventionEntityTypeBuilder entityTypeBuilder, - IConventionContext context) - { - var navigations = GetNavigationsWithAttribute(entityTypeBuilder.Metadata); - if (navigations == null) - { - return; - } - - foreach (var navigationTuple in navigations) - { - var (navigationPropertyInfo, targetClrType) = navigationTuple; - var attributes = navigationPropertyInfo.GetCustomAttributes(inherit: true); - foreach (var attribute in attributes) - { - ProcessEntityTypeAdded(entityTypeBuilder, navigationPropertyInfo, targetClrType, attribute, context); - if (((ConventionContext)context).ShouldStopProcessing()) - { - return; - } - } - } - } - /// /// Called after an entity type is ignored. /// @@ -67,7 +37,7 @@ public virtual void ProcessEntityTypeAdded( /// The name of the ignored entity type. /// The ignored entity type. /// Additional information associated with convention execution. - public virtual void ProcessEntityTypeIgnored( + public virtual void ProcessTypeIgnored( IConventionModelBuilder modelBuilder, string name, Type? type, @@ -103,7 +73,7 @@ public virtual void ProcessEntityTypeIgnored( var attributes = navigationPropertyInfo.GetCustomAttributes(inherit: true); foreach (var attribute in attributes) { - ProcessEntityTypeIgnored(modelBuilder, type, navigationPropertyInfo, targetClrType, attribute, context); + ProcessTypeIgnored(modelBuilder, type, navigationPropertyInfo, targetClrType, attribute, context); if (((ConventionContext)context).ShouldStopProcessing()) { return; @@ -112,6 +82,36 @@ public virtual void ProcessEntityTypeIgnored( } } + /// + /// Called after an entity type is added to the model. + /// + /// The builder for the entity type. + /// Additional information associated with convention execution. + public virtual void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionContext context) + { + var navigations = GetNavigationsWithAttribute(entityTypeBuilder.Metadata); + if (navigations == null) + { + return; + } + + foreach (var navigationTuple in navigations) + { + var (navigationPropertyInfo, targetClrType) = navigationTuple; + var attributes = navigationPropertyInfo.GetCustomAttributes(inherit: true); + foreach (var attribute in attributes) + { + ProcessEntityTypeAdded(entityTypeBuilder, navigationPropertyInfo, targetClrType, attribute, context); + if (((ConventionContext)context).ShouldStopProcessing()) + { + return; + } + } + } + } + /// /// Called after an entity type is removed from the model. /// @@ -364,37 +364,37 @@ private static IEnumerable GetAttributes(Mem } /// - /// Called for every navigation property that has an attribute after an entity type is added to the model. + /// Called for every navigation property that has an attribute after an entity type is ignored. /// - /// The builder for the entity type. + /// The builder for the model. + /// The ignored entity type. /// The navigation member info. - /// The CLR type of the target entity type + /// The CLR type of the target entity type. /// The attribute. /// Additional information associated with convention execution. - public virtual void ProcessEntityTypeAdded( - IConventionEntityTypeBuilder entityTypeBuilder, + public virtual void ProcessTypeIgnored( + IConventionModelBuilder modelBuilder, + Type type, MemberInfo navigationMemberInfo, Type targetClrType, TAttribute attribute, - IConventionContext context) + IConventionContext context) => throw new NotSupportedException(); /// - /// Called for every navigation property that has an attribute after an entity type is ignored. + /// Called for every navigation property that has an attribute after an entity type is added to the model. /// - /// The builder for the model. - /// The ignored entity type. + /// The builder for the entity type. /// The navigation member info. - /// The CLR type of the target entity type. + /// The CLR type of the target entity type /// The attribute. /// Additional information associated with convention execution. - public virtual void ProcessEntityTypeIgnored( - IConventionModelBuilder modelBuilder, - Type type, + public virtual void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, MemberInfo navigationMemberInfo, Type targetClrType, TAttribute attribute, - IConventionContext context) + IConventionContext context) => throw new NotSupportedException(); /// diff --git a/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs b/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs index 77e7d51b36c..20e176575cf 100644 --- a/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs +++ b/src/EFCore/Metadata/Conventions/NonNullableReferencePropertyConvention.cs @@ -13,7 +13,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// public class NonNullableReferencePropertyConvention : NonNullableConventionBase, IPropertyAddedConvention, - IPropertyFieldChangedConvention + IPropertyFieldChangedConvention, + IComplexPropertyAddedConvention, + IComplexPropertyFieldChangedConvention { /// /// Creates a new instance of . @@ -33,27 +35,50 @@ private void Process(IConventionPropertyBuilder propertyBuilder) } } - /// - /// Called after a property is added to the entity type. - /// - /// The builder for the property. - /// Additional information associated with convention execution. + private void Process(IConventionComplexPropertyBuilder propertyBuilder) + { + if (propertyBuilder.Metadata.GetIdentifyingMemberInfo() is MemberInfo memberInfo + && IsNonNullableReferenceType(propertyBuilder.ModelBuilder, memberInfo)) + { + propertyBuilder.IsRequired(true); + } + } + + /// public virtual void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) => Process(propertyBuilder); - /// - /// Called after the backing field for a property is changed. - /// - /// The builder for the property. - /// The new field. - /// The old field. - /// Additional information associated with convention execution. + /// public virtual void ProcessPropertyFieldChanged( IConventionPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo, IConventionContext context) + { + if (propertyBuilder.Metadata.PropertyInfo == null) + { + Process(propertyBuilder); + } + } + + /// + public void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) => Process(propertyBuilder); + + /// + public void ProcessComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo, + IConventionContext context) + { + if (propertyBuilder.Metadata.PropertyInfo == null) + { + Process(propertyBuilder); + } + } } diff --git a/src/EFCore/Metadata/Conventions/NotMappedMemberAttributeConvention.cs b/src/EFCore/Metadata/Conventions/NotMappedMemberAttributeConvention.cs index 60d97b098f7..98ae30b9dcd 100644 --- a/src/EFCore/Metadata/Conventions/NotMappedMemberAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/NotMappedMemberAttributeConvention.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -12,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class NotMappedMemberAttributeConvention : IEntityTypeAddedConvention +public class NotMappedMemberAttributeConvention : IEntityTypeAddedConvention, IComplexPropertyAddedConvention { /// /// Creates a new instance of . @@ -28,17 +29,11 @@ public NotMappedMemberAttributeConvention(ProviderConventionSetBuilderDependenci /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// - /// Called after an entity type is added to the model. - /// - /// The builder for the entity type. - /// Additional information associated with convention execution. + /// public virtual void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context) { - Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder)); - var entityType = entityTypeBuilder.Metadata; var members = entityType.GetRuntimeProperties().Values.Cast() .Concat(entityType.GetRuntimeFields().Values); @@ -53,6 +48,25 @@ public virtual void ProcessEntityTypeAdded( } } + /// + public void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + var complexType = propertyBuilder.Metadata.ComplexType; + var members = complexType.GetRuntimeProperties().Values.Cast() + .Concat(complexType.GetRuntimeFields().Values); + + foreach (var member in members) + { + if (Attribute.IsDefined(member, typeof(NotMappedAttribute), inherit: true) + && ShouldIgnore(member)) + { + complexType.Builder.Ignore(member.GetSimpleMemberName(), fromDataAnnotation: true); + } + } + } + /// /// Returns a value indicating whether the given CLR member should be ignored. /// diff --git a/src/EFCore/Metadata/Conventions/NotMappedEntityTypeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/NotMappedTypeAttributeConvention.cs similarity index 82% rename from src/EFCore/Metadata/Conventions/NotMappedEntityTypeAttributeConvention.cs rename to src/EFCore/Metadata/Conventions/NotMappedTypeAttributeConvention.cs index 9b62ba7e3ce..1557ee96143 100644 --- a/src/EFCore/Metadata/Conventions/NotMappedEntityTypeAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/NotMappedTypeAttributeConvention.cs @@ -11,13 +11,13 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class NotMappedEntityTypeAttributeConvention : EntityTypeAttributeConventionBase +public class NotMappedTypeAttributeConvention : TypeAttributeConventionBase { /// - /// Creates a new instance of . + /// Creates a new instance of . /// /// Parameter object containing dependencies for this convention. - public NotMappedEntityTypeAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) + public NotMappedTypeAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) : base(dependencies) { } diff --git a/src/EFCore/Metadata/Conventions/OwnedAttributeConvention.cs b/src/EFCore/Metadata/Conventions/OwnedAttributeConvention.cs new file mode 100644 index 00000000000..e3629a8a8e2 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/OwnedAttributeConvention.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// A convention that configures the entity types that have the as owned. +/// +/// +/// See Model building conventions for more information and examples. +/// +public class OwnedAttributeConvention : TypeAttributeConventionBase, + IComplexPropertyAddedConvention +{ + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + public OwnedAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) + : base(dependencies) + { + } + + /// + protected override void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + OwnedAttribute attribute, + IConventionContext context) + { + entityTypeBuilder.ModelBuilder.Owned(entityTypeBuilder.Metadata.ClrType, fromDataAnnotation: true); + if (!entityTypeBuilder.Metadata.IsInModel) + { + context.StopProcessing(); + } + } + + /// + protected override void ProcessComplexTypeAdded( + IConventionComplexTypeBuilder complexTypeBuilder, + OwnedAttribute attribute, + IConventionContext context) + { + var complexProperty = complexTypeBuilder.Metadata.ComplexProperty; + var entityTypeBuilder = ReplaceWithEntityType(complexTypeBuilder, shouldBeOwned: true); + if (entityTypeBuilder == null) + { + return; + } + + context.StopProcessing(); + + var memberInfo = complexProperty.GetIdentifyingMemberInfo(); + if (memberInfo != null + && complexProperty.Builder is IConventionEntityTypeBuilder conventionEntityTypeBuilder) + { + conventionEntityTypeBuilder.HasOwnership( + entityTypeBuilder.Metadata, memberInfo, fromDataAnnotation: true); + } + } +} diff --git a/src/EFCore/Metadata/Conventions/OwnedEntityTypeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/OwnedEntityTypeAttributeConvention.cs deleted file mode 100644 index 086405ce2aa..00000000000 --- a/src/EFCore/Metadata/Conventions/OwnedEntityTypeAttributeConvention.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; - -/// -/// A convention that configures the entity types that have the as owned. -/// -/// -/// See Model building conventions for more information and examples. -/// -public class OwnedEntityTypeAttributeConvention : EntityTypeAttributeConventionBase -{ - /// - /// Creates a new instance of . - /// - /// Parameter object containing dependencies for this convention. - public OwnedEntityTypeAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) - : base(dependencies) - { - } - - /// - /// Called after an entity type is added to the model if it has an attribute. - /// - /// The builder for the entity type. - /// The attribute. - /// Additional information associated with convention execution. - protected override void ProcessEntityTypeAdded( - IConventionEntityTypeBuilder entityTypeBuilder, - OwnedAttribute attribute, - IConventionContext context) - { - entityTypeBuilder.ModelBuilder.Owned(entityTypeBuilder.Metadata.ClrType, fromDataAnnotation: true); - if (!entityTypeBuilder.Metadata.IsInModel) - { - context.StopProcessing(); - } - } -} diff --git a/src/EFCore/Metadata/Conventions/PrecisionAttributeConvention.cs b/src/EFCore/Metadata/Conventions/PrecisionAttributeConvention.cs index 55dbc3e5720..1d8b85a6611 100644 --- a/src/EFCore/Metadata/Conventions/PrecisionAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/PrecisionAttributeConvention.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using System.ComponentModel.DataAnnotations.Schema; + namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// @@ -20,13 +23,7 @@ public PrecisionAttributeConvention(ProviderConventionSetBuilderDependencies dep { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, PrecisionAttribute attribute, @@ -40,4 +37,21 @@ protected override void ProcessPropertyAdded( propertyBuilder.HasScale(attribute.Scale, fromDataAnnotation: true); } } + + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + PrecisionAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + var member = property.GetIdentifyingMemberInfo(); + if (member != null + && Attribute.IsDefined(member, typeof(ForeignKeyAttribute), inherit: true)) + { + throw new InvalidOperationException(CoreStrings.AttributeNotOnEntityTypeProperty( + "Precision", property.DeclaringType.DisplayName(), property.Name)); + } + } } diff --git a/src/EFCore/Metadata/Conventions/PropertyAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/PropertyAttributeConventionBase.cs index cf6a46fd7a0..b25003b6168 100644 --- a/src/EFCore/Metadata/Conventions/PropertyAttributeConventionBase.cs +++ b/src/EFCore/Metadata/Conventions/PropertyAttributeConventionBase.cs @@ -10,10 +10,18 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// A base type for conventions that perform configuration based on an attribute applied to a property. /// /// -/// See Model building conventions for more information and examples. +/// +/// The deriving class must implement and +/// to also handle complex properties. +/// +/// +/// See Model building conventions for more information and examples. +/// /// /// The attribute type to look for. -public abstract class PropertyAttributeConventionBase : IPropertyAddedConvention, IPropertyFieldChangedConvention +public abstract class PropertyAttributeConventionBase : + IPropertyAddedConvention, + IPropertyFieldChangedConvention where TAttribute : Attribute { /// @@ -30,11 +38,7 @@ protected PropertyAttributeConventionBase(ProviderConventionSetBuilderDependenci /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// - /// Called after a property is added to the entity type. - /// - /// The builder for the property. - /// Additional information associated with convention execution. + /// public virtual void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) @@ -50,15 +54,68 @@ public virtual void ProcessPropertyAdded( Process(propertyBuilder, memberInfo, (IReadableConventionContext)context); } + /// + public virtual void ProcessPropertyFieldChanged( + IConventionPropertyBuilder propertyBuilder, + FieldInfo? newFieldInfo, + FieldInfo? oldFieldInfo, + IConventionContext context) + { + if (newFieldInfo != null + && propertyBuilder.Metadata.PropertyInfo == null) + { + Process(propertyBuilder, newFieldInfo, (IReadableConventionContext)context); + } + } + + private void Process(IConventionPropertyBuilder propertyBuilder, MemberInfo memberInfo, IReadableConventionContext context) + { + if (!Attribute.IsDefined(memberInfo, typeof(TAttribute), inherit: true)) + { + return; + } + + var attributes = memberInfo.GetCustomAttributes(inherit: true); + + foreach (var attribute in attributes) + { + ProcessPropertyAdded(propertyBuilder, attribute, memberInfo, context); + if (context.ShouldStopProcessing()) + { + break; + } + } + } + /// - /// Called after the backing field for a property is changed. + /// Called after a complex property is added to an type-like object. + /// + /// The builder for the complex property. + /// Additional information associated with convention execution. + public virtual void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + Check.NotNull(propertyBuilder, nameof(propertyBuilder)); + + var memberInfo = propertyBuilder.Metadata.GetIdentifyingMemberInfo(); + if (memberInfo == null) + { + return; + } + + Process(propertyBuilder, memberInfo, (IReadableConventionContext)context); + } + + /// + /// Called after the backing field for a complex property is changed. /// /// The builder for the property. /// The new field. /// The old field. /// Additional information associated with convention execution. - public virtual void ProcessPropertyFieldChanged( - IConventionPropertyBuilder propertyBuilder, + public virtual void ProcessComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo, IConventionContext context) @@ -70,7 +127,7 @@ public virtual void ProcessPropertyFieldChanged( } } - private void Process(IConventionPropertyBuilder propertyBuilder, MemberInfo memberInfo, IReadableConventionContext context) + private void Process(IConventionComplexPropertyBuilder propertyBuilder, MemberInfo memberInfo, IReadableConventionContext context) { if (!Attribute.IsDefined(memberInfo, typeof(TAttribute), inherit: true)) { @@ -101,4 +158,18 @@ protected abstract void ProcessPropertyAdded( TAttribute attribute, MemberInfo clrMember, IConventionContext context); + + /// + /// Called after a complex property is added to a type with an attribute on the associated CLR property or field. + /// + /// The builder for the property. + /// The attribute. + /// The member that has the attribute. + /// Additional information associated with convention execution. + protected virtual void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + TAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + => throw new NotSupportedException(); } diff --git a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs index b855a79c91d..4a73cb154ca 100644 --- a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -13,7 +14,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// public class PropertyDiscoveryConvention : IEntityTypeAddedConvention, - IEntityTypeBaseTypeChangedConvention + IEntityTypeBaseTypeChangedConvention, + IComplexPropertyAddedConvention { /// /// Creates a new instance of . @@ -29,23 +31,31 @@ public PropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies depe /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// - /// Called after an entity type is added to the model. - /// - /// The builder for the entity type. - /// Additional information associated with convention execution. + /// public virtual void ProcessEntityTypeAdded( IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext context) => Process(entityTypeBuilder); - /// - /// Called after the base type of an entity type changes. - /// - /// The builder for the entity type. - /// The new base entity type. - /// The old base entity type. - /// Additional information associated with convention execution. + /// + public void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + var complexType = propertyBuilder.Metadata.ComplexType; + var model = complexType.Model; + foreach (var propertyInfo in complexType.GetRuntimeProperties().Values) + { + if (!Dependencies.MemberClassifier.IsCandidatePrimitiveProperty(propertyInfo, model)) + { + continue; + } + + complexType.Builder.Property(propertyInfo); + } + } + + /// public virtual void ProcessEntityTypeBaseTypeChanged( IConventionEntityTypeBuilder entityTypeBuilder, IConventionEntityType? newBaseType, diff --git a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs index 0fc7dd606d9..b0c28d5cacc 100644 --- a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs @@ -15,8 +15,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// See Model building conventions for more information and examples. /// public class RelationshipDiscoveryConvention : + ITypeIgnoredConvention, IEntityTypeAddedConvention, - IEntityTypeIgnoredConvention, IEntityTypeBaseTypeChangedConvention, IEntityTypeMemberIgnoredConvention, INavigationRemovedConvention, @@ -1134,12 +1134,13 @@ private static bool IsCandidateNavigationProperty( => sourceEntityType.Builder.IsIgnored(navigationName) == false && sourceEntityType.FindProperty(navigationName) == null && sourceEntityType.FindServiceProperty(navigationName) == null + && sourceEntityType.FindComplexProperty(navigationName) == null && (memberInfo is not PropertyInfo propertyInfo || propertyInfo.GetIndexParameters().Length == 0) && (!sourceEntityType.IsKeyless || (memberInfo as PropertyInfo)?.PropertyType.TryGetSequenceType() == null); /// - public virtual void ProcessEntityTypeIgnored( + public virtual void ProcessTypeIgnored( IConventionModelBuilder modelBuilder, string name, Type? type, diff --git a/src/EFCore/Metadata/Conventions/RequiredPropertyAttributeConvention.cs b/src/EFCore/Metadata/Conventions/RequiredPropertyAttributeConvention.cs index fb395f87983..0fca17b54ce 100644 --- a/src/EFCore/Metadata/Conventions/RequiredPropertyAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/RequiredPropertyAttributeConvention.cs @@ -11,7 +11,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class RequiredPropertyAttributeConvention : PropertyAttributeConventionBase +public class RequiredPropertyAttributeConvention : PropertyAttributeConventionBase, + IComplexPropertyAddedConvention, + IComplexPropertyFieldChangedConvention { /// /// Creates a new instance of . @@ -22,17 +24,19 @@ public RequiredPropertyAttributeConvention(ProviderConventionSetBuilderDependenc { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, RequiredAttribute attribute, MemberInfo clrMember, IConventionContext context) => propertyBuilder.IsRequired(true, fromDataAnnotation: true); + + /// + protected override void ProcessPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + RequiredAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + => propertyBuilder.IsRequired(true, fromDataAnnotation: true); } diff --git a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs index 2f0a65583ed..f158cbe1bca 100644 --- a/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs +++ b/src/EFCore/Metadata/Conventions/RuntimeModelConvention.cs @@ -29,10 +29,7 @@ public RuntimeModelConvention( /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - /// - /// Called after a model is finalized and can no longer be mutated. - /// - /// The model. + /// public virtual IModel ProcessModelFinalized(IModel model) => Create(model); @@ -73,6 +70,14 @@ protected virtual RuntimeModel Create(IModel model) (ServiceParameterBinding)Create(serviceProperty.ParameterBinding, runtimeEntityType); } + foreach (var property in entityType.GetDeclaredComplexProperties()) + { + var runtimeProperty = Create(property, runtimeEntityType); + CreateAnnotations( + property, runtimeProperty, static (convention, annotations, source, target, runtime) => + convention.ProcessComplexPropertyAnnotations(annotations, source, target, runtime)); + } + foreach (var key in entityType.GetDeclaredKeys()) { var runtimeKey = Create(key, runtimeEntityType); @@ -333,31 +338,56 @@ protected virtual void ProcessTypeMappingConfigurationAnnotations( } } - private static RuntimeProperty Create(IProperty property, RuntimeEntityType runtimeEntityType) - => runtimeEntityType.AddProperty( - property.Name, - property.ClrType, - property.Sentinel, - property.PropertyInfo, - property.FieldInfo, - property.GetPropertyAccessMode(), - property.IsNullable, - property.IsConcurrencyToken, - property.ValueGenerated, - property.GetBeforeSaveBehavior(), - property.GetAfterSaveBehavior(), - property.GetMaxLength(), - property.IsUnicode(), - property.GetPrecision(), - property.GetScale(), - property.GetProviderClrType(), - property.GetValueGeneratorFactory(), - property.GetValueConverter(), - property.GetValueComparer(), - property.GetKeyValueComparer(), - property.GetProviderValueComparer(), - property.GetJsonValueReaderWriter(), - property.GetTypeMapping()); + private static RuntimeProperty Create(IProperty property, RuntimeTypeBase runtimeType) + => runtimeType is RuntimeEntityType runtimeEntityType + ? runtimeEntityType.AddProperty( + property.Name, + property.ClrType, + property.Sentinel, + property.PropertyInfo, + property.FieldInfo, + property.GetPropertyAccessMode(), + property.IsNullable, + property.IsConcurrencyToken, + property.ValueGenerated, + property.GetBeforeSaveBehavior(), + property.GetAfterSaveBehavior(), + property.GetMaxLength(), + property.IsUnicode(), + property.GetPrecision(), + property.GetScale(), + property.GetProviderClrType(), + property.GetValueGeneratorFactory(), + property.GetValueConverter(), + property.GetValueComparer(), + property.GetKeyValueComparer(), + property.GetProviderValueComparer(), + property.GetJsonValueReaderWriter(), + property.GetTypeMapping()) + : ((RuntimeComplexType)runtimeType).AddProperty( + property.Name, + property.ClrType, + property.Sentinel, + property.PropertyInfo, + property.FieldInfo, + property.GetPropertyAccessMode(), + property.IsNullable, + property.IsConcurrencyToken, + property.ValueGenerated, + property.GetBeforeSaveBehavior(), + property.GetAfterSaveBehavior(), + property.GetMaxLength(), + property.IsUnicode(), + property.GetPrecision(), + property.GetScale(), + property.GetProviderClrType(), + property.GetValueGeneratorFactory(), + property.GetValueConverter(), + property.GetValueComparer(), + property.GetKeyValueComparer(), + property.GetProviderValueComparer(), + property.GetJsonValueReaderWriter(), + property.GetTypeMapping()); /// /// Updates the property annotations that will be set on the read-only object. @@ -417,6 +447,107 @@ protected virtual void ProcessServicePropertyAnnotations( } } + private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeEntityType runtimeEntityType) + { + var runtimeComplexProperty = runtimeEntityType.AddComplexProperty( + complexProperty.Name, + complexProperty.ClrType, + complexProperty.ComplexType.Name, + complexProperty.ComplexType.ClrType, + complexProperty.PropertyInfo, + complexProperty.FieldInfo, + complexProperty.GetPropertyAccessMode(), + complexProperty.IsNullable, + complexProperty.IsCollection, + complexProperty.ComplexType.GetChangeTrackingStrategy(), + complexProperty.ComplexType.FindIndexerPropertyInfo(), + complexProperty.ComplexType.IsPropertyBag); + + var complexType = complexProperty.ComplexType; + var runtimeComplexType = runtimeComplexProperty.ComplexType; + + foreach (var property in complexType.GetProperties()) + { + var runtimeProperty = Create(property, runtimeComplexType); + CreateAnnotations( + property, runtimeProperty, static (convention, annotations, source, target, runtime) => + convention.ProcessPropertyAnnotations(annotations, source, target, runtime)); + } + + foreach (var property in complexType.GetComplexProperties()) + { + var runtimeProperty = Create(property, runtimeComplexType); + CreateAnnotations( + property, runtimeProperty, static (convention, annotations, source, target, runtime) => + convention.ProcessComplexPropertyAnnotations(annotations, source, target, runtime)); + } + + return runtimeComplexProperty; + } + + private RuntimeComplexProperty Create(IComplexProperty complexProperty, RuntimeComplexType runtimeComplexType) + { + var runtimeComplexProperty = runtimeComplexType.AddComplexProperty( + complexProperty.Name, + complexProperty.ClrType, + complexProperty.ComplexType.Name, + complexProperty.ComplexType.ClrType, + complexProperty.PropertyInfo, + complexProperty.FieldInfo, + complexProperty.GetPropertyAccessMode(), + complexProperty.IsNullable, + complexProperty.IsCollection, + complexProperty.ComplexType.GetChangeTrackingStrategy(), + complexProperty.ComplexType.FindIndexerPropertyInfo(), + complexProperty.ComplexType.IsPropertyBag); + + var complexType = complexProperty.ComplexType; + var newRuntimeComplexType = runtimeComplexProperty.ComplexType; + + foreach (var property in complexType.GetProperties()) + { + var runtimeProperty = Create(property, newRuntimeComplexType); + CreateAnnotations( + property, runtimeProperty, static (convention, annotations, source, target, runtime) => + convention.ProcessPropertyAnnotations(annotations, source, target, runtime)); + } + + foreach (var property in complexType.GetComplexProperties()) + { + var runtimeProperty = Create(property, newRuntimeComplexType); + CreateAnnotations( + property, runtimeProperty, static (convention, annotations, source, target, runtime) => + convention.ProcessComplexPropertyAnnotations(annotations, source, target, runtime)); + } + + return runtimeComplexProperty; + } + + /// + /// Updates the property annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source property. + /// The target property that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected virtual void ProcessComplexPropertyAnnotations( + Dictionary annotations, + IComplexProperty property, + RuntimeComplexProperty runtimeProperty, + bool runtime) + { + if (!runtime) + { + foreach (var (key, _) in annotations) + { + if (CoreAnnotationNames.AllNames.Contains(key)) + { + annotations.Remove(key); + } + } + } + } + private static RuntimeKey Create(IKey key, RuntimeEntityType runtimeEntityType) => runtimeEntityType.AddKey(runtimeEntityType.FindProperties(key.Properties.Select(p => p.Name))!); diff --git a/src/EFCore/Metadata/Conventions/StringLengthAttributeConvention.cs b/src/EFCore/Metadata/Conventions/StringLengthAttributeConvention.cs index 26b92c066a0..1fedb4c210f 100644 --- a/src/EFCore/Metadata/Conventions/StringLengthAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/StringLengthAttributeConvention.cs @@ -22,13 +22,7 @@ public StringLengthAttributeConvention(ProviderConventionSetBuilderDependencies { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, StringLengthAttribute attribute, diff --git a/src/EFCore/Metadata/Conventions/TimestampAttributeConvention.cs b/src/EFCore/Metadata/Conventions/TimestampAttributeConvention.cs index 3ef8cac9f5a..4ef36668025 100644 --- a/src/EFCore/Metadata/Conventions/TimestampAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/TimestampAttributeConvention.cs @@ -22,13 +22,7 @@ public TimestampAttributeConvention(ProviderConventionSetBuilderDependencies dep { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, TimestampAttribute attribute, diff --git a/src/EFCore/Metadata/Conventions/TypeAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/TypeAttributeConventionBase.cs new file mode 100644 index 00000000000..4bf7904f143 --- /dev/null +++ b/src/EFCore/Metadata/Conventions/TypeAttributeConventionBase.cs @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; + +/// +/// A base type for conventions that perform configuration based on an attribute specified on an entity type. +/// +/// +/// See Model building conventions for more information and examples. +/// +/// The attribute type to look for. +public abstract class TypeAttributeConventionBase : IEntityTypeAddedConvention + where TAttribute : Attribute +{ + /// + /// Creates a new instance of . + /// + /// Parameter object containing dependencies for this convention. + protected TypeAttributeConventionBase(ProviderConventionSetBuilderDependencies dependencies) + { + Dependencies = dependencies; + } + + /// + /// Dependencies for this service. + /// + protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } + + /// + /// Called after an entity type is added to the model. + /// + /// The builder for the entity type. + /// Additional information associated with convention execution. + public virtual void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionContext context) + { + var type = entityTypeBuilder.Metadata.ClrType; + + var attributes = type.GetCustomAttributes(true); + foreach (var attribute in attributes) + { + ProcessEntityTypeAdded(entityTypeBuilder, attribute, context); + if (((IReadableConventionContext)context).ShouldStopProcessing()) + { + return; + } + } + } + + /// + /// Called after a complex property is added to a type-like object. + /// + /// The builder for the complex property. + /// Additional information associated with convention execution. + public void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + var complexType = propertyBuilder.Metadata.ComplexType; + var type = complexType.ClrType; + var attributes = type.GetCustomAttributes(true); + foreach (var attribute in attributes) + { + ProcessComplexTypeAdded(complexType.Builder, attribute, context); + if (((IReadableConventionContext)context).ShouldStopProcessing()) + { + return; + } + } + } + + /// + /// Called after an entity type is added to the model if it has an attribute. + /// + /// The builder for the entity type. + /// The attribute. + /// Additional information associated with convention execution. + protected abstract void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + TAttribute attribute, + IConventionContext context); + + /// + /// Called after an complex type is added to the model if it has an attribute. + /// + /// The builder for the complex type. + /// The attribute. + /// Additional information associated with convention execution. + protected virtual void ProcessComplexTypeAdded( + IConventionComplexTypeBuilder complexTypeBuilder, + TAttribute attribute, + IConventionContext context) + => throw new NotSupportedException(); + + /// + /// Tries to replace the complex type with an entity type. + /// + /// The complex type builder. + /// A value indicating whether the new entity type should be owned. + /// The builder for the new entity type. + protected virtual IConventionEntityTypeBuilder? ReplaceWithEntityType( + IConventionComplexTypeBuilder complexTypeBuilder, + bool? shouldBeOwned = null) + { + var modelBuilder = complexTypeBuilder.ModelBuilder; + if (!modelBuilder.CanHaveEntity(complexTypeBuilder.Metadata.ClrType, fromDataAnnotation: true)) + { + return null; + } + + var complexProperty = complexTypeBuilder.Metadata.ComplexProperty; + switch (complexProperty.DeclaringType.Builder) + { + case IConventionEntityTypeBuilder conventionEntityTypeBuilder: + if (conventionEntityTypeBuilder.HasNoComplexProperty(complexProperty, fromDataAnnotation: true) == null) + { + return null; + } + break; + case IConventionComplexTypeBuilder conventionComplexTypeBuilder: + if (conventionComplexTypeBuilder.HasNoComplexProperty(complexProperty, fromDataAnnotation: true) == null) + { + return null; + } + break; + } + + return complexTypeBuilder.ModelBuilder.Entity(complexTypeBuilder.Metadata.ClrType, shouldBeOwned, fromDataAnnotation: true); + } +} diff --git a/src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs b/src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs index 96ca3f21f2d..972deb58b03 100644 --- a/src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs +++ b/src/EFCore/Metadata/Conventions/UnicodeAttributeConvention.cs @@ -20,13 +20,7 @@ public UnicodeAttributeConvention(ProviderConventionSetBuilderDependencies depen { } - /// - /// Called after a property is added to the entity type with an attribute on the associated CLR property or field. - /// - /// The builder for the property. - /// The attribute. - /// The member that has the attribute. - /// Additional information associated with convention execution. + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, UnicodeAttribute attribute, diff --git a/src/EFCore/Metadata/EntityTypeFullNameComparer.cs b/src/EFCore/Metadata/EntityTypeFullNameComparer.cs index 2edf328b749..ab42efeca2b 100644 --- a/src/EFCore/Metadata/EntityTypeFullNameComparer.cs +++ b/src/EFCore/Metadata/EntityTypeFullNameComparer.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// /// An implementation of and to compare -/// instances by name including the defining entity type when present. +/// instances by the full unique name. /// /// /// This type is typically used by database providers (and other extensions). It is generally diff --git a/src/EFCore/Metadata/ForeignKeyComparer.cs b/src/EFCore/Metadata/ForeignKeyComparer.cs index ba5c2a2e163..a3a61479829 100644 --- a/src/EFCore/Metadata/ForeignKeyComparer.cs +++ b/src/EFCore/Metadata/ForeignKeyComparer.cs @@ -50,8 +50,8 @@ public int Compare(IReadOnlyForeignKey? x, IReadOnlyForeignKey? y) return result; } - result = EntityTypeFullNameComparer.Instance.Compare(x?.PrincipalEntityType, y?.PrincipalEntityType); - return result != 0 ? result : EntityTypeFullNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); + result = TypeBaseNameComparer.Instance.Compare(x?.PrincipalEntityType, y?.PrincipalEntityType); + return result != 0 ? result : TypeBaseNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); } /// @@ -73,8 +73,8 @@ public int GetHashCode(IReadOnlyForeignKey obj) var hashCode = new HashCode(); hashCode.Add(obj.PrincipalKey.Properties, PropertyListComparer.Instance); hashCode.Add(obj.Properties, PropertyListComparer.Instance); - hashCode.Add(obj.PrincipalEntityType, EntityTypeFullNameComparer.Instance); - hashCode.Add(obj.DeclaringEntityType, EntityTypeFullNameComparer.Instance); + hashCode.Add(obj.PrincipalEntityType, TypeBaseNameComparer.Instance); + hashCode.Add(obj.DeclaringEntityType, TypeBaseNameComparer.Instance); return hashCode.ToHashCode(); } } diff --git a/src/EFCore/Metadata/IComplexProperty.cs b/src/EFCore/Metadata/IComplexProperty.cs new file mode 100644 index 00000000000..a3c8888c63c --- /dev/null +++ b/src/EFCore/Metadata/IComplexProperty.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a complex property of a structural type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IComplexProperty : IReadOnlyComplexProperty, IPropertyBase +{ + /// + /// Gets the associated complex type. + /// + new IComplexType ComplexType { get; } +} diff --git a/src/EFCore/Metadata/IComplexType.cs b/src/EFCore/Metadata/IComplexType.cs new file mode 100644 index 00000000000..e49eec386bd --- /dev/null +++ b/src/EFCore/Metadata/IComplexType.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the type of a complex property of a structural type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IComplexType : IReadOnlyComplexType, ITypeBase +{ + /// + /// Gets the associated property. + /// + new IComplexProperty ComplexProperty { get; } + + /// + /// Gets the entity type on which the complex property chain is declared. + /// + IEntityType ITypeBase.FundamentalEntityType + => (IEntityType)((IReadOnlyComplexType)this).FundamentalEntityType; + + /// + /// Gets the for the preferred constructor. + /// + InstantiationBinding? ConstructorBinding { get; } +} diff --git a/src/EFCore/Metadata/IConstructorBindingFactory.cs b/src/EFCore/Metadata/IConstructorBindingFactory.cs index 1eb5a643ac0..0e474667080 100644 --- a/src/EFCore/Metadata/IConstructorBindingFactory.cs +++ b/src/EFCore/Metadata/IConstructorBindingFactory.cs @@ -58,6 +58,18 @@ void GetBindings( out InstantiationBinding constructorBinding, out InstantiationBinding? serviceOnlyBinding); + /// + /// Create a for the constructor with most parameters and + /// the constructor with only service property parameters. + /// + /// The complex type. + /// The binding for the constructor with most parameters. + /// The binding for the constructor with only service property parameters. + void GetBindings( + IReadOnlyComplexType complexType, + out InstantiationBinding constructorBinding, + out InstantiationBinding? serviceOnlyBinding); + /// /// Attempts to create a for the given entity type and /// diff --git a/src/EFCore/Metadata/IConventionComplexProperty.cs b/src/EFCore/Metadata/IConventionComplexProperty.cs new file mode 100644 index 00000000000..fbcb0945015 --- /dev/null +++ b/src/EFCore/Metadata/IConventionComplexProperty.cs @@ -0,0 +1,49 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a complex property of a structural type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Model building conventions for more information and examples. +/// +/// +public interface IConventionComplexProperty : IReadOnlyComplexProperty, IConventionPropertyBase +{ + /// + /// Gets the builder that can be used to configure this property. + /// + /// If the property has been removed from the model. + new IConventionComplexPropertyBuilder Builder { get; } + + /// + /// Gets the associated complex type. + /// + new IConventionComplexType ComplexType { get; } + + /// + /// Sets a value indicating whether this property can contain . + /// + /// + /// A value indicating whether this property can contain . + /// to reset to default. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + bool? SetIsNullable(bool? nullable, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetIsNullableConfigurationSource(); +} diff --git a/src/EFCore/Metadata/IConventionComplexType.cs b/src/EFCore/Metadata/IConventionComplexType.cs new file mode 100644 index 00000000000..bd2dcb09a38 --- /dev/null +++ b/src/EFCore/Metadata/IConventionComplexType.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the type of a complex property of a structural type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Model building conventions for more information and examples. +/// +/// +public interface IConventionComplexType : IReadOnlyComplexType, IConventionTypeBase +{ + /// + /// Gets the builder that can be used to configure this property. + /// + /// If the property has been removed from the model. + new IConventionComplexTypeBuilder Builder { get; } + + /// + /// Gets the associated property. + /// + new IConventionComplexProperty ComplexProperty { get; } +} diff --git a/src/EFCore/Metadata/IConventionEntityType.cs b/src/EFCore/Metadata/IConventionEntityType.cs index bdef687dd02..86cf54d541b 100644 --- a/src/EFCore/Metadata/IConventionEntityType.cs +++ b/src/EFCore/Metadata/IConventionEntityType.cs @@ -26,11 +26,6 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The configuration source. ConfigurationSource GetConfigurationSource(); - /// - /// Gets the model this entity belongs to. - /// - new IConventionModel Model { get; } - /// /// Gets the builder that can be used to configure this entity type. /// @@ -48,21 +43,6 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// bool IsKeyless { get; } - /// - /// Sets the change tracking strategy to use for this entity type. This strategy indicates how the - /// context detects changes to properties for an instance of the entity type. - /// - /// The strategy to use. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - ChangeTrackingStrategy? SetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetChangeTrackingStrategyConfigurationSource(); - /// /// Sets the LINQ expression filter automatically applied to queries for this entity type. /// @@ -148,13 +128,13 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas IConventionEntityType? SetBaseType(IConventionEntityType? entityType, bool fromDataAnnotation = false); /// - /// Returns the configuration source for the BaseType property. + /// Returns the configuration source for the property. /// - /// The configuration source for the BaseType property. + /// The configuration source for the property. ConfigurationSource? GetBaseTypeConfigurationSource(); /// - /// Gets all types in the model from which a given entity type derives, starting with the root. + /// Gets all types in the model from which this entity type derives, starting with the root. /// /// /// The base types. @@ -163,7 +143,7 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas => GetAllBaseTypesAscending().Reverse(); /// - /// Gets all types in the model from which a given entity type derives, starting with the closest one. + /// Gets all types in the model from which this entity type derives, starting with the closest one. /// /// /// The base types. @@ -172,21 +152,21 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas => GetAllBaseTypesInclusiveAscending().Skip(1); /// - /// Returns all base types of the given , including the type itself, top to bottom. + /// Returns all base types of this entity type, including the type itself, top to bottom. /// /// Base types. new IEnumerable GetAllBaseTypesInclusive() => GetAllBaseTypesInclusiveAscending().Reverse(); /// - /// Returns all base types of the given entity type, including the type itself, bottom to top. + /// Returns all base types of this entity type, including the type itself, bottom to top. /// /// Base types. new IEnumerable GetAllBaseTypesInclusiveAscending() => ((IReadOnlyEntityType)this).GetAllBaseTypesInclusiveAscending().Cast(); /// - /// Gets all types in the model that derive from a given entity type. + /// Gets all types in the model that derive from this entity type. /// /// The derived types. new IEnumerable GetDerivedTypes() @@ -200,17 +180,17 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas => ((IReadOnlyEntityType)this).GetDerivedTypesInclusive().Cast(); /// - /// Gets all types in the model that directly derive from a given entity type. + /// Gets all types in the model that directly derive from this entity type. /// /// The derived types. new IEnumerable GetDirectlyDerivedTypes() => ((IReadOnlyEntityType)this).GetDirectlyDerivedTypes().Cast(); /// - /// Gets the root base type for a given entity type. + /// Gets the root base type for this entity type. /// /// - /// The root base type. If the given entity type is not a derived type, then the same entity type is returned. + /// The root base type. If this entity type is not a derived type, then the same entity type is returned. /// new IConventionEntityType GetRootType() => (IConventionEntityType)((IReadOnlyEntityType)this).GetRootType(); @@ -586,6 +566,7 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// Adds a new skip navigation property to this entity type. /// /// The name of the skip navigation property to add. + /// The navigation type. /// /// /// The corresponding CLR type member or for a shadow navigation. @@ -603,6 +584,7 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The newly created skip navigation property. IConventionSkipNavigation? AddSkipNavigation( string name, + Type? navigationType, MemberInfo? memberInfo, IConventionEntityType targetEntityType, bool collection, @@ -779,190 +761,6 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// The removed index, or if the index was not found. IConventionIndex? RemoveIndex(IReadOnlyIndex index); - /// - /// Adds a property to this entity type. - /// - /// The corresponding member on the entity class. - /// Indicates whether the configuration was specified using a data annotation. - /// The newly created property. - [RequiresUnreferencedCode("Currently used only in tests")] - IConventionProperty? AddProperty(MemberInfo memberInfo, bool fromDataAnnotation = false) - => AddProperty( - memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), - memberInfo, setTypeConfigurationSource: true, fromDataAnnotation); - - /// - /// Adds a property to this entity type. - /// - /// The name of the property to add. - /// Indicates whether the configuration was specified using a data annotation. - /// The newly created property. - IConventionProperty? AddProperty(string name, bool fromDataAnnotation = false); - - /// - /// Adds a property to this entity type. - /// - /// The name of the property to add. - /// The type of value the property will hold. - /// Indicates whether the type configuration source should be set. - /// Indicates whether the configuration was specified using a data annotation. - /// The newly created property. - IConventionProperty? AddProperty( - string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - bool setTypeConfigurationSource = true, - bool fromDataAnnotation = false); - - /// - /// Adds a property to this entity type. - /// - /// The name of the property to add. - /// The type of value the property will hold. - /// - /// - /// The corresponding CLR type member or for a shadow property. - /// - /// - /// An indexer with a parameter and return type can be used. - /// - /// - /// Indicates whether the type configuration source should be set. - /// Indicates whether the configuration was specified using a data annotation. - /// The newly created property. - IConventionProperty? AddProperty( - string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - MemberInfo? memberInfo, - bool setTypeConfigurationSource = true, - bool fromDataAnnotation = false); - - /// - /// Adds a property backed by and indexer to this entity type. - /// - /// The name of the property to add. - /// The type of value the property will hold. - /// Indicates whether the type configuration source should be set. - /// Indicates whether the configuration was specified using a data annotation. - /// The newly created property. - IConventionProperty? AddIndexerProperty( - string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - bool setTypeConfigurationSource = true, - bool fromDataAnnotation = false) - { - var indexerPropertyInfo = FindIndexerPropertyInfo(); - if (indexerPropertyInfo == null) - { - throw new InvalidOperationException( - CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); - } - - return AddProperty(name, propertyType, indexerPropertyInfo, setTypeConfigurationSource, fromDataAnnotation); - } - - /// - /// Gets the property with a given name. Returns if no property with the given name is defined. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The name of the property. - /// The property, or if none is found. - new IConventionProperty? FindProperty(string name); - - /// - /// Gets the properties defined on this entity type. - /// - /// - /// This API only returns scalar properties and does not return navigation properties. Use - /// to get navigation properties. - /// - /// The properties defined on this entity type. - new IEnumerable GetProperties(); - - /// - /// Gets a property on the given entity type. Returns if no property is found. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The property on the entity class. - /// The property, or if none is found. - new IConventionProperty? FindProperty(MemberInfo memberInfo) - => (IConventionProperty?)((IReadOnlyEntityType)this).FindProperty(memberInfo); - - /// - /// Finds matching properties on the given entity type. Returns if any property is not found. - /// - /// - /// This API only finds scalar properties and does not find navigation or service properties. - /// - /// The property names. - /// The properties, or if any property is not found. - new IReadOnlyList? FindProperties(IReadOnlyList propertyNames) - => (IReadOnlyList?)((IReadOnlyEntityType)this).FindProperties(propertyNames); - - /// - /// Gets a property with the given name. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The property name. - /// The property. - new IConventionProperty GetProperty(string name) - => (IConventionProperty)((IReadOnlyEntityType)this).GetProperty(name); - - /// - /// Finds a property declared on the type with the given name. - /// Does not return properties defined on a base type. - /// - /// The property name. - /// The property, or if none is found. - new IConventionProperty? FindDeclaredProperty(string name) - => (IConventionProperty?)((IReadOnlyEntityType)this).FindDeclaredProperty(name); - - /// - /// Gets all non-navigation properties declared on this entity type. - /// - /// - /// This method does not return properties declared on base types. - /// It is useful when iterating over all entity types to avoid processing the same property more than once. - /// Use to also return properties declared on base types. - /// - /// Declared non-navigation properties. - new IEnumerable GetDeclaredProperties() - => ((IReadOnlyEntityType)this).GetDeclaredProperties().Cast(); - - /// - /// Gets all non-navigation properties declared on the types derived from this entity type. - /// - /// - /// This method does not return properties declared on the given entity type itself. - /// Use to return properties declared on this - /// and base entity typed types. - /// - /// Derived non-navigation properties. - new IEnumerable GetDerivedProperties() - => ((IReadOnlyEntityType)this).GetDerivedProperties().Cast(); - - /// - /// Removes a property from this entity type. - /// - /// The name of the property to remove. - /// The property that was removed. - IConventionProperty? RemoveProperty(string name); - - /// - /// Removes a property from this entity type. - /// - /// The property to remove. - /// The removed property, or if the property was not found. - IConventionProperty? RemoveProperty(IReadOnlyProperty property); - /// /// Adds a service property to this entity type. /// @@ -1062,4 +860,25 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// or the existing trigger was configured from a higher source. /// IConventionTrigger? RemoveTrigger(string name); + + /// + /// Sets the to use for navigations of this entity type. + /// + /// + /// Note that individual navigations can override this access mode. The value set here will + /// be used for any navigation for which no override has been specified. + /// + /// The , or to clear the mode set. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + PropertyAccessMode? SetNavigationAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false) + => (PropertyAccessMode?)SetOrRemoveAnnotation( + CoreAnnotationNames.NavigationAccessMode, propertyAccessMode, fromDataAnnotation)?.Value; + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetNavigationAccessModeConfigurationSource() + => FindAnnotation(CoreAnnotationNames.NavigationAccessMode)?.GetConfigurationSource(); } diff --git a/src/EFCore/Metadata/IConventionProperty.cs b/src/EFCore/Metadata/IConventionProperty.cs index 9cd79384d95..a2674a41936 100644 --- a/src/EFCore/Metadata/IConventionProperty.cs +++ b/src/EFCore/Metadata/IConventionProperty.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Represents a scalar property of an entity type. +/// Represents a scalar property of a structural type. /// /// /// @@ -27,9 +27,10 @@ public interface IConventionProperty : IReadOnlyProperty, IConventionPropertyBas new IConventionPropertyBuilder Builder { get; } /// - /// Gets the type that this property belongs to. + /// Gets the entity type that this property belongs to. /// - new IConventionEntityType DeclaringEntityType { get; } + [Obsolete("Use DeclaringType and cast to IConventionEntityType or IConventionComplexType")] + new IConventionEntityType DeclaringEntityType => (IConventionEntityType)DeclaringType; /// /// Returns the configuration source for . @@ -96,26 +97,12 @@ public interface IConventionProperty : IReadOnlyProperty, IConventionPropertyBas /// The configuration source for . ConfigurationSource? GetIsConcurrencyTokenConfigurationSource(); - /// - /// Sets the sentinel value that indicates that this property is not set. - /// - /// The sentinel value. - /// Indicates whether the configuration was specified using a data annotation. - /// The configured value. - object? SetSentinel(object? sentinel, bool fromDataAnnotation = false); - - /// - /// Returns the configuration source for . - /// - /// The configuration source for . - ConfigurationSource? GetSentinelConfigurationSource(); - /// /// Returns a value indicating whether the property was created implicitly and isn't based on the CLR model. /// /// A value indicating whether the property was created implicitly and isn't based on the CLR model. bool IsImplicitlyCreated() - => (IsShadowProperty() || (DeclaringEntityType.IsPropertyBag && IsIndexerProperty())) + => (IsShadowProperty() || (DeclaringType.IsPropertyBag && IsIndexerProperty())) && GetConfigurationSource() == ConfigurationSource.Convention; /// @@ -132,7 +119,7 @@ bool IsImplicitlyCreated() /// /// The list of all associated principal properties including the given property. new IReadOnlyList GetPrincipals() - => ((IReadOnlyProperty)this).GetPrincipals().Cast().ToList(); + => GetPrincipals(); /// /// Gets all foreign keys that use this property (including composite foreign keys in which this property @@ -303,6 +290,20 @@ bool IsImplicitlyCreated() /// The configuration source for . ConfigurationSource? GetAfterSaveBehaviorConfigurationSource(); + /// + /// Sets the sentinel value that indicates that this property is not set. + /// + /// The sentinel value. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + object? SetSentinel(object? sentinel, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetSentinelConfigurationSource(); + /// /// Sets the factory to use for generating values for this property, or to clear any previously set factory. /// @@ -316,8 +317,8 @@ bool IsImplicitlyCreated() /// /// Indicates whether the configuration was specified using a data annotation. /// The configured value. - Func? SetValueGeneratorFactory( - Func? valueGeneratorFactory, + Func? SetValueGeneratorFactory( + Func? valueGeneratorFactory, bool fromDataAnnotation = false); /// @@ -334,8 +335,7 @@ bool IsImplicitlyCreated() /// Indicates whether the configuration was specified using a data annotation. /// The configured value. Type? SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] - Type? valueGeneratorFactory, + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactory, bool fromDataAnnotation = false); /// @@ -361,8 +361,7 @@ bool IsImplicitlyCreated() /// Indicates whether the configuration was specified using a data annotation. /// The configured value. Type? SetValueConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? converterType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, bool fromDataAnnotation = false); /// @@ -403,8 +402,7 @@ bool IsImplicitlyCreated() /// The configured value. [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? SetValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, bool fromDataAnnotation = false); /// @@ -431,8 +429,7 @@ bool IsImplicitlyCreated() /// The configured value. [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? SetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, bool fromDataAnnotation = false); /// diff --git a/src/EFCore/Metadata/IConventionTypeBase.cs b/src/EFCore/Metadata/IConventionTypeBase.cs index 06f982987ea..3a2d058eb4c 100644 --- a/src/EFCore/Metadata/IConventionTypeBase.cs +++ b/src/EFCore/Metadata/IConventionTypeBase.cs @@ -1,6 +1,7 @@ // 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 Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata; @@ -24,6 +25,18 @@ public interface IConventionTypeBase : IReadOnlyTypeBase, IConventionAnnotatable /// new IConventionModel Model { get; } + /// + /// Gets the builder that can be used to configure this type. + /// + /// If the type has been removed from the model. + new IConventionTypeBaseBuilder Builder { get; } + + /// + /// Gets this entity type or the one on which the complex property chain is declared. + /// + new IConventionEntityType FundamentalEntityType + => (IConventionEntityType)this; + /// /// Marks the given member name as ignored, preventing conventions from adding a matching property /// or navigation to the type. @@ -65,44 +78,380 @@ bool IsIgnored(string memberName) => FindIgnoredConfigurationSource(memberName) != null; /// - /// Sets the to use for properties of this type. + /// Adds a property to this entity type. + /// + /// The corresponding member on the entity class. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + [RequiresUnreferencedCode("Currently used only in tests")] + IConventionProperty? AddProperty(MemberInfo memberInfo, bool fromDataAnnotation = false) + => AddProperty( + memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), + memberInfo, setTypeConfigurationSource: true, fromDataAnnotation); + + /// + /// Adds a property to this entity type. + /// + /// The name of the property to add. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionProperty? AddProperty(string name, bool fromDataAnnotation = false); + + /// + /// Adds a property to this entity type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// Indicates whether the type configuration source should be set. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionProperty? AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + bool setTypeConfigurationSource = true, + bool fromDataAnnotation = false); + + /// + /// Adds a property to this entity type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// + /// + /// The corresponding CLR type member. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// Indicates whether the type configuration source should be set. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionProperty? AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo memberInfo, + bool setTypeConfigurationSource = true, + bool fromDataAnnotation = false); + + /// + /// Adds a property backed by and indexer to this entity type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// Indicates whether the type configuration source should be set. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionProperty? AddIndexerProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + bool setTypeConfigurationSource = true, + bool fromDataAnnotation = false) + { + var indexerPropertyInfo = FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); + } + + return AddProperty(name, propertyType, indexerPropertyInfo, setTypeConfigurationSource, fromDataAnnotation); + } + + /// + /// Gets the property with a given name. Returns if no property with the given name is defined. /// /// - /// Note that individual properties and navigations can override this access mode. The value set here will - /// be used for any property or navigation for which no override has been specified. + /// This API only finds scalar properties and does not find navigation, complex or service properties. /// - /// The , or to clear the mode set. + /// The name of the property. + /// The property, or if none is found. + new IConventionProperty? FindProperty(string name); + + /// + /// Gets a property on the given entity type. Returns if no property is found. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// The property on the entity class. + /// The property, or if none is found. + new IConventionProperty? FindProperty(MemberInfo memberInfo) + => (IConventionProperty?)((IReadOnlyTypeBase)this).FindProperty(memberInfo); + + /// + /// Finds matching properties on the given entity type. Returns if any property is not found. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// The property names. + /// The properties, or if any property is not found. + new IReadOnlyList? FindProperties(IReadOnlyList propertyNames) + => (IReadOnlyList?)((IReadOnlyTypeBase)this).FindProperties(propertyNames); + + /// + /// Gets a property with the given name. + /// + /// + /// This API only finds scalar properties and does not find navigation properties. + /// + /// The property name. + /// The property. + new IConventionProperty GetProperty(string name) + => (IConventionProperty)((IReadOnlyTypeBase)this).GetProperty(name); + + /// + /// Finds a property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// The property name. + /// The property, or if none is found. + new IConventionProperty? FindDeclaredProperty(string name) + => (IConventionProperty?)((IReadOnlyTypeBase)this).FindDeclaredProperty(name); + + /// + /// Gets all scalar properties declared on this type. + /// + /// + /// This method does not return properties declared on base types. + /// It is useful when iterating over all entity types to avoid processing the same property more than once. + /// Use to also return properties declared on base types. + /// + /// Declared non-navigation properties. + new IEnumerable GetDeclaredProperties(); + + /// + /// Gets all scalar properties declared on the types derived from this type. + /// + /// + /// This method does not return properties declared on the given type itself. + /// Use to return properties declared on this + /// and base entity typed types. + /// + /// Derived non-navigation properties. + new IEnumerable GetDerivedProperties() + => ((IReadOnlyTypeBase)this).GetDerivedProperties().Cast(); + + /// + /// Gets all scalar properties defined on this type. + /// + /// + /// This API only returns scalar properties and does not return navigation, complex or service properties. + /// + /// The properties defined on this type. + new IEnumerable GetProperties(); + + /// + /// Removes a property from this type. + /// + /// The name of the property to remove. + /// The property that was removed. + IConventionProperty? RemoveProperty(string name); + + /// + /// Removes a property from this type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IConventionProperty? RemoveProperty(IReadOnlyProperty property); + + /// + /// Adds a property to this type. + /// + /// The corresponding member on the type. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + [RequiresUnreferencedCode("Currently used only in tests")] + IConventionComplexProperty? AddComplexProperty(MemberInfo memberInfo, bool collection = false, bool fromDataAnnotation = false) + => AddComplexProperty( + memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), + memberInfo, memberInfo.GetMemberType(), collection, fromDataAnnotation); + + /// + /// Adds a property to this type. + /// + /// The name of the property to add. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexProperty(string name, bool collection = false, bool fromDataAnnotation = false); + + /// + /// Adds a property to this type. + /// + /// The name of the property to add. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, + bool fromDataAnnotation = false); + + /// + /// Adds a property to this type. + /// + /// The name of the property to add. + /// The property type. + /// + /// + /// The corresponding CLR type member. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, + bool fromDataAnnotation = false); + + /// + /// Adds a property backed by and indexer to this type. + /// + /// The name of the property to add. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// Indicates whether the configuration was specified using a data annotation. + /// The newly created property. + IConventionComplexProperty? AddComplexIndexerProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, + bool fromDataAnnotation = false) + { + var indexerPropertyInfo = FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); + } + + return AddComplexProperty(name, propertyType, indexerPropertyInfo, complexType, fromDataAnnotation); + } + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// + /// This API only finds complex properties and does not find navigation, scalar or service properties. + /// + /// The name of the property. + /// The property, or if none is found. + new IConventionComplexProperty? FindComplexProperty(string name); + + /// + /// Gets a complex property with the given member info. Returns if no property is found. + /// + /// + /// This API only finds complex properties and does not find navigation, scalar or service properties. + /// + /// The member on the entity class. + /// The property, or if none is found. + new IConventionComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IConventionComplexProperty?)((IReadOnlyEntityType)this).FindComplexProperty(memberInfo); + + /// + /// Finds a property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// + /// This API only finds complex properties and does not find navigation, scalar or service properties. + /// + /// The property name. + /// The property, or if none is found. + new IConventionComplexProperty? FindDeclaredComplexProperty(string name) + => (IConventionComplexProperty?)((IReadOnlyEntityType)this).FindDeclaredComplexProperty(name); + + /// + /// Gets the complex properties defined on this type. + /// + /// + /// This API only returns complex properties and does not find navigation, scalar or service properties. + /// + /// The complex properties defined on this type. + new IEnumerable GetComplexProperties(); + + /// + /// Gets the complex properties declared on this type. + /// + /// Declared complex properties. + new IEnumerable GetDeclaredComplexProperties(); + + /// + /// Gets the complex properties declared on the types derived from this type. + /// + /// + /// This method does not return complex properties declared on the given type itself. + /// Use to return complex properties declared on this + /// and base typed types. + /// + /// Derived complex properties. + new IEnumerable GetDerivedComplexProperties() + => ((IReadOnlyEntityType)this).GetDerivedComplexProperties().Cast(); + + /// + /// Removes a property from this type. + /// + /// The name of the property to remove. + /// The property that was removed. + IConventionComplexProperty? RemoveComplexProperty(string name); + + /// + /// Removes a property from this type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IConventionComplexProperty? RemoveComplexProperty(IConventionComplexProperty property); + + /// + /// Sets the change tracking strategy to use for this type. This strategy indicates how the + /// context detects changes to properties for an instance of the type. + /// + /// The strategy to use. /// Indicates whether the configuration was specified using a data annotation. /// The configured value. - PropertyAccessMode? SetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false) - => (PropertyAccessMode?)SetOrRemoveAnnotation(CoreAnnotationNames.PropertyAccessMode, propertyAccessMode, fromDataAnnotation) - ?.Value; + ChangeTrackingStrategy? SetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy, bool fromDataAnnotation = false); /// - /// Returns the configuration source for . + /// Returns the configuration source for . /// - /// The configuration source for . - ConfigurationSource? GetPropertyAccessModeConfigurationSource() - => FindAnnotation(CoreAnnotationNames.PropertyAccessMode)?.GetConfigurationSource(); + /// The configuration source for . + ConfigurationSource? GetChangeTrackingStrategyConfigurationSource(); /// - /// Sets the to use for navigations of this entity type. + /// Sets the to use for properties of this type. /// /// - /// Note that individual navigations can override this access mode. The value set here will - /// be used for any navigation for which no override has been specified. + /// Note that individual properties and navigations can override this access mode. The value set here will + /// be used for any property or navigation for which no override has been specified. /// /// The , or to clear the mode set. /// Indicates whether the configuration was specified using a data annotation. /// The configured value. - PropertyAccessMode? SetNavigationAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false) - => (PropertyAccessMode?)SetOrRemoveAnnotation( - CoreAnnotationNames.NavigationAccessMode, propertyAccessMode, fromDataAnnotation)?.Value; + PropertyAccessMode? SetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation = false) + => (PropertyAccessMode?)SetOrRemoveAnnotation(CoreAnnotationNames.PropertyAccessMode, propertyAccessMode, fromDataAnnotation) + ?.Value; /// - /// Returns the configuration source for . + /// Returns the configuration source for . /// - /// The configuration source for . - ConfigurationSource? GetNavigationAccessModeConfigurationSource() - => FindAnnotation(CoreAnnotationNames.NavigationAccessMode)?.GetConfigurationSource(); + /// The configuration source for . + ConfigurationSource? GetPropertyAccessModeConfigurationSource() + => FindAnnotation(CoreAnnotationNames.PropertyAccessMode)?.GetConfigurationSource(); } diff --git a/src/EFCore/Metadata/IEntityType.cs b/src/EFCore/Metadata/IEntityType.cs index 31ec35acb13..a4f96f176cd 100644 --- a/src/EFCore/Metadata/IEntityType.cs +++ b/src/EFCore/Metadata/IEntityType.cs @@ -74,27 +74,27 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase => ((IReadOnlyEntityType)this).GetAllBaseTypesInclusiveAscending().Cast(); /// - /// Gets all types in the model that derive from a given entity type. + /// Gets all types in the model that derive from this entity type. /// /// The derived types. new IEnumerable GetDerivedTypes() => ((IReadOnlyEntityType)this).GetDerivedTypes().Cast(); /// - /// Returns all derived types of the given , including the type itself. + /// Returns all derived types of this entity type, including the type itself. /// /// Derived types. new IEnumerable GetDerivedTypesInclusive() => ((IReadOnlyEntityType)this).GetDerivedTypesInclusive().Cast(); /// - /// Gets all types in the model that directly derive from a given entity type. + /// Gets all types in the model that directly derive from this entity type. /// /// The derived types. new IEnumerable GetDirectlyDerivedTypes(); /// - /// Returns all the derived types of the given , including the type itself, + /// Returns all the derived types of this entity type, including the type itself, /// which are not . /// /// Non-abstract, derived types. @@ -154,7 +154,7 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// /// This method does not return keys declared on base types. /// It is useful when iterating over all entity types to avoid processing the same key more than once. - /// Use to also return keys declared on base types. + /// Use to also return keys declared on base types. /// /// Declared keys. new IEnumerable GetDeclaredKeys(); @@ -423,94 +423,6 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// The indexes defined on this entity type. new IEnumerable GetIndexes(); - /// - /// Gets a property on the given entity type. Returns if no property is found. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The property on the entity class. - /// The property, or if none is found. - new IProperty? FindProperty(MemberInfo memberInfo) - => (IProperty?)((IReadOnlyEntityType)this).FindProperty(memberInfo); - - /// - /// Gets the property with a given name. Returns if no property with the given name is defined. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The name of the property. - /// The property, or if none is found. - new IProperty? FindProperty(string name); - - /// - /// Finds matching properties on the given entity type. Returns if any property is not found. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. - /// - /// The property names. - /// The properties, or if any property is not found. - new IReadOnlyList? FindProperties( - IReadOnlyList propertyNames) - => (IReadOnlyList?)((IReadOnlyEntityType)this).FindProperties(propertyNames); - - /// - /// Gets a property with the given name. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The property name. - /// The property. - new IProperty GetProperty(string name) - => (IProperty)((IReadOnlyEntityType)this).GetProperty(name); - - /// - /// Finds a property declared on the type with the given name. - /// Does not return properties defined on a base type. - /// - /// The property name. - /// The property, or if none is found. - new IProperty? FindDeclaredProperty(string name); - - /// - /// Gets all non-navigation properties declared on the given . - /// - /// - /// This method does not return properties declared on base types. - /// It is useful when iterating over all entity types to avoid processing the same property more than once. - /// Use to also return properties declared on base types. - /// - /// Declared non-navigation properties. - new IEnumerable GetDeclaredProperties(); - - /// - /// Gets all non-navigation properties declared on the types derived from this entity type. - /// - /// - /// This method does not return properties declared on the given entity type itself. - /// Use to return properties declared on this - /// and base entity typed types. - /// - /// Derived non-navigation properties. - new IEnumerable GetDerivedProperties() - => ((IReadOnlyEntityType)this).GetDerivedProperties().Cast(); - - /// - /// Gets the properties defined on this entity type. - /// - /// - /// This API only returns scalar properties and does not return navigation properties. Use - /// to get navigation properties. - /// - /// The properties defined on this entity type. - new IEnumerable GetProperties(); - /// /// Returns the properties contained in foreign keys. /// diff --git a/src/EFCore/Metadata/IMutableComplexProperty.cs b/src/EFCore/Metadata/IMutableComplexProperty.cs new file mode 100644 index 00000000000..46822c630dc --- /dev/null +++ b/src/EFCore/Metadata/IMutableComplexProperty.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a complex property of a structural type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Modeling entity types and relationships for more information and +/// examples. +/// +/// +public interface IMutableComplexProperty : IReadOnlyComplexProperty, IMutablePropertyBase +{ + /// + /// Gets the associated complex type. + /// + new IMutableComplexType ComplexType { get; } + + /// + /// Gets or sets a value indicating whether this property can contain . + /// + new bool IsNullable { get; set; } + + /// + bool IReadOnlyComplexProperty.IsNullable => + IsNullable; +} diff --git a/src/EFCore/Metadata/IMutableComplexType.cs b/src/EFCore/Metadata/IMutableComplexType.cs new file mode 100644 index 00000000000..d2a465f6b9a --- /dev/null +++ b/src/EFCore/Metadata/IMutableComplexType.cs @@ -0,0 +1,27 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the type of a complex property of a structural type. +/// +/// +/// +/// This interface is used during model creation and allows the metadata to be modified. +/// Once the model is built, represents a read-only view of the same metadata. +/// +/// +/// See Modeling entity types and relationships for more information and +/// examples. +/// +/// +public interface IMutableComplexType : IReadOnlyComplexType, IMutableTypeBase +{ + /// + /// Gets the associated property. + /// + new IMutableComplexProperty ComplexProperty { get; } +} diff --git a/src/EFCore/Metadata/IMutableEntityType.cs b/src/EFCore/Metadata/IMutableEntityType.cs index a1823a5b51f..020be1fe0f5 100644 --- a/src/EFCore/Metadata/IMutableEntityType.cs +++ b/src/EFCore/Metadata/IMutableEntityType.cs @@ -18,11 +18,6 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// public interface IMutableEntityType : IReadOnlyEntityType, IMutableTypeBase { - /// - /// Gets the model this entity belongs to. - /// - new IMutableModel Model { get; } - /// /// Gets or sets the base type of this entity type. Returns if this is not a derived type in an inheritance /// hierarchy. @@ -43,13 +38,6 @@ public interface IMutableEntityType : IReadOnlyEntityType, IMutableTypeBase /// bool IsKeyless { get; set; } - /// - /// Sets the change tracking strategy to use for this entity type. This strategy indicates how the - /// context detects changes to properties for an instance of the entity type. - /// - /// The strategy to use. - void SetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy); - /// /// Sets the LINQ expression filter automatically applied to queries for this entity type. /// @@ -90,7 +78,7 @@ void RemoveDiscriminatorValue() => RemoveAnnotation(CoreAnnotationNames.DiscriminatorValue); /// - /// Gets all types in the model from which a given entity type derives, starting with the root. + /// Gets all types in the model from which this entity type derives, starting with the root. /// /// /// The base types. @@ -99,7 +87,7 @@ void RemoveDiscriminatorValue() => GetAllBaseTypesAscending().Reverse(); /// - /// Gets all types in the model from which a given entity type derives, starting with the closest one. + /// Gets all types in the model from which this entity type derives, starting with the closest one. /// /// /// The base types. @@ -115,14 +103,14 @@ void RemoveDiscriminatorValue() => ((IReadOnlyEntityType)this).GetAllBaseTypesInclusive().Cast(); /// - /// Returns all base types of the given entity type, including the type itself, bottom to top. + /// Returns all base types of this entity type, including the type itself, bottom to top. /// /// Base types. new IEnumerable GetAllBaseTypesInclusiveAscending() => ((IReadOnlyEntityType)this).GetAllBaseTypesInclusiveAscending().Cast(); /// - /// Gets all types in the model that derive from a given entity type. + /// Gets all types in the model that derive from this entity type. /// /// The derived types. new IEnumerable GetDerivedTypes() @@ -136,7 +124,7 @@ void RemoveDiscriminatorValue() => ((IReadOnlyEntityType)this).GetDerivedTypesInclusive().Cast(); /// - /// Gets all types in the model that directly derive from a given entity type. + /// Gets all types in the model that directly derive from this entity type. /// /// The derived types. new IEnumerable GetDirectlyDerivedTypes() @@ -505,6 +493,40 @@ IMutableSkipNavigation AddSkipNavigation( MemberInfo? memberInfo, IMutableEntityType targetEntityType, bool collection, + bool onDependent) + => AddSkipNavigation( + name, + navigationType: null, + memberInfo, + targetEntityType, + collection, + onDependent); + + /// + /// Adds a new skip navigation property to this entity type. + /// + /// The name of the skip navigation property to add. + /// The navigation type. + /// + /// + /// The corresponding CLR type member or for a shadow navigation. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// The entity type that the skip navigation property will hold an instance(s) of. + /// Whether the navigation property is a collection property. + /// + /// Whether the navigation property is defined on the dependent side of the underlying foreign key. + /// + /// The newly created skip navigation property. + IMutableSkipNavigation AddSkipNavigation( + string name, + Type? navigationType, + MemberInfo? memberInfo, + IMutableEntityType targetEntityType, + bool collection, bool onDependent); /// @@ -664,174 +686,6 @@ IMutableIndex AddIndex(IMutableProperty property, string name) /// The removed index, or if the index was not found. IMutableIndex? RemoveIndex(IReadOnlyIndex index); - /// - /// Adds a property to this entity type. - /// - /// The name of the property to add. - /// The type of value the property will hold. - /// - /// - /// The corresponding CLR type member or for a shadow property. - /// - /// - /// An indexer with a parameter and return type can be used. - /// - /// - /// The newly created property. - IMutableProperty AddProperty( - string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - MemberInfo? memberInfo); - - /// - /// Gets a property on the given entity type. Returns if no property is found. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The property on the entity class. - /// The property, or if none is found. - new IMutableProperty? FindProperty(MemberInfo memberInfo) - => (IMutableProperty?)((IReadOnlyEntityType)this).FindProperty(memberInfo); - - /// - /// Gets the property with a given name. Returns if no property with the given name is defined. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find - /// a navigation property. - /// - /// The name of the property. - /// The property, or if none is found. - new IMutableProperty? FindProperty(string name); - - /// - /// Finds matching properties on the given entity type. Returns if any property is not found. - /// - /// - /// This API only finds scalar properties and does not find navigation or service properties. - /// - /// The property names. - /// The properties, or if any property is not found. - new IReadOnlyList? FindProperties(IReadOnlyList propertyNames) - => (IReadOnlyList?)((IReadOnlyEntityType)this).FindProperties(propertyNames); - - /// - /// Finds a property declared on the type with the given name. - /// Does not return properties defined on a base type. - /// - /// The property name. - /// The property, or if none is found. - new IMutableProperty? FindDeclaredProperty(string name) - => (IMutableProperty?)((IReadOnlyEntityType)this).FindDeclaredProperty(name); - - /// - /// Gets a property with the given name. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The property name. - /// The property. - new IMutableProperty GetProperty(string name) - => (IMutableProperty)((IReadOnlyEntityType)this).GetProperty(name); - - /// - /// Adds a property to this entity type. - /// - /// The corresponding member on the entity class. - /// The newly created property. - [RequiresUnreferencedCode("Currently used only in tests")] - IMutableProperty AddProperty(MemberInfo memberInfo) - => AddProperty(memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), memberInfo); - - /// - /// Adds a property to this entity type. - /// - /// The name of the property to add. - /// The newly created property. - IMutableProperty AddProperty(string name); - - /// - /// Adds a property to this entity type. - /// - /// The name of the property to add. - /// The type of value the property will hold. - /// The newly created property. - IMutableProperty AddProperty(string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType); - - /// - /// Adds a property backed up by an indexer to this entity type. - /// - /// The name of the property to add. - /// The type of value the property will hold. - /// The newly created property. - IMutableProperty AddIndexerProperty( - string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType) - { - var indexerPropertyInfo = FindIndexerPropertyInfo(); - if (indexerPropertyInfo == null) - { - throw new InvalidOperationException( - CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); - } - - return AddProperty(name, propertyType, indexerPropertyInfo); - } - - /// - /// Gets all non-navigation properties declared on this entity type. - /// - /// - /// This method does not return properties declared on base types. - /// It is useful when iterating over all entity types to avoid processing the same property more than once. - /// Use to also return properties declared on base types. - /// - /// Declared non-navigation properties. - new IEnumerable GetDeclaredProperties() - => ((IReadOnlyEntityType)this).GetDeclaredProperties().Cast(); - - /// - /// Gets all non-navigation properties declared on the types derived from this entity type. - /// - /// - /// This method does not return properties declared on the given entity type itself. - /// Use to return properties declared on this - /// and base entity typed types. - /// - /// Derived non-navigation properties. - new IEnumerable GetDerivedProperties() - => ((IReadOnlyEntityType)this).GetDerivedProperties().Cast(); - - /// - /// Gets the properties defined on this entity type. - /// - /// - /// This API only returns scalar properties and does not return navigation properties. Use - /// to get navigation - /// properties. - /// - /// The properties defined on this entity type. - new IEnumerable GetProperties(); - - /// - /// Removes a property from this entity type. - /// - /// The name of the property to remove. - /// The removed property, or if the property was not found. - IMutableProperty? RemoveProperty(string name); - - /// - /// Removes a property from this entity type. - /// - /// The property to remove. - /// The removed property, or if the property was not found. - IMutableProperty? RemoveProperty(IReadOnlyProperty property); - /// /// Adds a service property to this entity type. /// @@ -926,4 +780,15 @@ IMutableProperty AddIndexerProperty( /// The removed trigger or if no trigger with the given name was found. /// IMutableTrigger? RemoveTrigger(string name); + + /// + /// Sets the to use for navigations of this entity type. + /// + /// + /// Note that individual navigations can override this access mode. The value set here will + /// be used for any navigation for which no override has been specified. + /// + /// The , or to clear the mode set. + void SetNavigationAccessMode(PropertyAccessMode? propertyAccessMode) + => SetOrRemoveAnnotation(CoreAnnotationNames.NavigationAccessMode, propertyAccessMode); } diff --git a/src/EFCore/Metadata/IMutableProperty.cs b/src/EFCore/Metadata/IMutableProperty.cs index 936c2616a91..fa4cabc33bb 100644 --- a/src/EFCore/Metadata/IMutableProperty.cs +++ b/src/EFCore/Metadata/IMutableProperty.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Represents a scalar property of an entity type. +/// Represents a scalar property of a structural type. /// /// /// @@ -22,9 +22,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata; public interface IMutableProperty : IReadOnlyProperty, IMutablePropertyBase { /// - /// Gets the type that this property belongs to. + /// Gets the entity type that this property belongs to. /// - new IMutableEntityType DeclaringEntityType { get; } + [Obsolete("Use DeclaringType and cast to IMutableEntityType or IMutableComplexType")] + new IMutableEntityType DeclaringEntityType => (IMutableEntityType)DeclaringType; /// /// Gets or sets a value indicating whether this property can contain . @@ -68,7 +69,7 @@ public interface IMutableProperty : IReadOnlyProperty, IMutablePropertyBase /// /// The list of all associated principal properties including the given property. new IReadOnlyList GetPrincipals() - => ((IReadOnlyProperty)this).GetPrincipals().Cast().ToList(); + => GetPrincipals(); /// /// Gets all foreign keys that use this property (including composite foreign keys in which this property @@ -189,7 +190,7 @@ public interface IMutableProperty : IReadOnlyProperty, IMutablePropertyBase /// A factory that will be used to create the value generator, or to /// clear any previously set factory. /// - void SetValueGeneratorFactory(Func? valueGeneratorFactory); + void SetValueGeneratorFactory(Func? valueGeneratorFactory); /// /// Sets the factory to use for generating values for this property, or to clear any previously set factory. @@ -203,8 +204,7 @@ public interface IMutableProperty : IReadOnlyProperty, IMutablePropertyBase /// clear any previously set factory. /// void SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] - Type? valueGeneratorFactory); + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactory); /// /// Sets the custom for this property. @@ -269,4 +269,16 @@ void SetProviderValueComparer( /// from the type mapping. /// void SetJsonValueReaderWriterType(Type? readerWriterType); + + /// + bool IReadOnlyProperty.IsNullable => + IsNullable; + + /// + ValueGenerated IReadOnlyProperty.ValueGenerated => + ValueGenerated; + + /// + bool IReadOnlyProperty.IsConcurrencyToken => + IsConcurrencyToken; } diff --git a/src/EFCore/Metadata/IMutableTypeBase.cs b/src/EFCore/Metadata/IMutableTypeBase.cs index 71d3685fb96..594fde8b388 100644 --- a/src/EFCore/Metadata/IMutableTypeBase.cs +++ b/src/EFCore/Metadata/IMutableTypeBase.cs @@ -1,6 +1,7 @@ // 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 Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata; @@ -25,6 +26,12 @@ public interface IMutableTypeBase : IReadOnlyTypeBase, IMutableAnnotatable /// new IMutableModel Model { get; } + /// + /// Gets this entity type or the one on which the complex property chain is declared. + /// + new IMutableEntityType FundamentalEntityType + => (IMutableEntityType)this; + /// /// Marks the given member name as ignored, preventing conventions from adding a matching property /// or navigation to the type. @@ -54,24 +61,342 @@ public interface IMutableTypeBase : IReadOnlyTypeBase, IMutableAnnotatable IEnumerable GetIgnoredMembers(); /// - /// Sets the to use for properties and navigations of this entity type. + /// Adds a property to this type. + /// + /// The corresponding member on the CLR type. + /// The newly created property. + [RequiresUnreferencedCode("Currently used only in tests")] + IMutableProperty AddProperty(MemberInfo memberInfo) + => AddProperty(memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), memberInfo); + + /// + /// Adds a property to this type. + /// + /// The name of the property to add. + /// The newly created property. + IMutableProperty AddProperty(string name); + + /// + /// Adds a property to this type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The newly created property. + IMutableProperty AddProperty(string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType); + + /// + /// Adds a property to this type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// + /// + /// The corresponding CLR type member. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// The newly created property. + IMutableProperty AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo memberInfo); + + /// + /// Adds a property backed up by an indexer to this type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The newly created property. + IMutableProperty AddIndexerProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType) + { + var indexerPropertyInfo = FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); + } + + return AddProperty(name, propertyType, indexerPropertyInfo); + } + + /// + /// Gets a property on the given type. Returns if no property is found. /// /// - /// Note that individual properties and navigations can override this access mode. The value set here will - /// be used for any property or navigation for which no override has been specified. + /// This API only finds scalar properties and does not find navigation, complex or service properties. /// - /// The , or to clear the mode set. - void SetPropertyAccessMode(PropertyAccessMode? propertyAccessMode) - => SetOrRemoveAnnotation(CoreAnnotationNames.PropertyAccessMode, propertyAccessMode); + /// The property on the class. + /// The property, or if none is found. + new IMutableProperty? FindProperty(MemberInfo memberInfo) + => (IMutableProperty?)((IReadOnlyTypeBase)this).FindProperty(memberInfo); + + /// + /// Gets the property with a given name. Returns if no property with the given name is defined. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// a navigation property. + /// + /// The name of the property. + /// The property, or if none is found. + new IMutableProperty? FindProperty(string name); + + /// + /// Finds matching properties on this type. Returns if any property is not found. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// The property names. + /// The properties, or if any property is not found. + new IReadOnlyList? FindProperties(IReadOnlyList propertyNames) + => (IReadOnlyList?)((IReadOnlyTypeBase)this).FindProperties(propertyNames); + + /// + /// Finds a property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// The property name. + /// The property, or if none is found. + new IMutableProperty? FindDeclaredProperty(string name) + => (IMutableProperty?)((IReadOnlyTypeBase)this).FindDeclaredProperty(name); + + /// + /// Gets a property with the given name. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// The property name. + /// The property. + new IMutableProperty GetProperty(string name) + => (IMutableProperty)((IReadOnlyTypeBase)this).GetProperty(name); + + /// + /// Gets all scalar properties declared on this type. + /// + /// + /// This method does not return properties declared on base types. + /// It is useful when iterating over all types to avoid processing the same property more than once. + /// Use to also return properties declared on base types. + /// + /// Declared scalar properties. + new IEnumerable GetDeclaredProperties(); + + /// + /// Gets all scalar properties declared on the types derived from this type. + /// + /// + /// This method does not return properties declared on the given type itself. + /// Use to return properties declared on this + /// and base typed types. + /// + /// Derived scalar properties. + new IEnumerable GetDerivedProperties() + => ((IReadOnlyTypeBase)this).GetDerivedProperties().Cast(); + + /// + /// Gets all scalar properties defined on this type. + /// + /// + /// This API only returns scalar properties and does not return navigation, complex or service properties. + /// properties. + /// + /// The properties defined on this type. + new IEnumerable GetProperties(); + + /// + /// Removes a property from this type. + /// + /// The name of the property to remove. + /// The removed property, or if the property was not found. + IMutableProperty? RemoveProperty(string name); + + /// + /// Removes a property from this type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IMutableProperty? RemoveProperty(IReadOnlyProperty property); + + /// + /// Adds a complex property to this type. + /// + /// The corresponding member on the class. + /// Indicates whether the property represents a collection. + /// The newly created property. + [RequiresUnreferencedCode("Currently used only in tests")] + IMutableComplexProperty AddComplexProperty(MemberInfo memberInfo, bool collection = false) + => AddComplexProperty(memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), + collection ? memberInfo.GetMemberType().GetSequenceType() : memberInfo.GetMemberType(), collection); + + /// + /// Adds a complex property to this type. + /// + /// The name of the property to add. + /// Indicates whether the property represents a collection. + /// The newly created property. + IMutableComplexProperty AddComplexProperty(string name, bool collection = false); /// - /// Sets the to use for navigations of this entity type. + /// Adds a complex property to this type. + /// + /// The name of the property to add. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// The newly created property. + IMutableComplexProperty AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false); + + /// + /// Adds a complex property to this type. + /// + /// The name of the property to add. + /// The property type. + /// + /// + /// The corresponding CLR type member. + /// + /// + /// An indexer with a parameter and return type can be used. + /// + /// + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// The newly created property. + IMutableComplexProperty AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false); + + /// + /// Adds a complex property backed up by an indexer to this type. + /// + /// The name of the property to add. + /// The property type. + /// The type of value the property will hold. + /// Indicates whether the property represents a collection. + /// The newly created property. + IMutableComplexProperty AddComplexIndexerProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false) + { + var indexerPropertyInfo = FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); + } + + return AddComplexProperty(name, propertyType, indexerPropertyInfo, complexType, collection); + } + + /// + /// Gets a complex property on the given type. Returns if no property is found. + /// + /// + /// This API only finds complex properties and does not find navigation, scalar or service properties. + /// + /// The member on the CLR type. + /// The property, or if none is found. + new IMutableComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IMutableComplexProperty?)((IReadOnlyEntityType)this).FindComplexProperty(memberInfo); + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. /// /// - /// Note that individual navigations can override this access mode. The value set here will - /// be used for any navigation for which no override has been specified. + /// This API only finds complex properties and does not find navigation, scalar or service properties. + /// + /// The name of the property. + /// The property, or if none is found. + new IMutableComplexProperty? FindComplexProperty(string name); + + /// + /// Finds a complex property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// + /// This API only finds complex properties and does not find navigation, scalar or service properties. + /// + /// The property name. + /// The property, or if none is found. + new IMutableComplexProperty? FindDeclaredComplexProperty(string name) + => (IMutableComplexProperty?)((IReadOnlyEntityType)this).FindDeclaredComplexProperty(name); + + /// + /// Gets all complex properties declared on this type. + /// + /// + /// This method does not return properties declared on base types. + /// It is useful when iterating over all types to avoid processing the same property more than once. + /// Use to also return properties declared on base types. + /// + /// Declared complex properties. + new IEnumerable GetDeclaredComplexProperties(); + + /// + /// Gets all complex properties declared on the types derived from this type. + /// + /// + /// This method does not return properties declared on the given type itself. + /// Use to return properties declared on this + /// and base typed types. + /// + /// Derived complex properties. + new IEnumerable GetDerivedComplexProperties() + => ((IReadOnlyEntityType)this).GetDerivedComplexProperties().Cast(); + + /// + /// Gets the properties defined on this type. + /// + /// + /// This API only returns complex properties and does not find navigation, scalar or service properties. + /// + /// The properties defined on this type. + new IEnumerable GetComplexProperties(); + + /// + /// Removes a property from this type. + /// + /// The name of the property to remove. + /// The removed property, or if the property was not found. + IMutableComplexProperty? RemoveComplexProperty(string name); + + /// + /// Removes a property from this type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IMutableComplexProperty? RemoveComplexProperty(IReadOnlyProperty property); + + /// + /// Sets the change tracking strategy to use for this type. This strategy indicates how the + /// context detects changes to properties for an instance of the type. + /// + /// The strategy to use. + void SetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy); + + /// + /// Sets the to use for properties and navigations of this type. + /// + /// + /// Note that individual properties and navigations can override this access mode. The value set here will + /// be used for any property or navigation for which no override has been specified. /// /// The , or to clear the mode set. - void SetNavigationAccessMode(PropertyAccessMode? propertyAccessMode) - => SetOrRemoveAnnotation(CoreAnnotationNames.NavigationAccessMode, propertyAccessMode); + void SetPropertyAccessMode(PropertyAccessMode? propertyAccessMode) + => SetOrRemoveAnnotation(CoreAnnotationNames.PropertyAccessMode, propertyAccessMode); } diff --git a/src/EFCore/Metadata/IProperty.cs b/src/EFCore/Metadata/IProperty.cs index 40ee5b4c195..8b7bc5337c2 100644 --- a/src/EFCore/Metadata/IProperty.cs +++ b/src/EFCore/Metadata/IProperty.cs @@ -1,13 +1,12 @@ // 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 Microsoft.EntityFrameworkCore.Internal; namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Represents a scalar property of an entity type. +/// Represents a scalar property of a structural type. /// /// /// See Modeling entity types and relationships for more information and examples. @@ -15,9 +14,10 @@ namespace Microsoft.EntityFrameworkCore.Metadata; public interface IProperty : IReadOnlyProperty, IPropertyBase { /// - /// Gets the type that this property belongs to. + /// Gets the entity type that this property belongs to. /// - new IEntityType DeclaringEntityType { get; } + [Obsolete("Use DeclaringType and cast to IEntityType or IComplexType")] + new IEntityType DeclaringEntityType => (IEntityType)DeclaringType; /// /// Creates an for values of the given property type. @@ -41,7 +41,7 @@ IEqualityComparer CreateKeyEqualityComparer() /// /// The list of all associated principal properties including the given property. new IReadOnlyList GetPrincipals() - => ((IReadOnlyProperty)this).GetPrincipals().Cast().ToList(); + => GetPrincipals(); /// /// Gets all foreign keys that use this property (including composite foreign keys in which this property @@ -98,7 +98,8 @@ IEqualityComparer CreateKeyEqualityComparer() /// The comparer. new ValueComparer GetProviderValueComparer(); - internal const DynamicallyAccessedMemberTypes DynamicallyAccessedMemberTypes = + + internal const System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes DynamicallyAccessedMemberTypes = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties diff --git a/src/EFCore/Metadata/IPropertyParameterBindingFactory.cs b/src/EFCore/Metadata/IPropertyParameterBindingFactory.cs index d97fd1c3ad8..b00eccc4cd8 100644 --- a/src/EFCore/Metadata/IPropertyParameterBindingFactory.cs +++ b/src/EFCore/Metadata/IPropertyParameterBindingFactory.cs @@ -31,4 +31,16 @@ public interface IPropertyParameterBindingFactory IEntityType entityType, Type parameterType, string parameterName); + + /// + /// Finds a specifically for an in the model. + /// + /// The complex type on which the is defined. + /// The parameter name. + /// The parameter type. + /// The parameter binding, or if none was found. + ParameterBinding? FindParameter( + IComplexType complexType, + Type parameterType, + string parameterName); } diff --git a/src/EFCore/Metadata/IReadOnlyComplexProperty.cs b/src/EFCore/Metadata/IReadOnlyComplexProperty.cs new file mode 100644 index 00000000000..63684be49bb --- /dev/null +++ b/src/EFCore/Metadata/IReadOnlyComplexProperty.cs @@ -0,0 +1,126 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using System.Text; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a complex property of a structural type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IReadOnlyComplexProperty : IReadOnlyPropertyBase +{ + /// + /// Gets the associated complex type. + /// + IReadOnlyComplexType ComplexType { get; } + + /// + /// Gets a value indicating whether this property can contain . + /// + bool IsNullable { get; } + + /// + /// Gets a value indicating whether this property represents a collection. + /// + bool IsCollection { get; } + + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); + + try + { + builder.Append(indentString); + + var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; + if (singleLine) + { + builder.Append($"ComplexProperty: {DeclaringType.DisplayName()}."); + } + + builder.Append(Name).Append(" ("); + + var field = GetFieldName(); + if (field == null) + { + builder.Append("no field, "); + } + else if (!field.EndsWith(">k__BackingField", StringComparison.Ordinal)) + { + builder.Append(field).Append(", "); + } + + builder.Append(ClrType.ShortDisplayName()).Append(')'); + + if (IsShadowProperty()) + { + builder.Append(" Shadow"); + } + + if (IsIndexerProperty()) + { + builder.Append(" Indexer"); + } + + if (!IsNullable) + { + builder.Append(" Required"); + } + + if (Sentinel != null && !Equals(Sentinel, ClrType.GetDefaultValue())) + { + builder.Append(" Sentinel:").Append(Sentinel); + } + + if (GetPropertyAccessMode() != PropertyAccessMode.PreferField) + { + builder.Append(" PropertyAccessMode.").Append(GetPropertyAccessMode()); + } + + if ((options & MetadataDebugStringOptions.IncludePropertyIndexes) != 0 + && ((AnnotatableBase)this).IsReadOnly) + { + var indexes = ((IProperty)this).GetPropertyIndexes(); + builder.Append(' ').Append(indexes.Index); + builder.Append(' ').Append(indexes.OriginalValueIndex); + builder.Append(' ').Append(indexes.RelationshipIndex); + builder.Append(' ').Append(indexes.ShadowIndex); + builder.Append(' ').Append(indexes.StoreGenerationIndex); + } + + if (!singleLine) + { + if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent + 2)); + } + + builder.AppendLine().Append(indentString).Append(ComplexType.ToDebugString(options, indent + 1)); + } + } + catch (Exception exception) + { + builder.AppendLine().AppendLine(CoreStrings.DebugViewError(exception.Message)); + } + + return builder.ToString(); + } +} diff --git a/src/EFCore/Metadata/IReadOnlyComplexType.cs b/src/EFCore/Metadata/IReadOnlyComplexType.cs new file mode 100644 index 00000000000..91865c009f3 --- /dev/null +++ b/src/EFCore/Metadata/IReadOnlyComplexType.cs @@ -0,0 +1,97 @@ +// 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; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents the type of a complex property of a structural type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IReadOnlyComplexType : IReadOnlyTypeBase +{ + /// + /// Gets the associated property. + /// + IReadOnlyComplexProperty ComplexProperty { get; } + + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); + + try + { + builder + .Append(indentString) + .Append("ComplexType: ") + .Append(DisplayName()); + + if (HasSharedClrType) + { + builder.Append(" CLR Type: ").Append(ClrType.ShortDisplayName()); + } + + if (IsAbstract()) + { + builder.Append(" Abstract"); + } + + if (this is EntityType + && GetChangeTrackingStrategy() != ChangeTrackingStrategy.Snapshot) + { + builder.Append(" ChangeTrackingStrategy.").Append(GetChangeTrackingStrategy()); + } + + if ((options & MetadataDebugStringOptions.SingleLine) == 0) + { + var properties = GetProperties().ToList(); + if (properties.Count != 0) + { + builder.AppendLine().Append(indentString).Append(" Properties: "); + foreach (var property in properties) + { + builder.AppendLine().Append(property.ToDebugString(options, indent + 4)); + } + } + + var complexProperties = GetComplexProperties().ToList(); + if (complexProperties.Count != 0) + { + builder.AppendLine().Append(indentString).Append(" Complex properties: "); + foreach (var complexProperty in complexProperties) + { + builder.AppendLine().Append(complexProperty.ToDebugString(options, indent + 4)); + } + } + + if ((options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent: indent + 2)); + } + } + } + catch (Exception exception) + { + builder.AppendLine().AppendLine(CoreStrings.DebugViewError(exception.Message)); + } + + return builder.ToString(); + } +} diff --git a/src/EFCore/Metadata/IReadOnlyEntityType.cs b/src/EFCore/Metadata/IReadOnlyEntityType.cs index aa1f891fd64..de02b2a452f 100644 --- a/src/EFCore/Metadata/IReadOnlyEntityType.cs +++ b/src/EFCore/Metadata/IReadOnlyEntityType.cs @@ -20,13 +20,6 @@ public interface IReadOnlyEntityType : IReadOnlyTypeBase /// IReadOnlyEntityType? BaseType { get; } - /// - /// Gets the change tracking strategy being used for this entity type. This strategy indicates how the - /// context detects changes to properties for an instance of the entity type. - /// - /// The change tracking strategy. - ChangeTrackingStrategy GetChangeTrackingStrategy(); - /// /// Gets the data stored in the model for the given entity type. /// @@ -88,7 +81,7 @@ string GetDefaultDiscriminatorValue() => !HasSharedClrType ? ClrType.ShortDisplayName() : ShortName(); /// - /// Gets all types in the model from which a given entity type derives, starting with the root. + /// Gets all types in the model from which this entity type derives, starting with the root. /// /// /// The base types. @@ -97,7 +90,7 @@ IEnumerable GetAllBaseTypes() => GetAllBaseTypesAscending().Reverse(); /// - /// Gets all types in the model from which a given entity type derives, starting with the closest one. + /// Gets all types in the model from which this entity type derives, starting with the closest one. /// /// /// The base types. @@ -127,7 +120,7 @@ IEnumerable GetAllBaseTypesInclusiveAscending() } /// - /// Gets all types in the model that derive from a given entity type. + /// Gets all types in the model that derive from this entity type. /// /// The derived types. IEnumerable GetDerivedTypes(); @@ -140,7 +133,7 @@ IEnumerable GetDerivedTypesInclusive() => new[] { this }.Concat(GetDerivedTypes()); /// - /// Gets all types in the model that directly derive from a given entity type, in a deterministic top-to-bottom ordering. + /// Gets all types in the model that directly derive from this entity type. /// /// The derived types. IEnumerable GetDirectlyDerivedTypes(); @@ -162,6 +155,17 @@ IEnumerable GetConcreteDerivedTypesInclusive() IReadOnlyEntityType GetRootType() => BaseType?.GetRootType() ?? this; + /// + /// Determines if this type derives from (or is the same as) a given type. + /// + /// The type to check whether it derives from this type. + /// + /// if derives from (or is the same as) this type, + /// otherwise . + /// + bool IReadOnlyTypeBase.IsAssignableFrom(IReadOnlyTypeBase derivedType) + => derivedType is IReadOnlyEntityType derivedEntityType && IsAssignableFrom(derivedEntityType); + /// /// Determines if this entity type derives from (or is the same as) a given entity type. /// @@ -198,17 +202,6 @@ bool IsAssignableFrom(IReadOnlyEntityType derivedType) return false; } - /// - /// Determines if this entity type derives from (but is not the same as) a given entity type. - /// - /// The entity type to check if it is a base type of this entity type. - /// - /// if this entity type derives from (but is not the same as) , - /// otherwise . - /// - bool IsStrictlyDerivedFrom(IReadOnlyEntityType baseType) - => this != Check.NotNull(baseType, nameof(baseType)) && baseType.IsAssignableFrom(this); - /// /// Returns the closest entity type that is a parent of both given entity types. If one of the given entities is /// a parent of the other, that parent is returned. Returns if the two entity types aren't @@ -602,112 +595,6 @@ bool IsInOwnershipPath(IReadOnlyEntityType targetType) /// The indexes defined on this entity type. IEnumerable GetIndexes(); - /// - /// Gets the property with a given name. Returns if no property with the given name is defined. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The name of the property. - /// The property, or if none is found. - IReadOnlyProperty? FindProperty(string name); - - /// - /// Gets a property with the given member info. Returns if no property is found. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The member on the entity class. - /// The property, or if none is found. - IReadOnlyProperty? FindProperty(MemberInfo memberInfo) - => (Check.NotNull(memberInfo, nameof(memberInfo)) as PropertyInfo)?.IsIndexerProperty() == true - ? null - : FindProperty(memberInfo.GetSimpleMemberName()); - - /// - /// Finds matching properties on the given entity type. Returns if any property is not found. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. - /// - /// The property names. - /// The properties, or if any property is not found. - IReadOnlyList? FindProperties(IReadOnlyList propertyNames); - - /// - /// Finds a property declared on the type with the given name. - /// Does not return properties defined on a base type. - /// - /// The property name. - /// The property, or if none is found. - IReadOnlyProperty? FindDeclaredProperty(string name); - - /// - /// Gets a property with the given name. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The property name. - /// The property. - IReadOnlyProperty GetProperty(string name) - { - Check.NotEmpty(name, nameof(name)); - - var property = FindProperty(name); - if (property == null) - { - if (FindNavigation(name) != null - || FindSkipNavigation(name) != null) - { - throw new InvalidOperationException( - CoreStrings.PropertyIsNavigation( - name, DisplayName(), - nameof(EntityEntry.Property), nameof(EntityEntry.Reference), nameof(EntityEntry.Collection))); - } - - throw new InvalidOperationException(CoreStrings.PropertyNotFound(name, DisplayName())); - } - - return property; - } - - /// - /// Gets all non-navigation properties declared on this entity type. - /// - /// - /// This method does not return properties declared on base types. - /// It is useful when iterating over all entity types to avoid processing the same property more than once. - /// Use to also return properties declared on base types. - /// - /// Declared non-navigation properties. - IEnumerable GetDeclaredProperties(); - - /// - /// Gets all non-navigation properties declared on the types derived from this entity type. - /// - /// - /// This method does not return properties declared on the given entity type itself. - /// Use to return properties declared on this - /// and base entity typed types. - /// - /// Derived non-navigation properties. - IEnumerable GetDerivedProperties(); - - /// - /// Gets the properties defined on this entity type. - /// - /// - /// This API only returns scalar properties and does not return navigation properties. Use - /// to get navigation properties. - /// - /// The properties defined on this entity type. - IEnumerable GetProperties(); - /// /// Gets the service property with a given name. /// Returns if no property with the given name is defined. @@ -768,6 +655,16 @@ IReadOnlyProperty GetProperty(string name) /// IEnumerable GetDeclaredTriggers(); + /// + /// Gets the being used for navigations of this entity type. + /// + /// + /// Note that individual navigations can override this access mode. The value returned here will + /// be used for any navigation for which no override has been specified. + /// + /// The access mode being used. + PropertyAccessMode GetNavigationAccessMode(); + /// /// /// Creates a human-readable representation of the given metadata. @@ -855,6 +752,16 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt } } + var complexProperties = GetDeclaredComplexProperties().ToList(); + if (complexProperties.Count != 0) + { + builder.AppendLine().Append(indentString).Append(" Complex properties: "); + foreach (var complexProperty in complexProperties) + { + builder.AppendLine().Append(complexProperty.ToDebugString(options, indent + 4)); + } + } + var serviceProperties = GetDeclaredServiceProperties().ToList(); if (serviceProperties.Count != 0) { diff --git a/src/EFCore/Metadata/IReadOnlyIndex.cs b/src/EFCore/Metadata/IReadOnlyIndex.cs index f061bc98189..2ed08f0fde8 100644 --- a/src/EFCore/Metadata/IReadOnlyIndex.cs +++ b/src/EFCore/Metadata/IReadOnlyIndex.cs @@ -79,7 +79,7 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt ", ", Properties.Select( p => singleLine - ? p.DeclaringEntityType.DisplayName(omitSharedType: true) + "." + p.Name + ? p.DeclaringType.DisplayName(omitSharedType: true) + "." + p.Name : p.Name)); if (Name != null) diff --git a/src/EFCore/Metadata/IReadOnlyKey.cs b/src/EFCore/Metadata/IReadOnlyKey.cs index ade5587dbba..971cb03aea5 100644 --- a/src/EFCore/Metadata/IReadOnlyKey.cs +++ b/src/EFCore/Metadata/IReadOnlyKey.cs @@ -66,7 +66,7 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt builder.AppendJoin( ", ", Properties.Select( p => singleLine - ? p.DeclaringEntityType.DisplayName(omitSharedType: true) + "." + p.Name + ? p.DeclaringType.DisplayName(omitSharedType: true) + "." + p.Name : p.Name)); if (IsPrimaryKey()) diff --git a/src/EFCore/Metadata/IReadOnlyNavigation.cs b/src/EFCore/Metadata/IReadOnlyNavigation.cs index 3553402dc5e..fb37e1af464 100644 --- a/src/EFCore/Metadata/IReadOnlyNavigation.cs +++ b/src/EFCore/Metadata/IReadOnlyNavigation.cs @@ -41,15 +41,6 @@ public interface IReadOnlyNavigation : IReadOnlyNavigationBase get => IsOnDependent ? ForeignKey.PrincipalToDependent : ForeignKey.DependentToPrincipal; } - /// - /// Gets a value indicating whether the navigation property is a collection property. - /// - new bool IsCollection - { - [DebuggerStepThrough] - get => !IsOnDependent && !ForeignKey.IsUnique; - } - /// /// Gets the foreign key that defines the relationship this navigation property will navigate. /// @@ -97,7 +88,7 @@ IReadOnlyEntityType IReadOnlyNavigationBase.TargetEntityType bool IReadOnlyNavigationBase.IsCollection { [DebuggerStepThrough] - get => IsCollection; + get => !IsOnDependent && !ForeignKey.IsUnique; } /// diff --git a/src/EFCore/Metadata/IReadOnlyProperty.cs b/src/EFCore/Metadata/IReadOnlyProperty.cs index 08bbfe85bf8..e48674fd83d 100644 --- a/src/EFCore/Metadata/IReadOnlyProperty.cs +++ b/src/EFCore/Metadata/IReadOnlyProperty.cs @@ -1,428 +1,440 @@ // 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; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.Json; +using System.Text; -namespace Microsoft.EntityFrameworkCore.Metadata; - -/// -/// Represents a scalar property of an entity type. -/// -/// -/// See Modeling entity types and relationships for more information and examples. -/// -public interface IReadOnlyProperty : IReadOnlyPropertyBase +namespace Microsoft.EntityFrameworkCore.Metadata { /// - /// Gets the entity type that this property belongs to. - /// - IReadOnlyEntityType DeclaringEntityType { get; } - - /// - /// Gets a value indicating whether this property can contain . - /// - bool IsNullable { get; } - - /// - /// Gets a value indicating when a value for this property will be generated by the database. Even when the - /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than - /// having one generated by the database) when the entity is added and a value is assigned, or the property is - /// marked as modified for an existing entity. See - /// and for more information and examples. - /// - ValueGenerated ValueGenerated { get; } - - /// - /// Gets a value indicating whether this property is used as a concurrency token. When a property is configured - /// as a concurrency token the value in the database will be checked when an instance of this entity type - /// is updated or deleted during to ensure it has not changed since - /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the - /// changes will not be applied to the database. - /// - bool IsConcurrencyToken { get; } - - /// - /// Returns the for the given property from a finalized model. - /// - /// The type mapping. - CoreTypeMapping GetTypeMapping() - { - var mapping = FindTypeMapping(); - if (mapping == null) - { - throw new InvalidOperationException(CoreStrings.ModelNotFinalized(nameof(GetTypeMapping))); - } - - return mapping; - } - - /// - /// Returns the type mapping for this property. - /// - /// The type mapping, or if none was found. - CoreTypeMapping? FindTypeMapping(); - - /// - /// Gets the maximum length of data that is allowed in this property. For example, if the property is a - /// then this is the maximum number of characters. - /// - /// - /// The maximum length, -1 if the property has no maximum length, or if the maximum length hasn't been - /// set. - /// - int? GetMaxLength(); - - /// - /// Gets the precision of data that is allowed in this property. - /// For example, if the property is a then this is the maximum number of digits. - /// - /// The precision, or if none is defined. - int? GetPrecision(); - - /// - /// Gets the scale of data that is allowed in this property. - /// For example, if the property is a then this is the maximum number of decimal places. - /// - /// The scale, or if none is defined. - int? GetScale(); - - /// - /// Gets a value indicating whether or not the property can persist Unicode characters. - /// - /// The Unicode setting, or if none is defined. - bool? IsUnicode(); - - /// - /// Gets a value indicating whether or not this property can be modified before the entity is - /// saved to the database. - /// - /// - /// - /// If , then an exception - /// will be thrown if a value is assigned to this property when it is in - /// the state. - /// - /// - /// If , then any value - /// set will be ignored when it is in the state. - /// - /// - /// The before save behavior for this property. - PropertySaveBehavior GetBeforeSaveBehavior(); - - /// - /// Gets a value indicating whether or not this property can be modified after the entity is - /// saved to the database. + /// Represents a scalar property of a structural type. /// /// - /// - /// If , then an exception - /// will be thrown if a new value is assigned to this property after the entity exists in the database. - /// - /// - /// If , then any modification to the - /// property value of an entity that already exists in the database will be ignored. - /// + /// See Modeling entity types and relationships for more information and examples. /// - /// The after save behavior for this property. - PropertySaveBehavior GetAfterSaveBehavior(); - - /// - /// Gets the factory that has been set to generate values for this property, if any. - /// - /// The factory, or if no factory has been set. - Func? GetValueGeneratorFactory(); - - /// - /// Gets the custom set for this property. - /// - /// The converter, or if none has been set. - ValueConverter? GetValueConverter(); - - /// - /// Gets the type that the property value will be converted to before being sent to the database provider. - /// - /// The provider type, or if none has been set. - Type? GetProviderClrType(); - - /// - /// Gets the for this property, or if none is set. - /// - /// The comparer, or if none has been set. - ValueComparer? GetValueComparer(); - - /// - /// Gets the to use with keys for this property, or if none is set. - /// - /// The comparer, or if none has been set. - ValueComparer? GetKeyValueComparer(); - - /// - /// Gets the to use for the provider values for this property. - /// - /// The comparer, or if none has been set. - ValueComparer? GetProviderValueComparer(); + public interface IReadOnlyProperty : IReadOnlyPropertyBase + { + /// + /// Gets the entity type that this property belongs to. + /// + [Obsolete("Use DeclaringType and cast to IReadOnlyEntityType or IReadOnlyComplexType")] + IReadOnlyEntityType DeclaringEntityType => (IReadOnlyEntityType)DeclaringType; + + /// + /// Gets a value indicating whether this property can contain . + /// + bool IsNullable { get; } + + /// + /// Gets a value indicating when a value for this property will be generated by the database. Even when the + /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than + /// having one generated by the database) when the entity is added and a value is assigned, or the property is + /// marked as modified for an existing entity. See + /// and for more information and examples. + /// + ValueGenerated ValueGenerated { get; } + + /// + /// Gets a value indicating whether this property is used as a concurrency token. When a property is configured + /// as a concurrency token the value in the database will be checked when an instance of this entity type + /// is updated or deleted during to ensure it has not changed since + /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the + /// changes will not be applied to the database. + /// + bool IsConcurrencyToken { get; } + + /// + /// Returns the for the given property from a finalized model. + /// + /// The type mapping. + CoreTypeMapping GetTypeMapping() + { + var mapping = FindTypeMapping(); + if (mapping == null) + { + throw new InvalidOperationException(CoreStrings.ModelNotFinalized(nameof(GetTypeMapping))); + } - /// - /// Gets the for this property, or if none is set. - /// - /// The reader/writer, or if none has been set. - JsonValueReaderWriter? GetJsonValueReaderWriter(); + return mapping; + } - /// - /// Finds the first principal property that the given property is constrained by - /// if the given property is part of a foreign key. - /// - /// The first associated principal property, or if none exists. - IReadOnlyProperty? FindFirstPrincipal() - { - foreach (var foreignKey in GetContainingForeignKeys()) + /// + /// Returns the type mapping for this property. + /// + /// The type mapping, or if none was found. + CoreTypeMapping? FindTypeMapping(); + + /// + /// Gets the maximum length of data that is allowed in this property. For example, if the property is a + /// then this is the maximum number of characters. + /// + /// + /// The maximum length, -1 if the property has no maximum length, or if the maximum length hasn't been + /// set. + /// + int? GetMaxLength(); + + /// + /// Gets the precision of data that is allowed in this property. + /// For example, if the property is a then this is the maximum number of digits. + /// + /// The precision, or if none is defined. + int? GetPrecision(); + + /// + /// Gets the scale of data that is allowed in this property. + /// For example, if the property is a then this is the maximum number of decimal places. + /// + /// The scale, or if none is defined. + int? GetScale(); + + /// + /// Gets a value indicating whether or not the property can persist Unicode characters. + /// + /// The Unicode setting, or if none is defined. + bool? IsUnicode(); + + /// + /// Gets a value indicating whether or not this property can be modified before the entity is + /// saved to the database. + /// + /// + /// + /// If , then an exception + /// will be thrown if a value is assigned to this property when it is in + /// the state. + /// + /// + /// If , then any value + /// set will be ignored when it is in the state. + /// + /// + /// The before save behavior for this property. + PropertySaveBehavior GetBeforeSaveBehavior(); + + /// + /// Gets a value indicating whether or not this property can be modified after the entity is + /// saved to the database. + /// + /// + /// + /// If , then an exception + /// will be thrown if a new value is assigned to this property after the entity exists in the database. + /// + /// + /// If , then any modification to the + /// property value of an entity that already exists in the database will be ignored. + /// + /// + /// The after save behavior for this property. + PropertySaveBehavior GetAfterSaveBehavior(); + + /// + /// Gets the factory that has been set to generate values for this property, if any. + /// + /// The factory, or if no factory has been set. + Func? GetValueGeneratorFactory(); + + /// + /// Gets the custom set for this property. + /// + /// The converter, or if none has been set. + ValueConverter? GetValueConverter(); + + /// + /// Gets the type that the property value will be converted to before being sent to the database provider. + /// + /// The provider type, or if none has been set. + Type? GetProviderClrType(); + + /// + /// Gets the for this property, or if none is set. + /// + /// The comparer, or if none has been set. + ValueComparer? GetValueComparer(); + + /// + /// Gets the to use with keys for this property, or if none is set. + /// + /// The comparer, or if none has been set. + ValueComparer? GetKeyValueComparer(); + + /// + /// Gets the to use for the provider values for this property. + /// + /// The comparer, or if none has been set. + ValueComparer? GetProviderValueComparer(); + + /// + /// Gets the for this property, or if none is set. + /// + /// The reader/writer, or if none has been set. + JsonValueReaderWriter? GetJsonValueReaderWriter(); + + /// + /// Finds the first principal property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The first associated principal property, or if none exists. + IReadOnlyProperty? FindFirstPrincipal() { - for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + foreach (var foreignKey in GetContainingForeignKeys()) { - if (this == foreignKey.Properties[propertyIndex]) + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) { - return foreignKey.PrincipalKey.Properties[propertyIndex]; + if (this == foreignKey.Properties[propertyIndex]) + { + return foreignKey.PrincipalKey.Properties[propertyIndex]; + } } } - } - return null; - } + return null; + } - /// - /// Finds the list of principal properties including the given property that the given property is constrained by - /// if the given property is part of a foreign key. - /// - /// The list of all associated principal properties including the given property. - IReadOnlyList GetPrincipals() - { - var principals = new List { this }; - AddPrincipals(this, principals); - return principals; - } + /// + /// Finds the list of principal properties including the given property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The list of all associated principal properties including the given property. + IReadOnlyList GetPrincipals() + => GetPrincipals(); + + /// + /// Finds the list of principal properties including the given property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The list of all associated principal properties including the given property. + IReadOnlyList GetPrincipals() + where T : IReadOnlyProperty + { + var principals = new List { (T)this }; + AddPrincipals((T)this, principals); + return principals; + } - private static void AddPrincipals(IReadOnlyProperty property, List visited) - { - foreach (var foreignKey in property.GetContainingForeignKeys()) + private static void AddPrincipals(T property, List visited) + where T : IReadOnlyProperty { - for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + foreach (var foreignKey in property.GetContainingForeignKeys()) { - if (property == foreignKey.Properties[propertyIndex]) + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) { - var principal = foreignKey.PrincipalKey.Properties[propertyIndex]; - if (!visited.Contains(principal)) + if (ReferenceEquals(property, foreignKey.Properties[propertyIndex])) { - visited.Add(principal); + var principal = (T)foreignKey.PrincipalKey.Properties[propertyIndex]; + if (!visited.Contains(principal)) + { + visited.Add(principal); - AddPrincipals(principal, visited); + AddPrincipals(principal, visited); + } } } } } - } - - /// - /// Gets a value indicating whether this property is used as a foreign key (or part of a composite foreign key). - /// - /// if the property is used as a foreign key, otherwise . - bool IsForeignKey(); - - /// - /// Gets all foreign keys that use this property (including composite foreign keys in which this property - /// is included). - /// - /// The foreign keys that use this property. - IEnumerable GetContainingForeignKeys(); - - /// - /// Gets a value indicating whether this property is used as an index (or part of a composite index). - /// - /// if the property is used as an index, otherwise . - bool IsIndex(); - - /// - /// Gets a value indicating whether this property is used as a unique index (or part of a unique composite index). - /// - /// if the property is used as an unique index, otherwise . - bool IsUniqueIndex() - => GetContainingIndexes().Any(e => e.IsUnique); - - /// - /// Gets all indexes that use this property (including composite indexes in which this property - /// is included). - /// - /// The indexes that use this property. - IEnumerable GetContainingIndexes(); - - /// - /// Gets a value indicating whether this property is used as the primary key (or part of a composite primary key). - /// - /// if the property is used as the primary key, otherwise . - bool IsPrimaryKey() - => FindContainingPrimaryKey() != null; - - /// - /// Gets the primary key that uses this property (including a composite primary key in which this property - /// is included). - /// - /// The primary that use this property, or if it is not part of the primary key. - IReadOnlyKey? FindContainingPrimaryKey(); - /// - /// Gets a value indicating whether this property is used as the primary key or alternate key - /// (or part of a composite primary or alternate key). - /// - /// if the property is used as a key, otherwise . - bool IsKey(); + /// + /// Gets a value indicating whether this property is used as a foreign key (or part of a composite foreign key). + /// + /// if the property is used as a foreign key, otherwise . + bool IsForeignKey(); + + /// + /// Gets all foreign keys that use this property (including composite foreign keys in which this property + /// is included). + /// + /// The foreign keys that use this property. + IEnumerable GetContainingForeignKeys(); + + /// + /// Gets a value indicating whether this property is used as an index (or part of a composite index). + /// + /// if the property is used as an index, otherwise . + bool IsIndex(); + + /// + /// Gets a value indicating whether this property is used as a unique index (or part of a unique composite index). + /// + /// if the property is used as an unique index, otherwise . + bool IsUniqueIndex() + => GetContainingIndexes().Any(e => e.IsUnique); + + /// + /// Gets all indexes that use this property (including composite indexes in which this property + /// is included). + /// + /// The indexes that use this property. + IEnumerable GetContainingIndexes(); + + /// + /// Gets a value indicating whether this property is used as the primary key (or part of a composite primary key). + /// + /// if the property is used as the primary key, otherwise . + bool IsPrimaryKey() + => FindContainingPrimaryKey() != null; + + /// + /// Gets the primary key that uses this property (including a composite primary key in which this property + /// is included). + /// + /// The primary that use this property, or if it is not part of the primary key. + IReadOnlyKey? FindContainingPrimaryKey(); + + /// + /// Gets a value indicating whether this property is used as the primary key or alternate key + /// (or part of a composite primary or alternate key). + /// + /// if the property is used as a key, otherwise . + bool IsKey(); + + /// + /// Gets all primary or alternate keys that use this property (including composite keys in which this property + /// is included). + /// + /// The primary and alternate keys that use this property. + IEnumerable GetContainingKeys(); + + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); - /// - /// Gets all primary or alternate keys that use this property (including composite keys in which this property - /// is included). - /// - /// The primary and alternate keys that use this property. - IEnumerable GetContainingKeys(); + try + { + builder.Append(indentString); - /// - /// - /// Creates a human-readable representation of the given metadata. - /// - /// - /// Warning: Do not rely on the format of the returned string. - /// It is designed for debugging only and may change arbitrarily between releases. - /// - /// - /// Options for generating the string. - /// The number of indent spaces to use before each new line. - /// A human-readable representation. - string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) - { - var builder = new StringBuilder(); - var indentString = new string(' ', indent); + var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; + if (singleLine) + { + builder.Append($"Property: {DeclaringType.DisplayName()}."); + } - try - { - builder.Append(indentString); + builder.Append(Name).Append(" ("); - var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; - if (singleLine) - { - builder.Append($"Property: {DeclaringEntityType.DisplayName()}."); - } + var field = GetFieldName(); + if (field == null) + { + builder.Append("no field, "); + } + else if (!field.EndsWith(">k__BackingField", StringComparison.Ordinal)) + { + builder.Append(field).Append(", "); + } - builder.Append(Name).Append(" ("); + builder.Append(ClrType.ShortDisplayName()).Append(')'); - var field = GetFieldName(); - if (field == null) - { - builder.Append("no field, "); - } - else if (!field.EndsWith(">k__BackingField", StringComparison.Ordinal)) - { - builder.Append(field).Append(", "); - } + if (IsShadowProperty()) + { + builder.Append(" Shadow"); + } - builder.Append(ClrType.ShortDisplayName()).Append(')'); + if (IsIndexerProperty()) + { + builder.Append(" Indexer"); + } - if (IsShadowProperty()) - { - builder.Append(" Shadow"); - } + if (!IsNullable) + { + builder.Append(" Required"); + } - if (IsIndexerProperty()) - { - builder.Append(" Indexer"); - } + if (IsPrimaryKey()) + { + builder.Append(" PK"); + } - if (!IsNullable) - { - builder.Append(" Required"); - } + if (IsForeignKey()) + { + builder.Append(" FK"); + } - if (IsPrimaryKey()) - { - builder.Append(" PK"); - } + if (IsKey() + && !IsPrimaryKey()) + { + builder.Append(" AlternateKey"); + } - if (IsForeignKey()) - { - builder.Append(" FK"); - } + if (IsIndex()) + { + builder.Append(" Index"); + } - if (IsKey() - && !IsPrimaryKey()) - { - builder.Append(" AlternateKey"); - } + if (IsConcurrencyToken) + { + builder.Append(" Concurrency"); + } - if (IsIndex()) - { - builder.Append(" Index"); - } + if (Sentinel != null && !Equals(Sentinel, ClrType.GetDefaultValue())) + { + builder.Append(" Sentinel:").Append(Sentinel); + } - if (IsConcurrencyToken) - { - builder.Append(" Concurrency"); - } + if (GetBeforeSaveBehavior() != PropertySaveBehavior.Save) + { + builder.Append(" BeforeSave:").Append(GetBeforeSaveBehavior()); + } - if (GetBeforeSaveBehavior() != PropertySaveBehavior.Save) - { - builder.Append(" BeforeSave:").Append(GetBeforeSaveBehavior()); - } + if (GetAfterSaveBehavior() != PropertySaveBehavior.Save) + { + builder.Append(" AfterSave:").Append(GetAfterSaveBehavior()); + } - if (GetAfterSaveBehavior() != PropertySaveBehavior.Save) - { - builder.Append(" AfterSave:").Append(GetAfterSaveBehavior()); - } + if (ValueGenerated != ValueGenerated.Never) + { + builder.Append(" ValueGenerated.").Append(ValueGenerated); + } - if (ValueGenerated != ValueGenerated.Never) - { - builder.Append(" ValueGenerated.").Append(ValueGenerated); - } + if (GetMaxLength() != null) + { + builder.Append(" MaxLength(").Append(GetMaxLength()).Append(')'); + } - if (GetMaxLength() != null) - { - builder.Append(" MaxLength(").Append(GetMaxLength()).Append(')'); - } + if (IsUnicode() == false) + { + builder.Append(" ANSI"); + } - if (IsUnicode() == false) - { - builder.Append(" Ansi"); - } + if (GetPropertyAccessMode() != PropertyAccessMode.PreferField) + { + builder.Append(" PropertyAccessMode.").Append(GetPropertyAccessMode()); + } - if (GetPropertyAccessMode() != PropertyAccessMode.PreferField) - { - builder.Append(" PropertyAccessMode.").Append(GetPropertyAccessMode()); - } + if ((options & MetadataDebugStringOptions.IncludePropertyIndexes) != 0 + && ((AnnotatableBase)this).IsReadOnly) + { + var indexes = ((IProperty)this).GetPropertyIndexes(); + builder.Append(' ').Append(indexes.Index); + builder.Append(' ').Append(indexes.OriginalValueIndex); + builder.Append(' ').Append(indexes.RelationshipIndex); + builder.Append(' ').Append(indexes.ShadowIndex); + builder.Append(' ').Append(indexes.StoreGenerationIndex); + } - if (Sentinel != null && !Equals(Sentinel, ClrType.GetDefaultValue())) - { - builder.Append(" Sentinel:").Append(Sentinel); + if (!singleLine && (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent + 2)); + } } - - if ((options & MetadataDebugStringOptions.IncludePropertyIndexes) != 0 - && ((AnnotatableBase)this).IsReadOnly) + catch (Exception exception) { - var indexes = ((IProperty)this).GetPropertyIndexes(); - builder.Append(' ').Append(indexes.Index); - builder.Append(' ').Append(indexes.OriginalValueIndex); - builder.Append(' ').Append(indexes.RelationshipIndex); - builder.Append(' ').Append(indexes.ShadowIndex); - builder.Append(' ').Append(indexes.StoreGenerationIndex); + builder.AppendLine().AppendLine(CoreStrings.DebugViewError(exception.Message)); } - if (!singleLine && (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) - { - builder.Append(AnnotationsToDebugString(indent + 2)); - } + return builder.ToString(); } - catch (Exception exception) - { - builder.AppendLine().AppendLine(CoreStrings.DebugViewError(exception.Message)); - } - - return builder.ToString(); } } diff --git a/src/EFCore/Metadata/IReadOnlySkipNavigation.cs b/src/EFCore/Metadata/IReadOnlySkipNavigation.cs index 3ce55b9e5f5..d7017c74085 100644 --- a/src/EFCore/Metadata/IReadOnlySkipNavigation.cs +++ b/src/EFCore/Metadata/IReadOnlySkipNavigation.cs @@ -26,15 +26,6 @@ public interface IReadOnlySkipNavigation : IReadOnlyNavigationBase /// new IReadOnlySkipNavigation Inverse { get; } - /// - /// Gets the inverse navigation. - /// - IReadOnlyNavigationBase IReadOnlyNavigationBase.Inverse - { - [DebuggerStepThrough] - get => Inverse; - } - /// /// Gets the foreign key to the join type. /// @@ -130,4 +121,13 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt return builder.ToString(); } + + /// + /// Gets the inverse navigation. + /// + IReadOnlyNavigationBase IReadOnlyNavigationBase.Inverse + { + [DebuggerStepThrough] + get => Inverse; + } } diff --git a/src/EFCore/Metadata/IReadOnlyTypeBase.cs b/src/EFCore/Metadata/IReadOnlyTypeBase.cs index 9aa78564c5f..9bbedbd5564 100644 --- a/src/EFCore/Metadata/IReadOnlyTypeBase.cs +++ b/src/EFCore/Metadata/IReadOnlyTypeBase.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Represents a type in the model. +/// Represents a structural type in the model. /// /// /// See Modeling entity types and relationships for more information and examples. @@ -18,6 +18,12 @@ public interface IReadOnlyTypeBase : IReadOnlyAnnotatable /// IReadOnlyModel Model { get; } + /// + /// Gets this entity type or the one on which the complex property chain is declared. + /// + IReadOnlyEntityType FundamentalEntityType + => (IReadOnlyEntityType)this; + /// /// Gets the name of this type. /// @@ -35,19 +41,19 @@ public interface IReadOnlyTypeBase : IReadOnlyAnnotatable Type ClrType { get; } /// - /// Gets a value indicating whether this entity type is mapped to a that - /// other entity types are also mapped to. + /// Gets a value indicating whether this structural type is mapped to a that + /// other structural types are also mapped to. /// bool HasSharedClrType { get; } /// - /// Gets a value indicating whether this entity type has an indexer which is able to contain arbitrary properties + /// Gets a value indicating whether this structural type has an indexer which is able to contain arbitrary properties /// and a method that can be used to determine whether a given indexer property contains a value. /// bool IsPropertyBag { get; } /// - /// Gets a value indicating whether this entity type represents an abstract type. + /// Gets a value indicating whether this structural type represents an abstract type. /// /// if the type is abstract, otherwise. [DebuggerStepThrough] @@ -55,7 +61,7 @@ bool IsAbstract() => ClrType.IsAbstract; /// - /// Gets the friendly display name for the given . + /// Gets the friendly display name for this structural type. /// /// The display name. [DebuggerStepThrough] @@ -150,24 +156,190 @@ string ShortName() } /// - /// Gets the being used for properties and navigations of this type. + /// Determines if this type derives from (or is the same as) a given type. + /// + /// The type to check whether it derives from this type. + /// + /// if derives from (or is the same as) this type, + /// otherwise . + /// + bool IsAssignableFrom(IReadOnlyTypeBase derivedType) + => this == derivedType; + + /// + /// Determines if this type derives from (but is not the same as) a given type. + /// + /// The type to check if it is a base type of this type. + /// + /// if this type derives from (but is not the same as) , + /// otherwise . + /// + bool IsStrictlyDerivedFrom(IReadOnlyTypeBase baseType) + => this != Check.NotNull(baseType, nameof(baseType)) && baseType.IsAssignableFrom(this); + + /// + /// Gets the property with a given name. Returns if no property with the given name is defined. /// /// - /// Note that individual properties and navigations can override this access mode. The value returned here will - /// be used for any property or navigation for which no override has been specified. + /// This API only finds scalar properties and does not find navigation, complex or service properties. /// - /// The access mode being used. - PropertyAccessMode GetPropertyAccessMode(); + /// The name of the property. + /// The property, or if none is found. + IReadOnlyProperty? FindProperty(string name); + + /// + /// Gets a property with the given member info. Returns if no property is found. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// The member on the class. + /// The property, or if none is found. + IReadOnlyProperty? FindProperty(MemberInfo memberInfo) + => (Check.NotNull(memberInfo, nameof(memberInfo)) as PropertyInfo)?.IsIndexerProperty() == true + ? null + : FindProperty(memberInfo.GetSimpleMemberName()); + + /// + /// Finds matching properties on the given type. Returns if any property is not found. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// The property names. + /// The properties, or if any property is not found. + IReadOnlyList? FindProperties(IReadOnlyList propertyNames); + + /// + /// Finds a property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// The property name. + /// The property, or if none is found. + IReadOnlyProperty? FindDeclaredProperty(string name); + + /// + /// Gets a property with the given name. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// The property name. + /// The property. + IReadOnlyProperty GetProperty(string name) + { + Check.NotEmpty(name, nameof(name)); + + var property = FindProperty(name); + return property == null + ? throw new InvalidOperationException(CoreStrings.PropertyNotFound(name, DisplayName())) + : property; + } + + /// + /// Gets all scalar properties declared on this type. + /// + /// + /// This method does not return properties declared on base types. + /// It is useful when iterating over all types to avoid processing the same property more than once. + /// Use to also return properties declared on base types. + /// + /// Declared scalar properties. + IEnumerable GetDeclaredProperties(); + + /// + /// Gets all scalar properties declared on the types derived from this type. + /// + /// + /// This method does not return properties declared on the given type itself. + /// Use to return properties declared on this + /// and base types. + /// + /// Derived scalar properties. + IEnumerable GetDerivedProperties(); + + /// + /// Gets all scalar properties defined on this type. + /// + /// + /// This API only returns scalar properties and does not return navigation, complex or service properties. + /// + /// The properties defined on this type. + IEnumerable GetProperties(); + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// + /// This API only finds complex properties and does not find navigation, scalar or service properties. + /// + /// The name of the property. + /// The property, or if none is found. + IReadOnlyComplexProperty? FindComplexProperty(string name); + + /// + /// Gets a complex property with the given member info. Returns if no property is found. + /// + /// + /// This API only finds complex properties and does not find navigation, scalar or service properties. + /// + /// The member on the class. + /// The property, or if none is found. + IReadOnlyComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (Check.NotNull(memberInfo, nameof(memberInfo)) as PropertyInfo)?.IsIndexerProperty() == true + ? null + : FindComplexProperty(memberInfo.GetSimpleMemberName()); + + /// + /// Finds a property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// The property name. + /// The property, or if none is found. + IReadOnlyComplexProperty? FindDeclaredComplexProperty(string name); /// - /// Gets the being used for navigations of this type. + /// Gets the complex properties defined on this type. /// /// - /// Note that individual navigations can override this access mode. The value returned here will - /// be used for any navigation for which no override has been specified. + /// This API only returns complex properties and does not find navigation, scalar or service properties. + /// + /// The complex properties defined on this type. + IEnumerable GetComplexProperties(); + + /// + /// Gets the complex properties declared on this type. + /// + /// Declared complex properties. + IEnumerable GetDeclaredComplexProperties(); + + /// + /// Gets the complex properties declared on the types derived from this type. + /// + /// + /// This method does not return complex properties declared on the given type itself. + /// Use to return complex properties declared on this + /// and base typed types. + /// + /// Derived complex properties. + IEnumerable GetDerivedComplexProperties(); + + /// + /// Gets the change tracking strategy being used for this type. This strategy indicates how the + /// context detects changes to properties for an instance of the type. + /// + /// The change tracking strategy. + ChangeTrackingStrategy GetChangeTrackingStrategy(); + + /// + /// Gets the being used for properties and navigations of this type. + /// + /// + /// Note that individual properties and navigations can override this access mode. The value returned here will + /// be used for any property or navigation for which no override has been specified. /// /// The access mode being used. - PropertyAccessMode GetNavigationAccessMode(); + PropertyAccessMode GetPropertyAccessMode(); /// /// Returns the for the indexer on the associated CLR type if one exists. diff --git a/src/EFCore/Metadata/ITypeBase.cs b/src/EFCore/Metadata/ITypeBase.cs index 2005c627d2d..c036c6b1194 100644 --- a/src/EFCore/Metadata/ITypeBase.cs +++ b/src/EFCore/Metadata/ITypeBase.cs @@ -15,4 +15,155 @@ public interface ITypeBase : IReadOnlyTypeBase, IAnnotatable /// Gets the model that this type belongs to. /// new IModel Model { get; } + + /// + /// Gets this entity type or the one on which the complex property chain is declared. + /// + new IEntityType FundamentalEntityType + => (IEntityType)this; + + /// + /// Gets a property on the given type. Returns if no property is found. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// The member on the CLR type. + /// The property, or if none is found. + new IProperty? FindProperty(MemberInfo memberInfo) + => (IProperty?)((IReadOnlyEntityType)this).FindProperty(memberInfo); + + /// + /// Gets the property with a given name. Returns if no property with the given name is defined. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// The name of the property. + /// The property, or if none is found. + new IProperty? FindProperty(string name); + + /// + /// Finds matching properties on the given type. Returns if any property is not found. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// The property names. + /// The properties, or if any property is not found. + new IReadOnlyList? FindProperties( + IReadOnlyList propertyNames) + => (IReadOnlyList?)((IReadOnlyEntityType)this).FindProperties(propertyNames); + + /// + /// Gets a property with the given name. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// The property name. + /// The property. + new IProperty GetProperty(string name) + => (IProperty)((IReadOnlyEntityType)this).GetProperty(name); + + /// + /// Finds a property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// The property name. + /// The property, or if none is found. + new IProperty? FindDeclaredProperty(string name) + => (IProperty?)((IReadOnlyEntityType)this).FindDeclaredProperty(name); + + /// + /// Gets all non-navigation properties declared on this type. + /// + /// + /// This method does not return properties declared on base types. + /// It is useful when iterating over all types to avoid processing the same property more than once. + /// Use to also return properties declared on base types. + /// + /// Declared non-navigation properties. + new IEnumerable GetDeclaredProperties(); + + /// + /// Gets all non-navigation properties declared on the types derived from this type. + /// + /// + /// This method does not return properties declared on the given type itself. + /// Use to return properties declared on this + /// and base typed types. + /// + /// Derived non-navigation properties. + new IEnumerable GetDerivedProperties() + => ((IReadOnlyEntityType)this).GetDerivedProperties().Cast(); + + /// + /// Gets the properties defined on this type. + /// + /// + /// This API only returns scalar properties and does not return navigation, complex or service properties. + /// + /// The properties defined on this type. + new IEnumerable GetProperties(); + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// + /// This API only finds complex properties and does not find navigation, scalar or service properties. + /// + /// The name of the property. + /// The property, or if none is found. + new IComplexProperty? FindComplexProperty(string name); + + /// + /// Gets a complex property with the given member info. Returns if no property is found. + /// + /// + /// This API only finds complex properties and does not find navigation, scalar or service properties. + /// + /// The member on the entity class. + /// The property, or if none is found. + new IComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IComplexProperty?)((IReadOnlyEntityType)this).FindComplexProperty(memberInfo); + + /// + /// Finds a property declared on the type with the given name. + /// Does not return properties defined on a base type. + /// + /// + /// This API only finds complex properties and does not find navigation, scalar or service properties. + /// + /// The property name. + /// The property, or if none is found. + new IComplexProperty? FindDeclaredComplexProperty(string name) + => (IComplexProperty?)((IReadOnlyEntityType)this).FindDeclaredComplexProperty(name); + + /// + /// Gets the complex properties defined on this entity type. + /// + /// + /// This API only returns complex properties and does not find navigation, scalar or service properties. + /// + /// The complex properties defined on this entity type. + new IEnumerable GetComplexProperties(); + + /// + /// Gets the complex properties declared on this entity type. + /// + /// Declared complex properties. + new IEnumerable GetDeclaredComplexProperties(); + + /// + /// Gets the complex properties declared on the types derived from this entity type. + /// + /// + /// This method does not return complex properties declared on the given entity type itself. + /// Use to return complex properties declared on this + /// and base entity typed types. + /// + /// Derived complex properties. + new IEnumerable GetDerivedComplexProperties() + => ((IReadOnlyEntityType)this).GetDerivedComplexProperties().Cast(); } diff --git a/src/EFCore/Metadata/IndexComparer.cs b/src/EFCore/Metadata/IndexComparer.cs index 3ee62dfde53..d1d9eb15ae1 100644 --- a/src/EFCore/Metadata/IndexComparer.cs +++ b/src/EFCore/Metadata/IndexComparer.cs @@ -39,7 +39,7 @@ private IndexComparer() public int Compare(IReadOnlyIndex? x, IReadOnlyIndex? y) { var result = PropertyListComparer.Instance.Compare(x?.Properties, y?.Properties); - return result != 0 ? result : EntityTypeFullNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); + return result != 0 ? result : TypeBaseNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); } /// @@ -60,7 +60,7 @@ public int GetHashCode(IReadOnlyIndex obj) { var hashCode = new HashCode(); hashCode.Add(obj.Properties, PropertyListComparer.Instance); - hashCode.Add(obj.DeclaringEntityType, EntityTypeFullNameComparer.Instance); + hashCode.Add(obj.DeclaringEntityType, TypeBaseNameComparer.Instance); return hashCode.ToHashCode(); } } diff --git a/src/EFCore/Metadata/Internal/AdHocMapper.cs b/src/EFCore/Metadata/Internal/AdHocMapper.cs index 5f47aa16d6c..539f7f9078f 100644 --- a/src/EFCore/Metadata/Internal/AdHocMapper.cs +++ b/src/EFCore/Metadata/Internal/AdHocMapper.cs @@ -47,15 +47,15 @@ private ConventionSet ConventionSet _conventionSet.Remove(typeof(ForeignKeyPropertyDiscoveryConvention)); _conventionSet.Remove(typeof(IndexAttributeConvention)); _conventionSet.Remove(typeof(KeyAttributeConvention)); - _conventionSet.Remove(typeof(KeylessEntityTypeAttributeConvention)); + _conventionSet.Remove(typeof(KeylessAttributeConvention)); _conventionSet.Remove(typeof(ManyToManyJoinEntityTypeConvention)); _conventionSet.Remove(typeof(RequiredNavigationAttributeConvention)); _conventionSet.Remove(typeof(NavigationBackingFieldAttributeConvention)); _conventionSet.Remove(typeof(InversePropertyAttributeConvention)); _conventionSet.Remove(typeof(NavigationEagerLoadingConvention)); _conventionSet.Remove(typeof(NonNullableNavigationConvention)); - _conventionSet.Remove(typeof(NotMappedEntityTypeAttributeConvention)); - _conventionSet.Remove(typeof(OwnedEntityTypeAttributeConvention)); + _conventionSet.Remove(typeof(NotMappedTypeAttributeConvention)); + _conventionSet.Remove(typeof(OwnedAttributeConvention)); _conventionSet.Remove(typeof(QueryFilterRewritingConvention)); _conventionSet.Remove(typeof(ServicePropertyDiscoveryConvention)); _conventionSet.Remove(typeof(ValueGenerationConvention)); diff --git a/src/EFCore/Metadata/Internal/ComplexProperty.cs b/src/EFCore/Metadata/Internal/ComplexProperty.cs new file mode 100644 index 00000000000..c36953cde41 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexProperty.cs @@ -0,0 +1,349 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class ComplexProperty : PropertyBase, IMutableComplexProperty, IConventionComplexProperty, IComplexProperty +{ + private InternalComplexPropertyBuilder? _builder; + private bool? _isNullable; + + private ConfigurationSource? _isNullableConfigurationSource; + + /// + /// 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 ComplexProperty( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + PropertyInfo? propertyInfo, + FieldInfo? fieldInfo, + TypeBase declaringType, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type targetType, + bool collection, + ConfigurationSource configurationSource) + : base(name, propertyInfo, fieldInfo, configurationSource) + { + ClrType = type; + DeclaringType = declaringType; + IsCollection = collection; + ComplexType = new ComplexType( + declaringType.GetOwnedName(targetType.ShortDisplayName(), name), + targetType, this, configurationSource); + _builder = new InternalComplexPropertyBuilder(this, declaringType.Model.Builder); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder Builder + { + [DebuggerStepThrough] + get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexType ComplexType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsCollection { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsInModel + => _builder is not null + && DeclaringType.IsInModel; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void SetRemovedFromModel() + { + _builder = null; + ComplexType.SetRemovedFromModel(); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsNullable + { + get => _isNullable ?? DefaultIsNullable; + set => SetIsNullable(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? SetIsNullable(bool? nullable, ConfigurationSource configurationSource) + { + EnsureMutable(); + + var isChanging = (nullable ?? DefaultIsNullable) != IsNullable; + if (nullable == null) + { + _isNullable = null; + _isNullableConfigurationSource = null; + if (isChanging) + { + OnPropertyNullableChanged(); + } + + return nullable; + } + + if (nullable.Value) + { + if (!ClrType.IsNullableType()) + { + throw new InvalidOperationException( + CoreStrings.CannotBeNullable(Name, DeclaringType.DisplayName(), ClrType.ShortDisplayName())); + } + } + + _isNullableConfigurationSource = configurationSource.Max(_isNullableConfigurationSource); + + _isNullable = nullable; + + return isChanging + ? OnPropertyNullableChanged() + : nullable; + } + + private bool DefaultIsNullable + => ClrType.IsNullableType(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetIsNullableConfigurationSource() + => _isNullableConfigurationSource; + + /// + /// 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 bool? OnPropertyNullableChanged() + => DeclaringType.Model.ConventionDispatcher.OnComplexPropertyNullabilityChanged(Builder); + + /// + /// 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 FieldInfo? OnFieldInfoSet(FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) + => DeclaringType.Model.ConventionDispatcher.OnComplexPropertyFieldChanged(Builder, newFieldInfo, oldFieldInfo); + + /// + /// 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 TypeBase DeclaringType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] + public override Type ClrType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool IsCompatible( + string propertyName, + MemberInfo memberInfo, + TypeBase sourceType, + Type targetType, + bool shouldBeCollection, + bool shouldThrow) + { + var memberClrType = memberInfo.GetMemberType().TryGetSequenceType(); + if (shouldBeCollection + && memberClrType?.IsAssignableFrom(targetType) != true) + { + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.NavigationCollectionWrongClrType( + propertyName, + sourceType.DisplayName(), + memberInfo.GetMemberType().ShortDisplayName(), + targetType.ShortDisplayName())); + } + + return false; + } + + if (!shouldBeCollection + && !memberInfo.GetMemberType().IsAssignableFrom(targetType)) + { + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.NavigationSingleWrongClrType( + propertyName, + sourceType.DisplayName(), + memberInfo.GetMemberType().ShortDisplayName(), + targetType.ShortDisplayName())); + } + + return false; + } + + return true; + } + + /// + /// Runs the conventions when an annotation was set or removed. + /// + /// The key of the set annotation. + /// The annotation set. + /// The old annotation. + /// The annotation that was set. + protected override IConventionAnnotation? OnAnnotationSet( + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + => DeclaringType.Model.ConventionDispatcher.OnComplexPropertyAnnotationChanged(Builder, name, annotation, oldAnnotation); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual DebugView DebugView + => new( + () => ((IReadOnlyEntityType)this).ToDebugString(), + () => ((IReadOnlyEntityType)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + /// 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 string ToString() + => ((IReadOnlyComplexProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + /// 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. + /// + IConventionComplexPropertyBuilder IConventionComplexProperty.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// 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. + /// + IReadOnlyComplexType IReadOnlyComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + /// 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. + /// + IMutableComplexType IMutableComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + /// 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. + /// + IConventionComplexType IConventionComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + /// 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. + /// + IComplexType IComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool? IConventionComplexProperty.SetIsNullable(bool? nullable, bool fromDataAnnotation) + => SetIsNullable( + nullable, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore/Metadata/Internal/ComplexPropertyConfiguration.cs b/src/EFCore/Metadata/Internal/ComplexPropertyConfiguration.cs new file mode 100644 index 00000000000..3912cc0e635 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexPropertyConfiguration.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class ComplexPropertyConfiguration : AnnotatableBase +{ + /// + /// 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 ComplexPropertyConfiguration(Type clrType) + { + ClrType = clrType; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Type ClrType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void Apply(IMutableComplexProperty property) + { + foreach (var annotation in GetAnnotations()) + { + if (!CoreAnnotationNames.AllNames.Contains(annotation.Name)) + { + property.SetAnnotation(annotation.Name, annotation.Value); + } + } + } +} diff --git a/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs b/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs new file mode 100644 index 00000000000..b1a324e145d --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs @@ -0,0 +1,183 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class ComplexPropertySnapshot +{ + /// + /// 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 ComplexPropertySnapshot( + InternalComplexPropertyBuilder complexPropertyBuilder, + PropertiesSnapshot? properties, + List? indexes, + List<(InternalKeyBuilder, ConfigurationSource?)>? keys, + List? relationships) + { + ComplexPropertyBuilder = complexPropertyBuilder; + ComplexTypeBuilder = ComplexProperty.ComplexType.Builder; + Properties = properties ?? new PropertiesSnapshot(null, null, null, null); + if (indexes != null) + { + Properties.Add(indexes); + } + + if (keys != null) + { + Properties.Add(keys); + } + + if (relationships != null) + { + Properties.Add(relationships); + } + } + + private InternalComplexPropertyBuilder ComplexPropertyBuilder { [DebuggerStepThrough] get; } + private InternalComplexTypeBuilder ComplexTypeBuilder { [DebuggerStepThrough] get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty ComplexProperty => ComplexPropertyBuilder.Metadata; + private ComplexType ComplexType => ComplexTypeBuilder.Metadata; + private PropertiesSnapshot Properties { [DebuggerStepThrough] get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? Attach(InternalTypeBaseBuilder typeBaseBuilder) + { + var newProperty = typeBaseBuilder.Metadata.FindComplexProperty(ComplexProperty.Name); + if (newProperty == ComplexProperty) + { + return newProperty.Builder; + } + + InternalComplexPropertyBuilder? complexPropertyBuilder; + var configurationSource = ComplexProperty.GetConfigurationSource(); + if (newProperty != null + && (newProperty.GetConfigurationSource().Overrides(configurationSource) + || (ComplexProperty.ClrType == newProperty.ClrType + && ComplexProperty.Name == newProperty.Name + && ComplexProperty.GetIdentifyingMemberInfo() == newProperty.GetIdentifyingMemberInfo()))) + { + complexPropertyBuilder = newProperty.Builder; + newProperty.UpdateConfigurationSource(configurationSource); + } + else + { + complexPropertyBuilder = ComplexProperty.IsIndexerProperty() + ? typeBaseBuilder.ComplexIndexerProperty( + ComplexProperty.ClrType, + ComplexProperty.Name, + ComplexType.ClrType, + ComplexProperty.IsCollection, + configurationSource) + : typeBaseBuilder.ComplexProperty( + ComplexProperty.ClrType, + ComplexProperty.Name, + ComplexProperty.GetIdentifyingMemberInfo(), + ComplexType.ClrType, + ComplexProperty.IsCollection, + configurationSource); + + if (complexPropertyBuilder is null) + { + return null; + } + } + + return MergeConfiguration(complexPropertyBuilder); + } + + private InternalComplexPropertyBuilder MergeConfiguration(InternalComplexPropertyBuilder complexPropertyBuilder) + { + complexPropertyBuilder.MergeAnnotationsFrom(ComplexProperty); + + var oldIsNullableConfigurationSource = ComplexProperty.GetIsNullableConfigurationSource(); + if (oldIsNullableConfigurationSource.HasValue) + { + complexPropertyBuilder.IsRequired(!ComplexProperty.IsNullable, oldIsNullableConfigurationSource.Value); + } + + var oldPropertyAccessModeConfigurationSource = ComplexProperty.GetPropertyAccessModeConfigurationSource(); + if (oldPropertyAccessModeConfigurationSource.HasValue) + { + complexPropertyBuilder.UsePropertyAccessMode( + ((IReadOnlyProperty)ComplexProperty).GetPropertyAccessMode(), oldPropertyAccessModeConfigurationSource.Value); + } + + var oldFieldInfoConfigurationSource = ComplexProperty.GetFieldInfoConfigurationSource(); + if (oldFieldInfoConfigurationSource.HasValue + && complexPropertyBuilder.CanSetField(ComplexProperty.FieldInfo, oldFieldInfoConfigurationSource)) + { + complexPropertyBuilder.HasField(ComplexProperty.FieldInfo, oldFieldInfoConfigurationSource.Value); + } + + complexPropertyBuilder.MergeAnnotationsFrom(ComplexProperty); + if (ComplexProperty.GetIsNullableConfigurationSource() != null) + { + complexPropertyBuilder.IsRequired(!ComplexProperty.IsNullable, ComplexProperty.GetIsNullableConfigurationSource()!.Value); + } + + var complexTypeBuilder = complexPropertyBuilder.Metadata.ComplexType.Builder; + complexTypeBuilder.MergeAnnotationsFrom(ComplexType); + + foreach (var ignoredMember in ComplexType.GetIgnoredMembers()) + { + complexTypeBuilder.Ignore(ignoredMember, ComplexType.FindDeclaredIgnoredConfigurationSource(ignoredMember)!.Value); + } + + if (ComplexType.GetChangeTrackingStrategyConfigurationSource() != null) + { + complexTypeBuilder.Metadata.SetChangeTrackingStrategy( + ComplexType.GetChangeTrackingStrategy(), ComplexType.GetChangeTrackingStrategyConfigurationSource()!.Value); + } + + Properties.Attach(complexTypeBuilder); + + if (ComplexType.GetConstructorBindingConfigurationSource() != null) + { + complexTypeBuilder.Metadata.SetConstructorBinding( + Create(ComplexType.ConstructorBinding, complexTypeBuilder.Metadata), + ComplexType.GetConstructorBindingConfigurationSource()!.Value); + } + + if (ComplexType.GetServiceOnlyConstructorBindingConfigurationSource() != null) + { + complexTypeBuilder.Metadata.SetServiceOnlyConstructorBinding( + Create(ComplexType.ServiceOnlyConstructorBinding, complexTypeBuilder.Metadata), + ComplexType.GetServiceOnlyConstructorBindingConfigurationSource()!.Value); + } + + return complexPropertyBuilder; + } + + private static InstantiationBinding? Create(InstantiationBinding? instantiationBinding, ComplexType complexType) + => instantiationBinding?.With( + instantiationBinding.ParameterBindings.Select(binding => Create(binding, complexType)).ToList()); + + private static ParameterBinding Create(ParameterBinding parameterBinding, ComplexType complexType) + => parameterBinding.With( + parameterBinding.ConsumedProperties.Select( + property => + (IPropertyBase?)complexType.FindProperty(property.Name) + ?? complexType.FindComplexProperty(property.Name)!).ToArray()); +} diff --git a/src/EFCore/Metadata/Internal/ComplexType.cs b/src/EFCore/Metadata/Internal/ComplexType.cs new file mode 100644 index 00000000000..70d54cd5464 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexType.cs @@ -0,0 +1,772 @@ +// 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 Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class ComplexType : TypeBase, IMutableComplexType, IConventionComplexType, IRuntimeComplexType +{ + private InternalComplexTypeBuilder? _builder; + + private ConfigurationSource? _baseTypeConfigurationSource; + private ConfigurationSource? _constructorBindingConfigurationSource; + private ConfigurationSource? _serviceOnlyConstructorBindingConfigurationSource; + + // Warning: Never access these fields directly as access needs to be thread-safe + private PropertyCounts? _counts; + + // _serviceOnlyConstructorBinding needs to be set as well whenever _constructorBinding is set + private InstantiationBinding? _constructorBinding; + private InstantiationBinding? _serviceOnlyConstructorBinding; + + private Func? _originalValuesFactory; + private Func? _temporaryValuesFactory; + private Func? _storeGeneratedValuesFactory; + private Func? _shadowValuesFactory; + private Func? _emptyShadowValuesFactory; + private IProperty[]? _foreignKeyProperties; + private IProperty[]? _valueGeneratingProperties; + + /// + /// 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 ComplexType( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + ComplexProperty property, + ConfigurationSource configurationSource) + : base(name, type, property.DeclaringType.Model, configurationSource) + { + if (!type.IsValidComplexType()) + { + throw new ArgumentException(CoreStrings.InvalidComplexType(type)); + } + + if (EntityType.DynamicProxyGenAssemblyName.Equals( + type.Assembly.GetName().Name, StringComparison.Ordinal)) + { + throw new ArgumentException( + CoreStrings.AddingProxyTypeAsEntityType(type.FullName)); + } + + ComplexProperty = property; + _builder = new InternalComplexTypeBuilder(this, property.DeclaringType.Model.Builder); + } + + /// + /// 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 new virtual InternalComplexTypeBuilder Builder + { + [DebuggerStepThrough] + get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); + } + + /// + /// 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 InternalTypeBaseBuilder BaseBuilder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty ComplexProperty { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual EntityType FundamentalEntityType + => ComplexProperty.DeclaringType switch + { + EntityType entityType => entityType, + ComplexType declaringComplexType => declaringComplexType.FundamentalEntityType, + _ => throw new NotImplementedException() + }; + + /// + /// 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 bool IsInModel + => _builder is not null + && ComplexProperty.IsInModel; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void SetRemovedFromModel() + { + _builder = null; + BaseType?.DirectlyDerivedTypes.Remove(this); + } + + /// + /// 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. + /// + new public virtual ComplexType? BaseType + => (ComplexType?)base.BaseType; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexType? SetBaseType(ComplexType? newBaseType, ConfigurationSource configurationSource) + { + EnsureMutable(); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + + if (BaseType == newBaseType) + { + UpdateBaseTypeConfigurationSource(configurationSource); + newBaseType?.UpdateConfigurationSource(configurationSource); + return newBaseType; + } + else if (BaseType == null) + { + throw new NotImplementedException(); + } + + var originalBaseType = BaseType; + + BaseType?.DirectlyDerivedTypes.Remove(this); + base.BaseType = null; + + if (newBaseType != null) + { + if (!newBaseType.ClrType.IsAssignableFrom(ClrType)) + { + throw new InvalidOperationException( + CoreStrings.NotAssignableClrBaseType( + DisplayName(), newBaseType.DisplayName(), ClrType.ShortDisplayName(), + newBaseType.ClrType.ShortDisplayName())); + } + + if (newBaseType.InheritsFrom(this)) + { + throw new InvalidOperationException(CoreStrings.CircularInheritance(DisplayName(), newBaseType.DisplayName())); + } + + var conflictingMember = newBaseType.GetMembers() + .Select(p => p.Name) + .SelectMany(FindMembersInHierarchy) + .FirstOrDefault(); + + if (conflictingMember != null) + { + var baseProperty = newBaseType.FindMembersInHierarchy(conflictingMember.Name).Single(); + throw new InvalidOperationException( + CoreStrings.DuplicatePropertiesOnBase( + DisplayName(), + newBaseType.DisplayName(), + conflictingMember.DeclaringType.DisplayName(), + conflictingMember.Name, + baseProperty.DeclaringType.DisplayName(), + baseProperty.Name)); + } + + base.BaseType = newBaseType; + newBaseType.DirectlyDerivedTypes.Add(this); + } + + UpdateBaseTypeConfigurationSource(configurationSource); + newBaseType?.UpdateConfigurationSource(configurationSource); + + return newBaseType; + //return (ComplexType?)Model.ConventionDispatcher.OnComplexTypeBaseTypeChanged(Builder, newBaseType, originalBaseType); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public virtual ConfigurationSource? GetBaseTypeConfigurationSource() + => _baseTypeConfigurationSource; + + [DebuggerStepThrough] + private void UpdateBaseTypeConfigurationSource(ConfigurationSource configurationSource) + => _baseTypeConfigurationSource = configurationSource.Max(_baseTypeConfigurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public virtual IEnumerable GetDirectlyDerivedTypes() + => base.DirectlyDerivedTypes.Cast(); + + private bool InheritsFrom(ComplexType type) + { + var currentType = this; + + do + { + if (type == currentType) + { + return true; + } + } + while ((currentType = currentType.BaseType) != null); + + return false; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsAssignableFrom(ComplexType derivedType) + { + Check.NotNull(derivedType, nameof(derivedType)); + + if (derivedType == this) + { + return true; + } + + if (!GetDirectlyDerivedTypes().Any()) + { + return false; + } + + var baseType = derivedType.BaseType; + while (baseType != null) + { + if (baseType == this) + { + return true; + } + + baseType = baseType.BaseType; + } + + return false; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public virtual ComplexType GetRootType() + => BaseType?.GetRootType() ?? this; + + /// + /// 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. + /// + private string DisplayName() + => ((IReadOnlyComplexType)this).DisplayName(); + + /// + /// Runs the conventions when an annotation was set or removed. + /// + /// The key of the set annotation. + /// The annotation set. + /// The old annotation. + /// The annotation that was set. + protected override IConventionAnnotation? OnAnnotationSet( + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + => Model.ConventionDispatcher.OnComplexTypeAnnotationChanged(Builder, name, annotation, oldAnnotation); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override IEnumerable GetMembers() + => GetProperties().Cast() + .Concat(GetComplexProperties()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override IEnumerable GetDeclaredMembers() + => GetDeclaredProperties().Cast() + .Concat(GetDeclaredComplexProperties()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override IEnumerable FindMembersInHierarchy(string name) + => FindPropertiesInHierarchy(name).Cast() + .Concat(FindComplexPropertiesInHierarchy(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. + /// + public virtual PropertyCounts Counts + => NonCapturingLazyInitializer.EnsureInitialized( + ref _counts, this, static complexType => + { + complexType.EnsureReadOnly(); + return complexType.CalculateCounts(); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func OriginalValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _originalValuesFactory, this, + static complexType => + { + complexType.EnsureReadOnly(); + return new OriginalValuesFactoryFactory().Create(complexType); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func StoreGeneratedValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _storeGeneratedValuesFactory, this, + static complexType => + { + complexType.EnsureReadOnly(); + return new StoreGeneratedValuesFactoryFactory().CreateEmpty(complexType); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func TemporaryValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _temporaryValuesFactory, this, + static complexType => + { + complexType.EnsureReadOnly(); + return new TemporaryValuesFactoryFactory().Create(complexType); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func ShadowValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _shadowValuesFactory, this, + static complexType => + { + complexType.EnsureReadOnly(); + return new ShadowValuesFactoryFactory().Create(complexType); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func EmptyShadowValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _emptyShadowValuesFactory, this, + static complexType => + { + complexType.EnsureReadOnly(); + return new EmptyShadowValuesFactoryFactory().CreateEmpty(complexType); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList ForeignKeyProperties + => NonCapturingLazyInitializer.EnsureInitialized( + ref _foreignKeyProperties, this, + static entityType => + { + entityType.EnsureReadOnly(); + + return entityType.GetProperties().Where(p => p.IsForeignKey()).ToArray(); + }); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList ValueGeneratingProperties + => NonCapturingLazyInitializer.EnsureInitialized( + ref _valueGeneratingProperties, this, + static complexType => + { + complexType.EnsureReadOnly(); + + return complexType.GetProperties().Where(p => p.RequiresValueGenerator()).ToArray(); + }); + + /// + /// 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 string? OnTypeMemberIgnored(string name) + => Model.ConventionDispatcher.OnComplexTypeMemberIgnored(Builder, 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. + /// + public virtual InstantiationBinding? ConstructorBinding + { + get => IsReadOnly && !ClrType.IsAbstract + ? NonCapturingLazyInitializer.EnsureInitialized( + ref _constructorBinding, this, static complexType => + { + ((IModel)complexType.Model).GetModelDependencies().ConstructorBindingFactory.GetBindings( + (IReadOnlyEntityType)complexType, + out complexType._constructorBinding, + out complexType._serviceOnlyConstructorBinding); + }) + : _constructorBinding; + + set => SetConstructorBinding(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InstantiationBinding? SetConstructorBinding( + InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + { + EnsureMutable(); + + _constructorBinding = constructorBinding; + + if (_constructorBinding == null) + { + _constructorBindingConfigurationSource = null; + } + else + { + UpdateConstructorBindingConfigurationSource(configurationSource); + } + + return constructorBinding; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetConstructorBindingConfigurationSource() + => _constructorBindingConfigurationSource; + + private void UpdateConstructorBindingConfigurationSource(ConfigurationSource configurationSource) + => _constructorBindingConfigurationSource = configurationSource.Max(_constructorBindingConfigurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InstantiationBinding? ServiceOnlyConstructorBinding + { + get => _serviceOnlyConstructorBinding; + set => SetServiceOnlyConstructorBinding(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InstantiationBinding? SetServiceOnlyConstructorBinding( + InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + { + EnsureMutable(); + + _serviceOnlyConstructorBinding = constructorBinding; + + if (_serviceOnlyConstructorBinding == null) + { + _serviceOnlyConstructorBindingConfigurationSource = null; + } + else + { + UpdateServiceOnlyConstructorBindingConfigurationSource(configurationSource); + } + + return constructorBinding; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetServiceOnlyConstructorBindingConfigurationSource() + => _serviceOnlyConstructorBindingConfigurationSource; + + private void UpdateServiceOnlyConstructorBindingConfigurationSource(ConfigurationSource configurationSource) + => _serviceOnlyConstructorBindingConfigurationSource = + configurationSource.Max(_serviceOnlyConstructorBindingConfigurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual DebugView DebugView + => new( + () => ((IReadOnlyEntityType)this).ToDebugString(), + () => ((IReadOnlyEntityType)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + /// 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 string ToString() + => ((IReadOnlyComplexProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + #region Explicit interface implementations + + /// + /// 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. + /// + IConventionComplexTypeBuilder IConventionComplexType.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// 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. + /// + IConventionAnnotatableBuilder IConventionAnnotatable.Builder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// 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. + /// + IReadOnlyModel IReadOnlyTypeBase.Model + { + [DebuggerStepThrough] + get => 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. + /// + IMutableModel IMutableTypeBase.Model + { + [DebuggerStepThrough] + get => 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. + /// + IModel ITypeBase.Model + { + [DebuggerStepThrough] + get => 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. + /// + IComplexProperty IComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + /// 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. + /// + IReadOnlyComplexProperty IReadOnlyComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + /// 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. + /// + IConventionComplexProperty IConventionComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + /// 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. + /// + IMutableComplexProperty IMutableComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + /// 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. + /// + IReadOnlyEntityType IReadOnlyTypeBase.FundamentalEntityType + { + [DebuggerStepThrough] + get => FundamentalEntityType; + } + + /// + /// 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. + /// + IMutableEntityType IMutableTypeBase.FundamentalEntityType + { + [DebuggerStepThrough] + get => FundamentalEntityType; + } + + /// + /// 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. + /// + IConventionEntityType IConventionTypeBase.FundamentalEntityType + { + [DebuggerStepThrough] + get => FundamentalEntityType; + } + + /// + /// 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. + /// + IEntityType ITypeBase.FundamentalEntityType + { + [DebuggerStepThrough] + get => FundamentalEntityType; + } + + #endregion +} diff --git a/src/EFCore/Metadata/Internal/ComplexTypeExtensions.cs b/src/EFCore/Metadata/Internal/ComplexTypeExtensions.cs new file mode 100644 index 00000000000..54d85423508 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexTypeExtensions.cs @@ -0,0 +1,87 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ReSharper disable ArgumentsStyleOther +// ReSharper disable ArgumentsStyleNamedExpression +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class ComplexTypeExtensions +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool UseEagerSnapshots(this IReadOnlyComplexType complexType) + { + var changeTrackingStrategy = complexType.GetChangeTrackingStrategy(); + + return changeTrackingStrategy == ChangeTrackingStrategy.Snapshot + || changeTrackingStrategy == ChangeTrackingStrategy.ChangedNotifications; + } + + /// + /// 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 PropertyCounts CalculateCounts(this IRuntimeComplexType complexType) + { + var propertyIndex = 0; + var complexPropertyIndex = 0; + var originalValueIndex = 0; + var shadowIndex = 0; + var storeGenerationIndex = 0; + var relationshipIndex = ((IRuntimeTypeBase)complexType.ComplexProperty.DeclaringType).Counts.RelationshipCount; + + var baseCounts = (complexType as ComplexType)?.BaseType?.Counts; + if (baseCounts != null) + { + propertyIndex = baseCounts.PropertyCount; + originalValueIndex = baseCounts.OriginalValueCount; + shadowIndex = baseCounts.ShadowCount; + storeGenerationIndex = baseCounts.StoreGeneratedCount; + } + + foreach (var property in complexType.GetProperties()) + { + var indexes = new PropertyIndexes( + index: propertyIndex++, + originalValueIndex: property.RequiresOriginalValue() ? originalValueIndex++ : -1, + shadowIndex: property.IsShadowProperty() ? shadowIndex++ : -1, + relationshipIndex: property.IsKey() || property.IsForeignKey() ? relationshipIndex++ : -1, + storeGenerationIndex: property.MayBeStoreGenerated() ? storeGenerationIndex++ : -1); + + ((IRuntimePropertyBase)property).PropertyIndexes = indexes; + } + + foreach (var complexProperty in complexType.GetComplexProperties()) + { + var indexes = new PropertyIndexes( + index: complexPropertyIndex++, + originalValueIndex: -1, + shadowIndex: complexProperty.IsShadowProperty() ? shadowIndex++ : -1, + relationshipIndex: -1, + storeGenerationIndex: -1); + + ((IRuntimePropertyBase)complexProperty).PropertyIndexes = indexes; + } + + return new PropertyCounts( + propertyIndex, + navigationCount: 0, + complexPropertyIndex, + originalValueIndex, + shadowIndex, + relationshipCount: 0, + storeGenerationIndex); + } +} diff --git a/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs b/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs index 45fe64271ab..b40b5816385 100644 --- a/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs +++ b/src/EFCore/Metadata/Internal/ConstructorBindingFactory.cs @@ -42,7 +42,8 @@ public virtual void GetBindings( out InstantiationBinding? serviceOnlyBinding) => GetBindings( entityType, - static (f, e, p, n) => f?.Bind((IConventionEntityType)e, p, n), + static (f, e, p, n) => f.FindParameter((IEntityType)e, p, n), + static (f, e, p, n) => f?.Bind(e, p, n), out constructorBinding, out serviceOnlyBinding); @@ -58,7 +59,8 @@ public virtual void GetBindings( out InstantiationBinding? serviceOnlyBinding) => GetBindings( entityType, - static (f, e, p, n) => f?.Bind((IMutableEntityType)e, p, n), + static (f, e, p, n) => f.FindParameter((IEntityType)e, p, n), + static (f, e, p, n) => f?.Bind(e, p, n), out constructorBinding, out serviceOnlyBinding); @@ -74,15 +76,35 @@ public virtual void GetBindings( out InstantiationBinding? serviceOnlyBinding) => GetBindings( entityType, + static (f, e, p, n) => f.FindParameter((IEntityType)e, p, n), static (f, e, p, n) => f?.Bind(e, p, n), out constructorBinding, out serviceOnlyBinding); - private void GetBindings( - IReadOnlyEntityType entityType, - Func bind, + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void GetBindings( + IReadOnlyComplexType complexType, + out InstantiationBinding constructorBinding, + out InstantiationBinding? serviceOnlyBinding) + => GetBindings( + complexType, + static (f, e, p, n) => f.FindParameter((IComplexType)e, p, n), + static (f, e, p, n) => null, + out constructorBinding, + out serviceOnlyBinding); + + private void GetBindings( + T entityType, + Func bindToProperty, + Func bind, out InstantiationBinding constructorBinding, out InstantiationBinding? serviceOnlyBinding) + where T : IReadOnlyTypeBase { var maxServiceParams = 0; var maxServiceOnlyParams = 0; @@ -98,7 +120,7 @@ private void GetBindings( // Trying to find the constructor with the most service properties // followed by the least scalar property parameters if (TryBindConstructor( - entityType, constructor, bind, out var binding, out var failures)) + entityType, constructor, bindToProperty, bind, out var binding, out var failures)) { var serviceParamCount = binding.ParameterBindings.OfType().Count(); var propertyParamCount = binding.ParameterBindings.Count - serviceParamCount; @@ -192,7 +214,8 @@ public virtual bool TryBindConstructor( => TryBindConstructor( entityType, constructor, - static (f, e, p, n) => f?.Bind((IMutableEntityType)e, p, n), + static (f, e, p, n) => f.FindParameter((IEntityType)e, p, n), + static (f, e, p, n) => f?.Bind(e, p, n), out binding, out unboundParameters); @@ -210,40 +233,63 @@ public virtual bool TryBindConstructor( => TryBindConstructor( entityType, constructor, - static (f, e, p, n) => f?.Bind((IConventionEntityType)e, p, n), + static (f, e, p, n) => f.FindParameter((IEntityType)e, p, n), + static (f, e, p, n) => f?.Bind(e, p, n), out binding, out unboundParameters); - private bool TryBindConstructor( - IReadOnlyEntityType entityType, + private bool TryBindConstructor( + T entityType, ConstructorInfo constructor, - Func bind, + Func bindToProperty, + Func bind, [NotNullWhen(true)] out InstantiationBinding? binding, [NotNullWhen(false)] out IEnumerable? unboundParameters) + where T : IReadOnlyTypeBase { - IEnumerable<(ParameterInfo Parameter, ParameterBinding? Binding)> bindings - = constructor.GetParameters().Select( - p => (p, string.IsNullOrEmpty(p.Name) - ? null - : _propertyFactory.FindParameter((IEntityType)entityType, p.ParameterType, p.Name) - ?? bind(_factories.FindFactory(p.ParameterType, p.Name), entityType, p.ParameterType, p.Name))) - .ToList(); - - if (bindings.Any(b => b.Binding == null)) + var bindings = new List(); + List? unboundParametersList = null; + foreach (var parameter in constructor.GetParameters()) + { + var parameterBinding = BindParameter(entityType, bindToProperty, bind, parameter); + if (parameterBinding == null) + { + unboundParametersList ??= new List(); + unboundParametersList.Add(parameter); + } + else + { + bindings.Add(parameterBinding); + } + } + + if (unboundParametersList != null) { - unboundParameters = bindings.Where(b => b.Binding == null).Select(b => b.Parameter); + unboundParameters = unboundParametersList; binding = null; return false; } unboundParameters = null; - binding = new ConstructorBinding(constructor, bindings.Select(b => b.Binding).ToList()!); + binding = new ConstructorBinding(constructor, bindings); return true; } - private static string FormatConstructorString(IReadOnlyEntityType entityType, InstantiationBinding binding) + private ParameterBinding? BindParameter( + T entityType, + Func bindToProperty, + Func bind, + ParameterInfo p) + where T : IReadOnlyTypeBase + => string.IsNullOrEmpty(p.Name) + ? null + : bindToProperty(_propertyFactory, entityType, p.ParameterType, p.Name) + ?? bind(_factories.FindFactory(p.ParameterType, p.Name), entityType, p.ParameterType, p.Name); + + private static string FormatConstructorString(T entityType, InstantiationBinding binding) + where T : IReadOnlyTypeBase => entityType.ClrType.ShortDisplayName() + "(" + string.Join(", ", binding.ParameterBindings.Select(b => b.ParameterType.ShortDisplayName())) diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index dc249917a9c..e97e55d22bf 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -1,7 +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.ComponentModel; using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; @@ -16,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public class EntityType : TypeBase, IMutableEntityType, IConventionEntityType, IRuntimeEntityType { - private const string DynamicProxyGenAssemblyName = "DynamicProxyGenAssembly2"; + internal const string DynamicProxyGenAssemblyName = "DynamicProxyGenAssembly2"; private readonly SortedSet _foreignKeys = new(ForeignKeyComparer.Instance); @@ -30,8 +29,6 @@ private readonly SortedDictionary _skipNavigations private readonly SortedDictionary _serviceProperties = new(StringComparer.Ordinal); - private readonly SortedDictionary _properties; - private readonly SortedDictionary, Index> _unnamedIndexes = new(PropertyListComparer.Instance); @@ -48,14 +45,11 @@ private readonly SortedDictionary _triggers private Key? _primaryKey; private bool? _isKeyless; private bool _isOwned; - private EntityType? _baseType; - private ChangeTrackingStrategy? _changeTrackingStrategy; private InternalEntityTypeBuilder? _builder; private ConfigurationSource? _primaryKeyConfigurationSource; private ConfigurationSource? _isKeylessConfigurationSource; private ConfigurationSource? _baseTypeConfigurationSource; - private ConfigurationSource? _changeTrackingStrategyConfigurationSource; private ConfigurationSource? _constructorBindingConfigurationSource; private ConfigurationSource? _serviceOnlyConstructorBindingConfigurationSource; @@ -84,7 +78,6 @@ private readonly SortedDictionary _triggers public EntityType(string name, Model model, bool owned, ConfigurationSource configurationSource) : base(name, Model.DefaultPropertyBagType, model, configurationSource) { - _properties = new SortedDictionary(new PropertyNameComparer(this)); _builder = new InternalEntityTypeBuilder(this, model.Builder); _isOwned = owned; } @@ -114,7 +107,6 @@ public EntityType( CoreStrings.AddingProxyTypeAsEntityType(type.FullName)); } - _properties = new SortedDictionary(new PropertyNameComparer(this)); _builder = new InternalEntityTypeBuilder(this, model.Builder); _isOwned = owned; } @@ -145,7 +137,6 @@ public EntityType( CoreStrings.AddingProxyTypeAsEntityType(type.FullName)); } - _properties = new SortedDictionary(new PropertyNameComparer(this)); _builder = new InternalEntityTypeBuilder(this, model.Builder); _isOwned = owned; } @@ -156,7 +147,7 @@ public EntityType( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalEntityTypeBuilder Builder + public new virtual InternalEntityTypeBuilder Builder { [DebuggerStepThrough] get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); @@ -168,7 +159,19 @@ public virtual InternalEntityTypeBuilder Builder /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool IsInModel + protected override InternalTypeBaseBuilder BaseBuilder + { + [DebuggerStepThrough] + get => Builder; + } + + /// + /// 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 bool IsInModel => _builder is not null; /// @@ -178,7 +181,34 @@ public virtual bool IsInModel /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual void SetRemovedFromModel() - => _builder = null; + { + if (_foreignKeys.Count > 0) + { + foreach (var foreignKey in GetDeclaredForeignKeys().ToList()) + { + if (foreignKey.PrincipalEntityType != this) + { + RemoveForeignKey(foreignKey); + } + } + } + + if (_skipNavigations.Count > 0) + { + foreach (var skipNavigation in GetDeclaredSkipNavigations().ToList()) + { + if (skipNavigation.TargetEntityType != this) + { + RemoveSkipNavigation(skipNavigation); + } + } + } + + _builder = null; + BaseType?.DirectlyDerivedTypes.Remove(this); + + Model.ConventionDispatcher.OnEntityTypeRemoved(Model.Builder, this); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -186,8 +216,8 @@ public virtual void SetRemovedFromModel() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual EntityType? BaseType - => _baseType; + new public virtual EntityType? BaseType + => (EntityType?)base.BaseType; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -197,7 +227,7 @@ public virtual EntityType? BaseType /// public virtual bool IsKeyless { - get => RootType()._isKeyless ?? false; + get => GetRootType()._isKeyless ?? false; set => SetIsKeyless(value, ConfigurationSource.Explicit); } @@ -255,10 +285,10 @@ private string DisplayName() if (keyless == true) { - if (_baseType != null) + if (BaseType != null) { throw new InvalidOperationException( - CoreStrings.DerivedEntityTypeHasNoKey(DisplayName(), RootType().DisplayName())); + CoreStrings.DerivedEntityTypeHasNoKey(DisplayName(), GetRootType().DisplayName())); } if (_keys.Count != 0) @@ -306,17 +336,17 @@ private void UpdateIsKeylessConfigurationSource(ConfigurationSource configuratio EnsureMutable(); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - if (_baseType == newBaseType) + if (BaseType == newBaseType) { UpdateBaseTypeConfigurationSource(configurationSource); newBaseType?.UpdateConfigurationSource(configurationSource); return newBaseType; } - var originalBaseType = _baseType; + var originalBaseType = BaseType; - _baseType?._directlyDerivedTypes.Remove(this); - _baseType = null; + BaseType?.DirectlyDerivedTypes.Remove(this); + base.BaseType = null; if (newBaseType != null) { @@ -360,19 +390,19 @@ private void UpdateIsKeylessConfigurationSource(ConfigurationSource configuratio if (conflictingMember != null) { - var baseProperty = newBaseType.FindMembersInHierarchy(conflictingMember.Name).Single(); + var baseMember = newBaseType.FindMembersInHierarchy(conflictingMember.Name).Single(); throw new InvalidOperationException( CoreStrings.DuplicatePropertiesOnBase( DisplayName(), newBaseType.DisplayName(), - ((IReadOnlyTypeBase)conflictingMember.DeclaringType).DisplayName(), + conflictingMember.DeclaringType.DisplayName(), conflictingMember.Name, - ((IReadOnlyTypeBase)baseProperty.DeclaringType).DisplayName(), - baseProperty.Name)); + baseMember.DeclaringType.DisplayName(), + baseMember.Name)); } - _baseType = newBaseType; - _baseType._directlyDerivedTypes.Add(this); + base.BaseType = newBaseType; + newBaseType.DirectlyDerivedTypes.Add(this); } UpdateBaseTypeConfigurationSource(configurationSource); @@ -381,42 +411,6 @@ private void UpdateIsKeylessConfigurationSource(ConfigurationSource configuratio return (EntityType?)Model.ConventionDispatcher.OnEntityTypeBaseTypeChanged(Builder, newBaseType, originalBaseType); } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual void OnTypeRemoved() - { - if (_foreignKeys.Count > 0) - { - foreach (var foreignKey in GetDeclaredForeignKeys().ToList()) - { - if (foreignKey.PrincipalEntityType != this) - { - RemoveForeignKey(foreignKey); - } - } - } - - if (_skipNavigations.Count > 0) - { - foreach (var skipNavigation in GetDeclaredSkipNavigations().ToList()) - { - if (skipNavigation.TargetEntityType != this) - { - RemoveSkipNavigation(skipNavigation); - } - } - } - - _builder = null; - _baseType?._directlyDerivedTypes.Remove(this); - - Model.ConventionDispatcher.OnEntityTypeRemoved(Model.Builder, this); - } - /// /// 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 @@ -431,46 +425,6 @@ public virtual void OnTypeRemoved() private void UpdateBaseTypeConfigurationSource(ConfigurationSource configurationSource) => _baseTypeConfigurationSource = configurationSource.Max(_baseTypeConfigurationSource); - private readonly SortedSet _directlyDerivedTypes = new(EntityTypeFullNameComparer.Instance); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - public virtual IReadOnlySet GetDirectlyDerivedTypes() - => _directlyDerivedTypes; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IEnumerable GetDerivedTypes() - { - if (_directlyDerivedTypes.Count == 0) - { - return Enumerable.Empty(); - } - - var derivedTypes = new List(); - var type = this; - var currentTypeIndex = 0; - while (type != null) - { - derivedTypes.AddRange(type.GetDirectlyDerivedTypes()); - type = derivedTypes.Count > currentTypeIndex - ? derivedTypes[currentTypeIndex] - : null; - currentTypeIndex++; - } - - return derivedTypes; - } - /// /// 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 @@ -478,10 +432,8 @@ public virtual IEnumerable GetDerivedTypes() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - public virtual IEnumerable GetDerivedTypesInclusive() - => _directlyDerivedTypes.Count == 0 - ? new[] { this } - : new[] { this }.Concat(GetDerivedTypes()); + public virtual IEnumerable GetDirectlyDerivedTypes() + => DirectlyDerivedTypes.Cast(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -491,7 +443,7 @@ public virtual IEnumerable GetDerivedTypesInclusive() /// [DebuggerStepThrough] public virtual IEnumerable GetForeignKeysInHierarchy() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? GetForeignKeys() : GetForeignKeys().Concat(GetDerivedForeignKeys()); @@ -506,7 +458,7 @@ private bool InheritsFrom(EntityType entityType) return true; } } - while ((et = et._baseType) != null); + while ((et = et.BaseType) != null); return false; } @@ -518,7 +470,7 @@ private bool InheritsFrom(EntityType entityType) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - public virtual EntityType RootType() + public virtual EntityType GetRootType() => (EntityType)((IReadOnlyEntityType)this).GetRootType(); /// @@ -549,8 +501,9 @@ public override string ToString() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IEnumerable GetMembers() - => GetProperties().Cast() + public override IEnumerable GetMembers() + => GetProperties().Cast() + .Concat(GetComplexProperties()) .Concat(GetServiceProperties()) .Concat(GetNavigations()) .Concat(GetSkipNavigations()); @@ -561,8 +514,9 @@ public virtual IEnumerable GetMembers() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IEnumerable GetDeclaredMembers() - => GetDeclaredProperties().Cast() + public override IEnumerable GetDeclaredMembers() + => GetDeclaredProperties().Cast() + .Concat(GetDeclaredComplexProperties()) .Concat(GetDeclaredServiceProperties()) .Concat(GetDeclaredNavigations()) .Concat(GetDeclaredSkipNavigations()); @@ -573,8 +527,9 @@ public virtual IEnumerable GetDeclaredMembers() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IEnumerable FindMembersInHierarchy(string name) - => FindPropertiesInHierarchy(name).Cast() + public override IEnumerable FindMembersInHierarchy(string name) + => FindPropertiesInHierarchy(name).Cast() + .Concat(FindComplexPropertiesInHierarchy(name)) .Concat(FindServicePropertiesInHierarchy(name)) .Concat(FindNavigationsInHierarchy(name)) .Concat(FindSkipNavigationsInHierarchy(name)); @@ -606,9 +561,9 @@ public virtual IEnumerable FindMembersInHierarchy(string name) EnsureMutable(); Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - if (_baseType != null) + if (BaseType != null) { - throw new InvalidOperationException(CoreStrings.DerivedEntityTypeKey(DisplayName(), RootType().DisplayName())); + throw new InvalidOperationException(CoreStrings.DerivedEntityTypeKey(DisplayName(), GetRootType().DisplayName())); } var oldPrimaryKey = _primaryKey; @@ -636,7 +591,7 @@ public virtual IEnumerable FindMembersInHierarchy(string name) { foreach (var property in oldPrimaryKey.Properties) { - _properties.Remove(property.Name); + Properties.Remove(property.Name); property.PrimaryKey = null; } @@ -644,7 +599,7 @@ public virtual IEnumerable FindMembersInHierarchy(string name) foreach (var property in oldPrimaryKey.Properties) { - _properties.Add(property.Name, property); + Properties.Add(property.Name, property); } } @@ -652,7 +607,7 @@ public virtual IEnumerable FindMembersInHierarchy(string name) { foreach (var property in newKey.Properties) { - _properties.Remove(property.Name); + Properties.Remove(property.Name); property.PrimaryKey = newKey; } @@ -660,7 +615,7 @@ public virtual IEnumerable FindMembersInHierarchy(string name) foreach (var property in newKey.Properties) { - _properties.Add(property.Name, property); + Properties.Add(property.Name, property); } UpdatePrimaryKeyConfigurationSource(configurationSource); @@ -680,7 +635,7 @@ public virtual IEnumerable FindMembersInHierarchy(string name) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual Key? FindPrimaryKey() - => _baseType?.FindPrimaryKey() ?? _primaryKey; + => BaseType?.FindPrimaryKey() ?? _primaryKey; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -693,9 +648,9 @@ public virtual IEnumerable FindMembersInHierarchy(string name) Check.HasNoNulls(properties, nameof(properties)); Check.NotEmpty(properties, nameof(properties)); - if (_baseType != null) + if (BaseType != null) { - return _baseType.FindPrimaryKey(properties); + return BaseType.FindPrimaryKey(properties); } return _primaryKey != null @@ -755,9 +710,9 @@ private void UpdatePrimaryKeyConfigurationSource(ConfigurationSource configurati Check.HasNoNulls(properties, nameof(properties)); EnsureMutable(); - if (_baseType != null) + if (BaseType != null) { - throw new InvalidOperationException(CoreStrings.DerivedEntityTypeKey(DisplayName(), _baseType.DisplayName())); + throw new InvalidOperationException(CoreStrings.DerivedEntityTypeKey(DisplayName(), BaseType.DisplayName())); } if (IsKeyless) @@ -834,7 +789,7 @@ private void UpdatePrimaryKeyConfigurationSource(ConfigurationSource configurati Check.HasNoNulls(properties, nameof(properties)); Check.NotEmpty(properties, nameof(properties)); - return FindDeclaredKey(properties) ?? _baseType?.FindKey(properties); + return FindDeclaredKey(properties) ?? BaseType?.FindKey(properties); } /// @@ -867,12 +822,12 @@ public virtual IEnumerable GetDeclaredKeys() { Check.NotEmpty(properties, nameof(properties)); - var wrongEntityTypeProperty = properties.FirstOrDefault(p => !p.DeclaringEntityType.IsAssignableFrom(this)); + var wrongEntityTypeProperty = properties.FirstOrDefault(p => !((EntityType)p.DeclaringType).IsAssignableFrom(this)); if (wrongEntityTypeProperty != null) { throw new InvalidOperationException( CoreStrings.KeyWrongType( - properties.Format(), DisplayName(), wrongEntityTypeProperty.DeclaringEntityType.DisplayName())); + properties.Format(), DisplayName(), wrongEntityTypeProperty.DeclaringType.DisplayName())); } var key = FindDeclaredKey(properties); @@ -947,7 +902,7 @@ private void CheckKeyNotInUse(Key key) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetKeys() - => _baseType?.GetKeys().Concat(_keys.Values) ?? _keys.Values; + => BaseType?.GetKeys().Concat(_keys.Values) ?? _keys.Values; #endregion @@ -1098,10 +1053,10 @@ public virtual IEnumerable FindForeignKeys(IReadOnlyList FindForeignKeys(IReadOnlyList @@ -1192,9 +1147,9 @@ public virtual IEnumerable GetDeclaredForeignKeys() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetDerivedForeignKeys() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et._foreignKeys); + : GetDerivedTypes().SelectMany(et => et._foreignKeys); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1203,10 +1158,10 @@ public virtual IEnumerable GetDerivedForeignKeys() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetForeignKeys() - => _baseType != null + => BaseType != null ? _foreignKeys.Count == 0 - ? _baseType.GetForeignKeys() - : _baseType.GetForeignKeys().Concat(_foreignKeys) + ? BaseType.GetForeignKeys() + : BaseType.GetForeignKeys().Concat(_foreignKeys) : _foreignKeys; /// @@ -1264,9 +1219,9 @@ public virtual IEnumerable FindDeclaredForeignKeys(IReadOnlyList public virtual IEnumerable FindDerivedForeignKeys( IReadOnlyList properties) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.FindDeclaredForeignKeys(properties)); + : GetDerivedTypes().SelectMany(et => et.FindDeclaredForeignKeys(properties)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1278,9 +1233,9 @@ public virtual IEnumerable FindDerivedForeignKeys( IReadOnlyList properties, IReadOnlyKey principalKey, IReadOnlyEntityType principalEntityType) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : (IEnumerable)GetDerivedTypes() + : (IEnumerable)GetDerivedTypes() .Select(et => et.FindDeclaredForeignKey(properties, principalKey, principalEntityType)) .Where(fk => fk != null); @@ -1292,7 +1247,7 @@ public virtual IEnumerable FindDerivedForeignKeys( /// public virtual IEnumerable FindForeignKeysInHierarchy( IReadOnlyList properties) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? FindForeignKeys(properties) : FindForeignKeys(properties).Concat(FindDerivedForeignKeys(properties)); @@ -1306,7 +1261,7 @@ public virtual IEnumerable FindForeignKeysInHierarchy( IReadOnlyList properties, IReadOnlyKey principalKey, IReadOnlyEntityType principalEntityType) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? ToEnumerable(FindForeignKey(properties, principalKey, principalEntityType)) : ToEnumerable(FindForeignKey(properties, principalKey, principalEntityType)) .Concat(FindDerivedForeignKeys(properties, principalKey, principalEntityType)); @@ -1408,10 +1363,10 @@ public virtual IEnumerable FindForeignKeysInHierarchy( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetReferencingForeignKeys() - => _baseType != null + => BaseType != null ? (DeclaredReferencingForeignKeys?.Count ?? 0) == 0 - ? _baseType.GetReferencingForeignKeys() - : _baseType.GetReferencingForeignKeys().Concat(GetDeclaredReferencingForeignKeys()) + ? BaseType.GetReferencingForeignKeys() + : BaseType.GetReferencingForeignKeys().Concat(GetDeclaredReferencingForeignKeys()) : GetDeclaredReferencingForeignKeys(); /// @@ -1585,9 +1540,9 @@ public virtual IEnumerable GetDeclaredNavigations() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetDerivedNavigations() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.GetDeclaredNavigations()); + : GetDerivedTypes().SelectMany(et => et.GetDeclaredNavigations()); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1599,9 +1554,10 @@ public virtual IEnumerable FindDerivedNavigations(string name) { Check.NotNull(name, nameof(name)); - return _directlyDerivedTypes.Count == 0 + return DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : (IEnumerable)GetDerivedTypes().Select(et => et.FindDeclaredNavigation(name)).Where(n => n != null); + : (IEnumerable)GetDerivedTypes() + .Select(et => et.FindDeclaredNavigation(name)).Where(n => n != null); } /// @@ -1611,7 +1567,7 @@ public virtual IEnumerable FindDerivedNavigations(string name) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable FindNavigationsInHierarchy(string name) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? ToEnumerable(FindNavigation(name)) : ToEnumerable(FindNavigation(name)).Concat(FindDerivedNavigations(name)); @@ -1644,8 +1600,8 @@ public virtual IEnumerable FindNavigationsInHierarchy(string name) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetNavigations() - => _baseType != null - ? _navigations.Count == 0 ? _baseType.GetNavigations() : _baseType.GetNavigations().Concat(_navigations.Values) + => BaseType != null + ? _navigations.Count == 0 ? BaseType.GetNavigations() : BaseType.GetNavigations().Concat(_navigations.Values) : _navigations.Values; /// @@ -1656,6 +1612,7 @@ public virtual IEnumerable GetNavigations() /// public virtual SkipNavigation? AddSkipNavigation( string name, + Type? navigationType, MemberInfo? memberInfo, EntityType targetEntityType, bool collection, @@ -1671,7 +1628,7 @@ public virtual IEnumerable GetNavigations() { throw new InvalidOperationException( CoreStrings.ConflictingPropertyOrNavigation( - name, DisplayName(), ((IReadOnlyTypeBase)duplicateProperty.DeclaringType).DisplayName())); + name, DisplayName(), duplicateProperty.DeclaringType.DisplayName())); } if (memberInfo != null) @@ -1700,6 +1657,7 @@ public virtual IEnumerable GetNavigations() var skipNavigation = new SkipNavigation( name, + navigationType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, @@ -1724,39 +1682,6 @@ public virtual IEnumerable GetNavigations() return (SkipNavigation?)Model.ConventionDispatcher.OnSkipNavigationAdded(skipNavigation.Builder)?.Metadata; } - private Type? ValidateClrMember(string name, MemberInfo memberInfo, bool throwOnNameMismatch = true) - { - if (name != memberInfo.GetSimpleMemberName()) - { - if (memberInfo != FindIndexerPropertyInfo()) - { - if (throwOnNameMismatch) - { - throw new InvalidOperationException( - CoreStrings.PropertyWrongName( - name, - DisplayName(), - memberInfo.GetSimpleMemberName())); - } - - return memberInfo.GetMemberType(); - } - - var clashingMemberInfo = IsPropertyBag - ? null - : ClrType.GetMembersInHierarchy(name).FirstOrDefault(); - if (clashingMemberInfo != null) - { - throw new InvalidOperationException( - CoreStrings.PropertyClashingNonIndexer( - name, - DisplayName())); - } - } - - return null; - } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -1767,7 +1692,7 @@ public virtual IEnumerable GetNavigations() { Check.NotEmpty(name, nameof(name)); - return FindDeclaredSkipNavigation(name) ?? _baseType?.FindSkipNavigation(name); + return FindDeclaredSkipNavigation(name) ?? BaseType?.FindSkipNavigation(name); } /// @@ -1806,9 +1731,9 @@ public virtual IEnumerable GetDeclaredSkipNavigations() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetDerivedSkipNavigations() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.GetDeclaredSkipNavigations()); + : GetDerivedTypes().SelectMany(et => et.GetDeclaredSkipNavigations()); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1820,9 +1745,10 @@ public virtual IEnumerable FindDerivedSkipNavigations(string nam { Check.NotNull(name, nameof(name)); - return _directlyDerivedTypes.Count == 0 + return DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : (IEnumerable)GetDerivedTypes().Select(et => et.FindDeclaredSkipNavigation(name)).Where(n => n != null); + : (IEnumerable)GetDerivedTypes() + .Select(et => et.FindDeclaredSkipNavigation(name)).Where(n => n != null); } /// @@ -1832,7 +1758,7 @@ public virtual IEnumerable FindDerivedSkipNavigations(string nam /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable FindDerivedSkipNavigationsInclusive(string name) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? ToEnumerable(FindDeclaredSkipNavigation(name)) : ToEnumerable(FindDeclaredSkipNavigation(name)).Concat(FindDerivedSkipNavigations(name)); @@ -1843,7 +1769,7 @@ public virtual IEnumerable FindDerivedSkipNavigationsInclusive(s /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable FindSkipNavigationsInHierarchy(string name) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? ToEnumerable(FindSkipNavigation(name)) : ToEnumerable(FindSkipNavigation(name)).Concat(FindDerivedSkipNavigations(name)); @@ -1913,10 +1839,10 @@ public virtual IEnumerable FindSkipNavigationsInHierarchy(string /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetSkipNavigations() - => _baseType != null + => BaseType != null ? _skipNavigations.Count == 0 - ? _baseType.GetSkipNavigations() - : _baseType.GetSkipNavigations().Concat(_skipNavigations.Values) + ? BaseType.GetSkipNavigations() + : BaseType.GetSkipNavigations().Concat(_skipNavigations.Values) : _skipNavigations.Values; /// @@ -1926,10 +1852,10 @@ public virtual IEnumerable GetSkipNavigations() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetReferencingSkipNavigations() - => _baseType != null + => BaseType != null ? (DeclaredReferencingSkipNavigations?.Count ?? 0) == 0 - ? _baseType.GetReferencingSkipNavigations() - : _baseType.GetReferencingSkipNavigations().Concat(GetDeclaredReferencingSkipNavigations()) + ? BaseType.GetReferencingSkipNavigations() + : BaseType.GetReferencingSkipNavigations().Concat(GetDeclaredReferencingSkipNavigations()) : GetDeclaredReferencingSkipNavigations(); /// @@ -1948,9 +1874,9 @@ public virtual IEnumerable GetDeclaredReferencingSkipNavigations /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetDerivedReferencingSkipNavigations() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.GetDeclaredReferencingSkipNavigations()); + : GetDerivedTypes().SelectMany(et => et.GetDeclaredReferencingSkipNavigations()); private SortedSet? DeclaredReferencingSkipNavigations { get; set; } @@ -2105,7 +2031,7 @@ private static void UpdatePropertyIndexes(IReadOnlyList properties, In Check.HasNoNulls(properties, nameof(properties)); Check.NotEmpty(properties, nameof(properties)); - return FindDeclaredIndex(properties) ?? _baseType?.FindIndex(properties); + return FindDeclaredIndex(properties) ?? BaseType?.FindIndex(properties); } /// @@ -2118,7 +2044,7 @@ private static void UpdatePropertyIndexes(IReadOnlyList properties, In { Check.NotEmpty(name, nameof(name)); - return FindDeclaredIndex(name) ?? _baseType?.FindIndex(name); + return FindDeclaredIndex(name) ?? BaseType?.FindIndex(name); } /// @@ -2139,9 +2065,9 @@ public virtual IEnumerable GetDeclaredIndexes() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetDerivedIndexes() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.GetDeclaredIndexes()); + : GetDerivedTypes().SelectMany(et => et.GetDeclaredIndexes()); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2172,9 +2098,10 @@ public virtual IEnumerable GetDerivedIndexes() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable FindDerivedIndexes(IReadOnlyList properties) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : (IEnumerable)GetDerivedTypes().Select(et => et.FindDeclaredIndex(properties)).Where(i => i != null); + : (IEnumerable)GetDerivedTypes() + .Select(et => et.FindDeclaredIndex(properties)).Where(i => i != null); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2183,9 +2110,9 @@ public virtual IEnumerable FindDerivedIndexes(IReadOnlyList public virtual IEnumerable FindDerivedIndexes(string name) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : (IEnumerable)GetDerivedTypes() + : (IEnumerable)GetDerivedTypes() .Select(et => et.FindDeclaredIndex(Check.NotEmpty(name, nameof(name)))) .Where(i => i != null); @@ -2196,7 +2123,7 @@ public virtual IEnumerable FindDerivedIndexes(string name) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable FindIndexesInHierarchy(IReadOnlyList properties) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? ToEnumerable(FindIndex(properties)) : ToEnumerable(FindIndex(properties)).Concat(FindDerivedIndexes(properties)); @@ -2207,7 +2134,7 @@ public virtual IEnumerable FindIndexesInHierarchy(IReadOnlyList public virtual IEnumerable FindIndexesInHierarchy(string name) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? ToEnumerable(FindIndex(Check.NotEmpty(name, nameof(name)))) : ToEnumerable(FindIndex(Check.NotEmpty(name, nameof(name)))).Concat(FindDerivedIndexes(name)); @@ -2296,15 +2223,15 @@ public virtual IEnumerable FindIndexesInHierarchy(string name) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetIndexes() - => _baseType != null + => BaseType != null ? _namedIndexes.Count == 0 && _unnamedIndexes.Count == 0 - ? _baseType.GetIndexes() - : _baseType.GetIndexes().Concat(GetDeclaredIndexes()) + ? BaseType.GetIndexes() + : BaseType.GetIndexes().Concat(GetDeclaredIndexes()) : GetDeclaredIndexes(); #endregion - #region Properties + #region Lazy runtime logic /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2312,22 +2239,13 @@ public virtual IEnumerable GetIndexes() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual Property? AddProperty( - string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - ConfigurationSource? typeConfigurationSource, - ConfigurationSource configurationSource) - { - Check.NotNull(name, nameof(name)); - Check.NotNull(propertyType, nameof(propertyType)); - - return AddProperty( - name, - propertyType, - null, - typeConfigurationSource, - configurationSource); - } + public virtual PropertyCounts Counts + => NonCapturingLazyInitializer.EnsureInitialized( + ref _counts, this, static entityType => + { + entityType.EnsureReadOnly(); + return entityType.CalculateCounts(); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2335,16 +2253,14 @@ public virtual IEnumerable GetIndexes() /// 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. /// - [RequiresUnreferencedCode("Use an overload that accepts a type")] - public virtual Property? AddProperty( - MemberInfo memberInfo, - ConfigurationSource configurationSource) - => AddProperty( - memberInfo.GetSimpleMemberName(), - memberInfo.GetMemberType(), - memberInfo, - configurationSource, - configurationSource); + public virtual Func RelationshipSnapshotFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _relationshipSnapshotFactory, this, + static entityType => + { + entityType.EnsureReadOnly(); + return new RelationshipSnapshotFactoryFactory().Create(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2352,27 +2268,14 @@ public virtual IEnumerable GetIndexes() /// 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. /// - [RequiresUnreferencedCode("Use an overload that accepts a type")] - public virtual Property? AddProperty( - string name, - ConfigurationSource configurationSource) - { - MemberInfo? clrMember; - if (IsPropertyBag) - { - clrMember = FindIndexerPropertyInfo()!; - } - else - { - clrMember = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); - if (clrMember == null) + public virtual Func OriginalValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _originalValuesFactory, this, + static entityType => { - throw new InvalidOperationException(CoreStrings.NoPropertyType(name, DisplayName())); - } - } - - return AddProperty(clrMember, configurationSource); - } + entityType.EnsureReadOnly(); + return new OriginalValuesFactoryFactory().Create(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2380,83 +2283,14 @@ public virtual IEnumerable GetIndexes() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual Property? AddProperty( - string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - MemberInfo? memberInfo, - ConfigurationSource? typeConfigurationSource, - ConfigurationSource configurationSource) - { - Check.NotNull(name, nameof(name)); - Check.NotNull(propertyType, nameof(propertyType)); - Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - EnsureMutable(); - - var conflictingMember = FindMembersInHierarchy(name).FirstOrDefault(); - if (conflictingMember != null) - { - throw new InvalidOperationException( - CoreStrings.ConflictingPropertyOrNavigation( - name, DisplayName(), - ((IReadOnlyTypeBase)conflictingMember.DeclaringType).DisplayName())); - } - - if (memberInfo != null) - { - propertyType = ValidateClrMember(name, memberInfo, typeConfigurationSource != null) - ?? propertyType; - - if (memberInfo.DeclaringType?.IsAssignableFrom(ClrType) != true) - { - throw new InvalidOperationException( - CoreStrings.PropertyWrongEntityClrType( - memberInfo.Name, DisplayName(), memberInfo.DeclaringType?.ShortDisplayName())); - } - } - else if (IsPropertyBag) - { - memberInfo = FindIndexerPropertyInfo(); - } - else - { - memberInfo = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); - } - - if (memberInfo != null - && propertyType != memberInfo.GetMemberType() - && memberInfo != FindIndexerPropertyInfo()) - { - if (typeConfigurationSource != null) - { - throw new InvalidOperationException( - CoreStrings.PropertyWrongClrType( - name, - DisplayName(), - memberInfo.GetMemberType().ShortDisplayName(), - propertyType.ShortDisplayName())); - } - - propertyType = memberInfo.GetMemberType(); - } - - var property = new Property( - name, propertyType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, - configurationSource, typeConfigurationSource); - - _properties.Add(property.Name, property); - - if (Model.Configuration != null) - { - using (Model.ConventionDispatcher.DelayConventions()) + public virtual Func StoreGeneratedValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _storeGeneratedValuesFactory, this, + static entityType => { - Model.ConventionDispatcher.OnPropertyAdded(property.Builder); - Model.Configuration.ConfigureProperty(property); - return property; - } - } - - return (Property?)Model.ConventionDispatcher.OnPropertyAdded(property.Builder)?.Metadata; - } + entityType.EnsureReadOnly(); + return new StoreGeneratedValuesFactoryFactory().CreateEmpty(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2464,8 +2298,14 @@ public virtual IEnumerable GetIndexes() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual Property? FindProperty(string name) - => FindDeclaredProperty(Check.NotEmpty(name, nameof(name))) ?? _baseType?.FindProperty(name); + public virtual Func TemporaryValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _temporaryValuesFactory, this, + static entityType => + { + entityType.EnsureReadOnly(); + return new TemporaryValuesFactoryFactory().Create(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2473,10 +2313,14 @@ public virtual IEnumerable GetIndexes() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual Property? FindDeclaredProperty(string name) - => _properties.TryGetValue(Check.NotEmpty(name, nameof(name)), out var property) - ? property - : null; + public virtual Func ShadowValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _shadowValuesFactory, this, + static entityType => + { + entityType.EnsureReadOnly(); + return new ShadowValuesFactoryFactory().Create(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2484,8 +2328,14 @@ public virtual IEnumerable GetIndexes() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IEnumerable GetDeclaredProperties() - => _properties.Values; + public virtual Func EmptyShadowValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _emptyShadowValuesFactory, this, + static entityType => + { + entityType.EnsureReadOnly(); + return new EmptyShadowValuesFactoryFactory().CreateEmpty(entityType); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2493,278 +2343,15 @@ public virtual IEnumerable GetDeclaredProperties() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IEnumerable GetDerivedProperties() - => _directlyDerivedTypes.Count == 0 - ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.GetDeclaredProperties()); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IEnumerable FindDerivedProperties(string propertyName) - { - Check.NotNull(propertyName, nameof(propertyName)); - - return _directlyDerivedTypes.Count == 0 - ? Enumerable.Empty() - : (IEnumerable)GetDerivedTypes().Select(et => et.FindDeclaredProperty(propertyName)).Where(p => p != null); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IEnumerable FindDerivedPropertiesInclusive(string propertyName) - => _directlyDerivedTypes.Count == 0 - ? ToEnumerable(FindDeclaredProperty(propertyName)) - : ToEnumerable(FindDeclaredProperty(propertyName)).Concat(FindDerivedProperties(propertyName)); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IEnumerable FindPropertiesInHierarchy(string propertyName) - => _directlyDerivedTypes.Count == 0 - ? ToEnumerable(FindProperty(propertyName)) - : ToEnumerable(FindProperty(propertyName)).Concat(FindDerivedProperties(propertyName)); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IReadOnlyList? FindProperties(IReadOnlyList propertyNames) - { - Check.NotNull(propertyNames, nameof(propertyNames)); - - var properties = new List(propertyNames.Count); - foreach (var propertyName in propertyNames) - { - var property = FindProperty(propertyName); - if (property == null) - { - return null; - } - - properties.Add(property); - } - - return properties; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Property? RemoveProperty(string name) - { - Check.NotEmpty(name, nameof(name)); - - var property = FindDeclaredProperty(name); - return property == null - ? null - : RemoveProperty(property); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Property? RemoveProperty(Property property) - { - Check.NotNull(property, nameof(property)); - Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); - EnsureMutable(); - - if (property.DeclaringEntityType != this) - { - throw new InvalidOperationException( - CoreStrings.PropertyWrongType( - property.Name, - DisplayName(), - property.DeclaringEntityType.DisplayName())); - } - - CheckPropertyNotInUse(property); - - var removed = _properties.Remove(property.Name); - Check.DebugAssert(removed, "removed is false"); - - property.SetRemovedFromModel(); - - return (Property?)Model.ConventionDispatcher.OnPropertyRemoved(Builder, property); - } - - private void CheckPropertyNotInUse(Property property) - { - var containingKey = property.Keys?.FirstOrDefault(); - if (containingKey != null) - { - throw new InvalidOperationException( - CoreStrings.PropertyInUseKey(property.Name, DisplayName(), containingKey.Properties.Format())); - } - - var containingForeignKey = property.ForeignKeys?.FirstOrDefault(); - if (containingForeignKey != null) - { - throw new InvalidOperationException( - CoreStrings.PropertyInUseForeignKey( - property.Name, DisplayName(), - containingForeignKey.Properties.Format(), containingForeignKey.DeclaringEntityType.DisplayName())); - } - - var containingIndex = property.Indexes?.FirstOrDefault(); - if (containingIndex != null) - { - throw new InvalidOperationException( - CoreStrings.PropertyInUseIndex( - property.Name, DisplayName(), - containingIndex.DisplayName(), containingIndex.DeclaringEntityType.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. - /// - public virtual IEnumerable GetProperties() - => _baseType != null - ? _baseType.GetProperties().Concat(_properties.Values) - : _properties.Values; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual PropertyCounts Counts - => NonCapturingLazyInitializer.EnsureInitialized( - ref _counts, this, static entityType => - { - entityType.EnsureReadOnly(); - return entityType.CalculateCounts(); - }); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Func RelationshipSnapshotFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _relationshipSnapshotFactory, this, - static entityType => - { - entityType.EnsureReadOnly(); - return new RelationshipSnapshotFactoryFactory().Create(entityType); - }); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Func OriginalValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _originalValuesFactory, this, - static entityType => - { - entityType.EnsureReadOnly(); - return new OriginalValuesFactoryFactory().Create(entityType); - }); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Func StoreGeneratedValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _storeGeneratedValuesFactory, this, - static entityType => - { - entityType.EnsureReadOnly(); - return new StoreGeneratedValuesFactoryFactory().CreateEmpty(entityType); - }); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Func TemporaryValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _temporaryValuesFactory, this, - static entityType => - { - entityType.EnsureReadOnly(); - return new TemporaryValuesFactoryFactory().Create(entityType); - }); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Func ShadowValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _shadowValuesFactory, this, - static entityType => - { - entityType.EnsureReadOnly(); - return new ShadowValuesFactoryFactory().Create(entityType); - }); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Func EmptyShadowValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _emptyShadowValuesFactory, this, - static entityType => - { - entityType.EnsureReadOnly(); - return new EmptyShadowValuesFactoryFactory().CreateEmpty(entityType); - }); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IReadOnlyList ForeignKeyProperties - => NonCapturingLazyInitializer.EnsureInitialized( - ref _foreignKeyProperties, this, - static entityType => - { - entityType.EnsureReadOnly(); - - return entityType.GetProperties().Where(p => p.IsForeignKey()).ToArray(); - }); + public virtual IReadOnlyList ForeignKeyProperties + => NonCapturingLazyInitializer.EnsureInitialized( + ref _foreignKeyProperties, this, + static entityType => + { + entityType.EnsureReadOnly(); + + return entityType.GetProperties().Where(p => p.IsForeignKey()).ToArray(); + }); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2833,7 +2420,7 @@ public virtual ServiceProperty AddServiceProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ServiceProperty? FindServiceProperty(string name) - => FindDeclaredServiceProperty(Check.NotEmpty(name, nameof(name))) ?? _baseType?.FindServiceProperty(name); + => FindDeclaredServiceProperty(Check.NotEmpty(name, nameof(name))) ?? BaseType?.FindServiceProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2865,9 +2452,9 @@ public virtual IEnumerable FindDerivedServiceProperties(string { Check.NotNull(propertyName, nameof(propertyName)); - return _directlyDerivedTypes.Count == 0 + return DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : (IEnumerable)GetDerivedTypes() + : (IEnumerable)GetDerivedTypes() .Select(et => et.FindDeclaredServiceProperty(propertyName)) .Where(p => p != null); } @@ -2879,7 +2466,7 @@ public virtual IEnumerable FindDerivedServiceProperties(string /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable FindDerivedServicePropertiesInclusive(string propertyName) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? ToEnumerable(FindDeclaredServiceProperty(propertyName)) : ToEnumerable(FindDeclaredServiceProperty(propertyName)).Concat(FindDerivedServiceProperties(propertyName)); @@ -2890,7 +2477,7 @@ public virtual IEnumerable FindDerivedServicePropertiesInclusiv /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable FindServicePropertiesInHierarchy(string propertyName) - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? ToEnumerable(FindServiceProperty(propertyName)) : ToEnumerable(FindServiceProperty(propertyName)).Concat(FindDerivedServiceProperties(propertyName)); @@ -2946,7 +2533,7 @@ public virtual ServiceProperty RemoveServiceProperty(ServiceProperty property) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool HasServiceProperties() - => _serviceProperties.Count != 0 || _baseType != null && _baseType.HasServiceProperties(); + => _serviceProperties.Count != 0 || BaseType != null && BaseType.HasServiceProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2955,10 +2542,10 @@ public virtual bool HasServiceProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetServiceProperties() - => _baseType != null + => BaseType != null ? _serviceProperties.Count == 0 - ? _baseType.GetServiceProperties() - : _baseType.GetServiceProperties().Concat(_serviceProperties.Values) + ? BaseType.GetServiceProperties() + : BaseType.GetServiceProperties().Concat(_serviceProperties.Values) : _serviceProperties.Values; /// @@ -2977,9 +2564,9 @@ public virtual IEnumerable GetDeclaredServiceProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IEnumerable GetDerivedServiceProperties() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.GetDeclaredServiceProperties()); + : GetDerivedTypes().SelectMany(et => et.GetDeclaredServiceProperties()); #endregion @@ -3065,19 +2652,6 @@ public virtual IEnumerable GetDeclaredTriggers() #region Ignore - /// - /// 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 ConfigurationSource? FindIgnoredConfigurationSource(string name) - { - var ignoredSource = FindDeclaredIgnoredConfigurationSource(name); - - return BaseType == null ? ignoredSource : BaseType.FindIgnoredConfigurationSource(name).Max(ignoredSource); - } - /// /// 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 @@ -3257,9 +2831,9 @@ public virtual void AddData(IEnumerable data) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - [DebuggerStepThrough] - public virtual ChangeTrackingStrategy GetChangeTrackingStrategy() - => _changeTrackingStrategy ?? Model.GetChangeTrackingStrategy(); + public virtual PropertyAccessMode GetNavigationAccessMode() + => (PropertyAccessMode?)this[CoreAnnotationNames.NavigationAccessMode] + ?? GetPropertyAccessMode(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3267,80 +2841,11 @@ public virtual ChangeTrackingStrategy GetChangeTrackingStrategy() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual ChangeTrackingStrategy? SetChangeTrackingStrategy( - ChangeTrackingStrategy? changeTrackingStrategy, + public virtual PropertyAccessMode? SetNavigationAccessMode( + PropertyAccessMode? propertyAccessMode, ConfigurationSource configurationSource) - { - EnsureMutable(); - - if (changeTrackingStrategy != null) - { - var requireFullNotifications = - (bool?)Model[CoreAnnotationNames.FullChangeTrackingNotificationsRequired] == true; - var errorMessage = CheckChangeTrackingStrategy(this, changeTrackingStrategy.Value, requireFullNotifications); - if (errorMessage != null) - { - throw new InvalidOperationException(errorMessage); - } - } - - _changeTrackingStrategy = changeTrackingStrategy; - - _changeTrackingStrategyConfigurationSource = _changeTrackingStrategy == null - ? null - : configurationSource.Max(_changeTrackingStrategyConfigurationSource); - - return changeTrackingStrategy; - } - - /// - /// 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? CheckChangeTrackingStrategy( - IReadOnlyEntityType entityType, - ChangeTrackingStrategy value, - bool requireFullNotifications) - { - if (requireFullNotifications) - { - if (value != ChangeTrackingStrategy.ChangingAndChangedNotifications - && value != ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues) - { - return CoreStrings.FullChangeTrackingRequired( - entityType.DisplayName(), value, nameof(ChangeTrackingStrategy.ChangingAndChangedNotifications), - nameof(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues)); - } - } - else - { - if (value != ChangeTrackingStrategy.Snapshot - && !typeof(INotifyPropertyChanged).IsAssignableFrom(entityType.ClrType)) - { - return CoreStrings.ChangeTrackingInterfaceMissing(entityType.DisplayName(), value, nameof(INotifyPropertyChanged)); - } - - if (value is ChangeTrackingStrategy.ChangingAndChangedNotifications - or ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues - && !typeof(INotifyPropertyChanging).IsAssignableFrom(entityType.ClrType)) - { - return CoreStrings.ChangeTrackingInterfaceMissing(entityType.DisplayName(), value, nameof(INotifyPropertyChanging)); - } - } - - return null; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetChangeTrackingStrategyConfigurationSource() - => _changeTrackingStrategyConfigurationSource; + => (PropertyAccessMode?)SetOrRemoveAnnotation( + CoreAnnotationNames.NavigationAccessMode, propertyAccessMode, configurationSource)?.Value; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3432,7 +2937,7 @@ private void CheckDiscriminatorProperty(Property? property) CoreStrings.DiscriminatorPropertyMustBeOnRoot(DisplayName())); } - if (property.DeclaringEntityType != this) + if (property.DeclaringType != this) { throw new InvalidOperationException( CoreStrings.DiscriminatorPropertyNotFound(property.Name, DisplayName())); @@ -3637,30 +3142,6 @@ IMutableModel IMutableTypeBase.Model get => 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. - /// - IMutableModel IMutableEntityType.Model - { - [DebuggerStepThrough] - get => 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. - /// - IConventionModel IConventionEntityType.Model - { - [DebuggerStepThrough] - get => 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 @@ -3682,7 +3163,7 @@ IModel ITypeBase.Model IReadOnlyEntityType? IReadOnlyEntityType.BaseType { [DebuggerStepThrough] - get => _baseType; + get => BaseType; } /// @@ -3693,7 +3174,7 @@ IModel ITypeBase.Model /// IMutableEntityType? IMutableEntityType.BaseType { - get => _baseType; + get => BaseType; set => SetBaseType((EntityType?)value, ConfigurationSource.Explicit); } @@ -3742,31 +3223,8 @@ void IMutableEntityType.SetDiscriminatorProperty(IReadOnlyProperty? property) IReadOnlyProperty? property, bool fromDataAnnotation) => SetDiscriminatorProperty( - (Property?)property, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - void IMutableEntityType.SetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy) - => SetChangeTrackingStrategy(changeTrackingStrategy, ConfigurationSource.Explicit); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - ChangeTrackingStrategy? IConventionEntityType.SetChangeTrackingStrategy( - ChangeTrackingStrategy? changeTrackingStrategy, - bool fromDataAnnotation) - => SetChangeTrackingStrategy( - changeTrackingStrategy, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + (Property?)property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -3796,7 +3254,7 @@ void IMutableEntityType.SetQueryFilter(LambdaExpression? queryFilter) /// [DebuggerStepThrough] IEnumerable IReadOnlyEntityType.GetDerivedTypes() - => GetDerivedTypes(); + => GetDerivedTypes(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4439,12 +3897,13 @@ IEnumerable IEntityType.GetNavigations() [DebuggerStepThrough] IMutableSkipNavigation IMutableEntityType.AddSkipNavigation( string name, + Type? navigationType, MemberInfo? memberInfo, IMutableEntityType targetEntityType, bool collection, bool onDependent) => AddSkipNavigation( - name, memberInfo, (EntityType)targetEntityType, collection, onDependent, + name, navigationType, memberInfo, (EntityType)targetEntityType, collection, onDependent, ConfigurationSource.Explicit)!; /// @@ -4456,13 +3915,14 @@ IMutableSkipNavigation IMutableEntityType.AddSkipNavigation( [DebuggerStepThrough] IConventionSkipNavigation? IConventionEntityType.AddSkipNavigation( string name, + Type? navigationType, MemberInfo? memberInfo, IConventionEntityType targetEntityType, bool collection, bool onDependent, bool fromDataAnnotation) => AddSkipNavigation( - name, memberInfo, (EntityType)targetEntityType, collection, onDependent, + name, navigationType, memberInfo, (EntityType)targetEntityType, collection, onDependent, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -4853,301 +4313,6 @@ IEnumerable IEntityType.GetIndexes() IConventionIndex? IConventionEntityType.RemoveIndex(IReadOnlyIndex index) => RemoveIndex((Index)index); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IMutableProperty IMutableEntityType.AddProperty(string name) - => AddProperty(name, ConfigurationSource.Explicit)!; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IConventionProperty? IConventionEntityType.AddProperty(string name, bool fromDataAnnotation) - => AddProperty( - name, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IMutableProperty IMutableEntityType.AddProperty( - string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType) - => AddProperty( - name, - propertyType, - ConfigurationSource.Explicit, - ConfigurationSource.Explicit)!; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IConventionProperty? IConventionEntityType.AddProperty( - string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - bool setTypeConfigurationSource, - bool fromDataAnnotation) - => AddProperty( - name, - propertyType, - setTypeConfigurationSource - ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention - : null, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IMutableProperty IMutableEntityType.AddProperty( - string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - MemberInfo? memberInfo) - => AddProperty( - name, propertyType, memberInfo, - ConfigurationSource.Explicit, ConfigurationSource.Explicit)!; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IConventionProperty? IConventionEntityType.AddProperty( - string name, - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - MemberInfo? memberInfo, - bool setTypeConfigurationSource, - bool fromDataAnnotation) - => AddProperty( - name, - propertyType, - memberInfo, - setTypeConfigurationSource - ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention - : null, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IReadOnlyProperty? IReadOnlyEntityType.FindDeclaredProperty(string name) - => FindDeclaredProperty(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. - /// - [DebuggerStepThrough] - IProperty? IEntityType.FindDeclaredProperty(string name) - => FindDeclaredProperty(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. - /// - [DebuggerStepThrough] - IReadOnlyList? IReadOnlyEntityType.FindProperties(IReadOnlyList propertyNames) - => FindProperties(propertyNames); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IReadOnlyProperty? IReadOnlyEntityType.FindProperty(string name) - => FindProperty(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. - /// - [DebuggerStepThrough] - IMutableProperty? IMutableEntityType.FindProperty(string name) - => FindProperty(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. - /// - [DebuggerStepThrough] - IConventionProperty? IConventionEntityType.FindProperty(string name) - => FindProperty(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. - /// - [DebuggerStepThrough] - IProperty? IEntityType.FindProperty(string name) - => FindProperty(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. - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetDeclaredProperties() - => GetDeclaredProperties(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IEnumerable IEntityType.GetDeclaredProperties() - => GetDeclaredProperties(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetDerivedProperties() - => GetDerivedProperties(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetProperties() - => GetProperties(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IEnumerable IMutableEntityType.GetProperties() - => GetProperties(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IEnumerable IConventionEntityType.GetProperties() - => GetProperties(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IEnumerable IEntityType.GetProperties() - => GetProperties(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IEnumerable IEntityType.GetForeignKeyProperties() - => ForeignKeyProperties; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IEnumerable IEntityType.GetValueGeneratingProperties() - => ValueGeneratingProperties; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IMutableProperty? IMutableEntityType.RemoveProperty(string name) - => RemoveProperty(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. - /// - [DebuggerStepThrough] - IConventionProperty? IConventionEntityType.RemoveProperty(string name) - => RemoveProperty(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. - /// - [DebuggerStepThrough] - IMutableProperty? IMutableEntityType.RemoveProperty(IReadOnlyProperty property) - => RemoveProperty((Property)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. - /// - [DebuggerStepThrough] - IConventionProperty? IConventionEntityType.RemoveProperty(IReadOnlyProperty property) - => RemoveProperty((Property)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 @@ -5451,13 +4616,27 @@ IMutableTrigger IMutableEntityType.AddTrigger(string name) IConventionTrigger? IConventionEntityType.RemoveTrigger(string name) => RemoveTrigger(name); - #endregion + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IEntityType.GetForeignKeyProperties() + => ForeignKeyProperties; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IEntityType.GetValueGeneratingProperties() + => ValueGeneratingProperties; - private static IEnumerable ToEnumerable(T? element) - where T : class - => element == null - ? Enumerable.Empty() - : new[] { element }; + #endregion /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5525,7 +4704,7 @@ public virtual void Attach(InternalEntityTypeBuilder entityTypeBuilder) if (EntityType._baseTypeConfigurationSource != null) { - var baseType = EntityType._baseType; + var baseType = EntityType.BaseType; if (baseType?.IsInModel == false) { baseType = EntityType.Model.FindActualEntityType(baseType); @@ -5539,10 +4718,10 @@ public virtual void Attach(InternalEntityTypeBuilder entityTypeBuilder) entityTypeBuilder.Metadata.SetIsKeyless(EntityType.IsKeyless, EntityType._isKeylessConfigurationSource.Value); } - if (EntityType._changeTrackingStrategyConfigurationSource != null) + if (EntityType.GetChangeTrackingStrategyConfigurationSource() != null) { entityTypeBuilder.Metadata.SetChangeTrackingStrategy( - EntityType.GetChangeTrackingStrategy(), EntityType._changeTrackingStrategyConfigurationSource.Value); + EntityType.GetChangeTrackingStrategy(), EntityType.GetChangeTrackingStrategyConfigurationSource()!.Value); } foreach (var trigger in EntityType.GetDeclaredTriggers()) @@ -5599,6 +4778,7 @@ private static ParameterBinding Create(ParameterBinding parameterBinding, Entity property => (entityType.FindProperty(property.Name) ?? entityType.FindServiceProperty(property.Name) + ?? entityType.FindComplexProperty(property.Name) ?? entityType.FindNavigation(property.Name) ?? (IPropertyBase?)entityType.FindSkipNavigation(property.Name))!).ToArray()); } diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index 3eb5bef2b0f..bbe05b057be 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; - // ReSharper disable ArgumentsStyleOther // ReSharper disable ArgumentsStyleNamedExpression namespace Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -15,24 +13,6 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public static class EntityTypeExtensions { - /// - /// 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 DisplayName(this TypeBase entityType) - => ((IReadOnlyTypeBase)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. - /// - public static string ShortName(this TypeBase entityType) - => ((IReadOnlyTypeBase)entityType).ShortName(); - /// /// 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 @@ -158,16 +138,6 @@ public static bool IsInOwnershipPath(this IReadOnlyEntityType entityType, IReadO } } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - public static string GetOwnedName(this IReadOnlyTypeBase type, string simpleName, string ownershipNavigation) - => type.Name + "." + ownershipNavigation + "#" + simpleName; - /// /// 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 @@ -181,69 +151,6 @@ public static bool UseEagerSnapshots(this IReadOnlyEntityType entityType) return changeTrackingStrategy is ChangeTrackingStrategy.Snapshot or ChangeTrackingStrategy.ChangedNotifications; } - /// - /// 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 int StoreGeneratedCount(this IEntityType entityType) - => GetCounts(entityType).StoreGeneratedCount; - - /// - /// 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 int RelationshipPropertyCount(this IEntityType entityType) - => GetCounts(entityType).RelationshipCount; - - /// - /// 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 int OriginalValueCount(this IEntityType entityType) - => GetCounts(entityType).OriginalValueCount; - - /// - /// 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 int ShadowPropertyCount(this IEntityType entityType) - => GetCounts(entityType).ShadowCount; - - /// - /// 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 int NavigationCount(this IEntityType entityType) - => GetCounts(entityType).NavigationCount; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static int PropertyCount(this IEntityType entityType) - => GetCounts(entityType).PropertyCount; - - /// - /// 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 PropertyCounts GetCounts(this IEntityType entityType) - => ((IRuntimeEntityType)entityType).Counts; - /// /// 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 @@ -252,17 +159,18 @@ public static PropertyCounts GetCounts(this IEntityType entityType) /// public static PropertyCounts CalculateCounts(this IRuntimeEntityType entityType) { - var index = 0; + var propertyIndex = 0; var navigationIndex = 0; + var complexPropertyIndex = 0; var originalValueIndex = 0; var shadowIndex = 0; var relationshipIndex = 0; var storeGenerationIndex = 0; - var baseCounts = entityType.BaseType?.GetCounts(); + var baseCounts = entityType.BaseType?.Counts; if (baseCounts != null) { - index = baseCounts.PropertyCount; + propertyIndex = baseCounts.PropertyCount; navigationIndex = baseCounts.NavigationCount; originalValueIndex = baseCounts.OriginalValueCount; shadowIndex = baseCounts.ShadowCount; @@ -273,7 +181,7 @@ public static PropertyCounts CalculateCounts(this IRuntimeEntityType entityType) foreach (var property in entityType.GetDeclaredProperties()) { var indexes = new PropertyIndexes( - index: index++, + index: propertyIndex++, originalValueIndex: property.RequiresOriginalValue() ? originalValueIndex++ : -1, shadowIndex: property.IsShadowProperty() ? shadowIndex++ : -1, relationshipIndex: property.IsKey() || property.IsForeignKey() ? relationshipIndex++ : -1, @@ -282,6 +190,18 @@ public static PropertyCounts CalculateCounts(this IRuntimeEntityType entityType) ((IRuntimePropertyBase)property).PropertyIndexes = indexes; } + foreach (var complexProperty in entityType.GetDeclaredComplexProperties()) + { + var indexes = new PropertyIndexes( + index: complexPropertyIndex++, + originalValueIndex: -1, + shadowIndex: complexProperty.IsShadowProperty() ? shadowIndex++ : -1, + relationshipIndex: -1, + storeGenerationIndex: -1); + + ((IRuntimePropertyBase)complexProperty).PropertyIndexes = indexes; + } + var isNotifying = entityType.GetChangeTrackingStrategy() != ChangeTrackingStrategy.Snapshot; foreach (var navigation in entityType.GetDeclaredNavigations() @@ -310,23 +230,15 @@ public static PropertyCounts CalculateCounts(this IRuntimeEntityType entityType) } return new PropertyCounts( - index, + propertyIndex, navigationIndex, + complexPropertyIndex, originalValueIndex, shadowIndex, relationshipIndex, storeGenerationIndex); } - /// - /// 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 Func GetEmptyShadowValuesFactory(this IEntityType entityType) - => ((IRuntimeEntityType)entityType).EmptyShadowValuesFactory; - /// /// 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 @@ -384,16 +296,6 @@ public static IEnumerable FindDerivedNavigations( => entityType.GetDerivedTypes().Select(t => t.FindDeclaredNavigation(navigationName)!) .Where(n => n != null); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static IEnumerable GetPropertiesAndNavigations( - this IEntityType entityType) - => entityType.GetProperties().Concat(entityType.GetNavigations()); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -406,10 +308,10 @@ public static IProperty CheckPropertyBelongsToType( { Check.NotNull(property, nameof(property)); - if (!property.DeclaringEntityType.IsAssignableFrom(entityType)) + if ((property.DeclaringType as IEntityType)?.IsAssignableFrom(entityType) != true) { throw new InvalidOperationException( - CoreStrings.PropertyDoesNotBelong(property.Name, property.DeclaringEntityType.DisplayName(), entityType.DisplayName())); + CoreStrings.PropertyDoesNotBelong(property.Name, property.DeclaringType.DisplayName(), entityType.DisplayName())); } return property; diff --git a/src/EFCore/Metadata/Internal/ForeignKey.cs b/src/EFCore/Metadata/Internal/ForeignKey.cs index 7832a03aa01..a201ac836b9 100644 --- a/src/EFCore/Metadata/Internal/ForeignKey.cs +++ b/src/EFCore/Metadata/Internal/ForeignKey.cs @@ -1025,7 +1025,7 @@ private static void Validate( } var actualProperty = declaringEntityType.FindProperty(property.Name); - if (actualProperty?.DeclaringEntityType.IsAssignableFrom(property.DeclaringEntityType) != true + if (actualProperty?.DeclaringType.IsAssignableFrom(property.DeclaringType) != true || !property.IsInModel) { throw new InvalidOperationException( diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyExtensions.cs b/src/EFCore/Metadata/Internal/IRuntimeComplexType.cs similarity index 83% rename from src/EFCore.Relational/Metadata/Internal/RelationalPropertyExtensions.cs rename to src/EFCore/Metadata/Internal/IRuntimeComplexType.cs index 68cd26ccb04..272627b3619 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyExtensions.cs +++ b/src/EFCore/Metadata/Internal/IRuntimeComplexType.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 static class RelationalPropertyExtensions +public interface IRuntimeComplexType : IComplexType, IRuntimeTypeBase { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -17,7 +17,6 @@ public static class RelationalPropertyExtensions /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - [DebuggerStepThrough] - public static string? GetConfiguredColumnType(this IReadOnlyProperty property) - => (string?)property[RelationalAnnotationNames.ColumnType]; + IEnumerable IRuntimeTypeBase.GetSnapshottableMembers() + => GetProperties(); } diff --git a/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs b/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs index 1eb92d77e9d..1bfb9b0d5e8 100644 --- a/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs +++ b/src/EFCore/Metadata/Internal/IRuntimeEntityType.cs @@ -11,15 +11,14 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 interface IRuntimeEntityType : IEntityType +public interface IRuntimeEntityType : IEntityType, IRuntimeTypeBase { /// - /// 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. + /// Gets the base type of this entity type. Returns if this is not a derived type in an inheritance + /// hierarchy. /// - PropertyCounts Counts { get; } + new IRuntimeEntityType? BaseType + => (IRuntimeEntityType?)((IEntityType)this).BaseType; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -35,53 +34,6 @@ public interface IRuntimeEntityType : IEntityType /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - Func OriginalValuesFactory { get; } - - /// - /// 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. - /// - Func StoreGeneratedValuesFactory { get; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - Func TemporaryValuesFactory { get; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - Func ShadowValuesFactory { get; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - Func EmptyShadowValuesFactory { get; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - ConfigurationSource? GetConstructorBindingConfigurationSource(); - - /// - /// 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. - /// - ConfigurationSource? GetServiceOnlyConstructorBindingConfigurationSource(); + IEnumerable IRuntimeTypeBase.GetSnapshottableMembers() + => GetProperties().Concat(GetNavigations()); } diff --git a/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs b/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs new file mode 100644 index 00000000000..6bfa4bbd60f --- /dev/null +++ b/src/EFCore/Metadata/Internal/IRuntimeTypeBase.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 interface IRuntimeTypeBase : ITypeBase +{ + /// + /// 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. + /// + Func OriginalValuesFactory { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + Func StoreGeneratedValuesFactory { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + Func TemporaryValuesFactory { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + Func ShadowValuesFactory { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + Func EmptyShadowValuesFactory { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + PropertyCounts Counts { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + int OriginalValueCount + => Counts.OriginalValueCount; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + int PropertyCount + => Counts.PropertyCount; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + int ShadowPropertyCount + => Counts.ShadowCount; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + int StoreGeneratedCount + => Counts.StoreGeneratedCount; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + int RelationshipPropertyCount + => Counts.RelationshipCount; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + int NavigationCount + => Counts.NavigationCount; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + ConfigurationSource? GetConstructorBindingConfigurationSource(); + + /// + /// 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. + /// + ConfigurationSource? GetServiceOnlyConstructorBindingConfigurationSource(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IEnumerable GetSnapshottableMembers() + => throw new NotImplementedException(); +} diff --git a/src/EFCore/Metadata/Internal/InternalComplexPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalComplexPropertyBuilder.cs new file mode 100644 index 00000000000..8d792f89505 --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalComplexPropertyBuilder.cs @@ -0,0 +1,322 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class InternalComplexPropertyBuilder + : InternalPropertyBaseBuilder, IConventionComplexPropertyBuilder +{ + /// + /// 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 InternalComplexPropertyBuilder(ComplexProperty metadata, InternalModelBuilder modelBuilder) + : base(metadata, modelBuilder) + { + } + + /// + /// 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 InternalComplexPropertyBuilder This + => this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypeBuilder ComplexTypeBuilder => Metadata.ComplexType.Builder; + + /// + /// 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 ComplexPropertySnapshot? Detach(ComplexProperty complexProperty) + { + var complexType = complexProperty.ComplexType; + if (!complexProperty.IsInModel + || !complexType.IsInModel) + { + return null; + } + + var property = complexProperty.DeclaringType.FindDeclaredComplexProperty(complexProperty.Name); + if (property == null) + { + return null; + } + + var propertyBuilder = property.Builder; + // Reset convention configuration + propertyBuilder.IsRequired(null, ConfigurationSource.Convention); + + List? detachedRelationships = null; + foreach (var relationshipToBeDetached in complexType.FundamentalEntityType.GetDeclaredForeignKeys().ToList()) + { + if (!relationshipToBeDetached.Properties.Any(p => p.DeclaringType == complexType)) + { + continue; + } + + detachedRelationships ??= new List(); + + var detachedRelationship = InternalEntityTypeBuilder.DetachRelationship(relationshipToBeDetached, false); + if (detachedRelationship.Relationship.Metadata.GetConfigurationSource().Overrides(ConfigurationSource.DataAnnotation) + || relationshipToBeDetached.IsOwnership) + { + detachedRelationships.Add(detachedRelationship); + } + } + + List<(InternalKeyBuilder, ConfigurationSource?)>? detachedKeys = null; + foreach (var keyToDetach in complexType.FundamentalEntityType.GetDeclaredKeys().ToList()) + { + if (!keyToDetach.Properties.Any(p => p.DeclaringType == complexType)) + { + continue; + } + + foreach (var relationshipToBeDetached in keyToDetach.GetReferencingForeignKeys().ToList()) + { + if (!relationshipToBeDetached.IsInModel + || !relationshipToBeDetached.DeclaringEntityType.IsInModel) + { + // Referencing type might have been removed while removing other foreign keys + continue; + } + + detachedRelationships ??= new List(); + + var detachedRelationship = InternalEntityTypeBuilder.DetachRelationship(relationshipToBeDetached, true); + if (detachedRelationship.Relationship.Metadata.GetConfigurationSource().Overrides(ConfigurationSource.DataAnnotation) + || relationshipToBeDetached.IsOwnership) + { + detachedRelationships.Add(detachedRelationship); + } + } + + if (!keyToDetach.IsInModel) + { + continue; + } + + detachedKeys ??= new List<(InternalKeyBuilder, ConfigurationSource?)>(); + + var detachedKey = InternalEntityTypeBuilder.DetachKey(keyToDetach); + if (detachedKey.Item1.Metadata.GetConfigurationSource().Overrides(ConfigurationSource.Explicit)) + { + detachedKeys.Add(detachedKey); + } + } + + List? detachedIndexes = null; + foreach (var indexToBeDetached in complexType.FundamentalEntityType.GetDeclaredIndexes().ToList()) + { + if (!indexToBeDetached.Properties.Any(p => p.DeclaringType == complexType)) + { + continue; + } + + detachedIndexes ??= new List(); + + var detachedIndex = InternalEntityTypeBuilder.DetachIndex(indexToBeDetached); + if (detachedIndex.Metadata.GetConfigurationSource().Overrides(ConfigurationSource.Explicit)) + { + detachedIndexes.Add(detachedIndex); + } + } + + var detachedProperties = InternalTypeBaseBuilder.DetachProperties(complexType.GetDeclaredProperties().ToList()); + + var snapshot = new ComplexPropertySnapshot( + complexProperty.Builder, + detachedProperties, + detachedIndexes, + detachedKeys, + detachedRelationships); + + complexProperty.DeclaringType.RemoveComplexProperty(complexProperty); + + return snapshot; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? IsRequired(bool? required, ConfigurationSource configurationSource) + { + if (configurationSource != ConfigurationSource.Explicit + && !CanSetIsRequired(required, configurationSource)) + { + return null; + } + + Metadata.SetIsNullable(!required, configurationSource); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? configurationSource) + => (configurationSource.HasValue + && configurationSource.Value.Overrides(Metadata.GetIsNullableConfigurationSource())) + || (Metadata.IsNullable == !required); + + /// + /// 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. + /// + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// 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. + /// + IConventionComplexProperty IConventionComplexPropertyBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionComplexPropertyBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionComplexPropertyBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionComplexPropertyBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionComplexPropertyBuilder? IConventionComplexPropertyBuilder.IsRequired(bool? required, bool fromDataAnnotation) + => IsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionComplexPropertyBuilder.CanSetIsRequired(bool? required, bool fromDataAnnotation) + => CanSetIsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) + => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) + => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + bool fromDataAnnotation) + => UsePropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => CanSetPropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs new file mode 100644 index 00000000000..5bd103ec2dd --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs @@ -0,0 +1,613 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class InternalComplexTypeBuilder : InternalTypeBaseBuilder, IConventionComplexTypeBuilder +{ + /// + /// 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 InternalComplexTypeBuilder(ComplexType metadata, InternalModelBuilder modelBuilder) + : base(metadata, modelBuilder) + { + } + + /// + /// 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 new virtual ComplexType Metadata => (ComplexType)base.Metadata; + + /// + /// 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 bool CanAddProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + ConfigurationSource configurationSource, + bool checkClrProperty = false) + => !IsIgnored(propertyName, configurationSource) + && (propertyType == null + || Metadata.Model.Builder.CanBeConfigured(propertyType, TypeConfigurationType.Property, configurationSource)) + && (!checkClrProperty + || propertyType != null + || Metadata.GetRuntimeProperties().ContainsKey(propertyName)) + && Metadata.FindComplexPropertiesInHierarchy(propertyName) + .All( + m => configurationSource.Overrides(m.GetConfigurationSource()) + && m.GetConfigurationSource() != ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override bool CanAddComplexProperty( + string propertyName, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? targetType, + bool? collection, + ConfigurationSource configurationSource, + bool checkClrProperty = false) + => !IsIgnored(propertyName, configurationSource) + && (targetType == null || !ModelBuilder.IsIgnored(targetType, configurationSource)) + && (!checkClrProperty + || propertyType != null + || Metadata.GetRuntimeProperties().ContainsKey(propertyName)) + && Metadata.FindPropertiesInHierarchy(propertyName) + .All(m => configurationSource.Overrides(m.GetConfigurationSource()) + && m.GetConfigurationSource() != ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override InternalComplexTypeBuilder? Ignore(string name, ConfigurationSource configurationSource) + { + var ignoredConfigurationSource = Metadata.FindIgnoredConfigurationSource(name); + if (ignoredConfigurationSource.HasValue) + { + if (ignoredConfigurationSource.Value.Overrides(configurationSource)) + { + return this; + } + } + else if (!CanIgnore(name, configurationSource, shouldThrow: true)) + { + return null; + } + + using (Metadata.Model.DelayConventions()) + { + Metadata.AddIgnored(name, configurationSource); + + var property = Metadata.FindProperty(name); + if (property != null) + { + Check.DebugAssert(property.DeclaringType == Metadata, "property.DeclaringComplexType != ComplexType"); + + if (property.GetConfigurationSource() == ConfigurationSource.Explicit) + { + ModelBuilder.Metadata.ScopedModelDependencies?.Logger.MappedPropertyIgnoredWarning(property); + } + + var removedProperty = RemoveProperty(property, configurationSource); + + Check.DebugAssert(removedProperty != null, "removedProperty is null"); + } + else + { + var complexProperty = Metadata.FindComplexProperty(name); + if (complexProperty != null) + { + Check.DebugAssert(complexProperty.DeclaringType == Metadata, "property.DeclaringType != ComplexType"); + + if (complexProperty.GetConfigurationSource() == ConfigurationSource.Explicit) + { + ModelBuilder.Metadata.ScopedModelDependencies?.Logger.MappedComplexPropertyIgnoredWarning(complexProperty); + } + + var removedComplexProperty = Metadata.RemoveComplexProperty(complexProperty); + + Check.DebugAssert(removedComplexProperty != null, "removedProperty is null"); + } + } + + foreach (var derivedType in Metadata.GetDerivedTypes()) + { + var derivedIgnoredSource = derivedType.FindDeclaredIgnoredConfigurationSource(name); + if (derivedIgnoredSource.HasValue) + { + if (configurationSource.Overrides(derivedIgnoredSource)) + { + derivedType.RemoveIgnored(name); + } + + continue; + } + + var derivedProperty = derivedType.FindDeclaredProperty(name); + if (derivedProperty != null) + { + derivedType.Builder.RemoveProperty( + derivedProperty, configurationSource, + canOverrideSameSource: configurationSource != ConfigurationSource.Explicit); + } + else + { + var declaredComplexProperty = derivedType.FindDeclaredComplexProperty(name); + if (declaredComplexProperty != null) + { + if (configurationSource.Overrides(declaredComplexProperty.GetConfigurationSource()) + && declaredComplexProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + derivedType.RemoveComplexProperty(declaredComplexProperty); + } + } + } + } + } + + return this; + } + + /// + /// 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 bool CanIgnore(string name, ConfigurationSource configurationSource, bool shouldThrow) + { + var ignoredConfigurationSource = Metadata.FindIgnoredConfigurationSource(name); + if (ignoredConfigurationSource.HasValue) + { + return true; + } + + var property = Metadata.FindProperty(name); + if (property != null) + { + if (property.DeclaringType != Metadata) + { + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.InheritedPropertyCannotBeIgnored( + name, Metadata.DisplayName(), property.DeclaringType.DisplayName())); + } + + return false; + } + + if (!property.DeclaringType.Builder.CanRemoveProperty( + property, configurationSource, canOverrideSameSource: true)) + { + return false; + } + } + else + { + var complexProperty = Metadata.FindComplexProperty(name); + if (complexProperty != null) + { + if (complexProperty.DeclaringType != Metadata) + { + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.InheritedPropertyCannotBeIgnored( + name, Metadata.DisplayName(), complexProperty.DeclaringType.DisplayName())); + } + + return false; + } + + if (!configurationSource.Overrides(complexProperty.GetConfigurationSource())) + { + return false; + } + } + } + + 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 virtual InternalComplexTypeBuilder? HasBaseType( + ComplexType? baseComplexType, + ConfigurationSource configurationSource) + { + if (Metadata.BaseType == baseComplexType) + { + Metadata.SetBaseType(baseComplexType, configurationSource); + return this; + } + + if (!CanSetBaseType(baseComplexType, configurationSource)) + { + return null; + } + + using (Metadata.Model.DelayConventions()) + { + PropertiesSnapshot? detachedProperties = null; + List? detachedComplexProperties = null; + // We use at least DataAnnotation as ConfigurationSource while removing to allow us + // to remove metadata object which were defined in derived type + // while corresponding annotations were present on properties in base type. + var configurationSourceForRemoval = ConfigurationSource.DataAnnotation.Max(configurationSource); + if (baseComplexType != null) + { + var baseMemberNames = baseComplexType.GetMembers() + .ToDictionary(m => m.Name, m => (ConfigurationSource?)m.GetConfigurationSource()); + + var propertiesToDetach = + FindConflictingMembers( + Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredProperties()), + baseMemberNames, + p => baseComplexType.FindProperty(p.Name) != null, + p => p.DeclaringType.Builder.RemoveProperty(p, ConfigurationSource.Explicit)); + + if (propertiesToDetach != null) + { + detachedProperties = DetachProperties(propertiesToDetach); + } + + var complexPropertiesToDetach = + FindConflictingMembers( + Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredComplexProperties()), + baseMemberNames, + p => baseComplexType.FindComplexProperty(p.Name) != null, + p => p.DeclaringType.RemoveComplexProperty(p)); + + if (complexPropertiesToDetach != null) + { + detachedComplexProperties = new List(); + foreach (var complexPropertyToDetach in complexPropertiesToDetach) + { + detachedComplexProperties.Add(InternalComplexPropertyBuilder.Detach(complexPropertyToDetach)!); + } + } + + foreach (var ignoredMember in Metadata.GetIgnoredMembers().ToList()) + { + if (baseComplexType.FindIgnoredConfigurationSource(ignoredMember) + .Overrides(Metadata.FindDeclaredIgnoredConfigurationSource(ignoredMember))) + { + Metadata.RemoveIgnored(ignoredMember); + } + } + + baseComplexType.UpdateConfigurationSource(configurationSource); + } + + Metadata.SetBaseType(baseComplexType, configurationSource); + + if (detachedComplexProperties != null) + { + foreach (var detachedComplexProperty in detachedComplexProperties) + { + detachedComplexProperty.Attach( + detachedComplexProperty.ComplexProperty.DeclaringType.Builder); + } + } + + detachedProperties?.Attach(this); + } + + return this; + + List? FindConflictingMembers( + IEnumerable derivedMembers, + Dictionary baseMemberNames, + Func compatibleWithBaseMember, + Action removeMember) + where T : PropertyBase + { + List? membersToBeDetached = null; + List? membersToBeRemoved = null; + foreach (var member in derivedMembers) + { + ConfigurationSource? baseConfigurationSource = null; + if ((!member.GetConfigurationSource().OverridesStrictly( + baseComplexType.FindIgnoredConfigurationSource(member.Name)) + && member.GetConfigurationSource() != ConfigurationSource.Explicit) + || (baseMemberNames.TryGetValue(member.Name, out baseConfigurationSource) + && baseConfigurationSource.Overrides(member.GetConfigurationSource()) + && !compatibleWithBaseMember(member))) + { + if (baseConfigurationSource == ConfigurationSource.Explicit + && configurationSource == ConfigurationSource.Explicit + && member.GetConfigurationSource() == ConfigurationSource.Explicit) + { + throw new InvalidOperationException( + CoreStrings.DuplicatePropertiesOnBase( + Metadata.DisplayName(), + baseComplexType.DisplayName(), + ((IReadOnlyTypeBase)member.DeclaringType).DisplayName(), + member.Name, + baseComplexType.DisplayName(), + member.Name)); + } + + membersToBeRemoved ??= new List(); + + membersToBeRemoved.Add(member); + continue; + } + + if (baseConfigurationSource != null) + { + membersToBeDetached ??= new List(); + + membersToBeDetached.Add(member); + } + } + + if (membersToBeRemoved != null) + { + foreach (var memberToBeRemoved in membersToBeRemoved) + { + removeMember(memberToBeRemoved); + } + } + + return membersToBeDetached; + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetBaseType(ComplexType? baseComplexType, ConfigurationSource configurationSource) + { + if (Metadata.BaseType == baseComplexType + || configurationSource == ConfigurationSource.Explicit) + { + return true; + } + + if (!configurationSource.Overrides(Metadata.GetBaseTypeConfigurationSource())) + { + return false; + } + + if (baseComplexType == null) + { + return true; + } + + var baseMembers = baseComplexType.GetMembers() + .Where(m => m.GetConfigurationSource() == ConfigurationSource.Explicit) + .ToDictionary(m => m.Name); + + foreach (var derivedMember in Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredMembers())) + { + if (derivedMember.GetConfigurationSource() == ConfigurationSource.Explicit + && baseMembers.TryGetValue(derivedMember.Name, out var baseMember)) + { + switch (derivedMember) + { + case IReadOnlyProperty: + return baseMember is IReadOnlyProperty; + case IReadOnlyNavigation derivedNavigation: + return baseMember is IReadOnlyNavigation baseNavigation + && derivedNavigation.TargetEntityType == baseNavigation.TargetEntityType; + case IReadOnlyComplexProperty: + return baseMember is IReadOnlyComplexProperty; + case IReadOnlyServiceProperty: + return baseMember is IReadOnlyServiceProperty; + case IReadOnlySkipNavigation derivedSkipNavigation: + return baseMember is IReadOnlySkipNavigation baseSkipNavigation + && derivedSkipNavigation.TargetEntityType == baseSkipNavigation.TargetEntityType; + } + } + } + + 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 virtual InternalComplexTypeBuilder? HasConstructorBinding( + InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + { + if (CanSetConstructorBinding(constructorBinding, configurationSource)) + { + Metadata.SetConstructorBinding(constructorBinding, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetConstructorBinding(InstantiationBinding? constructorBinding, ConfigurationSource configurationSource) + => configurationSource.Overrides(Metadata.GetConstructorBindingConfigurationSource()) + || Metadata.ConstructorBinding == constructorBinding; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexTypeBuilder? HasServiceOnlyConstructorBinding( + InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + { + if (CanSetServiceOnlyConstructorBinding(constructorBinding, configurationSource)) + { + Metadata.SetServiceOnlyConstructorBinding(constructorBinding, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetServiceOnlyConstructorBinding( + InstantiationBinding? constructorBinding, + ConfigurationSource configurationSource) + => configurationSource.Overrides(Metadata.GetServiceOnlyConstructorBindingConfigurationSource()) + || Metadata.ServiceOnlyConstructorBinding == constructorBinding; + + IConventionComplexType IConventionComplexTypeBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionComplexTypeBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionComplexTypeBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionComplexTypeBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder IConventionComplexTypeBuilder.RemoveUnusedImplicitProperties( + IReadOnlyList properties) + => (IConventionComplexTypeBuilder)RemoveUnusedImplicitProperties(properties); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasNoProperty(IConventionProperty property, bool fromDataAnnotation) + => RemoveProperty( + (Property)property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) == null + ? null + : this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasNoComplexProperty( + IConventionComplexProperty complexProperty, bool fromDataAnnotation) + => (IConventionComplexTypeBuilder?)HasNoComplexProperty( + (ComplexProperty)complexProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.Ignore(string name, bool fromDataAnnotation) + => Ignore(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.HasChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + bool fromDataAnnotation) + => (IConventionComplexTypeBuilder?)HasChangeTrackingStrategy( + changeTrackingStrategy, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + bool fromDataAnnotation) + => (IConventionComplexTypeBuilder?)UsePropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 8a406133a34..69bb2e5ddb8 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; +using static System.Environment; namespace Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -13,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 InternalEntityTypeBuilder : AnnotatableBuilder, IConventionEntityTypeBuilder +public class InternalEntityTypeBuilder : InternalTypeBaseBuilder, IConventionEntityTypeBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -32,13 +33,7 @@ public InternalEntityTypeBuilder(EntityType metadata, InternalModelBuilder model /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - [DebuggerStepThrough] - IConventionKeyBuilder? IConventionEntityTypeBuilder.PrimaryKey( - IReadOnlyList? propertyNames, - bool fromDataAnnotation) - => PrimaryKey( - propertyNames, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + public new virtual EntityType Metadata => (EntityType)base.Metadata; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -396,7 +391,13 @@ public virtual bool CanRemoveKey(Key key, ConfigurationSource configurationSourc return detachedKeys; } - private static (InternalKeyBuilder, ConfigurationSource?) DetachKey(Key keyToDetach) + /// + /// 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 (InternalKeyBuilder, ConfigurationSource?) DetachKey(Key keyToDetach) { var entityTypeBuilder = keyToDetach.DeclaringEntityType.Builder; var keyBuilder = keyToDetach.Builder; @@ -473,232 +474,58 @@ public virtual bool CanRemoveKey(ConfigurationSource configurationSource) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalPropertyBuilder? Property( - Type? propertyType, - string propertyName, - ConfigurationSource? configurationSource) - => Property(propertyType, propertyName, typeConfigurationSource: configurationSource, configurationSource: configurationSource); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? Property( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, - string propertyName, - ConfigurationSource? typeConfigurationSource, - ConfigurationSource? configurationSource) - => Property( - propertyType, propertyName, memberInfo: null, - typeConfigurationSource, - configurationSource); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? Property(string propertyName, ConfigurationSource? configurationSource) - => Property(propertyType: null, propertyName, memberInfo: null, typeConfigurationSource: null, configurationSource); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? Property(MemberInfo memberInfo, ConfigurationSource? configurationSource) - => Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), memberInfo, configurationSource, configurationSource); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? IndexerProperty( - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, - string propertyName, - ConfigurationSource? configurationSource) + public override void RemoveMembersInHierarchy(string propertyName, ConfigurationSource configurationSource) { - var indexerPropertyInfo = Metadata.FindIndexerPropertyInfo(); - if (indexerPropertyInfo == null) - { - throw new InvalidOperationException( - CoreStrings.NonIndexerEntityType(propertyName, Metadata.DisplayName(), typeof(string).ShortDisplayName())); - } - - return Property(propertyType, propertyName, indexerPropertyInfo, configurationSource, configurationSource); - } + base.RemoveMembersInHierarchy(propertyName, configurationSource); - private InternalPropertyBuilder? Property( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, - string propertyName, - MemberInfo? memberInfo, - ConfigurationSource? typeConfigurationSource, - ConfigurationSource? configurationSource) - { - var entityType = Metadata; - List? propertiesToDetach = null; - var existingProperty = entityType.FindProperty(propertyName); - if (existingProperty != null) + foreach (var conflictingServiceProperty in Metadata.FindServicePropertiesInHierarchy(propertyName)) { - if (existingProperty.DeclaringEntityType != Metadata) - { - if (!IsIgnored(propertyName, configurationSource)) - { - Metadata.RemoveIgnored(propertyName); - } - - entityType = existingProperty.DeclaringEntityType; - } - - if (IsCompatible(memberInfo, existingProperty) - && (propertyType == null || propertyType == existingProperty.ClrType)) + if (conflictingServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) { - if (configurationSource.HasValue) - { - existingProperty.UpdateConfigurationSource(configurationSource.Value); - } - - if (propertyType != null - && typeConfigurationSource.HasValue) - { - existingProperty.UpdateTypeConfigurationSource(typeConfigurationSource.Value); - } - - return existingProperty.Builder; + conflictingServiceProperty.DeclaringEntityType.RemoveServiceProperty(conflictingServiceProperty); } - - if (memberInfo == null - || (memberInfo is PropertyInfo propertyInfo && propertyInfo.IsIndexerProperty())) - { - if (existingProperty.GetTypeConfigurationSource() is ConfigurationSource existingTypeConfigurationSource - && !typeConfigurationSource.Overrides(existingTypeConfigurationSource)) - { - return null; - } - - memberInfo ??= existingProperty.PropertyInfo ?? (MemberInfo?)existingProperty.FieldInfo; - } - else if (!configurationSource.Overrides(existingProperty.GetConfigurationSource())) - { - return null; - } - - propertyType ??= existingProperty.ClrType; - - propertiesToDetach = new List { existingProperty }; } - else + + foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(propertyName)) { - if (configurationSource != ConfigurationSource.Explicit - && (!configurationSource.HasValue - || !CanAddProperty(propertyType ?? memberInfo?.GetMemberType(), propertyName, configurationSource.Value))) + if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) { - return null; + throw new InvalidOperationException( + CoreStrings.ConflictingPropertyOrNavigation( + propertyName, Metadata.DisplayName(), conflictingNavigation.DeclaringEntityType.DisplayName())); } - memberInfo ??= Metadata.IsPropertyBag - ? null - : Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault(); - - if (propertyType == null) + var foreignKey = conflictingNavigation.ForeignKey; + if (foreignKey.GetConfigurationSource() == ConfigurationSource.Convention) { - if (memberInfo == null) - { - throw new InvalidOperationException(CoreStrings.NoPropertyType(propertyName, Metadata.DisplayName())); - } - - propertyType = memberInfo.GetMemberType(); - typeConfigurationSource = ConfigurationSource.Explicit; + foreignKey.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, ConfigurationSource.Convention); } - - foreach (var derivedType in Metadata.GetDerivedTypes()) + else { - var derivedProperty = derivedType.FindDeclaredProperty(propertyName); - if (derivedProperty != null) - { - propertiesToDetach ??= new List(); - - propertiesToDetach.Add(derivedProperty); - } + foreignKey.Builder.HasNavigation( + (string?)null, + conflictingNavigation.IsOnDependent, + configurationSource); } } - Check.DebugAssert(configurationSource is not null, "configurationSource is null"); - - InternalPropertyBuilder builder; - using (Metadata.Model.DelayConventions()) + foreach (var conflictingSkipNavigation in Metadata.FindSkipNavigationsInHierarchy(propertyName)) { - var detachedProperties = propertiesToDetach == null ? null : DetachProperties(propertiesToDetach); - - if (existingProperty == null) + if (conflictingSkipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) { - Metadata.RemoveIgnored(propertyName); - - foreach (var conflictingServiceProperty in Metadata.FindServicePropertiesInHierarchy(propertyName)) - { - if (conflictingServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) - { - conflictingServiceProperty.DeclaringEntityType.RemoveServiceProperty(conflictingServiceProperty); - } - } - - foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(propertyName)) - { - if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) - { - throw new InvalidOperationException( - CoreStrings.PropertyCalledOnNavigation(propertyName, Metadata.DisplayName())); - } - - var foreignKey = conflictingNavigation.ForeignKey; - if (foreignKey.GetConfigurationSource() == ConfigurationSource.Convention) - { - foreignKey.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, ConfigurationSource.Convention); - } - else if (foreignKey.Builder.HasNavigation( - (string?)null, - conflictingNavigation.IsOnDependent, - configurationSource.Value) - == null) - { - return null; - } - } - - foreach (var conflictingSkipNavigation in Metadata.FindSkipNavigationsInHierarchy(propertyName)) - { - if (conflictingSkipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) - { - continue; - } - - var inverse = conflictingSkipNavigation.Inverse; - if (inverse?.IsInModel == true - && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) - { - inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource.Value); - } - - conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation( - conflictingSkipNavigation, configurationSource.Value); - } + continue; } - builder = entityType.AddProperty( - propertyName, propertyType, memberInfo, typeConfigurationSource, configurationSource.Value)!.Builder; + var inverse = conflictingSkipNavigation.Inverse; + if (inverse?.IsInModel == true + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) + { + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); + } - detachedProperties?.Attach(this); + conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation( + conflictingSkipNavigation, configurationSource); } - - return builder.Metadata.IsInModel - ? builder - : Metadata.FindProperty(propertyName)?.Builder; } /// @@ -707,28 +534,7 @@ public virtual bool CanRemoveKey(ConfigurationSource configurationSource) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool CanHaveProperty( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, - string propertyName, - MemberInfo? memberInfo, - ConfigurationSource? typeConfigurationSource, - ConfigurationSource? configurationSource, - bool checkClrProperty = false) - { - var existingProperty = Metadata.FindProperty(propertyName); - return existingProperty != null - ? (IsCompatible(memberInfo, existingProperty) - && (propertyType == null || propertyType == existingProperty.ClrType)) - || ((memberInfo == null - || (memberInfo is PropertyInfo propertyInfo && propertyInfo.IsIndexerProperty())) - && (existingProperty.GetTypeConfigurationSource() is not ConfigurationSource existingTypeConfigurationSource - || typeConfigurationSource.Overrides(existingTypeConfigurationSource))) - || configurationSource.Overrides(existingProperty.GetConfigurationSource()) - : configurationSource.HasValue - && CanAddProperty(propertyType ?? memberInfo?.GetMemberType(), propertyName, configurationSource.Value, checkClrProperty); - } - - private bool CanAddProperty( + protected override bool CanAddProperty( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, string propertyName, ConfigurationSource configurationSource, @@ -740,116 +546,12 @@ private bool CanAddProperty( || propertyType != null || Metadata.GetRuntimeProperties().ContainsKey(propertyName)) && Metadata.FindServicePropertiesInHierarchy(propertyName).Cast() + .Concat(Metadata.FindComplexPropertiesInHierarchy(propertyName)) .Concat(Metadata.FindNavigationsInHierarchy(propertyName)) .Concat(Metadata.FindSkipNavigationsInHierarchy(propertyName)) - .All( - m => configurationSource.Overrides(m.GetConfigurationSource()) + .All(m => configurationSource.Overrides(m.GetConfigurationSource()) && m.GetConfigurationSource() != ConfigurationSource.Explicit); - private static bool IsCompatible(MemberInfo? newMemberInfo, Property existingProperty) - { - if (newMemberInfo == null) - { - return true; - } - - var existingMemberInfo = existingProperty.GetIdentifyingMemberInfo(); - if (existingMemberInfo == null) - { - return newMemberInfo == existingProperty.DeclaringType.FindIndexerPropertyInfo(); - } - - if (newMemberInfo == existingMemberInfo) - { - return true; - } - - var declaringType = (IMutableEntityType)existingProperty.DeclaringType; - if (!newMemberInfo.DeclaringType!.IsAssignableFrom(declaringType.ClrType)) - { - return existingMemberInfo.IsOverriddenBy(newMemberInfo); - } - - IMutableEntityType? existingMemberDeclaringEntityType = null; - foreach (var baseType in declaringType.GetAllBaseTypes()) - { - if (newMemberInfo.DeclaringType == baseType.ClrType) - { - return existingMemberDeclaringEntityType != null - && existingMemberInfo.IsOverriddenBy(newMemberInfo); - } - - if (existingMemberDeclaringEntityType == null - && existingMemberInfo.DeclaringType == baseType.ClrType) - { - existingMemberDeclaringEntityType = baseType; - } - } - - // newMemberInfo is declared on an unmapped base type, existingMemberInfo should be kept - return newMemberInfo.IsOverriddenBy(existingMemberInfo); - } - - private bool CanRemoveProperty( - Property property, - ConfigurationSource configurationSource, - bool canOverrideSameSource = true) - { - Check.NotNull(property, nameof(property)); - Check.DebugAssert(property.DeclaringEntityType == Metadata, "property.DeclaringEntityType != Metadata"); - - var currentConfigurationSource = property.GetConfigurationSource(); - return configurationSource.Overrides(currentConfigurationSource) - && (canOverrideSameSource || (configurationSource != currentConfigurationSource)); - } - - private ConfigurationSource? RemoveProperty( - Property property, - ConfigurationSource configurationSource, - bool canOverrideSameSource = true) - { - var currentConfigurationSource = property.GetConfigurationSource(); - if (!configurationSource.Overrides(currentConfigurationSource) - || !(canOverrideSameSource || (configurationSource != currentConfigurationSource))) - { - return null; - } - - using (Metadata.Model.DelayConventions()) - { - var detachedRelationships = property.GetContainingForeignKeys().ToList() - .Select(DetachRelationship).ToList(); - - foreach (var key in property.GetContainingKeys().ToList()) - { - detachedRelationships.AddRange( - key.GetReferencingForeignKeys().ToList() - .Select(DetachRelationship)); - var removed = key.DeclaringEntityType.Builder.HasNoKey(key, configurationSource); - Check.DebugAssert(removed != null, "removed is null"); - } - - foreach (var index in property.GetContainingIndexes().ToList()) - { - var removed = index.DeclaringEntityType.Builder.HasNoIndex(index, configurationSource); - Check.DebugAssert(removed != null, "removed is null"); - } - - if (property.IsInModel) - { - var removedProperty = Metadata.RemoveProperty(property.Name); - Check.DebugAssert(removedProperty == property, "removedProperty != property"); - } - - foreach (var relationshipSnapshot in detachedRelationships) - { - relationshipSnapshot.Attach(); - } - } - - return currentConfigurationSource; - } - /// /// 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 @@ -932,7 +634,7 @@ public virtual IMutableNavigationBase Navigation(string navigationName) } else { - foreach (var derivedType in Metadata.GetDerivedTypes()) + foreach (EntityType derivedType in Metadata.GetDerivedTypes()) { var derivedProperty = derivedType.FindDeclaredServiceProperty(propertyName); if (derivedProperty != null) @@ -962,53 +664,7 @@ public virtual IMutableNavigationBase Navigation(string navigationName) { Metadata.RemoveIgnored(propertyName); - foreach (var conflictingProperty in Metadata.FindPropertiesInHierarchy(propertyName).ToList()) - { - if (conflictingProperty.GetConfigurationSource() != ConfigurationSource.Explicit) - { - conflictingProperty.DeclaringEntityType.Builder.RemoveProperty(conflictingProperty, configurationSource.Value); - } - } - - foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(propertyName).ToList()) - { - if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) - { - continue; - } - - var foreignKey = conflictingNavigation.ForeignKey; - if (foreignKey.GetConfigurationSource() == ConfigurationSource.Convention) - { - foreignKey.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, ConfigurationSource.Convention); - } - else if (foreignKey.Builder.HasNavigation( - (string?)null, - conflictingNavigation.IsOnDependent, - configurationSource.Value) - == null) - { - return null; - } - } - - foreach (var conflictingSkipNavigation in Metadata.FindSkipNavigationsInHierarchy(propertyName).ToList()) - { - if (conflictingSkipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) - { - continue; - } - - var inverse = conflictingSkipNavigation.Inverse; - if (inverse?.IsInModel == true - && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) - { - inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource.Value); - } - - conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation( - conflictingSkipNavigation, configurationSource.Value); - } + RemoveMembersInHierarchy(propertyName, configurationSource.Value); } builder = Metadata.AddServiceProperty(memberInfo, serviceType, configurationSource.Value).Builder; @@ -1037,7 +693,7 @@ public virtual bool CanHaveServiceProperty(MemberInfo memberInfo, ConfigurationS { var existingProperty = Metadata.FindServiceProperty(memberInfo); return existingProperty != null - ? existingProperty.DeclaringEntityType == Metadata + ? existingProperty.DeclaringType == Metadata || configurationSource.Overrides(existingProperty.GetConfigurationSource()) : configurationSource.HasValue && CanAddServiceProperty(memberInfo, configurationSource.Value); @@ -1050,6 +706,7 @@ private bool CanAddServiceProperty(MemberInfo memberInfo, ConfigurationSource co && Metadata.Model.Builder.CanBeConfigured( memberInfo.GetMemberType(), TypeConfigurationType.ServiceProperty, configurationSource) && Metadata.FindPropertiesInHierarchy(propertyName).Cast() + .Concat(Metadata.FindComplexPropertiesInHierarchy(propertyName)) .Concat(Metadata.FindNavigationsInHierarchy(propertyName)) .Concat(Metadata.FindSkipNavigationsInHierarchy(propertyName)) .All( @@ -1073,6 +730,60 @@ private bool CanAddServiceProperty(MemberInfo memberInfo, ConfigurationSource co return builder; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalEntityTypeBuilder? HasNoServiceProperty( + ServiceProperty serviceProperty, + ConfigurationSource configurationSource) + { + if (!CanRemoveServiceProperty(serviceProperty, configurationSource)) + { + return null; + } + + Metadata.RemoveServiceProperty(serviceProperty); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveServiceProperty(ServiceProperty serviceProperty, ConfigurationSource configurationSource) + => configurationSource.Overrides(serviceProperty.GetConfigurationSource()); + + /// + /// 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 bool CanAddComplexProperty( + string propertyName, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? targetType, + bool? collection, + ConfigurationSource configurationSource, + bool checkClrProperty = false) + => !IsIgnored(propertyName, configurationSource) + && (targetType == null || !ModelBuilder.IsIgnored(targetType, configurationSource)) + && (!checkClrProperty + || propertyType != null + || Metadata.GetRuntimeProperties().ContainsKey(propertyName)) + && Metadata.FindPropertiesInHierarchy(propertyName).Cast() + .Concat(Metadata.FindServicePropertiesInHierarchy(propertyName)) + .Concat(Metadata.FindNavigationsInHierarchy(propertyName)) + .Concat(Metadata.FindSkipNavigationsInHierarchy(propertyName)) + .All(m => configurationSource.Overrides(m.GetConfigurationSource()) + && m.GetConfigurationSource() != ConfigurationSource.Explicit); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -1107,6 +818,7 @@ public virtual bool CanAddNavigation( && (type == null || CanBeNavigation(type, configurationSource)) && Metadata.FindPropertiesInHierarchy(navigationName).Cast() .Concat(Metadata.FindServicePropertiesInHierarchy(navigationName)) + .Concat(Metadata.FindComplexPropertiesInHierarchy(navigationName)) .Concat(Metadata.FindSkipNavigationsInHierarchy(navigationName)) .All( m => configurationSource.Overrides(m.GetConfigurationSource()) @@ -1142,6 +854,7 @@ private bool CanAddSkipNavigation( => !IsIgnored(skipNavigationName, configurationSource) && (type == null || CanBeNavigation(type, configurationSource)) && Metadata.FindPropertiesInHierarchy(skipNavigationName).Cast() + .Concat(Metadata.FindComplexPropertiesInHierarchy(skipNavigationName)) .Concat(Metadata.FindServicePropertiesInHierarchy(skipNavigationName)) .Concat(Metadata.FindNavigationsInHierarchy(skipNavigationName)) .All( @@ -1154,21 +867,7 @@ private bool CanAddSkipNavigation( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool IsIgnored(string name, ConfigurationSource? configurationSource) - { - Check.NotEmpty(name, nameof(name)); - - return configurationSource != ConfigurationSource.Explicit - && !configurationSource.OverridesStrictly(Metadata.FindIgnoredConfigurationSource(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. - /// - public virtual InternalEntityTypeBuilder? Ignore(string name, ConfigurationSource configurationSource) + public override InternalEntityTypeBuilder? Ignore(string name, ConfigurationSource configurationSource) { var ignoredConfigurationSource = Metadata.FindIgnoredConfigurationSource(name); if (ignoredConfigurationSource.HasValue) @@ -1226,7 +925,7 @@ public virtual bool IsIgnored(string name, ConfigurationSource? configurationSou var property = Metadata.FindProperty(name); if (property != null) { - Check.DebugAssert(property.DeclaringEntityType == Metadata, "property.DeclaringEntityType != Metadata"); + Check.DebugAssert(property.DeclaringType == Metadata, "property.DeclaringEntityType != Metadata"); if (property.GetConfigurationSource() == ConfigurationSource.Explicit) { @@ -1239,41 +938,58 @@ public virtual bool IsIgnored(string name, ConfigurationSource? configurationSou } else { - var skipNavigation = Metadata.FindSkipNavigation(name); - if (skipNavigation != null) + var complexProperty = Metadata.FindComplexProperty(name); + if (complexProperty != null) { - var inverse = skipNavigation.Inverse; - if (inverse?.IsInModel == true - && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) - { - inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); - } + Check.DebugAssert(complexProperty.DeclaringType == Metadata, "property.DeclaringType != Metadata"); - Check.DebugAssert( - skipNavigation.DeclaringEntityType == Metadata, "skipNavigation.DeclaringEntityType != Metadata"); - - if (skipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) + if (complexProperty.GetConfigurationSource() == ConfigurationSource.Explicit) { - ModelBuilder.Metadata.ScopedModelDependencies?.Logger.MappedNavigationIgnoredWarning(skipNavigation); + ModelBuilder.Metadata.ScopedModelDependencies?.Logger.MappedComplexPropertyIgnoredWarning(complexProperty); } - Metadata.Builder.HasNoSkipNavigation(skipNavigation, configurationSource); + var removedComplexProperty = Metadata.RemoveComplexProperty(complexProperty); + + Check.DebugAssert(removedComplexProperty != null, "removedProperty is null"); } else { - var serviceProperty = Metadata.FindServiceProperty(name); - if (serviceProperty != null) + var skipNavigation = Metadata.FindSkipNavigation(name); + if (skipNavigation != null) { + var inverse = skipNavigation.Inverse; + if (inverse?.IsInModel == true + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) + { + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); + } + Check.DebugAssert( - serviceProperty.DeclaringEntityType == Metadata, "serviceProperty.DeclaringEntityType != Metadata"); + skipNavigation.DeclaringEntityType == Metadata, "skipNavigation.DeclaringEntityType != Metadata"); + + if (skipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) + { + ModelBuilder.Metadata.ScopedModelDependencies?.Logger.MappedNavigationIgnoredWarning(skipNavigation); + } + + Metadata.Builder.HasNoSkipNavigation(skipNavigation, configurationSource); + } + else + { + var serviceProperty = Metadata.FindServiceProperty(name); + if (serviceProperty != null) + { + Check.DebugAssert( + serviceProperty.DeclaringEntityType == Metadata, "serviceProperty.DeclaringEntityType != Metadata"); - Metadata.RemoveServiceProperty(serviceProperty); + Metadata.RemoveServiceProperty(serviceProperty); + } } } } } - foreach (var derivedType in Metadata.GetDerivedTypes()) + foreach (EntityType derivedType in Metadata.GetDerivedTypes()) { var derivedIgnoredSource = derivedType.FindDeclaredIgnoredConfigurationSource(name); if (derivedIgnoredSource.HasValue) @@ -1322,29 +1038,41 @@ public virtual bool IsIgnored(string name, ConfigurationSource? configurationSou } else { - var skipNavigation = derivedType.FindDeclaredSkipNavigation(name); - if (skipNavigation != null) + var declaredComplexProperty = derivedType.FindDeclaredComplexProperty(name); + if (declaredComplexProperty != null) { - var inverse = skipNavigation.Inverse; - if (inverse?.IsInModel == true - && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) + if (configurationSource.Overrides(declaredComplexProperty.GetConfigurationSource()) + && declaredComplexProperty.GetConfigurationSource() != ConfigurationSource.Explicit) { - inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); - } - - if (skipNavigation.GetConfigurationSource() != ConfigurationSource.Explicit) - { - derivedType.Builder.HasNoSkipNavigation(skipNavigation, configurationSource); + derivedType.RemoveComplexProperty(declaredComplexProperty); } } else { - var derivedServiceProperty = derivedType.FindDeclaredServiceProperty(name); - if (derivedServiceProperty != null - && configurationSource.Overrides(derivedServiceProperty.GetConfigurationSource()) - && derivedServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + var skipNavigation = derivedType.FindDeclaredSkipNavigation(name); + if (skipNavigation != null) + { + var inverse = skipNavigation.Inverse; + if (inverse?.IsInModel == true + && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) + { + inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); + } + + if (skipNavigation.GetConfigurationSource() != ConfigurationSource.Explicit) + { + derivedType.Builder.HasNoSkipNavigation(skipNavigation, configurationSource); + } + } + else { - derivedType.RemoveServiceProperty(name); + var derivedServiceProperty = derivedType.FindDeclaredServiceProperty(name); + if (derivedServiceProperty != null + && configurationSource.Overrides(derivedServiceProperty.GetConfigurationSource()) + && derivedServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + derivedType.RemoveServiceProperty(name); + } } } } @@ -1361,10 +1089,7 @@ public virtual bool IsIgnored(string name, ConfigurationSource? configurationSou /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool CanIgnore(string name, ConfigurationSource configurationSource) - => CanIgnore(name, configurationSource, shouldThrow: false); - - private bool CanIgnore(string name, ConfigurationSource configurationSource, bool shouldThrow) + protected override bool CanIgnore(string name, ConfigurationSource configurationSource, bool shouldThrow) { var ignoredConfigurationSource = Metadata.FindIgnoredConfigurationSource(name); if (ignoredConfigurationSource.HasValue) @@ -1397,19 +1122,19 @@ private bool CanIgnore(string name, ConfigurationSource configurationSource, boo var property = Metadata.FindProperty(name); if (property != null) { - if (property.DeclaringEntityType != Metadata) + if (property.DeclaringType != Metadata) { if (shouldThrow) { throw new InvalidOperationException( CoreStrings.InheritedPropertyCannotBeIgnored( - name, Metadata.DisplayName(), property.DeclaringEntityType.DisplayName())); + name, Metadata.DisplayName(), property.DeclaringType.DisplayName())); } return false; } - if (!property.DeclaringEntityType.Builder.CanRemoveProperty( + if (!property.DeclaringType.Builder.CanRemoveProperty( property, configurationSource, canOverrideSameSource: true)) { return false; @@ -1417,48 +1142,71 @@ private bool CanIgnore(string name, ConfigurationSource configurationSource, boo } else { - var skipNavigation = Metadata.FindSkipNavigation(name); - if (skipNavigation != null) + var complexProperty = Metadata.FindComplexProperty(name); + if (complexProperty != null) { - if (skipNavigation.DeclaringEntityType != Metadata) + if (complexProperty.DeclaringType != Metadata) { if (shouldThrow) { throw new InvalidOperationException( CoreStrings.InheritedPropertyCannotBeIgnored( - name, Metadata.DisplayName(), skipNavigation.DeclaringEntityType.DisplayName())); + name, Metadata.DisplayName(), complexProperty.DeclaringType.DisplayName())); } return false; } - if (!configurationSource.Overrides(skipNavigation.GetConfigurationSource())) + if (!configurationSource.Overrides(complexProperty.GetConfigurationSource())) { return false; } } else { - var serviceProperty = Metadata.FindServiceProperty(name); - if (serviceProperty != null) + var skipNavigation = Metadata.FindSkipNavigation(name); + if (skipNavigation != null) { - if (serviceProperty.DeclaringEntityType != Metadata) + if (skipNavigation.DeclaringEntityType != Metadata) { if (shouldThrow) { throw new InvalidOperationException( CoreStrings.InheritedPropertyCannotBeIgnored( - name, Metadata.DisplayName(), serviceProperty.DeclaringEntityType.DisplayName())); + name, Metadata.DisplayName(), skipNavigation.DeclaringEntityType.DisplayName())); } return false; } - if (!configurationSource.Overrides(serviceProperty.GetConfigurationSource())) + if (!configurationSource.Overrides(skipNavigation.GetConfigurationSource())) { return false; } } + else + { + var serviceProperty = Metadata.FindServiceProperty(name); + if (serviceProperty != null) + { + if (serviceProperty.DeclaringEntityType != Metadata) + { + if (shouldThrow) + { + throw new InvalidOperationException( + CoreStrings.InheritedPropertyCannotBeIgnored( + name, Metadata.DisplayName(), serviceProperty.DeclaringEntityType.DisplayName())); + } + + return false; + } + + if (!configurationSource.Overrides(serviceProperty.GetConfigurationSource())) + { + return false; + } + } + } } } } @@ -1615,6 +1363,7 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo List? detachedRelationships = null; List? detachedSkipNavigations = null; PropertiesSnapshot? detachedProperties = null; + List? detachedComplexProperties = null; List? detachedServiceProperties = null; IReadOnlyList<(InternalKeyBuilder, ConfigurationSource?)>? detachedKeys = null; // We use at least DataAnnotation as ConfigurationSource while removing to allow us @@ -1628,7 +1377,7 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo var relationshipsToBeDetached = FindConflictingMembers( - Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredNavigations()), + Metadata.GetDerivedTypesInclusive().Cast().SelectMany(et => et.GetDeclaredNavigations()), baseMemberNames, n => { @@ -1680,7 +1429,7 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo } } - var foreignKeysUsingKeyProperties = Metadata.GetDerivedTypesInclusive() + var foreignKeysUsingKeyProperties = Metadata.GetDerivedTypesInclusive().Cast() .SelectMany(t => t.GetDeclaredForeignKeys()) .Where(fk => fk.Properties.Any(p => baseEntityType.FindProperty(p.Name)?.IsKey() == true)); @@ -1691,7 +1440,7 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo var skipNavigationsToDetach = FindConflictingMembers( - Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredSkipNavigations()), + Metadata.GetDerivedTypesInclusive().Cast().SelectMany(et => et.GetDeclaredSkipNavigations()), baseMemberNames, n => { @@ -1719,16 +1468,32 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredProperties()), baseMemberNames, p => baseEntityType.FindProperty(p.Name) != null, - p => p.DeclaringEntityType.Builder.RemoveProperty(p, ConfigurationSource.Explicit)); + p => p.DeclaringType.Builder.RemoveProperty(p, ConfigurationSource.Explicit)); if (propertiesToDetach != null) { detachedProperties = DetachProperties(propertiesToDetach); } + var complexPropertiesToDetach = + FindConflictingMembers( + Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredComplexProperties()), + baseMemberNames, + p => baseEntityType.FindComplexProperty(p.Name) != null, + p => p.DeclaringType.RemoveComplexProperty(p)); + + if (complexPropertiesToDetach != null) + { + detachedComplexProperties = new List(); + foreach (var complexPropertyToDetach in complexPropertiesToDetach) + { + detachedComplexProperties.Add(InternalComplexPropertyBuilder.Detach(complexPropertyToDetach)!); + } + } + var servicePropertiesToDetach = FindConflictingMembers( - Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredServiceProperties()), + Metadata.GetDerivedTypesInclusive().Cast().SelectMany(et => et.GetDeclaredServiceProperties()), baseMemberNames, n => baseEntityType.FindServiceProperty(n.Name) != null, p => p.DeclaringEntityType.RemoveServiceProperty(p)); @@ -1765,7 +1530,7 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo { removedInheritedPropertiesToDuplicate = new HashSet(); List? relationshipsToBeDetached = null; - foreach (var foreignKey in Metadata.GetDerivedTypesInclusive() + foreach (var foreignKey in Metadata.GetDerivedTypesInclusive().Cast() .SelectMany(t => t.GetDeclaredForeignKeys())) { var shouldBeDetached = false; @@ -1818,7 +1583,7 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo } List? indexesToBeDetached = null; - foreach (var index in Metadata.GetDerivedTypesInclusive().SelectMany(e => e.GetDeclaredIndexes())) + foreach (var index in Metadata.GetDerivedTypesInclusive().Cast().SelectMany(e => e.GetDeclaredIndexes())) { var shouldBeDetached = false; foreach (var property in index.Properties) @@ -1872,13 +1637,22 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo } } + if (detachedComplexProperties != null) + { + foreach (var detachedComplexProperty in detachedComplexProperties) + { + detachedComplexProperty.Attach( + detachedComplexProperty.ComplexProperty.DeclaringType.Builder); + } + } + detachedProperties?.Attach(this); if (detachedKeys != null) { foreach (var (internalKeyBuilder, value) in detachedKeys) { - var newKeyBuilder = internalKeyBuilder.Attach(Metadata.RootType().Builder, value); + var newKeyBuilder = internalKeyBuilder.Attach(Metadata.GetRootType().Builder, value); if (newKeyBuilder == null && internalKeyBuilder.Metadata.GetConfigurationSource() == ConfigurationSource.Explicit) { @@ -2006,7 +1780,7 @@ public virtual bool CanSetBaseType(EntityType? baseEntityType, ConfigurationSour return false; } - if (Metadata.GetDerivedTypesInclusive() + if (Metadata.GetDerivedTypesInclusive().Cast() .SelectMany(t => t.GetDeclaredForeignKeys()) .Where(fk => fk.Properties.Any(p => baseEntityType.FindProperty(p.Name)?.IsKey() == true)) .Any(fk => !configurationSourceForRemoval.Overrides(fk.GetPropertiesConfigurationSource()))) @@ -2030,6 +1804,8 @@ public virtual bool CanSetBaseType(EntityType? baseEntityType, ConfigurationSour case IReadOnlyNavigation derivedNavigation: return baseMember is IReadOnlyNavigation baseNavigation && derivedNavigation.TargetEntityType == baseNavigation.TargetEntityType; + case IReadOnlyComplexProperty: + return baseMember is IReadOnlyComplexProperty; case IReadOnlyServiceProperty: return baseMember is IReadOnlyServiceProperty; case IReadOnlySkipNavigation derivedSkipNavigation: @@ -2042,76 +1818,6 @@ public virtual bool CanSetBaseType(EntityType? baseEntityType, ConfigurationSour 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 PropertiesSnapshot? DetachProperties(IReadOnlyList propertiesToDetach) - { - if (propertiesToDetach.Count == 0) - { - return null; - } - - List? detachedRelationships = null; - foreach (var propertyToDetach in propertiesToDetach) - { - foreach (var relationship in propertyToDetach.GetContainingForeignKeys().ToList()) - { - detachedRelationships ??= new List(); - - detachedRelationships.Add(DetachRelationship(relationship)); - } - } - - var detachedIndexes = DetachIndexes(propertiesToDetach.SelectMany(p => p.GetContainingIndexes()).Distinct()); - - var keysToDetach = propertiesToDetach.SelectMany(p => p.GetContainingKeys()).Distinct().ToList(); - foreach (var key in keysToDetach) - { - foreach (var referencingForeignKey in key.GetReferencingForeignKeys().ToList()) - { - detachedRelationships ??= new List(); - - detachedRelationships.Add(DetachRelationship(referencingForeignKey)); - } - } - - var detachedKeys = DetachKeys(keysToDetach); - - var detachedProperties = new List(); - foreach (var propertyToDetach in propertiesToDetach) - { - var property = propertyToDetach.DeclaringEntityType.FindDeclaredProperty(propertyToDetach.Name); - if (property != null) - { - var propertyBuilder = property.Builder; - // Reset convention configuration - propertyBuilder.ValueGenerated(null, ConfigurationSource.Convention); - propertyBuilder.AfterSave(null, ConfigurationSource.Convention); - propertyBuilder.BeforeSave(null, ConfigurationSource.Convention); - ConfigurationSource? removedConfigurationSource; - if (property.DeclaringEntityType.IsInModel) - { - removedConfigurationSource = property.DeclaringEntityType.Builder - .RemoveProperty(property, property.GetConfigurationSource()); - } - else - { - removedConfigurationSource = property.GetConfigurationSource(); - property.DeclaringEntityType.RemoveProperty(property.Name); - } - - Check.DebugAssert(removedConfigurationSource.HasValue, "removedConfigurationSource.HasValue is false"); - detachedProperties.Add(propertyBuilder); - } - } - - return new PropertiesSnapshot(detachedProperties, detachedIndexes, detachedKeys, detachedRelationships); - } - /// /// 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 @@ -2295,11 +2001,11 @@ public static RelationshipSnapshot DetachRelationship(ForeignKey foreignKey, boo } List? detachedIndexes = null; - foreach (var index in entityType.GetDeclaredIndexes().ToList()) + foreach (var indexToBeDetached in entityType.GetDeclaredIndexes().ToList()) { detachedIndexes ??= new List(); - var detachedIndex = DetachIndex(index); + var detachedIndex = DetachIndex(indexToBeDetached); if (detachedIndex.Metadata.GetConfigurationSource().Overrides(ConfigurationSource.Explicit)) { detachedIndexes.Add(detachedIndex); @@ -2337,41 +2043,6 @@ private void RemoveKeyIfUnused(Key key, ConfigurationSource configurationSource HasNoKey(key, configurationSource); } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalEntityTypeBuilder RemoveUnusedImplicitProperties(IReadOnlyList properties) - where T : class, IConventionProperty - { - foreach (var property in properties) - { - if (property.IsInModel && property.IsImplicitlyCreated()) - { - RemovePropertyIfUnused((Property)(object)property, ConfigurationSource.Convention); - } - } - - return this; - } - - private static void RemovePropertyIfUnused(Property property, ConfigurationSource configurationSource) - { - if (!property.IsInModel - || !property.DeclaringEntityType.Builder.CanRemoveProperty(property, configurationSource) - || property.GetContainingIndexes().Any() - || property.GetContainingForeignKeys().Any() - || property.GetContainingKeys().Any()) - { - return; - } - - var removedProperty = property.DeclaringEntityType.RemoveProperty(property.Name); - Check.DebugAssert(removedProperty == property, "removedProperty != 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 @@ -2611,7 +2282,13 @@ public virtual bool CanRemoveIndex(Index index, ConfigurationSource configuratio return detachedIndexes; } - private static InternalIndexBuilder DetachIndex(Index indexToDetach) + /// + /// 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 InternalIndexBuilder DetachIndex(Index indexToDetach) { var entityTypeBuilder = indexToDetach.DeclaringEntityType.Builder; var indexBuilder = indexToDetach.Builder; @@ -3576,6 +3253,35 @@ private static InternalIndexBuilder DetachIndex(Index indexToDetach) return relationship; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalEntityTypeBuilder? HasNoNavigation( + Navigation navigation, + ConfigurationSource configurationSource) + { + if (!CanRemoveNavigation(navigation, configurationSource)) + { + return null; + } + + navigation.ForeignKey.Builder.HasNavigation((string?)null, navigation.IsOnDependent, configurationSource); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveNavigation(Navigation navigation, ConfigurationSource configurationSource) + => configurationSource.Overrides(navigation.GetConfigurationSource()); + /// /// 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 @@ -3626,7 +3332,7 @@ private static InternalIndexBuilder DetachIndex(Index indexToDetach) HasNoRelationship(ownership, configurationSource); } - foreach (var derivedType in entityType.GetDerivedTypes()) + foreach (EntityType derivedType in entityType.GetDerivedTypes()) { derivedType.SetIsOwned(false); var derivedOwnership = derivedType.FindDeclaredOwnership(); @@ -3669,7 +3375,7 @@ public virtual bool CanSetIsOwned(bool owned, ConfigurationSource configurationS } } - foreach (var derivedType in entityType.GetDerivedTypes()) + foreach (EntityType derivedType in entityType.GetDerivedTypes()) { if (!derivedType.IsOwned() && !configurationSource.OverridesStrictly(derivedType.GetConfigurationSource())) @@ -3698,7 +3404,7 @@ public virtual bool CanSetIsOwned(bool owned, ConfigurationSource configurationS return false; } - foreach (var derivedType in entityType.GetDerivedTypes()) + foreach (EntityType derivedType in entityType.GetDerivedTypes()) { if (derivedType.IsOwned() && !configurationSource.OverridesStrictly(derivedType.GetConfigurationSource())) @@ -3720,13 +3426,13 @@ public virtual bool CanSetIsOwned(bool owned, ConfigurationSource configurationS private bool RemoveNonOwnershipRelationships(ForeignKey? futureOwnership, ConfigurationSource configurationSource) { var ownership = Metadata.FindOwnership() ?? futureOwnership; - var incompatibleRelationships = Metadata.GetDerivedTypesInclusive() + var incompatibleRelationships = Metadata.GetDerivedTypesInclusive().Cast() .SelectMany(t => t.GetDeclaredForeignKeys()) .Where( fk => fk is { IsOwnership: false, PrincipalToDependent: not null } && !Contains(ownership, fk)) .Concat( - Metadata.GetDerivedTypesInclusive() + Metadata.GetDerivedTypesInclusive().Cast() .SelectMany(t => t.GetDeclaredReferencingForeignKeys()) .Where( fk => !fk.IsOwnership @@ -4064,7 +3770,7 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK ConfigurationSource? configurationSource) { var principalType = principalEntityTypeBuilder.Metadata; - var principalBaseEntityTypeBuilder = principalType.RootType().Builder; + var principalBaseEntityTypeBuilder = principalType.GetRootType().Builder; if (principalKey == null) { if (principalType.IsKeyless @@ -4211,20 +3917,22 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK public virtual InternalSkipNavigationBuilder? HasSkipNavigation( MemberIdentity navigation, EntityType targetEntityType, + Type? navigationType, MemberIdentity inverseNavigation, + Type? inverseNavigationType, ConfigurationSource configurationSource, bool? collections = null, bool? onDependent = null) { var skipNavigationBuilder = HasSkipNavigation( - navigation, targetEntityType, configurationSource, collections, onDependent); + navigation, targetEntityType, navigationType, configurationSource, collections, onDependent); if (skipNavigationBuilder == null) { return null; } var inverseSkipNavigationBuilder = targetEntityType.Builder.HasSkipNavigation( - inverseNavigation, Metadata, configurationSource, collections, onDependent); + inverseNavigation, Metadata, inverseNavigationType, configurationSource, collections, onDependent); if (inverseSkipNavigationBuilder == null) { HasNoSkipNavigation(skipNavigationBuilder.Metadata, configurationSource); @@ -4241,8 +3949,49 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual InternalSkipNavigationBuilder? HasSkipNavigation( - MemberIdentity navigationProperty, + MemberInfo navigation, + EntityType targetEntityType, + ConfigurationSource? configurationSource, + bool? collection = null, + bool? onDependent = null) + => HasSkipNavigation( + MemberIdentity.Create(navigation), + targetEntityType, + navigation.GetMemberType(), + configurationSource, + collection, + onDependent); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalSkipNavigationBuilder? HasSkipNavigation( + MemberIdentity navigation, + EntityType targetEntityType, + ConfigurationSource? configurationSource, + bool? collection = null, + bool? onDependent = null) + => HasSkipNavigation( + navigation, + targetEntityType, + navigation.MemberInfo?.GetMemberType(), + configurationSource, + collection, + onDependent); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalSkipNavigationBuilder? HasSkipNavigation( + MemberIdentity navigation, EntityType targetEntityType, + Type? navigationType, ConfigurationSource? configurationSource, bool? collection = null, bool? onDependent = null) @@ -4251,10 +4000,11 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK List<(InternalSkipNavigationBuilder Navigation, InternalSkipNavigationBuilder Inverse)>? detachedNavigations = null; InternalSkipNavigationBuilder builder; - var navigationName = navigationProperty.Name; + var navigationName = navigation.Name; if (navigationName != null) { - var memberInfo = navigationProperty.MemberInfo; + var memberInfo = navigation.MemberInfo; + navigationType ??= memberInfo?.GetMemberType(); var existingNavigation = Metadata.FindSkipNavigation(navigationName); if (existingNavigation != null) { @@ -4295,7 +4045,7 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK return null; } - foreach (var derivedType in Metadata.GetDerivedTypes()) + foreach (EntityType derivedType in Metadata.GetDerivedTypes()) { var conflictingNavigation = derivedType.FindDeclaredSkipNavigation(navigationName); if (conflictingNavigation != null) @@ -4306,418 +4056,62 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK } } - if (collection == null - && memberInfo != null) - { - var navigationType = memberInfo.GetMemberType(); - var navigationTargetClrType = navigationType.TryGetSequenceType(); - collection = navigationTargetClrType != null - && navigationType != targetEntityType.ClrType - && navigationTargetClrType.IsAssignableFrom(targetEntityType.ClrType); - } - - using (ModelBuilder.Metadata.DelayConventions()) - { - Metadata.RemoveIgnored(navigationName); - - foreach (var conflictingProperty in Metadata.FindPropertiesInHierarchy(navigationName)) - { - if (conflictingProperty.GetConfigurationSource() != ConfigurationSource.Explicit) - { - conflictingProperty.DeclaringEntityType.RemoveProperty(conflictingProperty); - } - } - - foreach (var conflictingServiceProperty in Metadata.FindServicePropertiesInHierarchy(navigationName)) - { - if (conflictingServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) - { - conflictingServiceProperty.DeclaringEntityType.RemoveServiceProperty(conflictingServiceProperty); - } - } - - foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(navigationName)) - { - if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) - { - continue; - } - - var conflictingForeignKey = conflictingNavigation.ForeignKey; - if (conflictingForeignKey.GetConfigurationSource() == ConfigurationSource.Convention) - { - conflictingForeignKey.DeclaringEntityType.Builder.HasNoRelationship( - conflictingForeignKey, ConfigurationSource.Convention); - } - else if (conflictingForeignKey.Builder.HasNavigation( - (string?)null, - conflictingNavigation.IsOnDependent, - configurationSource.Value) - == null) - { - return null; - } - } - - if (navigationsToDetach != null) - { - detachedNavigations = new List<(InternalSkipNavigationBuilder, InternalSkipNavigationBuilder)>(); - foreach (var navigationToDetach in navigationsToDetach) - { - var inverse = navigationToDetach.Inverse; - detachedNavigations.Add((DetachSkipNavigation(navigationToDetach)!, DetachSkipNavigation(inverse)!)); - } - } - - builder = Metadata.AddSkipNavigation( - navigationName, memberInfo, - targetEntityType, collection ?? true, onDependent ?? false, configurationSource.Value)!.Builder; - - if (detachedNavigations != null) - { - foreach (var (navigation, inverse) in detachedNavigations) - { - navigation.Attach(this, inverseBuilder: inverse); - } - } - } - } - else - { - var generatedNavigationName = targetEntityType.ShortName(); - navigationName = generatedNavigationName; - var uniquifier = 0; - while (Metadata.FindMembersInHierarchy(navigationName).Any()) - { - navigationName = generatedNavigationName + (++uniquifier); - } - - builder = Metadata.AddSkipNavigation( - navigationName, null, - targetEntityType, collection ?? true, onDependent ?? false, ConfigurationSource.Explicit)!.Builder; - } - - return builder.Metadata.IsInModel - ? builder - : Metadata.FindSkipNavigation(navigationName!)?.Builder; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalEntityTypeBuilder? HasNoSkipNavigation( - SkipNavigation skipNavigation, - ConfigurationSource configurationSource) - { - if (!CanRemoveSkipNavigation(skipNavigation, configurationSource)) - { - return null; - } - - if (skipNavigation.Inverse != null) - { - var removed = skipNavigation.Inverse.Builder.HasInverse(null, configurationSource); - Check.DebugAssert(removed != null, "Expected inverse to be removed"); - } - - if (skipNavigation.ForeignKey != null) - { - skipNavigation.Builder.HasForeignKey(null, configurationSource); - } - - Metadata.RemoveSkipNavigation(skipNavigation); - - return this; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool CanRemoveSkipNavigation(SkipNavigation skipNavigation, ConfigurationSource configurationSource) - => configurationSource.Overrides(skipNavigation.GetConfigurationSource()); - - private static InternalSkipNavigationBuilder? DetachSkipNavigation(SkipNavigation? skipNavigationToDetach) - { - if (skipNavigationToDetach is null || !skipNavigationToDetach.IsInModel) - { - return null; - } - - var builder = skipNavigationToDetach.Builder; - skipNavigationToDetach.DeclaringEntityType.Builder.HasNoSkipNavigation(skipNavigationToDetach, ConfigurationSource.Explicit); - return builder; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool ShouldReuniquifyTemporaryProperties(ForeignKey foreignKey) - => TryCreateUniqueProperties( - foreignKey.PrincipalKey.Properties.Count, - foreignKey.Properties, - foreignKey.PrincipalKey.Properties.Select(p => p.ClrType), - foreignKey.PrincipalKey.Properties.Select(p => p.Name), - foreignKey.IsRequired - && foreignKey.GetIsRequiredConfigurationSource().Overrides(ConfigurationSource.Convention), - foreignKey.DependentToPrincipal?.Name - ?? foreignKey.ReferencingSkipNavigations?.FirstOrDefault()?.Inverse?.Name - ?? foreignKey.PrincipalEntityType.ShortName()) - .Item1; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalPropertyBuilder? CreateUniqueProperty( - Type propertyType, - string propertyName, - bool required) - => CreateUniqueProperties( - new[] { propertyType }, - new[] { propertyName }, - required)?.First().Builder; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IReadOnlyList? CreateUniqueProperties( - IReadOnlyList propertyTypes, - IReadOnlyList propertyNames, - bool isRequired) - => TryCreateUniqueProperties( - propertyNames.Count, - null, - propertyTypes, - propertyNames, - isRequired, - "").Item2; - - private IReadOnlyList? CreateUniqueProperties( - IReadOnlyList principalProperties, - bool isRequired, - string baseName) - => TryCreateUniqueProperties( - principalProperties.Count, - null, - principalProperties.Select(p => p.ClrType), - principalProperties.Select(p => p.Name), - isRequired, - baseName).Item2; - - private (bool, IReadOnlyList?) TryCreateUniqueProperties( - int propertyCount, - IReadOnlyList? currentProperties, - IEnumerable principalPropertyTypes, - IEnumerable principalPropertyNames, - bool isRequired, - string baseName) - { - var newProperties = currentProperties == null ? new Property[propertyCount] : null; - var clrProperties = Metadata.GetRuntimeProperties(); - var clrFields = Metadata.GetRuntimeFields(); - var canReuniquify = false; - using var principalPropertyNamesEnumerator = principalPropertyNames.GetEnumerator(); - using var principalPropertyTypesEnumerator = principalPropertyTypes.GetEnumerator(); - for (var i = 0; - i < propertyCount - && principalPropertyNamesEnumerator.MoveNext() - && principalPropertyTypesEnumerator.MoveNext(); - i++) - { - var keyPropertyName = principalPropertyNamesEnumerator.Current; - var keyPropertyType = principalPropertyTypesEnumerator.Current; - - var keyModifiedBaseName = keyPropertyName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase) - ? keyPropertyName - : baseName + keyPropertyName; - string propertyName; - var clrType = keyPropertyType.MakeNullable(!isRequired); - var index = -1; - while (true) - { - propertyName = keyModifiedBaseName + (++index > 0 ? index.ToString(CultureInfo.InvariantCulture) : ""); - if (!Metadata.FindPropertiesInHierarchy(propertyName).Any() - && !clrProperties.ContainsKey(propertyName) - && !clrFields.ContainsKey(propertyName) - && !IsIgnored(propertyName, ConfigurationSource.Convention)) - { - if (currentProperties == null) - { - var propertyBuilder = Property( - clrType, propertyName, typeConfigurationSource: null, - configurationSource: ConfigurationSource.Convention); - - if (propertyBuilder == null) - { - return (false, null); - } - - if (index > 0) - { - propertyBuilder.HasAnnotation( - CoreAnnotationNames.PreUniquificationName, - keyModifiedBaseName, - ConfigurationSource.Convention); - } - - if (clrType.IsNullableType()) - { - propertyBuilder.IsRequired(isRequired, ConfigurationSource.Convention); - } - - newProperties![i] = propertyBuilder.Metadata; - } - else if (Metadata.Model.Builder.CanBeConfigured( - clrType, TypeConfigurationType.Property, ConfigurationSource.Convention)) - { - canReuniquify = true; - } - - break; - } - - var currentProperty = currentProperties?.SingleOrDefault(p => p.Name == propertyName); - if (currentProperty != null) - { - if (((IConventionProperty)currentProperty).IsImplicitlyCreated() - && currentProperty.ClrType != clrType - && isRequired) - { - canReuniquify = true; - } - - break; - } - } - } - - return (canReuniquify, newProperties); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IReadOnlyList? GetOrCreateProperties( - IReadOnlyList? propertyNames, - ConfigurationSource? configurationSource, - IReadOnlyList? referencedProperties = null, - bool required = false, - bool useDefaultType = false) - { - if (propertyNames == null) - { - return null; - } - - if (referencedProperties != null - && referencedProperties.Count != propertyNames.Count) - { - referencedProperties = null; - } + if (collection == null + && navigationType != null) + { + var navigationTargetClrType = navigationType.TryGetSequenceType(); + collection = navigationTargetClrType != null + && navigationType != targetEntityType.ClrType + && navigationTargetClrType.IsAssignableFrom(targetEntityType.ClrType); + } - var propertyList = new List(); - for (var i = 0; i < propertyNames.Count; i++) - { - var propertyName = propertyNames[i]; - var property = Metadata.FindProperty(propertyName); - if (property == null) + using (ModelBuilder.Metadata.DelayConventions()) { - var type = referencedProperties == null - ? useDefaultType - ? typeof(int) - : null - : referencedProperties[i].ClrType; + Metadata.RemoveIgnored(navigationName); - if (!configurationSource.HasValue) + if (navigationsToDetach != null) { - return null; + detachedNavigations = new List<(InternalSkipNavigationBuilder, InternalSkipNavigationBuilder)>(); + foreach (var navigationToDetach in navigationsToDetach) + { + var inverse = navigationToDetach.Inverse; + detachedNavigations.Add((DetachSkipNavigation(navigationToDetach)!, DetachSkipNavigation(inverse)!)); + } } - var propertyBuilder = Property( - required - ? type - : type?.MakeNullable(), - propertyName, - typeConfigurationSource: null, - configurationSource.Value); + RemoveMembersInHierarchy(navigationName, configurationSource.Value); - if (propertyBuilder == null) - { - return null; - } + builder = Metadata.AddSkipNavigation( + navigationName, navigationType, memberInfo, targetEntityType, + collection ?? true, onDependent ?? false, configurationSource.Value)!.Builder; - property = propertyBuilder.Metadata; - } - else if (configurationSource.HasValue) - { - if (ConfigurationSource.Convention.Overrides(property.GetTypeConfigurationSource()) - && (property.IsShadowProperty() || property.IsIndexerProperty()) - && (!property.IsNullable || (required && property.GetIsNullableConfigurationSource() == null)) - && property.ClrType.IsNullableType()) - { - property = property.DeclaringEntityType.Builder.Property( - property.ClrType.MakeNullable(false), - property.Name, - configurationSource.Value)! - .Metadata; - } - else + if (detachedNavigations != null) { - property = property.DeclaringEntityType.Builder.Property(property.Name, configurationSource.Value)!.Metadata; + foreach (var (detachedNavigation, inverse) in detachedNavigations) + { + detachedNavigation.Attach(this, inverseBuilder: inverse); + } } } - - propertyList.Add(property); - } - - return propertyList; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual IReadOnlyList? GetOrCreateProperties( - IEnumerable? clrMembers, - ConfigurationSource? configurationSource) - { - if (clrMembers == null) - { - return null; } - - var list = new List(); - foreach (var propertyInfo in clrMembers) + else { - var propertyBuilder = Property(propertyInfo, configurationSource); - if (propertyBuilder == null) + var generatedNavigationName = targetEntityType.ShortName(); + navigationName = generatedNavigationName; + var uniquifier = 0; + while (Metadata.FindMembersInHierarchy(navigationName).Any()) { - return null; + navigationName = generatedNavigationName + (++uniquifier); } - list.Add(propertyBuilder.Metadata); + builder = Metadata.AddSkipNavigation( + navigationName, navigationType, null, targetEntityType, + collection ?? true, onDependent ?? false, ConfigurationSource.Explicit)!.Builder; } - return list; + return builder.Metadata.IsInModel + ? builder + : Metadata.FindSkipNavigation(navigationName!)?.Builder; } /// @@ -4726,57 +4120,29 @@ public virtual bool ShouldReuniquifyTemporaryProperties(ForeignKey foreignKey) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IReadOnlyList? GetActualProperties( - IReadOnlyList? properties, - ConfigurationSource? configurationSource) + public virtual InternalEntityTypeBuilder? HasNoSkipNavigation( + SkipNavigation skipNavigation, + ConfigurationSource configurationSource) { - if (properties == null) + if (!CanRemoveSkipNavigation(skipNavigation, configurationSource)) { return null; } - var actualProperties = new Property[properties.Count]; - for (var i = 0; i < actualProperties.Length; i++) - { - var property = properties[i]; - var typeConfigurationSource = property.GetTypeConfigurationSource(); - var builder = property.IsInModel && property.DeclaringEntityType.IsAssignableFrom(Metadata) - ? property.Builder - : Property( - typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? property.ClrType : null, - property.Name, - property.GetIdentifyingMemberInfo(), - typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? typeConfigurationSource : null, - configurationSource); - if (builder == null) - { - return null; - } - - actualProperties[i] = builder.Metadata; + if (skipNavigation.Inverse != null) + { + var removed = skipNavigation.Inverse.Builder.HasInverse(null, configurationSource); + Check.DebugAssert(removed != null, "Expected inverse to be removed"); } - return actualProperties; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalEntityTypeBuilder? HasChangeTrackingStrategy( - ChangeTrackingStrategy? changeTrackingStrategy, - ConfigurationSource configurationSource) - { - if (CanSetChangeTrackingStrategy(changeTrackingStrategy, configurationSource)) + if (skipNavigation.ForeignKey != null) { - Metadata.SetChangeTrackingStrategy(changeTrackingStrategy, configurationSource); - - return this; + skipNavigation.Builder.HasForeignKey(null, configurationSource); } - return null; + Metadata.RemoveSkipNavigation(skipNavigation); + + return this; } /// @@ -4785,30 +4151,19 @@ public virtual bool ShouldReuniquifyTemporaryProperties(ForeignKey foreignKey) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool CanSetChangeTrackingStrategy( - ChangeTrackingStrategy? changeTrackingStrategy, - ConfigurationSource configurationSource) - => configurationSource.Overrides(Metadata.GetChangeTrackingStrategyConfigurationSource()) - || Metadata.GetChangeTrackingStrategy() == changeTrackingStrategy; + public virtual bool CanRemoveSkipNavigation(SkipNavigation skipNavigation, ConfigurationSource configurationSource) + => configurationSource.Overrides(skipNavigation.GetConfigurationSource()); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalEntityTypeBuilder? UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - ConfigurationSource configurationSource) + private static InternalSkipNavigationBuilder? DetachSkipNavigation(SkipNavigation? skipNavigationToDetach) { - if (CanSetPropertyAccessMode(propertyAccessMode, configurationSource)) + if (skipNavigationToDetach is null || !skipNavigationToDetach.IsInModel) { - Metadata.SetPropertyAccessMode(propertyAccessMode, configurationSource); - - return this; + return null; } - return null; + var builder = skipNavigationToDetach.Builder; + skipNavigationToDetach.DeclaringEntityType.Builder.HasNoSkipNavigation(skipNavigationToDetach, ConfigurationSource.Explicit); + return builder; } /// @@ -4817,9 +4172,18 @@ public virtual bool CanSetChangeTrackingStrategy( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, ConfigurationSource configurationSource) - => configurationSource.Overrides(((IConventionEntityType)Metadata).GetPropertyAccessModeConfigurationSource()) - || ((IConventionEntityType)Metadata).GetPropertyAccessMode() == propertyAccessMode; + public virtual bool ShouldReuniquifyTemporaryProperties(ForeignKey foreignKey) + => TryCreateUniqueProperties( + foreignKey.PrincipalKey.Properties.Count, + foreignKey.Properties, + foreignKey.PrincipalKey.Properties.Select(p => p.ClrType), + foreignKey.PrincipalKey.Properties.Select(p => p.Name), + foreignKey.IsRequired + && foreignKey.GetIsRequiredConfigurationSource().Overrides(ConfigurationSource.Convention), + foreignKey.DependentToPrincipal?.Name + ?? foreignKey.ReferencingSkipNavigations?.FirstOrDefault()?.Inverse?.Name + ?? foreignKey.PrincipalEntityType.ShortName()) + .Item1; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4937,7 +4301,7 @@ public virtual bool CanSetServiceOnlyConstructorBinding( => CanSetDiscriminator( Check.NotNull(memberInfo, nameof(memberInfo)).GetSimpleMemberName(), memberInfo.GetMemberType(), configurationSource) ? DiscriminatorBuilder( - Metadata.RootType().Builder.Property( + Metadata.GetRootType().Builder.Property( memberInfo, configurationSource), configurationSource) : null; @@ -4955,7 +4319,7 @@ public virtual bool CanSetServiceOnlyConstructorBinding( discriminatorProperty = null; } - return Metadata.RootType().Builder.Property( + return (InternalPropertyBuilder?)Metadata.GetRootType().Builder.Property( type ?? discriminatorProperty?.ClrType ?? DefaultDiscriminatorType, name ?? discriminatorProperty?.Name ?? DefaultDiscriminatorName, typeConfigurationSource: type != null ? configurationSource : null, @@ -4972,7 +4336,7 @@ public virtual bool CanSetServiceOnlyConstructorBinding( return null; } - var rootTypeBuilder = Metadata.RootType().Builder; + var rootTypeBuilder = Metadata.GetRootType().Builder; var discriminatorProperty = discriminatorPropertyBuilder.Metadata; // Make sure the property is on the root type discriminatorPropertyBuilder = rootTypeBuilder.Property( @@ -5063,7 +4427,7 @@ private void RemoveUnusedDiscriminatorProperty(Property? newDiscriminatorPropert if (oldDiscriminatorProperty?.IsInModel == true && oldDiscriminatorProperty != newDiscriminatorProperty) { - oldDiscriminatorProperty.DeclaringEntityType.Builder.RemoveUnusedImplicitProperties( + oldDiscriminatorProperty.DeclaringType.Builder.RemoveUnusedImplicitProperties( new[] { oldDiscriminatorProperty }); if (oldDiscriminatorProperty.IsInModel) @@ -5095,7 +4459,7 @@ private bool CanSetDiscriminator( && (discriminatorType == null || discriminatorProperty?.ClrType == discriminatorType)) || configurationSource.Overrides(Metadata.GetDiscriminatorPropertyConfigurationSource())) && (discriminatorProperty != null - || Metadata.RootType().Builder.CanAddDiscriminatorProperty( + || Metadata.GetRootType().Builder.CanAddDiscriminatorProperty( discriminatorType ?? DefaultDiscriminatorType, name ?? DefaultDiscriminatorName, typeConfigurationSource: discriminatorType != null @@ -5148,22 +4512,9 @@ IConventionEntityType IConventionEntityTypeBuilder.Metadata /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasBaseType( - IConventionEntityType? baseEntityType, - bool fromDataAnnotation) - => HasBaseType( - (EntityType?)baseEntityType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanSetBaseType(IConventionEntityType? baseEntityType, bool fromDataAnnotation) - => CanSetBaseType( - (EntityType?)baseEntityType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionEntityTypeBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5172,16 +4523,9 @@ bool IConventionEntityTypeBuilder.CanSetBaseType(IConventionEntityType? baseEnti /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionPropertyBuilder? IConventionEntityTypeBuilder.Property( - Type propertyType, - string propertyName, - bool setTypeConfigurationSource, - bool fromDataAnnotation) - => Property( - propertyType, - propertyName, setTypeConfigurationSource - ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention - : null, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionEntityTypeBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5190,8 +4534,9 @@ bool IConventionEntityTypeBuilder.CanSetBaseType(IConventionEntityType? baseEnti /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionPropertyBuilder? IConventionEntityTypeBuilder.Property(MemberInfo memberInfo, bool fromDataAnnotation) - => Property(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionEntityTypeBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5200,18 +4545,11 @@ bool IConventionEntityTypeBuilder.CanSetBaseType(IConventionEntityType? baseEnti /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanHaveProperty( - Type? propertyType, - string propertyName, + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasBaseType( + IConventionEntityType? baseEntityType, bool fromDataAnnotation) - => CanHaveProperty( - propertyType, - propertyName, - null, - propertyType != null - ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention - : null, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + => HasBaseType( + (EntityType?)baseEntityType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5220,13 +4558,9 @@ bool IConventionEntityTypeBuilder.CanHaveProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanHaveProperty(MemberInfo memberInfo, bool fromDataAnnotation) - => CanHaveProperty( - memberInfo.GetMemberType(), - memberInfo.Name, - memberInfo, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + bool IConventionEntityTypeBuilder.CanSetBaseType(IConventionEntityType? baseEntityType, bool fromDataAnnotation) + => CanSetBaseType( + (EntityType?)baseEntityType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5235,16 +4569,9 @@ bool IConventionEntityTypeBuilder.CanHaveProperty(MemberInfo memberInfo, bool fr /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionPropertyBuilder? IConventionEntityTypeBuilder.IndexerProperty( - Type propertyType, - string propertyName, - bool fromDataAnnotation) - => Property( - propertyType, - propertyName, - Metadata.FindIndexerPropertyInfo(), - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionEntityTypeBuilder IConventionEntityTypeBuilder.RemoveUnusedImplicitProperties( + IReadOnlyList properties) + => (IConventionEntityTypeBuilder)RemoveUnusedImplicitProperties(properties); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5253,16 +4580,12 @@ bool IConventionEntityTypeBuilder.CanHaveProperty(MemberInfo memberInfo, bool fr /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanHaveIndexerProperty( - Type propertyType, - string propertyName, - bool fromDataAnnotation) - => CanHaveProperty( - propertyType, - propertyName, - Metadata.FindIndexerPropertyInfo(), - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoProperty(IConventionProperty property, bool fromDataAnnotation) + => RemoveProperty( + (Property)property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) == null + ? null + : this; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5271,11 +4594,11 @@ bool IConventionEntityTypeBuilder.CanHaveIndexerProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IReadOnlyList? IConventionEntityTypeBuilder.GetOrCreateProperties( - IReadOnlyList? propertyNames, - bool fromDataAnnotation) - => GetOrCreateProperties( - propertyNames, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoComplexProperty( + IConventionComplexProperty complexProperty, bool fromDataAnnotation) + => (IConventionEntityTypeBuilder?)HasNoComplexProperty( + (ComplexProperty)complexProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5284,10 +4607,8 @@ bool IConventionEntityTypeBuilder.CanHaveIndexerProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IReadOnlyList? IConventionEntityTypeBuilder.GetOrCreateProperties( - IEnumerable? memberInfos, - bool fromDataAnnotation) - => GetOrCreateProperties(memberInfos, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionServicePropertyBuilder? IConventionEntityTypeBuilder.ServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation) + => ServiceProperty(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5296,9 +4617,9 @@ bool IConventionEntityTypeBuilder.CanHaveIndexerProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionEntityTypeBuilder IConventionEntityTypeBuilder.RemoveUnusedImplicitProperties( - IReadOnlyList properties) - => RemoveUnusedImplicitProperties(properties); + IConventionServicePropertyBuilder? IConventionEntityTypeBuilder.ServiceProperty( + Type serviceType, MemberInfo memberInfo, bool fromDataAnnotation) + => ServiceProperty(serviceType, memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5307,8 +4628,8 @@ IConventionEntityTypeBuilder IConventionEntityTypeBuilder.RemoveUnusedImplicitPr /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionServicePropertyBuilder? IConventionEntityTypeBuilder.ServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation) - => ServiceProperty(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + bool IConventionEntityTypeBuilder.CanHaveServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation) + => CanHaveServiceProperty(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5317,9 +4638,11 @@ IConventionEntityTypeBuilder IConventionEntityTypeBuilder.RemoveUnusedImplicitPr /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionServicePropertyBuilder? IConventionEntityTypeBuilder.ServiceProperty( - Type serviceType, MemberInfo memberInfo, bool fromDataAnnotation) - => ServiceProperty(serviceType, memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoServiceProperty( + IConventionServiceProperty serviceProperty, bool fromDataAnnotation) + => HasNoServiceProperty( + (ServiceProperty)serviceProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5328,17 +4651,10 @@ IConventionEntityTypeBuilder IConventionEntityTypeBuilder.RemoveUnusedImplicitPr /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanHaveServiceProperty(MemberInfo memberInfo, bool fromDataAnnotation) - => CanHaveServiceProperty(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionEntityTypeBuilder.IsIgnored(string name, bool fromDataAnnotation) - => IsIgnored(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + bool IConventionEntityTypeBuilder.CanRemoveServiceProperty(IConventionServiceProperty serviceProperty, bool fromDataAnnotation) + => CanRemoveServiceProperty( + (ServiceProperty)serviceProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5357,8 +4673,12 @@ bool IConventionEntityTypeBuilder.IsIgnored(string name, bool fromDataAnnotation /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanIgnore(string name, bool fromDataAnnotation) - => CanIgnore(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionKeyBuilder? IConventionEntityTypeBuilder.PrimaryKey( + IReadOnlyList? propertyNames, + bool fromDataAnnotation) + => PrimaryKey( + propertyNames, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5728,7 +5048,9 @@ bool IConventionEntityTypeBuilder.CanRemoveIndex(IConventionIndex index, bool fr => HasSkipNavigation( MemberIdentity.Create(navigation), (EntityType)targetEntityType, + navigation.GetMemberType(), MemberIdentity.Create(inverseNavigation), + inverseNavigation.GetMemberType(), fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, collections, onDependent); @@ -5948,6 +5270,30 @@ bool IConventionEntityTypeBuilder.CanHaveNavigation( type, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasNoNavigation(IConventionNavigation navigation, bool fromDataAnnotation) + => HasNoNavigation( + (Navigation)navigation, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionEntityTypeBuilder.CanRemoveNavigation(IConventionNavigation navigation, bool fromDataAnnotation) + => CanRemoveNavigation( + (Navigation)navigation, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -5972,6 +5318,7 @@ bool IConventionEntityTypeBuilder.CanHaveSkipNavigation(string skipNavigationNam => HasSkipNavigation( MemberIdentity.Create(navigation), (EntityType)targetEntityType, + navigation.GetMemberType(), fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, collection, onDependent); @@ -5981,12 +5328,14 @@ bool IConventionEntityTypeBuilder.CanHaveSkipNavigation(string skipNavigationNam IConventionSkipNavigationBuilder? IConventionEntityTypeBuilder.HasSkipNavigation( string navigationName, IConventionEntityType targetEntityType, + Type? navigationType, bool? collection, bool? onDependent, bool fromDataAnnotation) => HasSkipNavigation( MemberIdentity.Create(navigationName), (EntityType)targetEntityType, + navigationType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, collection, onDependent); @@ -6071,20 +5420,7 @@ bool IConventionEntityTypeBuilder.CanSetDefiningQuery(LambdaExpression? query, b IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.HasChangeTrackingStrategy( ChangeTrackingStrategy? changeTrackingStrategy, bool fromDataAnnotation) - => HasChangeTrackingStrategy( - changeTrackingStrategy, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanSetChangeTrackingStrategy( - ChangeTrackingStrategy? changeTrackingStrategy, - bool fromDataAnnotation) - => CanSetChangeTrackingStrategy( + => (IConventionEntityTypeBuilder?)HasChangeTrackingStrategy( changeTrackingStrategy, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -6097,18 +5433,7 @@ bool IConventionEntityTypeBuilder.CanSetChangeTrackingStrategy( IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) - => UsePropertyAccessMode( - propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - bool IConventionEntityTypeBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) - => CanSetPropertyAccessMode( + => (IConventionEntityTypeBuilder?)UsePropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -6237,19 +5562,6 @@ bool IConventionEntityTypeBuilder.CanSetDiscriminator(MemberInfo memberInfo, boo bool IConventionEntityTypeBuilder.CanRemoveDiscriminator(bool fromDataAnnotation) => CanRemoveDiscriminator(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - IConventionPropertyBuilder? IConventionEntityTypeBuilder.CreateUniqueProperty( - Type propertyType, - string basePropertyName, - bool required) - => CreateUniqueProperty(propertyType, basePropertyName, required); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index b24fac41ada..11ae679a8dd 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -264,78 +264,16 @@ public InternalForeignKeyBuilder( } } - if (navigationToPrincipalName != null) + if (navigationToPrincipalName != null + && !dependentEntityType.FindNavigationsInHierarchy(navigationToPrincipalName).Any()) { - foreach (var conflictingServiceProperty in dependentEntityType.FindServicePropertiesInHierarchy(navigationToPrincipalName)) - { - if (conflictingServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) - { - conflictingServiceProperty.DeclaringEntityType.RemoveServiceProperty(conflictingServiceProperty); - } - } - - foreach (var conflictingProperty in dependentEntityType.FindPropertiesInHierarchy(navigationToPrincipalName)) - { - if (conflictingProperty.GetConfigurationSource() != ConfigurationSource.Explicit) - { - conflictingProperty.DeclaringEntityType.RemoveProperty(conflictingProperty.Name); - } - } - - foreach (var conflictingSkipNavigation in dependentEntityType.FindSkipNavigationsInHierarchy(navigationToPrincipalName)) - { - if (conflictingSkipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) - { - continue; - } - - var inverse = conflictingSkipNavigation.Inverse; - if (inverse?.IsInModel == true - && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) - { - inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(conflictingSkipNavigation, configurationSource); - } - - conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation( - conflictingSkipNavigation, configurationSource); - } + dependentEntityType.Builder.RemoveMembersInHierarchy(navigationToPrincipalName, configurationSource); } - if (navigationToDependentName != null) + if (navigationToDependentName != null + && !principalEntityType.FindNavigationsInHierarchy(navigationToDependentName).Any()) { - foreach (var conflictingServiceProperty in principalEntityType.FindServicePropertiesInHierarchy(navigationToDependentName)) - { - if (conflictingServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) - { - conflictingServiceProperty.DeclaringEntityType.RemoveServiceProperty(conflictingServiceProperty); - } - } - - foreach (var conflictingProperty in principalEntityType.FindPropertiesInHierarchy(navigationToDependentName)) - { - if (conflictingProperty.GetConfigurationSource() != ConfigurationSource.Explicit) - { - conflictingProperty.DeclaringEntityType.RemoveProperty(conflictingProperty.Name); - } - } - - foreach (var conflictingSkipNavigation in principalEntityType.FindSkipNavigationsInHierarchy(navigationToDependentName)) - { - if (conflictingSkipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) - { - continue; - } - - var inverse = conflictingSkipNavigation.Inverse; - if (inverse?.IsInModel == true - && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) - { - inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); - } - - conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation( - conflictingSkipNavigation, configurationSource); - } + principalEntityType.Builder.RemoveMembersInHierarchy(navigationToDependentName, configurationSource); } InternalForeignKeyBuilder? builder; @@ -1559,7 +1497,7 @@ public virtual bool CanInvert( using var batch = Metadata.DeclaringEntityType.Model.DelayConventions(); var temporaryProperties = Metadata.Properties.Where( - p => (p.IsShadowProperty() || p.DeclaringEntityType.IsPropertyBag && p.IsIndexerProperty()) + p => (p.IsShadowProperty() || p.DeclaringType.IsPropertyBag && p.IsIndexerProperty()) && ConfigurationSource.Convention.Overrides(p.GetConfigurationSource())).ToList(); var keysToDetach = temporaryProperties.SelectMany( @@ -1597,7 +1535,7 @@ public virtual bool CanInvert( { foreach (var (internalKeyBuilder, configurationSource) in detachedKeys) { - internalKeyBuilder.Attach(Metadata.DeclaringEntityType.RootType().Builder, configurationSource); + internalKeyBuilder.Attach(Metadata.DeclaringEntityType.GetRootType().Builder, configurationSource); } } @@ -2445,7 +2383,7 @@ private bool CanSetPrincipalKey( if (principalProperties != null && principalProperties.Count != 0) { - principalKey = principalEntityTypeBuilder.Metadata.RootType().Builder + principalKey = principalEntityTypeBuilder.Metadata.GetRootType().Builder .HasKey(principalProperties, configurationSource)!.Metadata; } @@ -2926,7 +2864,7 @@ private static InternalForeignKeyBuilder MergeFacetsFrom(Navigation newNavigatio if (newRelationshipBuilder == null) { var principalKey = principalProperties != null - ? principalEntityType.RootType().Builder.HasKey(principalProperties, configurationSource)!.Metadata + ? principalEntityType.GetRootType().Builder.HasKey(principalProperties, configurationSource)!.Metadata : principalEntityType.FindPrimaryKey(); if (principalKey != null) { @@ -3999,6 +3937,24 @@ IConventionForeignKey IConventionForeignKeyBuilder.Metadata get => Metadata; } + /// + [DebuggerStepThrough] + IConventionForeignKeyBuilder? IConventionForeignKeyBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionForeignKeyBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionForeignKeyBuilder? IConventionForeignKeyBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionForeignKeyBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionForeignKeyBuilder? IConventionForeignKeyBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionForeignKeyBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// [DebuggerStepThrough] IConventionForeignKeyBuilder? IConventionForeignKeyBuilder.HasEntityTypes( diff --git a/src/EFCore/Metadata/Internal/InternalIndexBuilder.cs b/src/EFCore/Metadata/Internal/InternalIndexBuilder.cs index f55646fa897..089488a89eb 100644 --- a/src/EFCore/Metadata/Internal/InternalIndexBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalIndexBuilder.cs @@ -112,7 +112,43 @@ public virtual bool CanSetIsDescending(IReadOnlyList? descending, Configur /// doing so can result in application failures when updating to a new Entity Framework Core release. /// IConventionIndex IConventionIndexBuilder.Metadata - => Metadata; + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionIndexBuilder? IConventionIndexBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionIndexBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionIndexBuilder? IConventionIndexBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionIndexBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionIndexBuilder? IConventionIndexBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionIndexBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/InternalKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalKeyBuilder.cs index 0caca66515b..a4fe1d10b96 100644 --- a/src/EFCore/Metadata/Internal/InternalKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalKeyBuilder.cs @@ -59,5 +59,26 @@ public InternalKeyBuilder(Key key, InternalModelBuilder modelBuilder) } IConventionKey IConventionKeyBuilder.Metadata - => Metadata; + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + [DebuggerStepThrough] + IConventionKeyBuilder? IConventionKeyBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionKeyBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionKeyBuilder? IConventionKeyBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionKeyBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionKeyBuilder? IConventionKeyBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionKeyBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs index 831819651bf..2e75e045c57 100644 --- a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using System.Security.AccessControl; namespace Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -51,12 +52,24 @@ public override InternalModelBuilder ModelBuilder /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalEntityTypeBuilder? SharedTypeEntity( - string name, - [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type? type, + public virtual InternalEntityTypeBuilder? Entity( + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, ConfigurationSource configurationSource, - bool? shouldBeOwned = false) - => Entity(new TypeIdentity(name, type ?? Model.DefaultPropertyBagType), configurationSource, shouldBeOwned); + bool? shouldBeOwned = null) + => Entity(new TypeIdentity(type, Metadata), configurationSource, shouldBeOwned); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalEntityTypeBuilder? Entity( + string name, + string definingNavigationName, + EntityType definingEntityType, + ConfigurationSource configurationSource) + => Entity(new TypeIdentity(name), definingNavigationName, definingEntityType, configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -66,35 +79,43 @@ public override InternalModelBuilder ModelBuilder /// public virtual InternalEntityTypeBuilder? Entity( [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + string definingNavigationName, + EntityType definingEntityType, + ConfigurationSource configurationSource) + => Entity(new TypeIdentity(type, Metadata), definingNavigationName, definingEntityType, configurationSource); + + private InternalEntityTypeBuilder? Entity( + in TypeIdentity type, + string definingNavigationName, + EntityType definingEntityType, + ConfigurationSource configurationSource) + => SharedTypeEntity( + definingEntityType.GetOwnedName(type.Type?.ShortDisplayName() ?? type.Name, definingNavigationName), + type.Type, configurationSource, shouldBeOwned: true); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalEntityTypeBuilder? SharedTypeEntity( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type? type, ConfigurationSource configurationSource, - bool? shouldBeOwned = null) - => Entity(new TypeIdentity(type, Metadata), configurationSource, shouldBeOwned); + bool? shouldBeOwned = false) + => Entity(new TypeIdentity(name, type ?? Model.DefaultPropertyBagType), configurationSource, shouldBeOwned); private InternalEntityTypeBuilder? Entity( in TypeIdentity type, ConfigurationSource configurationSource, bool? shouldBeOwned) { - if (IsIgnored(type, configurationSource)) + if (!CanHaveEntity(type, configurationSource, shouldBeOwned, shouldThrow: configurationSource == ConfigurationSource.Explicit)) { return null; } - if (type.Type != null - && shouldBeOwned != null) - { - var configurationType = shouldBeOwned.Value - ? TypeConfigurationType.OwnedEntityType - : type.IsNamed - ? TypeConfigurationType.SharedTypeEntityType - : TypeConfigurationType.EntityType; - - if (!CanBeConfigured(type.Type, configurationType, configurationSource)) - { - return null; - } - } - using var batch = Metadata.DelayConventions(); var clrType = type.Type; EntityType? entityType; @@ -106,19 +127,6 @@ public override InternalModelBuilder ModelBuilder entityType = Metadata.FindEntityType(clrType); if (entityType != null) { - Check.DebugAssert( - entityType.Name != type.Name || !entityType.HasSharedClrType, - "Shared type entity types shouldn't be named the same as non-shared"); - - if (!configurationSource.OverridesStrictly(entityType.GetConfigurationSource()) - && !entityType.IsOwned()) - { - return configurationSource == ConfigurationSource.Explicit - ? throw new InvalidOperationException( - CoreStrings.ClashingNonSharedType(type.Name, clrType.ShortDisplayName())) - : null; - } - entityTypeSnapshot = InternalEntityTypeBuilder.DetachAllMembers(entityType); // TODO: Use convention batch to track replaced entity type, see #15898 @@ -193,44 +201,57 @@ public override InternalModelBuilder ModelBuilder : null; } } + else if (clrType != null) + { + var complexConfigurationSource = Metadata.FindIsComplexConfigurationSource(clrType); + if (complexConfigurationSource != null + && configurationSource == ConfigurationSource.Convention) + { + return null; + } + } - if (type.Type != null) + if (shouldBeOwned == null) { - if (shouldBeOwned == null) + if (type.Type == null) { - var configurationType = Metadata.Configuration?.GetConfigurationType(type.Type); - switch (configurationType) + return null; + } + + var configurationType = Metadata.Configuration?.GetConfigurationType(type.Type); + switch (configurationType) + { + case null: + break; + case TypeConfigurationType.EntityType: + case TypeConfigurationType.SharedTypeEntityType: { - case null: - break; - case TypeConfigurationType.EntityType: - case TypeConfigurationType.SharedTypeEntityType: - { - shouldBeOwned ??= false; - break; - } - case TypeConfigurationType.OwnedEntityType: + shouldBeOwned ??= false; + break; + } + case TypeConfigurationType.OwnedEntityType: + { + shouldBeOwned ??= true; + break; + } + default: + { + if (configurationSource != ConfigurationSource.Explicit) { - shouldBeOwned ??= true; - break; + return null; } - default: - { - if (configurationSource != ConfigurationSource.Explicit) - { - return null; - } - break; - } + break; } - - shouldBeOwned ??= Metadata.FindIsOwnedConfigurationSource(type.Type) != null; } + + shouldBeOwned ??= Metadata.FindIsOwnedConfigurationSource(type.Type) != null; } - else if (shouldBeOwned == null) + + if (type.IsNamed + && clrType != null) { - return null; + Metadata.AddShared(clrType, configurationSource); } Metadata.RemoveIgnored(type.Name); @@ -255,34 +276,109 @@ public override InternalModelBuilder ModelBuilder /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalEntityTypeBuilder? Entity( - string name, - string definingNavigationName, - EntityType definingEntityType, - ConfigurationSource configurationSource) - => Entity(new TypeIdentity(name), definingNavigationName, definingEntityType, configurationSource); + public virtual bool CanHaveEntity( + in TypeIdentity type, + ConfigurationSource configurationSource, + bool? shouldBeOwned, + bool shouldThrow = false) + { + if (IsIgnored(type, configurationSource)) + { + return false; + } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual InternalEntityTypeBuilder? Entity( - [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, - string definingNavigationName, - EntityType definingEntityType, - ConfigurationSource configurationSource) - => Entity(new TypeIdentity(type, Metadata), definingNavigationName, definingEntityType, configurationSource); + if (type.Type != null + && shouldBeOwned != null) + { + var configurationType = shouldBeOwned.Value + ? TypeConfigurationType.OwnedEntityType + : type.IsNamed + ? TypeConfigurationType.SharedTypeEntityType + : TypeConfigurationType.EntityType; - private InternalEntityTypeBuilder? Entity( - in TypeIdentity type, - string definingNavigationName, - EntityType definingEntityType, - ConfigurationSource configurationSource) - => SharedTypeEntity( - definingEntityType.GetOwnedName(type.Type?.ShortDisplayName() ?? type.Name, definingNavigationName), - type.Type, configurationSource, shouldBeOwned: true); + if (!CanBeConfigured(type.Type, configurationType, configurationSource)) + { + return false; + } + } + + var clrType = type.Type; + EntityType? entityType; + if (type.IsNamed) + { + if (clrType != null) + { + entityType = Metadata.FindEntityType(clrType); + if (entityType != null) + { + Check.DebugAssert( + entityType.Name != type.Name || !entityType.HasSharedClrType, + "Shared type entity types shouldn't be named the same as non-shared"); + + if (!configurationSource.OverridesStrictly(entityType.GetConfigurationSource()) + && !entityType.IsOwned()) + { + return shouldThrow + ? throw new InvalidOperationException( + CoreStrings.ClashingNonSharedType(type.Name, clrType.ShortDisplayName())) + : false; + } + } + } + + entityType = Metadata.FindEntityType(type.Name); + } + else + { + clrType = type.Type!; + var sharedConfigurationSource = Metadata.FindIsSharedConfigurationSource(clrType); + if (sharedConfigurationSource != null + && !configurationSource.OverridesStrictly(sharedConfigurationSource.Value)) + { + return shouldThrow + ? throw new InvalidOperationException(CoreStrings.ClashingSharedType(clrType.ShortDisplayName())) + : false; + } + + entityType = Metadata.FindEntityType(clrType); + } + + if (shouldBeOwned == false + && clrType != null + && (!configurationSource.OverridesStrictly(Metadata.FindIsOwnedConfigurationSource(clrType)) + || (Metadata.Configuration?.GetConfigurationType(clrType) == TypeConfigurationType.OwnedEntityType + && configurationSource != ConfigurationSource.Explicit))) + { + return shouldThrow + ? throw new InvalidOperationException( + CoreStrings.ClashingOwnedEntityType(clrType == null ? type.Name : clrType.ShortDisplayName())) + : false; + } + + if (entityType != null + && type.Type != null + && entityType.ClrType != type.Type + && !configurationSource.OverridesStrictly(entityType.GetConfigurationSource())) + { + return shouldThrow + ? throw new InvalidOperationException( + CoreStrings.ClashingMismatchedSharedType(type.Name, entityType.ClrType.ShortDisplayName())) + : false; + } + + if (entityType == null + && clrType != null) + { + var complexConfigurationSource = Metadata.FindIsComplexConfigurationSource(clrType); + if (complexConfigurationSource != null + && configurationSource == ConfigurationSource.Convention) + { + return false; + } + } + + return true; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -600,6 +696,15 @@ private bool CanIgnore(in TypeIdentity type, ConfigurationSource configurationSo return this; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveEntityType(EntityType entityType, ConfigurationSource configurationSource) + => configurationSource.Overrides(entityType.GetConfigurationSource()); + /// /// 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 @@ -676,6 +781,39 @@ IConventionModel IConventionModelBuilder.Metadata get => Metadata; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionModelBuilder? IConventionModelBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionModelBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionModelBuilder? IConventionModelBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionModelBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionModelBuilder? IConventionModelBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionModelBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -762,6 +900,56 @@ IConventionModel IConventionModelBuilder.Metadata bool fromDataAnnotation) => Owned(type, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionModelBuilder.CanHaveEntity(string name, bool fromDataAnnotation) + => CanHaveEntity( + new TypeIdentity(name), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + shouldBeOwned: null); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionModelBuilder.CanHaveEntity(Type type, bool fromDataAnnotation) + => CanHaveEntity( + new TypeIdentity(type, Metadata), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + shouldBeOwned: null); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionModelBuilder.CanHaveSharedTypeEntity(string name, Type? type, bool fromDataAnnotation) + => CanHaveEntity( + new TypeIdentity(name, type ?? Model.DefaultPropertyBagType), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + shouldBeOwned: null); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionModelBuilder.CanRemoveEntity(IConventionEntityType entityType, bool fromDataAnnotation) + => CanRemoveEntityType((EntityType)entityType, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs b/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs index 766dbfe52c5..91429cf7972 100644 --- a/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalNavigationBuilder.cs @@ -9,7 +9,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 InternalNavigationBuilder : InternalPropertyBaseBuilder, IConventionNavigationBuilder +public class InternalNavigationBuilder : + InternalPropertyBaseBuilder, IConventionNavigationBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -22,6 +23,15 @@ public InternalNavigationBuilder(Navigation metadata, InternalModelBuilder 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. + /// + protected override IConventionNavigationBuilder This + => this; + /// /// 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 @@ -193,7 +203,7 @@ public virtual bool CanSetIsRequired(bool? required, ConfigurationSource configu return null; } - IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata { [DebuggerStepThrough] get => Metadata; @@ -205,23 +215,48 @@ IConventionNavigation IConventionNavigationBuilder.Metadata get => Metadata; } - /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) - => CanSetPropertyAccessMode( - propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionNavigationBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionNavigationBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionNavigationBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation) - => UsePropertyAccessMode( + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => CanSetPropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionNavigationBuilder? IConventionNavigationBuilder.UsePropertyAccessMode( + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => UsePropertyAccessMode( @@ -229,43 +264,30 @@ bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) - => CanSetField( - fieldName, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) => HasField( fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionNavigationBuilder? IConventionNavigationBuilder.HasField(string? fieldName, bool fromDataAnnotation) + IConventionNavigationBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) => HasField( - fieldName, + fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) => CanSetField( - fieldInfo, + fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// - [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField( - fieldInfo, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionNavigationBuilder? IConventionNavigationBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField( + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => CanSetField( fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder`.cs b/src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder.cs similarity index 87% rename from src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder`.cs rename to src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder.cs index f8fa5b3d1ea..050d0cafce5 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder`.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBaseBuilder.cs @@ -9,7 +9,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 InternalPropertyBaseBuilder : AnnotatableBuilder +public abstract class InternalPropertyBaseBuilder + : AnnotatableBuilder + where TBuilder : class, IConventionPropertyBaseBuilder where TPropertyBase : PropertyBase { /// @@ -29,7 +31,15 @@ public InternalPropertyBaseBuilder(TPropertyBase metadata, InternalModelBuilder /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalPropertyBaseBuilder? HasField( + protected abstract TBuilder This { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TBuilder? HasField( string? fieldName, ConfigurationSource configurationSource) { @@ -37,7 +47,7 @@ public InternalPropertyBaseBuilder(TPropertyBase metadata, InternalModelBuilder { Metadata.SetField(fieldName, configurationSource); - return this; + return This; } return null; @@ -77,7 +87,7 @@ public virtual bool CanSetField(string? fieldName, ConfigurationSource? configur /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalPropertyBaseBuilder? HasField( + public virtual TBuilder? HasField( FieldInfo? fieldInfo, ConfigurationSource configurationSource) { @@ -85,7 +95,7 @@ public virtual bool CanSetField(string? fieldName, ConfigurationSource? configur { Metadata.SetFieldInfo(fieldInfo, configurationSource); - return this; + return This; } return null; @@ -111,7 +121,7 @@ public virtual bool CanSetField(FieldInfo? fieldInfo, ConfigurationSource? confi /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalPropertyBaseBuilder? UsePropertyAccessMode( + public virtual TBuilder? UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, ConfigurationSource configurationSource) { @@ -119,7 +129,7 @@ public virtual bool CanSetField(FieldInfo? fieldInfo, ConfigurationSource? confi { Metadata.SetPropertyAccessMode(propertyAccessMode, configurationSource); - return this; + return This; } return null; diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index a6d43af00c8..ccf46dd0d86 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -6,15 +6,16 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// -/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// 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 class InternalPropertyBuilder : InternalPropertyBaseBuilder, IConventionPropertyBuilder +public class InternalPropertyBuilder + : InternalPropertyBaseBuilder, IConventionPropertyBuilder { /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -25,7 +26,15 @@ public InternalPropertyBuilder(Property property, InternalModelBuilder modelBuil } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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 IConventionPropertyBuilder This => this; + + /// + /// 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. @@ -40,7 +49,7 @@ public InternalPropertyBuilder(Property property, InternalModelBuilder modelBuil if (required == false) { - using (Metadata.DeclaringEntityType.Model.DelayConventions()) + using (Metadata.DeclaringType.Model.DelayConventions()) { foreach (var key in Metadata.GetContainingKeys().ToList()) { @@ -49,7 +58,7 @@ public InternalPropertyBuilder(Property property, InternalModelBuilder modelBuil { throw new InvalidOperationException( CoreStrings.KeyPropertyCannotBeNullable( - Metadata.Name, Metadata.DeclaringEntityType.DisplayName(), key.Properties.Format())); + Metadata.Name, Metadata.DeclaringType.DisplayName(), key.Properties.Format())); } var removed = key.DeclaringEntityType.Builder.HasNoKey(key, configurationSource); @@ -68,7 +77,7 @@ public InternalPropertyBuilder(Property property, InternalModelBuilder modelBuil } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -82,7 +91,7 @@ public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? config && Metadata.GetContainingKeys().All(k => configurationSource.Overrides(k.GetConfigurationSource())))); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -100,7 +109,7 @@ public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? config } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -110,7 +119,7 @@ public virtual bool CanSetValueGenerated(ValueGenerated? valueGenerated, Configu || Metadata.ValueGenerated == valueGenerated; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -127,7 +136,7 @@ public virtual bool CanSetValueGenerated(ValueGenerated? valueGenerated, Configu } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -137,73 +146,79 @@ public virtual bool CanSetIsConcurrencyToken(bool? concurrencyToken, Configurati || Metadata.IsConcurrencyToken == concurrencyToken; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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 new virtual InternalPropertyBuilder? HasField(string? fieldName, ConfigurationSource configurationSource) - => (InternalPropertyBuilder?)base.HasField(fieldName, configurationSource); + public virtual InternalPropertyBuilder? HasSentinel(object? sentinel, ConfigurationSource configurationSource) + { + if (CanSetSentinel(sentinel, configurationSource)) + { + Metadata.SetSentinel(sentinel, configurationSource); + + return this; + } + + return null; + } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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 new virtual InternalPropertyBuilder? HasField(FieldInfo? fieldInfo, ConfigurationSource configurationSource) - => (InternalPropertyBuilder?)base.HasField(fieldInfo, configurationSource); + public virtual bool CanSetSentinel(object? sentinel, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetSentinelConfigurationSource()) + || Equals(Metadata.Sentinel, sentinel); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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 new virtual InternalPropertyBuilder? UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - ConfigurationSource configurationSource) - => (InternalPropertyBuilder?)base.UsePropertyAccessMode(propertyAccessMode, configurationSource); + public new virtual InternalPropertyBuilder? HasField(string? fieldName, ConfigurationSource configurationSource) + => base.HasField(fieldName, configurationSource) == null + ? null + : this; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalPropertyBuilder? HasMaxLength(int? maxLength, ConfigurationSource configurationSource) - { - if (CanSetMaxLength(maxLength, configurationSource)) - { - Metadata.SetMaxLength(maxLength, configurationSource); - - return this; - } - - return null; - } + public new virtual InternalPropertyBuilder? HasField(FieldInfo? fieldInfo, ConfigurationSource configurationSource) + => base.HasField(fieldInfo, configurationSource) == null + ? null + : this; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool CanSetMaxLength(int? maxLength, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetMaxLengthConfigurationSource()) - || Metadata.GetMaxLength() == maxLength; + public new virtual InternalPropertyBuilder? UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + ConfigurationSource configurationSource) + => base.UsePropertyAccessMode(propertyAccessMode, configurationSource) == null + ? null + : this; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalPropertyBuilder? HasSentinel(object? sentinel, ConfigurationSource configurationSource) + public virtual InternalPropertyBuilder? HasMaxLength(int? maxLength, ConfigurationSource configurationSource) { - if (CanSetSentinel(sentinel, configurationSource)) + if (CanSetMaxLength(maxLength, configurationSource)) { - Metadata.SetSentinel(sentinel, configurationSource); + Metadata.SetMaxLength(maxLength, configurationSource); return this; } @@ -212,17 +227,17 @@ public virtual bool CanSetMaxLength(int? maxLength, ConfigurationSource? configu } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool CanSetSentinel(object? sentinel, ConfigurationSource? configurationSource) - => configurationSource.Overrides(Metadata.GetSentinelConfigurationSource()) - || Equals(Metadata.Sentinel, sentinel); + public virtual bool CanSetMaxLength(int? maxLength, ConfigurationSource? configurationSource) + => configurationSource.Overrides(Metadata.GetMaxLengthConfigurationSource()) + || Metadata.GetMaxLength() == maxLength; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -240,7 +255,7 @@ public virtual bool CanSetSentinel(object? sentinel, ConfigurationSource? config } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -250,7 +265,7 @@ public virtual bool CanSetPrecision(int? precision, ConfigurationSource? configu || Metadata.GetPrecision() == precision; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -268,7 +283,7 @@ public virtual bool CanSetPrecision(int? precision, ConfigurationSource? configu } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -278,7 +293,7 @@ public virtual bool CanSetScale(int? scale, ConfigurationSource? configurationSo || Metadata.GetScale() == scale; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -296,7 +311,7 @@ public virtual bool CanSetScale(int? scale, ConfigurationSource? configurationSo } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -306,7 +321,7 @@ public virtual bool CanSetIsUnicode(bool? unicode, ConfigurationSource? configur || Metadata.IsUnicode() == unicode; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -324,7 +339,7 @@ public virtual bool CanSetIsUnicode(bool? unicode, ConfigurationSource? configur } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -334,7 +349,7 @@ public virtual bool CanSetBeforeSave(PropertySaveBehavior? behavior, Configurati || Metadata.GetBeforeSaveBehavior() == behavior; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -352,7 +367,7 @@ public virtual bool CanSetBeforeSave(PropertySaveBehavior? behavior, Configurati } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -364,7 +379,7 @@ public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, Configuratio || Metadata.GetAfterSaveBehavior() == behavior; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -375,7 +390,7 @@ public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, Configuratio { if (valueGeneratorType == null) { - return HasValueGenerator((Func?)null, configurationSource); + return HasValueGenerator((Func?)null, configurationSource); } if (!typeof(ValueGenerator).IsAssignableFrom(valueGeneratorType)) @@ -402,13 +417,13 @@ public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, Configuratio } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual InternalPropertyBuilder? HasValueGenerator( - Func? factory, + Func? factory, ConfigurationSource configurationSource) { if (CanSetValueGenerator(factory, configurationSource)) @@ -422,7 +437,7 @@ public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, Configuratio } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -442,7 +457,7 @@ public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, Configuratio } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -455,7 +470,7 @@ public virtual bool CanSetValueGenerator( && (Func?)Metadata[CoreAnnotationNames.ValueGeneratorFactory] == factory); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -468,7 +483,7 @@ public virtual bool CanSetValueGeneratorFactory( && (Type?)Metadata[CoreAnnotationNames.ValueGeneratorFactoryType] == factory); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -487,7 +502,7 @@ public virtual bool CanSetValueGeneratorFactory( } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -503,7 +518,7 @@ public virtual bool CanSetConversion( && configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource()); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -522,7 +537,7 @@ public virtual bool CanSetConversion( } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -533,7 +548,7 @@ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? && configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -554,7 +569,7 @@ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -567,7 +582,7 @@ public virtual bool CanSetConverter( && (Type?)Metadata[CoreAnnotationNames.ValueConverterType] == converterType); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -587,7 +602,7 @@ public virtual bool CanSetConverter( } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -597,7 +612,7 @@ public virtual bool CanSetTypeMapping(CoreTypeMapping? typeMapping, Configuratio || Metadata.TypeMapping == typeMapping; /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -617,7 +632,7 @@ public virtual bool CanSetTypeMapping(CoreTypeMapping? typeMapping, Configuratio } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -645,7 +660,7 @@ public virtual bool CanSetValueComparer(ValueComparer? comparer, ConfigurationSo } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -665,7 +680,7 @@ public virtual bool CanSetValueComparer(ValueComparer? comparer, ConfigurationSo } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -676,7 +691,7 @@ public virtual bool CanSetValueComparer(Type? comparerType, ConfigurationSource? && (Type?)Metadata[CoreAnnotationNames.ValueComparerType] == comparerType); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -696,7 +711,7 @@ public virtual bool CanSetValueComparer(Type? comparerType, ConfigurationSource? } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -713,7 +728,7 @@ public virtual bool CanSetProviderValueComparer(ValueComparer? comparer, Configu } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -733,7 +748,7 @@ public virtual bool CanSetProviderValueComparer(ValueComparer? comparer, Configu } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -745,15 +760,16 @@ public virtual bool CanSetProviderValueComparer( || (Metadata[CoreAnnotationNames.ProviderValueComparer] == null && (Type?)Metadata[CoreAnnotationNames.ProviderValueComparerType] == comparerType); + /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalPropertyBuilder? Attach(InternalEntityTypeBuilder entityTypeBuilder) + public virtual InternalPropertyBuilder? Attach(InternalTypeBaseBuilder typeBaseBuilder) { - var newProperty = entityTypeBuilder.Metadata.FindProperty(Metadata.Name); + var newProperty = typeBaseBuilder.Metadata.FindProperty(Metadata.Name); InternalPropertyBuilder? newPropertyBuilder; var configurationSource = Metadata.GetConfigurationSource(); var typeConfigurationSource = Metadata.GetTypeConfigurationSource(); @@ -761,7 +777,8 @@ public virtual bool CanSetProviderValueComparer( && (newProperty.GetConfigurationSource().Overrides(configurationSource) || newProperty.GetTypeConfigurationSource().Overrides(typeConfigurationSource) || (Metadata.ClrType == newProperty.ClrType - && Metadata.GetIdentifyingMemberInfo()?.Name == newProperty.GetIdentifyingMemberInfo()?.Name))) + && Metadata.Name == newProperty.Name + && Metadata.GetIdentifyingMemberInfo() == newProperty.GetIdentifyingMemberInfo()))) { newPropertyBuilder = newProperty.Builder; newProperty.UpdateConfigurationSource(configurationSource); @@ -775,11 +792,11 @@ public virtual bool CanSetProviderValueComparer( var identifyingMemberInfo = Metadata.GetIdentifyingMemberInfo(); newPropertyBuilder = Metadata.IsIndexerProperty() - ? entityTypeBuilder.IndexerProperty(Metadata.ClrType, Metadata.Name, configurationSource) + ? typeBaseBuilder.IndexerProperty(Metadata.ClrType, Metadata.Name, configurationSource) : identifyingMemberInfo == null - ? entityTypeBuilder.Property( + ? typeBaseBuilder.Property( Metadata.ClrType, Metadata.Name, Metadata.GetTypeConfigurationSource(), configurationSource) - : entityTypeBuilder.Property(identifyingMemberInfo, configurationSource); + : typeBaseBuilder.Property(identifyingMemberInfo, configurationSource); if (newPropertyBuilder is null) { @@ -855,19 +872,19 @@ public virtual bool CanSetProviderValueComparer( } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. /// - IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata { [DebuggerStepThrough] get => Metadata; } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -879,7 +896,43 @@ IConventionProperty IConventionPropertyBuilder.Metadata } /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null ? null : this; + + /// + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null ? null : this; + + /// + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + == null ? null : this; + + /// + /// 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. @@ -888,7 +941,7 @@ IConventionProperty IConventionPropertyBuilder.Metadata => IsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -897,7 +950,7 @@ bool IConventionPropertyBuilder.CanSetIsRequired(bool? required, bool fromDataAn => CanSetIsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -906,7 +959,7 @@ bool IConventionPropertyBuilder.CanSetIsRequired(bool? required, bool fromDataAn => ValueGenerated(valueGenerated, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -916,7 +969,7 @@ bool IConventionPropertyBuilder.CanSetValueGenerated(ValueGenerated? valueGenera valueGenerated, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -926,7 +979,7 @@ bool IConventionPropertyBuilder.CanSetValueGenerated(ValueGenerated? valueGenera concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -936,95 +989,83 @@ bool IConventionPropertyBuilder.CanSetIsConcurrencyToken(bool? concurrencyToken, concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) - => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionPropertyBuilder? IConventionPropertyBuilder.HasSentinel(object? sentinel, bool fromDataAnnotation) + => HasSentinel(sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasField(string? fieldName, bool fromDataAnnotation) - => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + bool IConventionPropertyBuilder.CanSetSentinel(object? sentinel, bool fromDataAnnotation) + => CanSetSentinel(sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) + => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) + IConventionPropertyBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( + IConventionPropertyBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => UsePropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IConventionPropertyBuilder? IConventionPropertyBuilder.UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation) - => UsePropertyAccessMode( - propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1033,7 +1074,7 @@ bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? => HasMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1042,25 +1083,7 @@ bool IConventionPropertyBuilder.CanSetMaxLength(int? maxLength, bool fromDataAnn => CanSetMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IConventionPropertyBuilder? IConventionPropertyBuilder.HasSentinel(object? sentinel, bool fromDataAnnotation) - => HasSentinel(sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool IConventionPropertyBuilder.CanSetSentinel(object? sentinel, bool fromDataAnnotation) - => CanSetSentinel(sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1069,7 +1092,7 @@ bool IConventionPropertyBuilder.CanSetSentinel(object? sentinel, bool fromDataAn => IsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1078,7 +1101,7 @@ bool IConventionPropertyBuilder.CanSetIsUnicode(bool? unicode, bool fromDataAnno => CanSetIsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1087,7 +1110,7 @@ bool IConventionPropertyBuilder.CanSetIsUnicode(bool? unicode, bool fromDataAnno => HasPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1096,7 +1119,7 @@ bool IConventionPropertyBuilder.CanSetPrecision(int? precision, bool fromDataAnn => CanSetPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1105,7 +1128,7 @@ bool IConventionPropertyBuilder.CanSetPrecision(int? precision, bool fromDataAnn => HasScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1114,7 +1137,7 @@ bool IConventionPropertyBuilder.CanSetScale(int? scale, bool fromDataAnnotation) => CanSetScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1123,7 +1146,7 @@ bool IConventionPropertyBuilder.CanSetScale(int? scale, bool fromDataAnnotation) => BeforeSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1132,7 +1155,7 @@ bool IConventionPropertyBuilder.CanSetBeforeSave(PropertySaveBehavior? behavior, => CanSetBeforeSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1141,7 +1164,7 @@ bool IConventionPropertyBuilder.CanSetBeforeSave(PropertySaveBehavior? behavior, => AfterSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1150,7 +1173,7 @@ bool IConventionPropertyBuilder.CanSetAfterSave(PropertySaveBehavior? behavior, => CanSetAfterSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1162,27 +1185,27 @@ bool IConventionPropertyBuilder.CanSetAfterSave(PropertySaveBehavior? behavior, valueGeneratorType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// IConventionPropertyBuilder? IConventionPropertyBuilder.HasValueGenerator( - Func? factory, + Func? factory, bool fromDataAnnotation) => HasValueGenerator(factory, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBuilder.CanSetValueGenerator(Func? factory, bool fromDataAnnotation) + bool IConventionPropertyBuilder.CanSetValueGenerator(Func? factory, bool fromDataAnnotation) => CanSetValueGenerator(factory, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1195,7 +1218,7 @@ bool IConventionPropertyBuilder.CanSetValueGenerator(Func - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1208,7 +1231,7 @@ bool IConventionPropertyBuilder.CanSetValueGeneratorFactory( fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1217,7 +1240,7 @@ bool IConventionPropertyBuilder.CanSetValueGeneratorFactory( => HasConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1226,7 +1249,7 @@ bool IConventionPropertyBuilder.CanSetConversion(ValueConverter? converter, bool => CanSetConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1237,7 +1260,7 @@ bool IConventionPropertyBuilder.CanSetConversion(ValueConverter? converter, bool => HasConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1248,7 +1271,7 @@ bool IConventionPropertyBuilder.CanSetConverter( => CanSetConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1257,7 +1280,7 @@ bool IConventionPropertyBuilder.CanSetConverter( => HasConversion(providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1274,7 +1297,7 @@ bool IConventionPropertyBuilder.CanSetTypeMapping(CoreTypeMapping typeMapping, b => CanSetTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1283,7 +1306,7 @@ bool IConventionPropertyBuilder.CanSetTypeMapping(CoreTypeMapping typeMapping, b => HasValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1292,7 +1315,7 @@ bool IConventionPropertyBuilder.CanSetValueComparer(ValueComparer? comparer, boo => CanSetValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1303,7 +1326,7 @@ bool IConventionPropertyBuilder.CanSetValueComparer(ValueComparer? comparer, boo => HasValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1314,7 +1337,7 @@ bool IConventionPropertyBuilder.CanSetValueComparer( => CanSetValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1323,7 +1346,7 @@ bool IConventionPropertyBuilder.CanSetValueComparer( => HasProviderValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1332,7 +1355,7 @@ bool IConventionPropertyBuilder.CanSetProviderValueComparer(ValueComparer? compa => CanSetProviderValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1343,7 +1366,7 @@ bool IConventionPropertyBuilder.CanSetProviderValueComparer(ValueComparer? compa => HasProviderValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. diff --git a/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs index 82c4be9d829..3cbab4a664c 100644 --- a/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalServicePropertyBuilder.cs @@ -9,7 +9,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 InternalServicePropertyBuilder : InternalPropertyBaseBuilder, IConventionServicePropertyBuilder +public class InternalServicePropertyBuilder : + InternalPropertyBaseBuilder, IConventionServicePropertyBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -22,6 +23,15 @@ public InternalServicePropertyBuilder(ServiceProperty property, InternalModelBui { } + /// + /// 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 IConventionServicePropertyBuilder This + => this; + /// /// 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 @@ -130,8 +140,11 @@ public virtual bool CanSetParameterBinding( /// 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. /// - IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata - => Metadata; + IConventionServiceProperty IConventionServicePropertyBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -139,8 +152,11 @@ IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata /// 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. /// - IConventionServiceProperty IConventionServicePropertyBuilder.Metadata - => Metadata; + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -148,8 +164,10 @@ IConventionServiceProperty IConventionServicePropertyBuilder.Metadata /// 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. /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) - => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + [DebuggerStepThrough] + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionServicePropertyBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -157,8 +175,10 @@ IConventionServiceProperty IConventionServicePropertyBuilder.Metadata /// 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. /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + [DebuggerStepThrough] + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionServicePropertyBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -166,8 +186,10 @@ IConventionServiceProperty IConventionServicePropertyBuilder.Metadata /// 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. /// - IConventionServicePropertyBuilder? IConventionServicePropertyBuilder.HasField(string? fieldName, bool fromDataAnnotation) - => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + [DebuggerStepThrough] + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionServicePropertyBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -175,8 +197,8 @@ IConventionServiceProperty IConventionServicePropertyBuilder.Metadata /// 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. /// - IConventionServicePropertyBuilder? IConventionServicePropertyBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) + => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -184,8 +206,8 @@ IConventionServiceProperty IConventionServicePropertyBuilder.Metadata /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) - => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -193,8 +215,8 @@ bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromData /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) + => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -202,11 +224,8 @@ bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromD /// 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. /// - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation) - => UsePropertyAccessMode( - propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -214,7 +233,7 @@ bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromD /// 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. /// - IConventionServicePropertyBuilder? IConventionServicePropertyBuilder.UsePropertyAccessMode( + IConventionServicePropertyBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => UsePropertyAccessMode( @@ -226,7 +245,7 @@ bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromD /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); diff --git a/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs b/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs index d0b13f59883..ca06fbeb94f 100644 --- a/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalSkipNavigationBuilder.cs @@ -9,7 +9,8 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 InternalSkipNavigationBuilder : InternalPropertyBaseBuilder, IConventionSkipNavigationBuilder +public class InternalSkipNavigationBuilder : + InternalPropertyBaseBuilder, IConventionSkipNavigationBuilder { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -28,28 +29,8 @@ public InternalSkipNavigationBuilder(SkipNavigation metadata, InternalModelBuild /// 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 new virtual InternalSkipNavigationBuilder? HasField(string? fieldName, ConfigurationSource configurationSource) - => (InternalSkipNavigationBuilder?)base.HasField(fieldName, configurationSource); - - /// - /// 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 new virtual InternalSkipNavigationBuilder? HasField(FieldInfo? fieldInfo, ConfigurationSource configurationSource) - => (InternalSkipNavigationBuilder?)base.HasField(fieldInfo, configurationSource); - - /// - /// 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 new virtual InternalSkipNavigationBuilder? UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - ConfigurationSource configurationSource) - => (InternalSkipNavigationBuilder?)base.UsePropertyAccessMode(propertyAccessMode, configurationSource); + protected override InternalSkipNavigationBuilder This + => this; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -227,6 +208,7 @@ public virtual bool CanSetInverse( var newSkipNavigationBuilder = entityTypeBuilder.HasSkipNavigation( Metadata.CreateMemberIdentity(), targetEntityType, + Metadata.ClrType, Metadata.GetConfigurationSource(), Metadata.IsCollection, Metadata.IsOnDependent); @@ -376,7 +358,7 @@ public virtual bool CanSetLazyLoadingEnabled(bool? lazyLoadingEnabled, Configura return null; } - IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata + IConventionPropertyBase IConventionPropertyBaseBuilder.Metadata { [DebuggerStepThrough] get => Metadata; @@ -388,60 +370,70 @@ IConventionSkipNavigation IConventionSkipNavigationBuilder.Metadata get => Metadata; } - /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) - => HasField( - fieldName, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionSkipNavigationBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) - => HasField( - fieldInfo, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionSkipNavigationBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionSkipNavigationBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionSkipNavigationBuilder? IConventionSkipNavigationBuilder.HasField(string? fieldName, bool fromDataAnnotation) + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.HasField(string? fieldName, bool fromDataAnnotation) => HasField( fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionSkipNavigationBuilder? IConventionSkipNavigationBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.HasField(FieldInfo? fieldInfo, bool fromDataAnnotation) => HasField( fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(string? fieldName, bool fromDataAnnotation) => CanSetField( fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) + bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromDataAnnotation) => CanSetField( fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// [DebuggerStepThrough] - IConventionPropertyBaseBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - bool fromDataAnnotation) - => UsePropertyAccessMode( - propertyAccessMode, - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - - /// - [DebuggerStepThrough] - IConventionSkipNavigationBuilder? IConventionSkipNavigationBuilder.UsePropertyAccessMode( + IConventionSkipNavigationBuilder? IConventionPropertyBaseBuilder.UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => UsePropertyAccessMode( @@ -450,7 +442,7 @@ bool IConventionPropertyBaseBuilder.CanSetField(FieldInfo? fieldInfo, bool fromD /// [DebuggerStepThrough] - bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode( + bool IConventionPropertyBaseBuilder.CanSetPropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) => CanSetPropertyAccessMode( diff --git a/src/EFCore/Metadata/Internal/InternalTriggerBuilder.cs b/src/EFCore/Metadata/Internal/InternalTriggerBuilder.cs index 0ed8fe691a0..6446a9a7bdc 100644 --- a/src/EFCore/Metadata/Internal/InternalTriggerBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalTriggerBuilder.cs @@ -45,4 +45,22 @@ IConventionTrigger IConventionTriggerBuilder.Metadata [DebuggerStepThrough] get => Metadata; } + + /// + [DebuggerStepThrough] + IConventionTriggerBuilder? IConventionTriggerBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionTriggerBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionTriggerBuilder? IConventionTriggerBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionTriggerBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + [DebuggerStepThrough] + IConventionTriggerBuilder? IConventionTriggerBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionTriggerBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs new file mode 100644 index 00000000000..7b7e2b9c75d --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs @@ -0,0 +1,1629 @@ +// 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.Globalization; + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public abstract class InternalTypeBaseBuilder : AnnotatableBuilder, + IConventionTypeBaseBuilder +{ + /// + /// 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 InternalTypeBaseBuilder(TypeBase metadata, InternalModelBuilder modelBuilder) + : base(metadata, modelBuilder) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool IsCompatible(MemberInfo? newMemberInfo, PropertyBase existingProperty) + { + if (newMemberInfo == null) + { + return true; + } + + var existingMemberInfo = existingProperty.GetIdentifyingMemberInfo(); + if (existingMemberInfo == null) + { + return newMemberInfo == existingProperty.DeclaringType.FindIndexerPropertyInfo(); + } + + if (newMemberInfo == existingMemberInfo) + { + return true; + } + + if (!newMemberInfo.DeclaringType!.IsAssignableFrom(existingProperty.DeclaringType.ClrType)) + { + return existingMemberInfo.IsOverriddenBy(newMemberInfo); + } + + IMutableEntityType? existingMemberDeclaringEntityType = null; + var declaringType = existingProperty.DeclaringType as IMutableEntityType; + if (declaringType != null) + { + foreach (var baseType in declaringType.GetAllBaseTypes()) + { + if (newMemberInfo.DeclaringType == baseType.ClrType) + { + return existingMemberDeclaringEntityType != null + && existingMemberInfo.IsOverriddenBy(newMemberInfo); + } + + if (existingMemberDeclaringEntityType == null + && existingMemberInfo.DeclaringType == baseType.ClrType) + { + existingMemberDeclaringEntityType = baseType; + } + } + } + + // newMemberInfo is declared on an unmapped base type, existingMemberInfo should be kept + return newMemberInfo.IsOverriddenBy(existingMemberInfo); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalPropertyBuilder? Property( + Type? propertyType, + string propertyName, + ConfigurationSource? configurationSource) + => Property(propertyType, propertyName, typeConfigurationSource: configurationSource, configurationSource: configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalPropertyBuilder? Property( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + ConfigurationSource? typeConfigurationSource, + ConfigurationSource? configurationSource) + => Property( + propertyType, propertyName, memberInfo: null, + typeConfigurationSource, + configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalPropertyBuilder? Property(string propertyName, ConfigurationSource? configurationSource) + => Property(propertyType: null, propertyName, memberInfo: null, typeConfigurationSource: null, configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalPropertyBuilder? Property(MemberInfo memberInfo, ConfigurationSource? configurationSource) + => Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), memberInfo, configurationSource, configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalPropertyBuilder? IndexerProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, + string propertyName, + ConfigurationSource? configurationSource) + { + var indexerPropertyInfo = Metadata.FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(propertyName, Metadata.DisplayName(), typeof(string).ShortDisplayName())); + } + + return Property(propertyType, propertyName, indexerPropertyInfo, configurationSource, configurationSource); + } + + /// + /// 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 InternalPropertyBuilder? Property( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + MemberInfo? memberInfo, + ConfigurationSource? typeConfigurationSource, + ConfigurationSource? configurationSource) + { + var entityType = Metadata; + List? propertiesToDetach = null; + var existingProperty = entityType.FindProperty(propertyName); + if (existingProperty != null) + { + if (existingProperty.DeclaringType != Metadata) + { + if (!IsIgnored(propertyName, configurationSource)) + { + Metadata.RemoveIgnored(propertyName); + } + + entityType = (EntityType)existingProperty.DeclaringType; + } + + if (IsCompatible(memberInfo, existingProperty) + && (propertyType == null || propertyType == existingProperty.ClrType)) + { + if (configurationSource.HasValue) + { + existingProperty.UpdateConfigurationSource(configurationSource.Value); + } + + if (propertyType != null + && typeConfigurationSource.HasValue) + { + existingProperty.UpdateTypeConfigurationSource(typeConfigurationSource.Value); + } + + return existingProperty.Builder; + } + + if (memberInfo == null + || (memberInfo is PropertyInfo propertyInfo && propertyInfo.IsIndexerProperty())) + { + if (existingProperty.GetTypeConfigurationSource() is ConfigurationSource existingTypeConfigurationSource + && !typeConfigurationSource.Overrides(existingTypeConfigurationSource)) + { + return null; + } + + memberInfo ??= existingProperty.PropertyInfo ?? (MemberInfo?)existingProperty.FieldInfo; + } + else if (!configurationSource.Overrides(existingProperty.GetConfigurationSource())) + { + return null; + } + + propertyType ??= existingProperty.ClrType; + + propertiesToDetach = new List { existingProperty }; + } + else + { + if (configurationSource != ConfigurationSource.Explicit + && (!configurationSource.HasValue + || !CanAddProperty(propertyType ?? memberInfo?.GetMemberType(), propertyName, configurationSource.Value))) + { + return null; + } + + memberInfo ??= Metadata.IsPropertyBag + ? null + : Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault(); + + if (propertyType == null) + { + if (memberInfo == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(propertyName, Metadata.DisplayName())); + } + + propertyType = memberInfo.GetMemberType(); + typeConfigurationSource = ConfigurationSource.Explicit; + } + + foreach (var derivedType in Metadata.GetDerivedTypes()) + { + var derivedProperty = derivedType.FindDeclaredProperty(propertyName); + if (derivedProperty != null) + { + propertiesToDetach ??= new List(); + + propertiesToDetach.Add(derivedProperty); + } + } + } + + Check.DebugAssert(configurationSource is not null, "configurationSource is null"); + + InternalPropertyBuilder builder; + using (Metadata.Model.DelayConventions()) + { + var detachedProperties = propertiesToDetach == null ? null : DetachProperties(propertiesToDetach); + + if (existingProperty == null) + { + Metadata.RemoveIgnored(propertyName); + + RemoveMembersInHierarchy(propertyName, configurationSource.Value); + } + + builder = entityType.AddProperty( + propertyName, propertyType, memberInfo, typeConfigurationSource, configurationSource.Value)!.Builder; + + detachedProperties?.Attach(this); + } + + return builder.Metadata.IsInModel + ? builder + : Metadata.FindProperty(propertyName)?.Builder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalPropertyBuilder? CreateUniqueProperty( + Type propertyType, + string propertyName, + bool required) + => CreateUniqueProperties( + new[] { propertyType }, + new[] { propertyName }, + required)?.First().Builder; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList? CreateUniqueProperties( + IReadOnlyList propertyTypes, + IReadOnlyList propertyNames, + bool isRequired) + => TryCreateUniqueProperties( + propertyNames.Count, + null, + propertyTypes, + propertyNames, + isRequired, + "").Item2; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList? CreateUniqueProperties( + IReadOnlyList principalProperties, + bool isRequired, + string baseName) + => TryCreateUniqueProperties( + principalProperties.Count, + null, + principalProperties.Select(p => p.ClrType), + principalProperties.Select(p => p.Name), + isRequired, + baseName).Item2; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual (bool, IReadOnlyList?) TryCreateUniqueProperties( + int propertyCount, + IReadOnlyList? currentProperties, + IEnumerable principalPropertyTypes, + IEnumerable principalPropertyNames, + bool isRequired, + string baseName) + { + var newProperties = currentProperties == null ? new Property[propertyCount] : null; + var clrProperties = Metadata.GetRuntimeProperties(); + var clrFields = Metadata.GetRuntimeFields(); + var canReuniquify = false; + using var principalPropertyNamesEnumerator = principalPropertyNames.GetEnumerator(); + using var principalPropertyTypesEnumerator = principalPropertyTypes.GetEnumerator(); + for (var i = 0; + i < propertyCount + && principalPropertyNamesEnumerator.MoveNext() + && principalPropertyTypesEnumerator.MoveNext(); + i++) + { + var keyPropertyName = principalPropertyNamesEnumerator.Current; + var keyPropertyType = principalPropertyTypesEnumerator.Current; + + var keyModifiedBaseName = keyPropertyName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase) + ? keyPropertyName + : baseName + keyPropertyName; + string propertyName; + var clrType = keyPropertyType.MakeNullable(!isRequired); + var index = -1; + while (true) + { + propertyName = keyModifiedBaseName + (++index > 0 ? index.ToString(CultureInfo.InvariantCulture) : ""); + if (!Metadata.FindPropertiesInHierarchy(propertyName).Any() + && !clrProperties.ContainsKey(propertyName) + && !clrFields.ContainsKey(propertyName) + && !IsIgnored(propertyName, ConfigurationSource.Convention)) + { + if (currentProperties == null) + { + var propertyBuilder = Property( + clrType, propertyName, typeConfigurationSource: null, + configurationSource: ConfigurationSource.Convention); + + if (propertyBuilder == null) + { + return (false, null); + } + + if (index > 0) + { + propertyBuilder.HasAnnotation( + CoreAnnotationNames.PreUniquificationName, + keyModifiedBaseName, + ConfigurationSource.Convention); + } + + if (clrType.IsNullableType()) + { + propertyBuilder.IsRequired(isRequired, ConfigurationSource.Convention); + } + + newProperties![i] = propertyBuilder.Metadata; + } + else if (Metadata.Model.Builder.CanBeConfigured( + clrType, TypeConfigurationType.Property, ConfigurationSource.Convention)) + { + canReuniquify = true; + } + + break; + } + + var currentProperty = currentProperties?.SingleOrDefault(p => p.Name == propertyName); + if (currentProperty != null) + { + if (((IConventionProperty)currentProperty).IsImplicitlyCreated() + && currentProperty.ClrType != clrType + && isRequired) + { + canReuniquify = true; + } + + break; + } + } + } + + return (canReuniquify, newProperties); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList? GetOrCreateProperties( + IReadOnlyList? propertyNames, + ConfigurationSource? configurationSource, + IReadOnlyList? referencedProperties = null, + bool required = false, + bool useDefaultType = false) + { + if (propertyNames == null) + { + return null; + } + + if (referencedProperties != null + && referencedProperties.Count != propertyNames.Count) + { + referencedProperties = null; + } + + var propertyList = new List(); + for (var i = 0; i < propertyNames.Count; i++) + { + var propertyName = propertyNames[i]; + var property = Metadata.FindProperty(propertyName); + if (property == null) + { + var type = referencedProperties == null + ? useDefaultType + ? typeof(int) + : null + : referencedProperties[i].ClrType; + + if (!configurationSource.HasValue) + { + return null; + } + + var propertyBuilder = Property( + required + ? type + : type?.MakeNullable(), + propertyName, + typeConfigurationSource: null, + configurationSource.Value); + + if (propertyBuilder == null) + { + return null; + } + + property = propertyBuilder.Metadata; + } + else if (configurationSource.HasValue) + { + if (ConfigurationSource.Convention.Overrides(property.GetTypeConfigurationSource()) + && (property.IsShadowProperty() || property.IsIndexerProperty()) + && (!property.IsNullable || (required && property.GetIsNullableConfigurationSource() == null)) + && property.ClrType.IsNullableType()) + { + property = property.DeclaringType.Builder.Property( + property.ClrType.MakeNullable(false), + property.Name, + configurationSource.Value)! + .Metadata; + } + else + { + property = property.DeclaringType.Builder.Property(property.Name, configurationSource.Value)!.Metadata; + } + } + + propertyList.Add(property); + } + + return propertyList; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList? GetOrCreateProperties( + IEnumerable? clrMembers, + ConfigurationSource? configurationSource) + { + if (clrMembers == null) + { + return null; + } + + var list = new List(); + foreach (var propertyInfo in clrMembers) + { + var propertyBuilder = Property(propertyInfo, configurationSource); + if (propertyBuilder == null) + { + return null; + } + + list.Add(propertyBuilder.Metadata); + } + + return list; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyList? GetActualProperties( + IReadOnlyList? properties, + ConfigurationSource? configurationSource) + { + if (properties == null) + { + return null; + } + + var actualProperties = new Property[properties.Count]; + for (var i = 0; i < actualProperties.Length; i++) + { + var property = properties[i]; + var typeConfigurationSource = property.GetTypeConfigurationSource(); + var builder = property.IsInModel && property.DeclaringType.IsAssignableFrom(Metadata) + ? property.Builder + : Property( + typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? property.ClrType : null, + property.Name, + property.GetIdentifyingMemberInfo(), + typeConfigurationSource.Overrides(ConfigurationSource.DataAnnotation) ? typeConfigurationSource : null, + configurationSource); + if (builder == null) + { + return null; + } + + actualProperties[i] = builder.Metadata; + } + + return actualProperties; + } + + /// + /// 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 PropertiesSnapshot? DetachProperties(IReadOnlyList propertiesToDetach) + { + if (propertiesToDetach.Count == 0) + { + return null; + } + + List? detachedRelationships = null; + foreach (var propertyToDetach in propertiesToDetach) + { + foreach (var relationship in propertyToDetach.GetContainingForeignKeys().ToList()) + { + detachedRelationships ??= new List(); + + detachedRelationships.Add(InternalEntityTypeBuilder.DetachRelationship(relationship)); + } + } + + var detachedIndexes = InternalEntityTypeBuilder.DetachIndexes(propertiesToDetach.SelectMany(p => p.GetContainingIndexes()).Distinct()); + + var keysToDetach = propertiesToDetach.SelectMany(p => p.GetContainingKeys()).Distinct().ToList(); + foreach (var key in keysToDetach) + { + foreach (var referencingForeignKey in key.GetReferencingForeignKeys().ToList()) + { + detachedRelationships ??= new List(); + + detachedRelationships.Add(InternalEntityTypeBuilder.DetachRelationship(referencingForeignKey)); + } + } + + var detachedKeys = InternalEntityTypeBuilder.DetachKeys(keysToDetach); + + var detachedProperties = new List(); + foreach (var propertyToDetach in propertiesToDetach) + { + var property = propertyToDetach.DeclaringType.FindDeclaredProperty(propertyToDetach.Name); + if (property != null) + { + var propertyBuilder = property.Builder; + // Reset convention configuration + propertyBuilder.ValueGenerated(null, ConfigurationSource.Convention); + propertyBuilder.AfterSave(null, ConfigurationSource.Convention); + propertyBuilder.BeforeSave(null, ConfigurationSource.Convention); + ConfigurationSource? removedConfigurationSource; + if (property.DeclaringType.IsInModel) + { + removedConfigurationSource = property.DeclaringType.Builder + .RemoveProperty(property, property.GetConfigurationSource()); + } + else + { + removedConfigurationSource = property.GetConfigurationSource(); + property.DeclaringType.RemoveProperty(property.Name); + } + + Check.DebugAssert(removedConfigurationSource.HasValue, "removedConfigurationSource.HasValue is false"); + detachedProperties.Add(propertyBuilder); + } + } + + return new PropertiesSnapshot(detachedProperties, detachedIndexes, detachedKeys, detachedRelationships); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void RemoveMembersInHierarchy(string propertyName, ConfigurationSource configurationSource) + { + foreach (var conflictingProperty in Metadata.FindPropertiesInHierarchy(propertyName)) + { + if (conflictingProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + conflictingProperty.DeclaringType.RemoveProperty(conflictingProperty); + } + } + + foreach (var conflictingComplexProperty in Metadata.FindComplexPropertiesInHierarchy(propertyName)) + { + if (conflictingComplexProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + conflictingComplexProperty.DeclaringType.RemoveComplexProperty(conflictingComplexProperty); + } + } + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanHaveProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + MemberInfo? memberInfo, + ConfigurationSource? typeConfigurationSource, + ConfigurationSource? configurationSource, + bool checkClrProperty = false) + { + var existingProperty = Metadata.FindProperty(propertyName); + return existingProperty != null + ? (IsCompatible(memberInfo, existingProperty) + && (propertyType == null || propertyType == existingProperty.ClrType)) + || ((memberInfo == null + || (memberInfo is PropertyInfo propertyInfo && propertyInfo.IsIndexerProperty())) + && (existingProperty.GetTypeConfigurationSource() is not ConfigurationSource existingTypeConfigurationSource + || typeConfigurationSource.Overrides(existingTypeConfigurationSource))) + || configurationSource.Overrides(existingProperty.GetConfigurationSource()) + : configurationSource.HasValue + && CanAddProperty(propertyType ?? memberInfo?.GetMemberType(), propertyName, configurationSource.Value, checkClrProperty); + } + + /// + /// 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 abstract bool CanAddProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + ConfigurationSource configurationSource, + bool checkClrProperty = false); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveProperty( + Property property, + ConfigurationSource configurationSource, + bool canOverrideSameSource = true) + { + Check.NotNull(property, nameof(property)); + Check.DebugAssert(property.DeclaringType == Metadata, "property.DeclaringEntityType != Metadata"); + + var currentConfigurationSource = property.GetConfigurationSource(); + return configurationSource.Overrides(currentConfigurationSource) + && (canOverrideSameSource || (configurationSource != currentConfigurationSource)); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? RemoveProperty( + Property property, + ConfigurationSource configurationSource, + bool canOverrideSameSource = true) + { + var currentConfigurationSource = property.GetConfigurationSource(); + if (!configurationSource.Overrides(currentConfigurationSource) + || !(canOverrideSameSource || (configurationSource != currentConfigurationSource))) + { + return null; + } + + using (Metadata.Model.DelayConventions()) + { + var detachedRelationships = property.GetContainingForeignKeys().ToList() + .Select(InternalEntityTypeBuilder.DetachRelationship).ToList(); + + foreach (var key in property.GetContainingKeys().ToList()) + { + detachedRelationships.AddRange( + key.GetReferencingForeignKeys().ToList() + .Select(InternalEntityTypeBuilder.DetachRelationship)); + var removed = key.DeclaringEntityType.Builder.HasNoKey(key, configurationSource); + Check.DebugAssert(removed != null, "removed is null"); + } + + foreach (var index in property.GetContainingIndexes().ToList()) + { + var removed = index.DeclaringEntityType.Builder.HasNoIndex(index, configurationSource); + Check.DebugAssert(removed != null, "removed is null"); + } + + if (property.IsInModel) + { + var removedProperty = Metadata.RemoveProperty(property.Name); + Check.DebugAssert(removedProperty == property, "removedProperty != property"); + } + + foreach (var relationshipSnapshot in detachedRelationships) + { + relationshipSnapshot.Attach(); + } + } + + return currentConfigurationSource; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalTypeBaseBuilder RemoveUnusedImplicitProperties(IReadOnlyList properties) + { + foreach (var property in properties) + { + if (property.IsInModel && property.IsImplicitlyCreated()) + { + RemovePropertyIfUnused((Property)property, ConfigurationSource.Convention); + } + } + + return this; + } + + /// + /// 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 RemovePropertyIfUnused(Property property, ConfigurationSource configurationSource) + { + if (!property.IsInModel + || !property.DeclaringType.Builder.CanRemoveProperty(property, configurationSource) + || property.GetContainingIndexes().Any() + || property.GetContainingForeignKeys().Any() + || property.GetContainingKeys().Any()) + { + return; + } + + var removedProperty = property.DeclaringType.RemoveProperty(property.Name); + Check.DebugAssert(removedProperty == property, "removedProperty != property"); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexIndexerProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, + string propertyName, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? complexType, + bool? collection, + ConfigurationSource? configurationSource) + { + var indexerPropertyInfo = Metadata.FindIndexerPropertyInfo(); + if (indexerPropertyInfo == null) + { + throw new InvalidOperationException( + CoreStrings.NonIndexerEntityType(propertyName, Metadata.DisplayName(), typeof(string).ShortDisplayName())); + } + + return ComplexProperty(propertyType, propertyName, indexerPropertyInfo, complexType, collection, configurationSource); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexProperty( + MemberInfo memberInfo, + bool? collection, + ConfigurationSource? configurationSource) + => ComplexProperty( + memberInfo.GetMemberType(), memberInfo.Name, memberInfo, complexType: null, collection, configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, + string propertyName, + bool? collection, + ConfigurationSource? configurationSource) + => ComplexProperty( + propertyType, propertyName, memberInfo: null, complexType: null, collection, configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalComplexPropertyBuilder? ComplexProperty( + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, + string propertyName, + MemberInfo? memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? complexType, + bool? collection, + ConfigurationSource? configurationSource) + { + var entityType = Metadata; + List? propertiesToDetach = null; + var existingComplexProperty = Metadata.FindComplexProperty(propertyName); + if (existingComplexProperty != null) + { + if (existingComplexProperty.DeclaringType != Metadata) + { + if (!IsIgnored(propertyName, configurationSource)) + { + Metadata.RemoveIgnored(propertyName); + } + + entityType = (EntityType)existingComplexProperty.DeclaringType; + } + + var existingComplexType = existingComplexProperty.ComplexType; + if (IsCompatible(memberInfo, existingComplexProperty) + && (propertyType == null + || existingComplexProperty.ClrType == propertyType) + && (complexType == null + || existingComplexType.ClrType == complexType) + && (collection == null + || collection.Value == existingComplexProperty.IsCollection)) + { + if (configurationSource.HasValue) + { + existingComplexProperty.UpdateConfigurationSource(configurationSource.Value); + } + + return existingComplexProperty.Builder; + } + + if (!configurationSource.Overrides(existingComplexProperty.GetConfigurationSource())) + { + return null; + } + + Debug.Assert(configurationSource.HasValue); + + memberInfo ??= existingComplexProperty.PropertyInfo ?? (MemberInfo?)existingComplexProperty.FieldInfo; + propertyType ??= existingComplexProperty.ClrType; + collection ??= existingComplexProperty.IsCollection; + complexType ??= existingComplexType.ClrType; + + propertiesToDetach = new List { existingComplexProperty }; + } + else + { + if (configurationSource != ConfigurationSource.Explicit + && (!configurationSource.HasValue + || !CanAddComplexProperty( + propertyName, propertyType ?? memberInfo?.GetMemberType(), complexType, collection, configurationSource.Value))) + { + return null; + } + + memberInfo ??= Metadata.IsPropertyBag + ? null + : Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault(); + + if (propertyType == null) + { + if (memberInfo == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(propertyName, Metadata.DisplayName())); + } + + propertyType = memberInfo.GetMemberType(); + } + + if (collection == false) + { + complexType = propertyType; + } + + if (collection == null + || complexType == null) + { + var elementType = propertyType.TryGetSequenceType(); + collection ??= elementType != null; + complexType ??= collection.Value ? elementType : propertyType; + } + + foreach (var derivedType in Metadata.GetDerivedTypes()) + { + var derivedProperty = derivedType.FindDeclaredComplexProperty(propertyName); + if (derivedProperty != null) + { + propertiesToDetach ??= new List(); + + propertiesToDetach.Add(derivedProperty); + } + } + } + + InternalComplexPropertyBuilder builder; + using (Metadata.Model.DelayConventions()) + { + Metadata.Model.AddComplex(complexType!, configurationSource.Value); + + foreach (var existingEntityType in Metadata.Model.FindEntityTypes(complexType!).ToList()) + { + Metadata.Model.Builder.HasNoEntityType(existingEntityType, ConfigurationSource.Convention); + } + + var detachedProperties = propertiesToDetach == null ? null : DetachProperties(propertiesToDetach); + if (existingComplexProperty == null) + { + Metadata.RemoveIgnored(propertyName); + + RemoveMembersInHierarchy(propertyName, configurationSource.Value); + } + + builder = entityType.AddComplexProperty( + propertyName, propertyType, memberInfo, complexType!, collection.Value, configurationSource.Value)!.Builder; + + if (detachedProperties != null) + { + foreach (var detachedProperty in detachedProperties) + { + detachedProperty.Attach(this); + } + } + } + + return builder.Metadata.IsInModel + ? builder + : Metadata.FindComplexProperty(propertyName)?.Builder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanHaveComplexProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + MemberInfo? memberInfo, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? complexType, + bool? collection, + ConfigurationSource? configurationSource) + { + propertyType ??= memberInfo?.GetMemberType(); + var existingComplexProperty = Metadata.FindComplexProperty(propertyName); + var existingComplexType = existingComplexProperty?.ComplexType; + return existingComplexProperty != null + ? (IsCompatible(memberInfo, existingComplexProperty) + && (propertyType == null + || existingComplexProperty.ClrType == propertyType) + && (complexType == null + || existingComplexType!.ClrType == complexType) + && (collection == null + || collection.Value == existingComplexProperty.IsCollection)) + || configurationSource.Overrides(existingComplexProperty.GetConfigurationSource()) + : configurationSource.HasValue + && CanAddComplexProperty(propertyName, propertyType, complexType, collection, configurationSource.Value); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public abstract bool CanAddComplexProperty( + string propertyName, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? targetType, + bool? collection, + ConfigurationSource configurationSource, + bool checkClrProperty = false); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalTypeBaseBuilder? HasNoComplexProperty( + ComplexProperty complexProperty, + ConfigurationSource configurationSource) + { + if (!CanRemoveComplexProperty(complexProperty, configurationSource)) + { + return null; + } + + Metadata.RemoveComplexProperty(complexProperty); + + return this; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanRemoveComplexProperty(ComplexProperty complexProperty, ConfigurationSource configurationSource) + => configurationSource.Overrides(complexProperty.GetConfigurationSource()); + + /// + /// 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 List? DetachProperties(IReadOnlyList propertiesToDetach) + { + if (propertiesToDetach.Count == 0) + { + return null; + } + + var detachedProperties = new List(); + foreach (var propertyToDetach in propertiesToDetach) + { + var snapshot = InternalComplexPropertyBuilder.Detach(propertyToDetach); + if (snapshot == null) + { + continue; + } + + detachedProperties.Add(snapshot); + } + + return detachedProperties; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsIgnored(string name, ConfigurationSource? configurationSource) + { + Check.NotEmpty(name, nameof(name)); + + return configurationSource != ConfigurationSource.Explicit + && !configurationSource.OverridesStrictly(Metadata.FindIgnoredConfigurationSource(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. + /// + public abstract InternalTypeBaseBuilder? Ignore(string name, ConfigurationSource configurationSource); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanIgnore(string name, ConfigurationSource configurationSource) + => CanIgnore(name, configurationSource, shouldThrow: false); + + /// + /// 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 abstract bool CanIgnore(string name, ConfigurationSource configurationSource, bool shouldThrow); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalTypeBaseBuilder? HasChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + ConfigurationSource configurationSource) + { + if (CanSetChangeTrackingStrategy(changeTrackingStrategy, configurationSource)) + { + Metadata.SetChangeTrackingStrategy(changeTrackingStrategy, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + ConfigurationSource configurationSource) + => configurationSource.Overrides(Metadata.GetChangeTrackingStrategyConfigurationSource()) + || Metadata.GetChangeTrackingStrategy() == changeTrackingStrategy; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalTypeBaseBuilder? UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + ConfigurationSource configurationSource) + { + if (CanSetPropertyAccessMode(propertyAccessMode, configurationSource)) + { + Metadata.SetPropertyAccessMode(propertyAccessMode, configurationSource); + + return this; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, ConfigurationSource configurationSource) + => configurationSource.Overrides(((IConventionTypeBase)Metadata).GetPropertyAccessModeConfigurationSource()) + || ((IConventionTypeBase)Metadata).GetPropertyAccessMode() == propertyAccessMode; + + IConventionTypeBase IConventionTypeBaseBuilder.Metadata + { + [DebuggerStepThrough] + get => Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionTypeBaseBuilder?)base.HasAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasNonNullAnnotation(string name, object? value, bool fromDataAnnotation) + => (IConventionTypeBaseBuilder?)base.HasNonNullAnnotation( + name, value, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasNoAnnotation(string name, bool fromDataAnnotation) + => (IConventionTypeBaseBuilder?)base.HasNoAnnotation( + name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionPropertyBuilder? IConventionTypeBaseBuilder.Property(MemberInfo memberInfo, bool fromDataAnnotation) + => Property(memberInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionPropertyBuilder? IConventionTypeBaseBuilder.Property( + Type propertyType, + string propertyName, + bool setTypeConfigurationSource, + bool fromDataAnnotation) + => Property( + propertyType, + propertyName, setTypeConfigurationSource + ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention + : null, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanHaveProperty( + Type? propertyType, + string propertyName, + bool fromDataAnnotation) + => CanHaveProperty( + propertyType, + propertyName, + null, + propertyType != null + ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention + : null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanHaveProperty(MemberInfo memberInfo, bool fromDataAnnotation) + => CanHaveProperty( + memberInfo.GetMemberType(), + memberInfo.Name, + memberInfo, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionPropertyBuilder? IConventionTypeBaseBuilder.IndexerProperty( + Type propertyType, + string propertyName, + bool fromDataAnnotation) + => Property( + propertyType, + propertyName, + Metadata.FindIndexerPropertyInfo(), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanHaveIndexerProperty( + Type propertyType, + string propertyName, + bool fromDataAnnotation) + => CanHaveProperty( + propertyType, + propertyName, + Metadata.FindIndexerPropertyInfo(), + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionPropertyBuilder? IConventionTypeBaseBuilder.CreateUniqueProperty( + Type propertyType, + string basePropertyName, + bool required) + => CreateUniqueProperty(propertyType, basePropertyName, required); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IReadOnlyList? IConventionTypeBaseBuilder.GetOrCreateProperties( + IReadOnlyList? propertyNames, + bool fromDataAnnotation) + => GetOrCreateProperties( + propertyNames, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IReadOnlyList? IConventionTypeBaseBuilder.GetOrCreateProperties( + IEnumerable? memberInfos, + bool fromDataAnnotation) + => GetOrCreateProperties(memberInfos, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder IConventionTypeBaseBuilder.RemoveUnusedImplicitProperties( + IReadOnlyList properties) + => RemoveUnusedImplicitProperties(properties); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasNoProperty(IConventionProperty property, bool fromDataAnnotation) + => RemoveProperty( + (Property)property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) == null + ? null + : this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanRemoveProperty(IConventionProperty property, bool fromDataAnnotation) + => CanRemoveProperty( + (Property)property, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionTypeBaseBuilder.ComplexProperty( + Type propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => ComplexProperty( + propertyType, + propertyName, + memberInfo: null, + complexType: complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionTypeBaseBuilder.ComplexProperty( + MemberInfo memberInfo, Type? complexType, bool fromDataAnnotation) + => ComplexProperty( + propertyType: memberInfo.GetMemberType(), + propertyName: memberInfo.Name, + memberInfo: memberInfo, + complexType: complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanHaveComplexProperty( + Type? propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => CanHaveComplexProperty( + propertyType, + propertyName, + memberInfo: null, + complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanHaveComplexProperty(MemberInfo memberInfo, Type? complexType, bool fromDataAnnotation) + => CanHaveComplexProperty( + memberInfo.GetMemberType(), + memberInfo.Name, + memberInfo, + complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexPropertyBuilder? IConventionTypeBaseBuilder.ComplexIndexerProperty( + Type propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => ComplexIndexerProperty( + propertyType, + propertyName, + complexType, + collection: null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanHaveComplexIndexerProperty( + Type propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => CanHaveComplexProperty( + propertyType, + propertyName, + Metadata.FindIndexerPropertyInfo(), + complexType, + collection: null, + configurationSource: fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasNoComplexProperty( + IConventionComplexProperty complexProperty, bool fromDataAnnotation) + => HasNoComplexProperty( + (ComplexProperty)complexProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanRemoveComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation) + => CanRemoveComplexProperty( + (ComplexProperty)complexProperty, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + bool IConventionTypeBaseBuilder.IsIgnored(string name, bool fromDataAnnotation) + => IsIgnored(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.Ignore(string memberName, bool fromDataAnnotation) + => Ignore(memberName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanIgnore(string name, bool fromDataAnnotation) + => CanIgnore(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.HasChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + bool fromDataAnnotation) + => HasChangeTrackingStrategy( + changeTrackingStrategy, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanSetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy, bool fromDataAnnotation) + => CanSetChangeTrackingStrategy( + changeTrackingStrategy, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionTypeBaseBuilder? IConventionTypeBaseBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + bool fromDataAnnotation) + => UsePropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + bool IConventionTypeBaseBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => CanSetPropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore/Metadata/Internal/Key.cs b/src/EFCore/Metadata/Internal/Key.cs index e2c429977de..866f3df6c71 100644 --- a/src/EFCore/Metadata/Internal/Key.cs +++ b/src/EFCore/Metadata/Internal/Key.cs @@ -54,7 +54,7 @@ public Key(IReadOnlyList properties, ConfigurationSource configuration public virtual EntityType DeclaringEntityType { [DebuggerStepThrough] - get => Properties[0].DeclaringEntityType; + get => (EntityType)Properties[0].DeclaringType; } /// diff --git a/src/EFCore/Metadata/Internal/Model.cs b/src/EFCore/Metadata/Internal/Model.cs index 9747de2a6b6..09fdf43e0a0 100644 --- a/src/EFCore/Metadata/Internal/Model.cs +++ b/src/EFCore/Metadata/Internal/Model.cs @@ -30,11 +30,12 @@ public class Model : ConventionAnnotatable, IMutableModel, IConventionModel, IRu private readonly ConcurrentDictionary _clrTypeNameMap = new(); private readonly Dictionary _ignoredTypeNames = new(StringComparer.Ordinal); private Dictionary? _ownedTypes; + private Dictionary? _complexTypes; private readonly Dictionary Types)> _sharedTypes = new() { - { DefaultPropertyBagType, (ConfigurationSource.Explicit, new SortedSet(EntityTypeFullNameComparer.Instance)) } + { DefaultPropertyBagType, (ConfigurationSource.Explicit, new SortedSet(TypeBaseNameComparer.Instance)) } }; private ConventionDispatcher? _conventionDispatcher; @@ -221,7 +222,7 @@ public virtual IEnumerable GetEntityTypes() } else { - var types = new SortedSet(EntityTypeFullNameComparer.Instance) { entityType }; + var types = new SortedSet(TypeBaseNameComparer.Instance) { entityType }; _sharedTypes.Add(entityType.ClrType, (entityType.GetConfigurationSource(), types)); } } @@ -329,7 +330,7 @@ private static void AssertCanRemove(EntityType entityType) var removed = _entityTypes.Remove(entityType.Name); Check.DebugAssert(removed, "removed is false"); - entityType.OnTypeRemoved(); + entityType.SetRemovedFromModel(); return entityType; } @@ -491,16 +492,6 @@ public virtual IEnumerable GetEntityTypes(string name) EntityType definingEntityType) => RemoveEntityType(FindEntityType(name, definingNavigationName, definingEntityType)); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual bool IsShared([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type type) - => FindIsSharedConfigurationSource(type) != null - || Configuration?.GetConfigurationType(type) == TypeConfigurationType.SharedTypeEntityType; - /// /// 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 @@ -551,7 +542,7 @@ public virtual bool IsShared([DynamicallyAccessedMembers(DynamicallyAccessedMemb ? existingEntityType.ClrType : null; - return ConventionDispatcher.OnEntityTypeIgnored(Builder, name, type); + return ConventionDispatcher.OnTypeIgnored(Builder, name, type); } /// @@ -738,6 +729,94 @@ public virtual void AddOwned(Type type, ConfigurationSource configurationSource) return null; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? FindIsComplexConfigurationSource(Type type) + { + if (_complexTypes == null) + { + return null; + } + + var currentType = type; + while (currentType != null) + { + if (_complexTypes.TryGetValue(GetDisplayName(currentType), out var configurationSource)) + { + return configurationSource; + } + + currentType = currentType.BaseType; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void AddComplex(Type type, ConfigurationSource configurationSource) + { + EnsureMutable(); + var name = GetDisplayName(type); + _complexTypes ??= new Dictionary(StringComparer.Ordinal); + + if (_complexTypes.TryGetValue(name, out var oldConfigurationSource)) + { + _complexTypes[name] = configurationSource.Max(oldConfigurationSource); + return; + } + + _complexTypes.Add(name, configurationSource); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? RemoveComplex(Type type) + { + EnsureMutable(); + + if (_complexTypes == null) + { + return null; + } + + var currentType = type; + while (currentType != null) + { + var name = GetDisplayName(type); + if (_complexTypes.Remove(name)) + { + return name; + } + + currentType = currentType.BaseType; + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsShared([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type type) + => FindIsSharedConfigurationSource(type) != null + || Configuration?.GetConfigurationType(type) == TypeConfigurationType.SharedTypeEntityType; + /// /// 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 @@ -768,7 +847,7 @@ public virtual void AddShared(Type type, ConfigurationSource configurationSource } else { - _sharedTypes.Add(type, (configurationSource, new SortedSet(EntityTypeFullNameComparer.Instance))); + _sharedTypes.Add(type, (configurationSource, new SortedSet(TypeBaseNameComparer.Instance))); } } diff --git a/src/EFCore/Metadata/Internal/ModelConfiguration.cs b/src/EFCore/Metadata/Internal/ModelConfiguration.cs index d0badc2e69e..16e9e7aae1f 100644 --- a/src/EFCore/Metadata/Internal/ModelConfiguration.cs +++ b/src/EFCore/Metadata/Internal/ModelConfiguration.cs @@ -16,6 +16,7 @@ public class ModelConfiguration { private readonly Dictionary _properties = new(); private readonly Dictionary _typeMappings = new(); + private readonly Dictionary _complexProperties = new(); private readonly HashSet _ignoredTypes = new(); private readonly Dictionary _configurationTypes = new(); @@ -143,13 +144,18 @@ public virtual ModelConfiguration Validate() configurationType = TypeConfigurationType.Ignored; configuredType = type; } - - if (_properties.ContainsKey(type)) + else if (_properties.ContainsKey(type)) { EnsureCompatible(TypeConfigurationType.Property, type, configurationType, configuredType); configurationType = TypeConfigurationType.Property; configuredType = type; } + else if (_complexProperties.ContainsKey(type)) + { + EnsureCompatible(TypeConfigurationType.ComplexType, type, configurationType, configuredType); + configurationType = TypeConfigurationType.ComplexType; + configuredType = type; + } if (configurationType.HasValue) { @@ -217,6 +223,26 @@ public virtual void ConfigureProperty(IMutableProperty property) } } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void ConfigureComplexProperty(IMutableComplexProperty property) + { + var types = property.ClrType.GetBaseTypesAndInterfacesInclusive(); + for (var i = types.Count - 1; i >= 0; i--) + { + var type = types[i]; + + if (_complexProperties.TryGetValue(type, out var configuration)) + { + configuration.Apply(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 @@ -297,6 +323,46 @@ public virtual PropertyConfiguration GetOrAddTypeMapping(Type type) ? property : null; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexPropertyConfiguration GetOrAddComplexProperty(Type type) + { + var property = FindComplexProperty(type); + if (property == null) + { + RemoveIgnored(type); + + property = new ComplexPropertyConfiguration(type); + _complexProperties.Add(type, property); + } + + return property; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexPropertyConfiguration? FindComplexProperty(Type type) + => _complexProperties.TryGetValue(type, out var property) + ? property + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool RemoveComplexProperty(Type type) + => _complexProperties.Remove(type); + /// /// 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 @@ -306,6 +372,7 @@ public virtual PropertyConfiguration GetOrAddTypeMapping(Type type) public virtual void AddIgnored(Type type) { RemoveProperty(type); + RemoveComplexProperty(type); _ignoredTypes.Add(type); } diff --git a/src/EFCore/Metadata/Internal/Navigation.cs b/src/EFCore/Metadata/Internal/Navigation.cs index 48d4c8a1f32..c0b42e4bfb6 100644 --- a/src/EFCore/Metadata/Internal/Navigation.cs +++ b/src/EFCore/Metadata/Internal/Navigation.cs @@ -185,7 +185,7 @@ public override void UpdateConfigurationSource(ConfigurationSource configuration /// public override PropertyAccessMode GetPropertyAccessMode() => (PropertyAccessMode)(this[CoreAnnotationNames.PropertyAccessMode] - ?? ((IReadOnlyTypeBase)DeclaringType).GetNavigationAccessMode()); + ?? DeclaringEntityType.GetNavigationAccessMode()); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/PropertiesSnapshot.cs b/src/EFCore/Metadata/Internal/PropertiesSnapshot.cs index 22d1620b55d..6d07cc0cccf 100644 --- a/src/EFCore/Metadata/Internal/PropertiesSnapshot.cs +++ b/src/EFCore/Metadata/Internal/PropertiesSnapshot.cs @@ -94,21 +94,24 @@ public virtual void Add(List<(InternalKeyBuilder, ConfigurationSource?)> keys) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual void Attach(InternalEntityTypeBuilder entityTypeBuilder) + public virtual void Attach(InternalTypeBaseBuilder typeBaseBuilder) { if (Properties != null) { foreach (var propertyBuilder in Properties) { - propertyBuilder.Attach(entityTypeBuilder); + propertyBuilder.Attach(typeBaseBuilder); } } + var entityTypeBuilder = typeBaseBuilder as InternalEntityTypeBuilder + ?? ((InternalComplexTypeBuilder)typeBaseBuilder).Metadata.FundamentalEntityType.Builder; + if (Keys != null) { foreach (var (internalKeyBuilder, configurationSource) in Keys) { - internalKeyBuilder.Attach(entityTypeBuilder.Metadata.RootType().Builder, configurationSource); + internalKeyBuilder.Attach(entityTypeBuilder.Metadata.GetRootType().Builder, configurationSource); } } diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index a5248557df7..e4818df48a5 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -16,17 +16,18 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public class Property : PropertyBase, IMutableProperty, IConventionProperty, IProperty { + private InternalPropertyBuilder? _builder; + private bool? _isConcurrencyToken; - private object? _sentinel; private bool? _isNullable; + private object? _sentinel; private ValueGenerated? _valueGenerated; private CoreTypeMapping? _typeMapping; - private InternalPropertyBuilder? _builder; private ConfigurationSource? _typeConfigurationSource; private ConfigurationSource? _isNullableConfigurationSource; - private ConfigurationSource? _isConcurrencyTokenConfigurationSource; private ConfigurationSource? _sentinelConfigurationSource; + private ConfigurationSource? _isConcurrencyTokenConfigurationSource; private ConfigurationSource? _valueGeneratedConfigurationSource; private ConfigurationSource? _typeMappingConfigurationSource; @@ -41,16 +42,27 @@ public Property( [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type clrType, PropertyInfo? propertyInfo, FieldInfo? fieldInfo, - EntityType declaringEntityType, + TypeBase declaringType, ConfigurationSource configurationSource, ConfigurationSource? typeConfigurationSource) : base(name, propertyInfo, fieldInfo, configurationSource) { - DeclaringEntityType = declaringEntityType; + DeclaringType = declaringType; ClrType = clrType; _typeConfigurationSource = typeConfigurationSource; + _builder = new InternalPropertyBuilder(this, declaringType.Model.Builder); + } - _builder = new InternalPropertyBuilder(this, declaringEntityType.Model.Builder); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual InternalPropertyBuilder Builder + { + [DebuggerStepThrough] + get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); } /// @@ -59,7 +71,9 @@ public Property( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual EntityType DeclaringEntityType { get; } + public virtual bool IsInModel + => _builder is not null + && DeclaringType.IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -67,11 +81,8 @@ public Property( /// 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 TypeBase DeclaringType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } + public virtual void SetRemovedFromModel() + => _builder = null; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -79,8 +90,21 @@ public override TypeBase DeclaringType /// 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. /// - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] - public override Type ClrType { get; } + protected override FieldInfo? OnFieldInfoSet(FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) + => DeclaringType.Model.ConventionDispatcher.OnPropertyFieldChanged(Builder, newFieldInfo, oldFieldInfo); + + /// + /// Runs the conventions when an annotation was set or removed. + /// + /// The key of the set annotation. + /// The annotation set. + /// The old annotation. + /// The annotation that was set. + protected override IConventionAnnotation? OnAnnotationSet( + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation) + => DeclaringType.Model.ConventionDispatcher.OnPropertyAnnotationChanged(Builder, name, annotation, oldAnnotation); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -88,11 +112,16 @@ public override TypeBase DeclaringType /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InternalPropertyBuilder Builder - { - [DebuggerStepThrough] - get => _builder ?? throw new InvalidOperationException(CoreStrings.ObjectRemovedFromModel); - } + public static bool AreCompatible(IReadOnlyList properties, EntityType entityType) + => properties.All( + property => + property.IsShadowProperty() + || (property.IsIndexerProperty() + ? property.PropertyInfo == entityType.FindIndexerPropertyInfo() + : ((property.PropertyInfo != null + && entityType.GetRuntimeProperties().ContainsKey(property.Name)) + || (property.FieldInfo != null + && entityType.GetRuntimeFields().ContainsKey(property.Name))))); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -100,9 +129,7 @@ public virtual InternalPropertyBuilder Builder /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool IsInModel - => _builder is not null - && DeclaringEntityType.IsInModel; + public override TypeBase DeclaringType { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -110,8 +137,8 @@ public virtual bool IsInModel /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual void SetRemovedFromModel() - => _builder = null; + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] + public override Type ClrType { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -160,7 +187,7 @@ public virtual bool IsNullable _isNullableConfigurationSource = null; if (isChanging) { - DeclaringEntityType.Model.ConventionDispatcher.OnPropertyNullableChanged(Builder); + OnPropertyNullableChanged(); } return nullable; @@ -171,12 +198,12 @@ public virtual bool IsNullable if (!ClrType.IsNullableType()) { throw new InvalidOperationException( - CoreStrings.CannotBeNullable(Name, DeclaringEntityType.DisplayName(), ClrType.ShortDisplayName())); + CoreStrings.CannotBeNullable(Name, DeclaringType.DisplayName(), ClrType.ShortDisplayName())); } if (Keys != null) { - throw new InvalidOperationException(CoreStrings.CannotBeNullablePK(Name, DeclaringEntityType.DisplayName())); + throw new InvalidOperationException(CoreStrings.CannotBeNullablePK(Name, DeclaringType.DisplayName())); } } @@ -185,21 +212,21 @@ public virtual bool IsNullable _isNullable = nullable; return isChanging - ? DeclaringEntityType.Model.ConventionDispatcher.OnPropertyNullableChanged(Builder) + ? OnPropertyNullableChanged() : nullable; } - private bool DefaultIsNullable - => ClrType.IsNullableType(); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual ConfigurationSource? GetIsNullableConfigurationSource() - => _isNullableConfigurationSource; + protected virtual bool? OnPropertyNullableChanged() + => DeclaringType.Model.ConventionDispatcher.OnPropertyNullabilityChanged(Builder); + + private bool DefaultIsNullable + => ClrType.IsNullableType(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -207,8 +234,8 @@ private bool DefaultIsNullable /// 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 FieldInfo? OnFieldInfoSet(FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) - => DeclaringEntityType.Model.ConventionDispatcher.OnPropertyFieldChanged(Builder, newFieldInfo, oldFieldInfo); + public virtual ConfigurationSource? GetIsNullableConfigurationSource() + => _isNullableConfigurationSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -299,50 +326,6 @@ private static bool DefaultIsConcurrencyToken public virtual ConfigurationSource? GetIsConcurrencyTokenConfigurationSource() => _isConcurrencyTokenConfigurationSource; - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual object? Sentinel - { - get => _sentinel ?? DefaultSentinel; - set => SetSentinel(value, ConfigurationSource.Explicit); - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual object? SetSentinel(object? sentinel, ConfigurationSource configurationSource) - { - EnsureMutable(); - - _sentinel = sentinel; - - _sentinelConfigurationSource = configurationSource.Max(_sentinelConfigurationSource); - - return sentinel; - } - - private object? DefaultSentinel - => (this is IProperty property - && property.TryGetMemberInfo(forMaterialization: false, forSet: false, out var member, out _) - ? member!.GetMemberType() - : ClrType).GetDefaultValue(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetSentinelConfigurationSource() - => _sentinelConfigurationSource; - /// /// 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 @@ -412,7 +395,7 @@ private object? DefaultSentinel /// public virtual int? SetPrecision(int? precision, ConfigurationSource configurationSource) { - if (precision is < 0) + if (precision != null && precision < 0) { throw new ArgumentOutOfRangeException(nameof(precision)); } @@ -561,7 +544,7 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior() public virtual string? CheckAfterSaveBehavior(PropertySaveBehavior behavior) => behavior != PropertySaveBehavior.Throw && IsKey() - ? CoreStrings.KeyPropertyMustBeReadOnly(Name, DeclaringEntityType.DisplayName()) + ? CoreStrings.KeyPropertyMustBeReadOnly(Name, DeclaringType.DisplayName()) : null; /// @@ -570,12 +553,56 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual Func? SetValueGeneratorFactory( - Func? factory, + public virtual object? Sentinel + { + get => _sentinel ?? DefaultSentinel; + set => SetSentinel(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual object? SetSentinel(object? sentinel, ConfigurationSource configurationSource) + { + EnsureMutable(); + + _sentinel = sentinel; + + _sentinelConfigurationSource = configurationSource.Max(_sentinelConfigurationSource); + + return sentinel; + } + + private object? DefaultSentinel + => (this is IProperty property + && property.TryGetMemberInfo(forMaterialization: false, forSet: false, out var member, out _) + ? member!.GetMemberType() + : ClrType).GetDefaultValue(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetSentinelConfigurationSource() + => _sentinelConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Func? SetValueGeneratorFactory( + Func? factory, ConfigurationSource configurationSource) { RemoveAnnotation(CoreAnnotationNames.ValueGeneratorFactoryType); - return (Func?) + return (Func?) SetAnnotation(CoreAnnotationNames.ValueGeneratorFactory, factory, configurationSource)?.Value; } @@ -586,8 +613,7 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual Type? SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] - Type? factoryType, + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? factoryType, ConfigurationSource configurationSource) { if (factoryType != null) @@ -617,9 +643,9 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual Func? GetValueGeneratorFactory() + public virtual Func? GetValueGeneratorFactory() { - var factory = (Func?)this[CoreAnnotationNames.ValueGeneratorFactory]; + var factory = (Func?)this[CoreAnnotationNames.ValueGeneratorFactory]; if (factory == null) { var factoryType = (Type?)this[CoreAnnotationNames.ValueGeneratorFactoryType]; @@ -669,8 +695,7 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual Type? SetValueConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? converterType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, ConfigurationSource configurationSource) { ValueConverter? converter = null; @@ -752,9 +777,8 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior() } return i == ForeignKey.LongestFkChainAllowedLength - ? throw new InvalidOperationException( - CoreStrings.RelationshipCycle( - DeclaringEntityType.DisplayName(), Name, "ValueConverter")) + ? throw new InvalidOperationException(CoreStrings.RelationshipCycle( + DeclaringType.DisplayName(), Name, "ValueConverter")) : null; } @@ -778,7 +802,7 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior() && converter.ModelClrType.UnwrapNullableType() != ClrType.UnwrapNullableType() ? CoreStrings.ConverterPropertyMismatch( converter.ModelClrType.ShortDisplayName(), - DeclaringEntityType.DisplayName(), + DeclaringType.DisplayName(), Name, ClrType.ShortDisplayName()) : null; @@ -844,9 +868,8 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior() } return i == ForeignKey.LongestFkChainAllowedLength - ? throw new InvalidOperationException( - CoreStrings.RelationshipCycle( - DeclaringEntityType.DisplayName(), Name, "ProviderClrType")) + ? throw new InvalidOperationException(CoreStrings.RelationshipCycle( + DeclaringType.DisplayName(), Name, "ProviderClrType")) : null; } @@ -875,7 +898,7 @@ public virtual CoreTypeMapping? TypeMapping get => IsReadOnly ? NonCapturingLazyInitializer.EnsureInitialized( ref _typeMapping, (IProperty)this, static property => - property.DeclaringEntityType.Model.GetModelDependencies().TypeMappingSource.FindMapping(property)!) + property.DeclaringType.Model.GetModelDependencies().TypeMappingSource.FindMapping(property)!) : _typeMapping; set => SetTypeMapping(value, ConfigurationSource.Explicit); @@ -932,8 +955,7 @@ public virtual CoreTypeMapping? TypeMapping /// [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] public virtual Type? SetValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, ConfigurationSource configurationSource) { ValueComparer? comparer = null; @@ -970,7 +992,7 @@ public virtual CoreTypeMapping? TypeMapping public virtual ValueComparer? GetValueComparer() => (GetValueComparer(null) ?? TypeMapping?.Comparer).ToNullableComparer(this); - private ValueComparer? GetValueComparer(HashSet? checkedProperties) + private ValueComparer? GetValueComparer(HashSet? checkedProperties) { var comparer = (ValueComparer?)this[CoreAnnotationNames.ValueComparer]; if (comparer != null) @@ -986,7 +1008,7 @@ public virtual CoreTypeMapping? TypeMapping if (checkedProperties == null) { - checkedProperties = new HashSet(); + checkedProperties = new HashSet(); } else if (checkedProperties.Contains(this)) { @@ -1035,8 +1057,7 @@ public virtual CoreTypeMapping? TypeMapping /// [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] public virtual Type? SetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, ConfigurationSource configurationSource) { ValueComparer? comparer = null; @@ -1076,7 +1097,7 @@ public virtual CoreTypeMapping? TypeMapping ? GetKeyValueComparer() : TypeMapping?.ProviderValueComparer); - private ValueComparer? GetProviderValueComparer(HashSet? checkedProperties) + private ValueComparer? GetProviderValueComparer(HashSet? checkedProperties) { var comparer = (ValueComparer?)this[CoreAnnotationNames.ProviderValueComparer]; if (comparer != null) @@ -1093,7 +1114,7 @@ public virtual CoreTypeMapping? TypeMapping if (checkedProperties == null) { - checkedProperties = new HashSet(); + checkedProperties = new HashSet(); } else if (checkedProperties.Contains(this)) { @@ -1124,7 +1145,7 @@ public virtual CoreTypeMapping? TypeMapping && comparer.Type.UnwrapNullableType() != ClrType.UnwrapNullableType() ? CoreStrings.ComparerPropertyMismatch( comparer.Type.ShortDisplayName(), - DeclaringEntityType.DisplayName(), + DeclaringType.DisplayName(), Name, ClrType.ShortDisplayName()) : null; @@ -1283,19 +1304,6 @@ public virtual bool IsIndex() public virtual IEnumerable GetContainingIndexes() => Indexes ?? Enumerable.Empty(); - /// - /// Runs the conventions when an annotation was set or removed. - /// - /// The key of the set annotation. - /// The annotation set. - /// The old annotation. - /// The annotation that was set. - protected override IConventionAnnotation? OnAnnotationSet( - string name, - IConventionAnnotation? annotation, - IConventionAnnotation? oldAnnotation) - => DeclaringType.Model.ConventionDispatcher.OnPropertyAnnotationChanged(Builder, name, annotation, oldAnnotation); - /// /// 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 @@ -1309,43 +1317,6 @@ public static string Format(IEnumerable properties) properties.Select(p => string.IsNullOrEmpty(p) ? "" : "'" + p + "'")) + "}"; - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static bool AreCompatible(IReadOnlyList properties, EntityType entityType) - => properties.All( - property => - property.IsShadowProperty() - || (property.IsIndexerProperty() - ? property.PropertyInfo == entityType.FindIndexerPropertyInfo() - : ((property.PropertyInfo != null - && entityType.GetRuntimeProperties().ContainsKey(property.Name)) - || (property.FieldInfo != null - && entityType.GetRuntimeFields().ContainsKey(property.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. - /// - public override string ToString() - => ((IReadOnlyProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual DebugView DebugView - => new( - () => ((IReadOnlyProperty)this).ToDebugString(), - () => ((IReadOnlyProperty)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); - /// /// 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 @@ -1358,66 +1329,6 @@ IConventionPropertyBuilder IConventionProperty.Builder get => Builder; } - /// - /// 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. - /// - IConventionAnnotatableBuilder IConventionAnnotatable.Builder - { - [DebuggerStepThrough] - get => Builder; - } - - /// - /// 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. - /// - IReadOnlyEntityType IReadOnlyProperty.DeclaringEntityType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } - - /// - /// 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. - /// - IMutableEntityType IMutableProperty.DeclaringEntityType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } - - /// - /// 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. - /// - IConventionEntityType IConventionProperty.DeclaringEntityType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } - - /// - /// 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. - /// - IEntityType IProperty.DeclaringEntityType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } - /// /// 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 @@ -1611,17 +1522,6 @@ IEnumerable IProperty.GetContainingKeys() => SetIsConcurrencyToken( concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - [DebuggerStepThrough] - object? IConventionProperty.SetSentinel(object? sentinel, bool fromDataAnnotation) - => SetSentinel( - sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -1757,7 +1657,18 @@ void IMutableProperty.SetAfterSaveBehavior(PropertySaveBehavior? afterSaveBehavi /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - void IMutableProperty.SetValueGeneratorFactory(Func? valueGeneratorFactory) + object? IConventionProperty.SetSentinel(object? sentinel, bool fromDataAnnotation) + => SetSentinel( + sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + void IMutableProperty.SetValueGeneratorFactory(Func? valueGeneratorFactory) => SetValueGeneratorFactory(valueGeneratorFactory, ConfigurationSource.Explicit); /// @@ -1767,8 +1678,8 @@ void IMutableProperty.SetValueGeneratorFactory(Func [DebuggerStepThrough] - Func? IConventionProperty.SetValueGeneratorFactory( - Func? valueGeneratorFactory, + Func? IConventionProperty.SetValueGeneratorFactory( + Func? valueGeneratorFactory, bool fromDataAnnotation) => SetValueGeneratorFactory( valueGeneratorFactory, @@ -1782,8 +1693,7 @@ void IMutableProperty.SetValueGeneratorFactory(Func [DebuggerStepThrough] void IMutableProperty.SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] - Type? valueGeneratorFactory) + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactory) => SetValueGeneratorFactory(valueGeneratorFactory, ConfigurationSource.Explicit); /// @@ -1794,8 +1704,7 @@ void IMutableProperty.SetValueGeneratorFactory( /// [DebuggerStepThrough] Type? IConventionProperty.SetValueGeneratorFactory( - [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] - Type? valueGeneratorFactory, + [DynamicallyAccessedMembers(ValueGeneratorFactory.DynamicallyAccessedMemberTypes)] Type? valueGeneratorFactory, bool fromDataAnnotation) => SetValueGeneratorFactory( valueGeneratorFactory, @@ -1842,8 +1751,7 @@ void IMutableProperty.SetValueConverter( /// [DebuggerStepThrough] Type? IConventionProperty.SetValueConverter( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? converterType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? converterType, bool fromDataAnnotation) => SetValueConverter( converterType, @@ -1913,8 +1821,7 @@ void IMutableProperty.SetValueComparer( [DebuggerStepThrough] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? IConventionProperty.SetValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, bool fromDataAnnotation) => SetValueComparer( comparerType, @@ -1969,8 +1876,7 @@ void IMutableProperty.SetProviderValueComparer(ValueComparer? comparer) /// [DebuggerStepThrough] void IMutableProperty.SetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType) + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType) => SetProviderValueComparer(comparerType, ConfigurationSource.Explicit); /// @@ -1982,8 +1888,7 @@ void IMutableProperty.SetProviderValueComparer( [DebuggerStepThrough] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? IConventionProperty.SetProviderValueComparer( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - Type? comparerType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type? comparerType, bool fromDataAnnotation) => SetProviderValueComparer( comparerType, @@ -2032,10 +1937,4 @@ void IMutableProperty.SetJsonValueReaderWriterType(Type? readerWriterType) [DebuggerStepThrough] JsonValueReaderWriter? IReadOnlyProperty.GetJsonValueReaderWriter() => GetJsonValueReaderWriter(); - - /// - /// Gets the sentinel value that indicates that this property is not set. - /// - object? IReadOnlyPropertyBase.Sentinel - => Sentinel; } diff --git a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs index 8c3fa13636a..512d781c674 100644 --- a/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs +++ b/src/EFCore/Metadata/Internal/PropertyAccessorsFactory.cs @@ -155,7 +155,7 @@ private static Func CreateOriginalValueGetter - /// Indicates whether the model is read-only. + /// 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. + /// + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] + public abstract Type ClrType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override bool IsReadOnly => DeclaringType.Model.IsReadOnly; @@ -213,6 +225,27 @@ public virtual void SetConfigurationSource(ConfigurationSource configurationSour return OnFieldInfoSet(fieldInfo, oldFieldInfo); } + /// + /// 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 FieldInfo? OnFieldInfoSet(FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) + => newFieldInfo; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetFieldInfoConfigurationSource() + => _fieldInfoConfigurationSource; + + private void UpdateFieldInfoConfigurationSource(ConfigurationSource configurationSource) + => _fieldInfoConfigurationSource = configurationSource.Max(_fieldInfoConfigurationSource); + /// /// 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 @@ -305,43 +338,12 @@ public virtual PropertyIndexes PropertyIndexes static property => { property.EnsureReadOnly(); - - var _ = (property.DeclaringType as EntityType)?.Counts; + var _ = ((IRuntimeTypeBase)property.DeclaringType).Counts; }); set => NonCapturingLazyInitializer.EnsureInitialized(ref _indexes, value); } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - protected virtual FieldInfo? OnFieldInfoSet(FieldInfo? newFieldInfo, FieldInfo? oldFieldInfo) - => newFieldInfo; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ConfigurationSource? GetFieldInfoConfigurationSource() - => _fieldInfoConfigurationSource; - - private void UpdateFieldInfoConfigurationSource(ConfigurationSource configurationSource) - => _fieldInfoConfigurationSource = configurationSource.Max(_fieldInfoConfigurationSource); - - /// - /// 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. - /// - [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] - public abstract Type ClrType { get; } - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/PropertyConfiguration.cs b/src/EFCore/Metadata/Internal/PropertyConfiguration.cs index 445873011d1..e5b9af38378 100644 --- a/src/EFCore/Metadata/Internal/PropertyConfiguration.cs +++ b/src/EFCore/Metadata/Internal/PropertyConfiguration.cs @@ -119,15 +119,6 @@ public virtual void SetMaxLength(int? maxLength) this[CoreAnnotationNames.MaxLength] = maxLength; } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual int? GetSentinel() - => (int?)this[CoreAnnotationNames.Sentinel]; - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/PropertyCounts.cs b/src/EFCore/Metadata/Internal/PropertyCounts.cs index 53e0fb8781f..4ba361ecf71 100644 --- a/src/EFCore/Metadata/Internal/PropertyCounts.cs +++ b/src/EFCore/Metadata/Internal/PropertyCounts.cs @@ -20,6 +20,7 @@ public class PropertyCounts public PropertyCounts( int propertyCount, int navigationCount, + int complexPropertyCount, int originalValueCount, int shadowCount, int relationshipCount, @@ -27,6 +28,7 @@ public PropertyCounts( { PropertyCount = propertyCount; NavigationCount = navigationCount; + ComplexPropertyCount = complexPropertyCount; OriginalValueCount = originalValueCount; ShadowCount = shadowCount; RelationshipCount = relationshipCount; @@ -49,6 +51,14 @@ public PropertyCounts( /// public virtual int NavigationCount { get; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual int ComplexPropertyCount { get; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/PropertyExtensions.cs b/src/EFCore/Metadata/Internal/PropertyExtensions.cs index f0903ede3de..325ecb4584b 100644 --- a/src/EFCore/Metadata/Internal/PropertyExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyExtensions.cs @@ -159,7 +159,7 @@ public static bool MayBeStoreGenerated(this IProperty property) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public static bool RequiresOriginalValue(this IReadOnlyProperty property) - => property.DeclaringEntityType.GetChangeTrackingStrategy() != ChangeTrackingStrategy.ChangingAndChangedNotifications + => property.DeclaringType.GetChangeTrackingStrategy() != ChangeTrackingStrategy.ChangingAndChangedNotifications || property.IsConcurrencyToken || property.IsKey() || property.IsForeignKey() diff --git a/src/EFCore/Metadata/Internal/PropertyNameComparer.cs b/src/EFCore/Metadata/Internal/PropertyNameComparer.cs index 00e0b95ccac..d8b3216c69d 100644 --- a/src/EFCore/Metadata/Internal/PropertyNameComparer.cs +++ b/src/EFCore/Metadata/Internal/PropertyNameComparer.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public sealed class PropertyNameComparer : IComparer { - private readonly IReadOnlyEntityType _entityType; + private readonly IReadOnlyEntityType? _entityType; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -19,9 +19,9 @@ public sealed class PropertyNameComparer : IComparer /// 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 PropertyNameComparer(IReadOnlyEntityType entityType) + public PropertyNameComparer(IReadOnlyTypeBase typeBase) { - _entityType = entityType; + _entityType = typeBase as IReadOnlyEntityType; } /// @@ -35,8 +35,7 @@ public int Compare(string? x, string? y) var xIndex = -1; var yIndex = -1; - var properties = _entityType.FindPrimaryKey()?.Properties; - + var properties = _entityType?.FindPrimaryKey()?.Properties; if (properties != null) { for (var i = 0; i < properties.Count; i++) diff --git a/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs b/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs index 5359693ab01..a4ca637b345 100644 --- a/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs +++ b/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs @@ -21,16 +21,53 @@ public class PropertyParameterBindingFactory : IPropertyParameterBindingFactory IEntityType entityType, Type parameterType, string parameterName) + => FindParameter(entityType.GetProperties(), parameterType, parameterName); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ParameterBinding? FindParameter( + IComplexType complexType, + Type parameterType, + string parameterName) + => FindParameter(complexType.GetProperties(), parameterType, parameterName); + + /// + /// 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. + /// + private static ParameterBinding? FindParameter( + IEnumerable properties, + Type parameterType, + string parameterName) { var candidateNames = GetCandidatePropertyNames(parameterName); - return entityType.GetProperties().Where( - p => p.ClrType == parameterType - && candidateNames.Any(c => c.Equals(p.Name, StringComparison.Ordinal))) - .Select(p => new PropertyParameterBinding(p)).FirstOrDefault(); + foreach (var property in properties) + { + if (property.ClrType != parameterType) + { + continue; + } + + foreach (var name in candidateNames) + { + if (name.Equals(property.Name, StringComparison.Ordinal)) + { + return new PropertyParameterBinding(property); + } + } + } + + return null; } - private static IList GetCandidatePropertyNames(string parameterName) + private static List GetCandidatePropertyNames(string parameterName) { var pascalized = char.ToUpperInvariant(parameterName[0]) + parameterName[1..]; diff --git a/src/EFCore/Metadata/Internal/SkipNavigation.cs b/src/EFCore/Metadata/Internal/SkipNavigation.cs index e80127fa9a4..c47bf47e2e1 100644 --- a/src/EFCore/Metadata/Internal/SkipNavigation.cs +++ b/src/EFCore/Metadata/Internal/SkipNavigation.cs @@ -16,6 +16,7 @@ public class SkipNavigation : PropertyBase, IMutableSkipNavigation, IConventionS private ConfigurationSource? _foreignKeyConfigurationSource; private ConfigurationSource? _inverseConfigurationSource; private InternalSkipNavigationBuilder? _builder; + private readonly Type _type; // Warning: Never access these fields directly as access needs to be thread-safe private IClrCollectionAccessor? _collectionAccessor; @@ -30,6 +31,7 @@ public class SkipNavigation : PropertyBase, IMutableSkipNavigation, IConventionS /// public SkipNavigation( string name, + Type? navigationType, PropertyInfo? propertyInfo, FieldInfo? fieldInfo, EntityType declaringEntityType, @@ -43,6 +45,11 @@ public SkipNavigation( TargetEntityType = targetEntityType; IsCollection = collection; IsOnDependent = onDependent; + _type = navigationType + ?? this.GetIdentifyingMemberInfo()?.GetMemberType() + ?? (IsCollection + ? typeof(IEnumerable<>).MakeGenericType(TargetEntityType.ClrType) + : TargetEntityType.ClrType); _builder = new InternalSkipNavigationBuilder(this, targetEntityType.Model.Builder); } @@ -67,10 +74,7 @@ private void ProcessForeignKey(ForeignKey foreignKey) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override Type ClrType - => this.GetIdentifyingMemberInfo()?.GetMemberType() - ?? (IsCollection - ? typeof(IEnumerable<>).MakeGenericType(TargetEntityType.ClrType) - : TargetEntityType.ClrType); + => _type; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -320,7 +324,7 @@ public virtual void UpdateInverseConfigurationSource(ConfigurationSource configu /// public override PropertyAccessMode GetPropertyAccessMode() => (PropertyAccessMode)(this[CoreAnnotationNames.PropertyAccessMode] - ?? ((IReadOnlyTypeBase)DeclaringType).GetNavigationAccessMode()); + ?? DeclaringEntityType.GetNavigationAccessMode()); /// /// Gets the sentinel value that indicates that this property is not set. diff --git a/src/EFCore/Metadata/Internal/SkipNavigationComparer.cs b/src/EFCore/Metadata/Internal/SkipNavigationComparer.cs index 331c30c6ece..2ae23a5d405 100644 --- a/src/EFCore/Metadata/Internal/SkipNavigationComparer.cs +++ b/src/EFCore/Metadata/Internal/SkipNavigationComparer.cs @@ -37,6 +37,6 @@ public int Compare(IReadOnlySkipNavigation? x, IReadOnlySkipNavigation? y) (null, null) => 0, (not null, not null) => StringComparer.Ordinal.Compare(x.Name, y.Name) is var compare && compare != 0 ? compare - : EntityTypeFullNameComparer.Instance.Compare(x.DeclaringEntityType, y.DeclaringEntityType) + : TypeBaseNameComparer.Instance.Compare(x.DeclaringEntityType, y.DeclaringEntityType) }; } diff --git a/src/EFCore/Metadata/Internal/TypeBase.cs b/src/EFCore/Metadata/Internal/TypeBase.cs index 1041ff83716..e81666242a1 100644 --- a/src/EFCore/Metadata/Internal/TypeBase.cs +++ b/src/EFCore/Metadata/Internal/TypeBase.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.Internal; @@ -14,11 +15,21 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public abstract class TypeBase : ConventionAnnotatable, IMutableTypeBase, IConventionTypeBase, ITypeBase { - private ConfigurationSource _configurationSource; + private readonly SortedDictionary _properties; + + private readonly SortedDictionary _complexProperties = + new SortedDictionary(StringComparer.Ordinal); private readonly Dictionary _ignoredMembers = new(StringComparer.Ordinal); + private TypeBase? _baseType; + private readonly SortedSet _directlyDerivedTypes = new(TypeBaseNameComparer.Instance); + private ChangeTrackingStrategy? _changeTrackingStrategy; + + private ConfigurationSource _configurationSource; + private ConfigurationSource? _changeTrackingStrategyConfigurationSource; + private bool _indexerPropertyInitialized; private PropertyInfo? _indexerPropertyInfo; private SortedDictionary? _runtimeProperties; @@ -43,6 +54,7 @@ protected TypeBase( Name = model.GetDisplayName(type); HasSharedClrType = false; IsPropertyBag = type.IsPropertyBagType(); + _properties = new SortedDictionary(new PropertyNameComparer(this)); } /// @@ -63,6 +75,7 @@ protected TypeBase( _configurationSource = configurationSource; HasSharedClrType = true; IsPropertyBag = type.IsPropertyBagType(); + _properties = new SortedDictionary(new PropertyNameComparer(this)); } /// @@ -91,6 +104,14 @@ protected TypeBase( public override bool IsReadOnly => Model.IsReadOnly; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public abstract bool IsInModel { get; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -121,9 +142,7 @@ public override bool IsReadOnly /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - [DebuggerStepThrough] - public virtual ConfigurationSource GetConfigurationSource() - => _configurationSource; + public virtual InternalTypeBaseBuilder Builder => BaseBuilder; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -131,8 +150,7 @@ public virtual ConfigurationSource GetConfigurationSource() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual void UpdateConfigurationSource(ConfigurationSource configurationSource) - => _configurationSource = configurationSource.Max(_configurationSource); + protected abstract InternalTypeBaseBuilder BaseBuilder { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -140,25 +158,28 @@ public virtual void UpdateConfigurationSource(ConfigurationSource configurationS /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IReadOnlyDictionary GetRuntimeProperties() + protected virtual TypeBase? BaseType { - if (_runtimeProperties == null) - { - var runtimeProperties = new SortedDictionary(StringComparer.Ordinal); - foreach (var property in ClrType.GetRuntimeProperties()) - { - if (!property.IsStatic() - && !runtimeProperties.ContainsKey(property.Name)) - { - runtimeProperties[property.Name] = property; - } - } + get => _baseType; + set => _baseType = value; + } - Interlocked.CompareExchange(ref _runtimeProperties, runtimeProperties, null); - } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected virtual SortedSet DirectlyDerivedTypes => _directlyDerivedTypes; - return _runtimeProperties; - } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDerivedTypes() + => GetDerivedTypes(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -166,24 +187,27 @@ public virtual IReadOnlyDictionary GetRuntimeProperties() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IReadOnlyDictionary GetRuntimeFields() + protected virtual IEnumerable GetDerivedTypes() + where T : TypeBase { - if (_runtimeFields == null) + if (DirectlyDerivedTypes.Count == 0) { - var runtimeFields = new SortedDictionary(StringComparer.Ordinal); - foreach (var field in ClrType.GetRuntimeFields()) - { - if (!field.IsStatic - && !runtimeFields.ContainsKey(field.Name)) - { - runtimeFields[field.Name] = field; - } - } + return Enumerable.Empty(); + } - Interlocked.CompareExchange(ref _runtimeFields, runtimeFields, null); + var derivedTypes = new List(); + var type = (T)this; + var currentTypeIndex = 0; + while (type != null) + { + derivedTypes.AddRange(type.DirectlyDerivedTypes.Cast()); + type = derivedTypes.Count > currentTypeIndex + ? derivedTypes[currentTypeIndex] + : null; + currentTypeIndex++; } - return _runtimeFields; + return derivedTypes; } /// @@ -192,17 +216,44 @@ public virtual IReadOnlyDictionary GetRuntimeFields() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual PropertyInfo? FindIndexerPropertyInfo() + [DebuggerStepThrough] + public virtual IEnumerable GetDerivedTypesInclusive() + => DirectlyDerivedTypes.Count == 0 + ? new[] { this } + : new[] { this }.Concat(GetDerivedTypes()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsAssignableFrom(TypeBase derivedType) { - if (!_indexerPropertyInitialized) + Check.NotNull(derivedType, nameof(derivedType)); + + if (derivedType == this) { - var indexerPropertyInfo = ClrType.FindIndexerProperty(); + return true; + } - Interlocked.CompareExchange(ref _indexerPropertyInfo, indexerPropertyInfo, null); - _indexerPropertyInitialized = true; + if (DirectlyDerivedTypes.Count == 0) + { + return false; } - return _indexerPropertyInfo; + var baseType = derivedType.BaseType; + while (baseType != null) + { + if (baseType == this) + { + return true; + } + + baseType = baseType.BaseType; + } + + return false; } /// @@ -211,9 +262,9 @@ public virtual IReadOnlyDictionary GetRuntimeFields() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual PropertyAccessMode GetPropertyAccessMode() - => (PropertyAccessMode?)this[CoreAnnotationNames.PropertyAccessMode] - ?? Model.GetPropertyAccessMode(); + [DebuggerStepThrough] + public virtual ConfigurationSource GetConfigurationSource() + => _configurationSource; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -221,11 +272,8 @@ public virtual PropertyAccessMode GetPropertyAccessMode() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual PropertyAccessMode? SetPropertyAccessMode( - PropertyAccessMode? propertyAccessMode, - ConfigurationSource configurationSource) - => (PropertyAccessMode?)SetOrRemoveAnnotation( - CoreAnnotationNames.PropertyAccessMode, propertyAccessMode, configurationSource)?.Value; + public virtual void UpdateConfigurationSource(ConfigurationSource configurationSource) + => _configurationSource = configurationSource.Max(_configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -233,9 +281,7 @@ public virtual PropertyAccessMode GetPropertyAccessMode() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual PropertyAccessMode GetNavigationAccessMode() - => (PropertyAccessMode?)this[CoreAnnotationNames.NavigationAccessMode] - ?? GetPropertyAccessMode(); + public abstract IEnumerable GetMembers(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -243,11 +289,7 @@ public virtual PropertyAccessMode GetNavigationAccessMode() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual PropertyAccessMode? SetNavigationAccessMode( - PropertyAccessMode? propertyAccessMode, - ConfigurationSource configurationSource) - => (PropertyAccessMode?)SetOrRemoveAnnotation( - CoreAnnotationNames.NavigationAccessMode, propertyAccessMode, configurationSource)?.Value; + public abstract IEnumerable GetDeclaredMembers(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -255,29 +297,71 @@ public virtual PropertyAccessMode GetNavigationAccessMode() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual string? AddIgnored(string name, ConfigurationSource configurationSource) - { - Check.NotNull(name, nameof(name)); - EnsureMutable(); + public abstract IEnumerable FindMembersInHierarchy(string name); - if (_ignoredMembers.TryGetValue(name, out var existingIgnoredConfigurationSource)) + /// + /// 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 Type? ValidateClrMember(string name, MemberInfo memberInfo, bool throwOnNameMismatch = true) + { + if (name != memberInfo.GetSimpleMemberName()) { - _ignoredMembers[name] = configurationSource.Max(existingIgnoredConfigurationSource); - return name; - } + if (memberInfo != FindIndexerPropertyInfo()) + { + if (throwOnNameMismatch) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongName( + name, + DisplayName(), + memberInfo.GetSimpleMemberName())); + } - _ignoredMembers[name] = configurationSource; + return memberInfo.GetMemberType(); + } - return OnTypeMemberIgnored(name); + var clashingMemberInfo = IsPropertyBag + ? null + : ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + if (clashingMemberInfo != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyClashingNonIndexer( + name, + DisplayName())); + } + } + + return null; } + #region Properties + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public abstract string? OnTypeMemberIgnored(string name); + public virtual Property? AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + ConfigurationSource? typeConfigurationSource, + ConfigurationSource configurationSource) + { + Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); + + return AddProperty( + name, + propertyType, + null, + typeConfigurationSource, + configurationSource); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -285,8 +369,16 @@ public virtual PropertyAccessMode GetNavigationAccessMode() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IEnumerable GetIgnoredMembers() - => _ignoredMembers.Keys; + [RequiresUnreferencedCode("Use an overload that accepts a type")] + public virtual Property? AddProperty( + MemberInfo memberInfo, + ConfigurationSource configurationSource) + => AddProperty( + memberInfo.GetSimpleMemberName(), + memberInfo.GetMemberType(), + memberInfo, + configurationSource, + configurationSource); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -294,10 +386,27 @@ public virtual IEnumerable GetIgnoredMembers() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual ConfigurationSource? FindDeclaredIgnoredConfigurationSource(string name) - => _ignoredMembers.TryGetValue(Check.NotEmpty(name, nameof(name)), out var ignoredConfigurationSource) - ? ignoredConfigurationSource - : null; + [RequiresUnreferencedCode("Use an overload that accepts a type")] + public virtual Property? AddProperty( + string name, + ConfigurationSource configurationSource) + { + MemberInfo? clrMember; + if (IsPropertyBag) + { + clrMember = FindIndexerPropertyInfo()!; + } + else + { + clrMember = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + if (clrMember == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(name, DisplayName())); + } + } + + return AddProperty(clrMember, configurationSource); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -305,8 +414,83 @@ public virtual IEnumerable GetIgnoredMembers() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual ConfigurationSource? FindIgnoredConfigurationSource(string name) - => FindDeclaredIgnoredConfigurationSource(name); + public virtual Property? AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo? memberInfo, + ConfigurationSource? typeConfigurationSource, + ConfigurationSource configurationSource) + { + Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + EnsureMutable(); + + var conflictingMember = FindMembersInHierarchy(name).FirstOrDefault(); + if (conflictingMember != null) + { + throw new InvalidOperationException( + CoreStrings.ConflictingPropertyOrNavigation( + name, DisplayName(), + ((IReadOnlyTypeBase)conflictingMember.DeclaringType).DisplayName())); + } + + if (memberInfo != null) + { + propertyType = ValidateClrMember(name, memberInfo, typeConfigurationSource != null) + ?? propertyType; + + if (memberInfo.DeclaringType?.IsAssignableFrom(ClrType) != true) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongEntityClrType( + memberInfo.Name, DisplayName(), memberInfo.DeclaringType?.ShortDisplayName())); + } + } + else if (IsPropertyBag) + { + memberInfo = FindIndexerPropertyInfo(); + } + else + { + memberInfo = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + } + + if (memberInfo != null + && propertyType != memberInfo.GetMemberType() + && memberInfo != FindIndexerPropertyInfo()) + { + if (typeConfigurationSource != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongClrType( + name, + DisplayName(), + memberInfo.GetMemberType().ShortDisplayName(), + propertyType.ShortDisplayName())); + } + + propertyType = memberInfo.GetMemberType(); + } + + var property = new Property( + name, propertyType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, + configurationSource, typeConfigurationSource); + + _properties.Add(property.Name, property); + + if (Model.Configuration != null) + { + using (Model.ConventionDispatcher.DelayConventions()) + { + Model.ConventionDispatcher.OnPropertyAdded(property.Builder); + Model.Configuration.ConfigureProperty(property); + return property; + } + } + + return (Property?)Model.ConventionDispatcher.OnPropertyAdded(property.Builder)?.Metadata; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -314,8 +498,8 @@ public virtual IEnumerable GetIgnoredMembers() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool IsIgnored(string name) - => FindIgnoredConfigurationSource(name) != null; + public virtual Property? FindProperty(string name) + => FindDeclaredProperty(Check.NotEmpty(name, nameof(name))) ?? _baseType?.FindProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -323,13 +507,19 @@ public virtual bool IsIgnored(string name) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual string? RemoveIgnored(string name) - { - Check.NotNull(name, nameof(name)); - EnsureMutable(); + public virtual Property? FindDeclaredProperty(string name) + => _properties.TryGetValue(Check.NotEmpty(name, nameof(name)), out var property) + ? property + : null; - return _ignoredMembers.Remove(name) ? name : null; - } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDeclaredProperties() + => _properties.Values; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -337,11 +527,10 @@ public virtual bool IsIgnored(string name) /// 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. /// - IReadOnlyModel IReadOnlyTypeBase.Model - { - [DebuggerStepThrough] - get => Model; - } + public virtual IEnumerable GetDerivedProperties() + => _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : GetDerivedTypes().SelectMany(et => et.GetDeclaredProperties()); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -349,10 +538,13 @@ IReadOnlyModel IReadOnlyTypeBase.Model /// 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. /// - IMutableModel IMutableTypeBase.Model + public virtual IEnumerable FindDerivedProperties(string propertyName) { - [DebuggerStepThrough] - get => Model; + Check.NotNull(propertyName, nameof(propertyName)); + + return _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : (IEnumerable)GetDerivedTypes().Select(et => et.FindDeclaredProperty(propertyName)).Where(p => p != null); } /// @@ -361,11 +553,10 @@ IMutableModel IMutableTypeBase.Model /// 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. /// - IConventionModel IConventionTypeBase.Model - { - [DebuggerStepThrough] - get => Model; - } + public virtual IEnumerable FindDerivedPropertiesInclusive(string propertyName) + => _directlyDerivedTypes.Count == 0 + ? ToEnumerable(FindDeclaredProperty(propertyName)) + : ToEnumerable(FindDeclaredProperty(propertyName)).Concat(FindDerivedProperties(propertyName)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -373,11 +564,10 @@ IConventionModel IConventionTypeBase.Model /// 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. /// - IModel ITypeBase.Model - { - [DebuggerStepThrough] - get => Model; - } + public virtual IEnumerable FindPropertiesInHierarchy(string propertyName) + => _directlyDerivedTypes.Count == 0 + ? ToEnumerable(FindProperty(propertyName)) + : ToEnumerable(FindProperty(propertyName)).Concat(FindDerivedProperties(propertyName)); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -385,11 +575,23 @@ IModel ITypeBase.Model /// 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. /// - [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] - Type IReadOnlyTypeBase.ClrType + public virtual IReadOnlyList? FindProperties(IReadOnlyList propertyNames) { - [DebuggerStepThrough] - get => ClrType; + Check.NotNull(propertyNames, nameof(propertyNames)); + + var properties = new List(propertyNames.Count); + foreach (var propertyName in propertyNames) + { + var property = FindProperty(propertyName); + if (property == null) + { + return null; + } + + properties.Add(property); + } + + return properties; } /// @@ -398,9 +600,15 @@ Type IReadOnlyTypeBase.ClrType /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - [DebuggerStepThrough] - string? IMutableTypeBase.AddIgnored(string name) - => AddIgnored(name, ConfigurationSource.Explicit); + public virtual Property? RemoveProperty(string name) + { + Check.NotEmpty(name, nameof(name)); + + var property = FindDeclaredProperty(name); + return property == null + ? null + : RemoveProperty(property); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -408,7 +616,1336 @@ Type IReadOnlyTypeBase.ClrType /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - [DebuggerStepThrough] - string? IConventionTypeBase.AddIgnored(string name, bool fromDataAnnotation) - => AddIgnored(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + public virtual Property? RemoveProperty(Property property) + { + Check.NotNull(property, nameof(property)); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + EnsureMutable(); + + if (property.DeclaringType != this) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongType( + property.Name, + DisplayName(), + property.DeclaringType.DisplayName())); + } + + CheckPropertyNotInUse(property); + + var removed = _properties.Remove(property.Name); + Check.DebugAssert(removed, "removed is false"); + + property.SetRemovedFromModel(); + + return (Property?)Model.ConventionDispatcher.OnPropertyRemoved(BaseBuilder, property); + } + + private void CheckPropertyNotInUse(Property property) + { + var containingKey = property.Keys?.FirstOrDefault(); + if (containingKey != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyInUseKey(property.Name, DisplayName(), containingKey.Properties.Format())); + } + + var containingForeignKey = property.ForeignKeys?.FirstOrDefault(); + if (containingForeignKey != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyInUseForeignKey( + property.Name, DisplayName(), + containingForeignKey.Properties.Format(), containingForeignKey.DeclaringEntityType.DisplayName())); + } + + var containingIndex = property.Indexes?.FirstOrDefault(); + if (containingIndex != null) + { + throw new InvalidOperationException( + CoreStrings.PropertyInUseIndex( + property.Name, DisplayName(), + containingIndex.DisplayName(), containingIndex.DeclaringEntityType.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. + /// + public virtual IEnumerable GetProperties() + => _baseType != null + ? _baseType.GetProperties().Concat(_properties.Values) + : _properties.Values; + + /// + /// 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 SortedDictionary Properties => _properties; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyDictionary GetRuntimeProperties() + { + if (_runtimeProperties == null) + { + var runtimeProperties = new SortedDictionary(StringComparer.Ordinal); + foreach (var property in ClrType.GetRuntimeProperties()) + { + if (!property.IsStatic() + && !runtimeProperties.ContainsKey(property.Name)) + { + runtimeProperties[property.Name] = property; + } + } + + Interlocked.CompareExchange(ref _runtimeProperties, runtimeProperties, null); + } + + return _runtimeProperties; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyDictionary GetRuntimeFields() + { + if (_runtimeFields == null) + { + var runtimeFields = new SortedDictionary(StringComparer.Ordinal); + foreach (var field in ClrType.GetRuntimeFields()) + { + if (!field.IsStatic + && !runtimeFields.ContainsKey(field.Name)) + { + runtimeFields[field.Name] = field; + } + } + + Interlocked.CompareExchange(ref _runtimeFields, runtimeFields, null); + } + + return _runtimeFields; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual PropertyInfo? FindIndexerPropertyInfo() + { + if (!_indexerPropertyInitialized) + { + var indexerPropertyInfo = ClrType.FindIndexerProperty(); + + Interlocked.CompareExchange(ref _indexerPropertyInfo, indexerPropertyInfo, null); + _indexerPropertyInitialized = true; + } + + return _indexerPropertyInfo; + } + + #endregion + + #region Complex properties + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type targetType, + bool collection, + ConfigurationSource configurationSource) + { + Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); + Check.NotNull(targetType, nameof(targetType)); + + return AddComplexProperty( + name, + propertyType, + memberInfo: null, + targetType, + collection, + configurationSource); + } + + /// + /// 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. + /// + [RequiresUnreferencedCode("Use an overload that accepts a type")] + public virtual ComplexProperty? AddComplexProperty( + MemberInfo memberInfo, + bool collection, + ConfigurationSource configurationSource) + => AddComplexProperty( + memberInfo.GetSimpleMemberName(), + memberInfo.GetMemberType(), + memberInfo, + memberInfo.GetMemberType(), + collection, + configurationSource); + + /// + /// 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. + /// + [RequiresUnreferencedCode("Use an overload that accepts a type")] + public virtual ComplexProperty? AddComplexProperty( + string name, + bool collection, + ConfigurationSource configurationSource) + { + MemberInfo? clrMember; + if (IsPropertyBag) + { + clrMember = FindIndexerPropertyInfo()!; + } + else + { + clrMember = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + if (clrMember == null) + { + throw new InvalidOperationException(CoreStrings.NoPropertyType(name, DisplayName())); + } + } + + return AddComplexProperty(clrMember, collection, configurationSource); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? AddComplexProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo? memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type targetType, + bool collection, + ConfigurationSource configurationSource) + { + Check.NotNull(name, nameof(name)); + Check.NotNull(propertyType, nameof(propertyType)); + Check.NotNull(targetType, nameof(targetType)); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + EnsureMutable(); + + var conflictingMember = FindMembersInHierarchy(name).FirstOrDefault(); + if (conflictingMember != null) + { + throw new InvalidOperationException( + CoreStrings.ConflictingPropertyOrNavigation( + name, DisplayName(), + conflictingMember.DeclaringType.DisplayName())); + } + + if (memberInfo != null) + { + propertyType = ValidateClrMember(name, memberInfo) + ?? propertyType; + + if (memberInfo.DeclaringType?.IsAssignableFrom(ClrType) != true) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongEntityClrType( + memberInfo.Name, DisplayName(), memberInfo.DeclaringType?.ShortDisplayName())); + } + } + else if (IsPropertyBag) + { + memberInfo = FindIndexerPropertyInfo(); + } + else + { + memberInfo = ClrType.GetMembersInHierarchy(name).FirstOrDefault(); + } + + if (memberInfo != null) + { + if (propertyType != memberInfo.GetMemberType() + && memberInfo != FindIndexerPropertyInfo()) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongClrType( + name, + DisplayName(), + memberInfo.GetMemberType().ShortDisplayName(), + propertyType.ShortDisplayName())); + } + + ComplexProperty.IsCompatible( + name, + memberInfo, + this, + targetType, + collection, + shouldThrow: true); + } + + var property = new ComplexProperty( + name, propertyType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, + targetType, collection, configurationSource); + + _complexProperties.Add(property.Name, property); + + if (Model.Configuration != null) + { + using (Model.ConventionDispatcher.DelayConventions()) + { + property = (ComplexProperty)Model.ConventionDispatcher.OnComplexPropertyAdded(property.Builder)!.Metadata; + Model.Configuration.ConfigureComplexProperty(property); + return property; + } + } + + return (ComplexProperty?)Model.ConventionDispatcher.OnComplexPropertyAdded(property.Builder)?.Metadata; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? FindComplexProperty(string name) + => FindDeclaredComplexProperty(Check.NotEmpty(name, nameof(name))) ?? BaseType?.FindComplexProperty(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. + /// + public virtual ComplexProperty? FindDeclaredComplexProperty(string name) + => _complexProperties.TryGetValue(Check.NotEmpty(name, nameof(name)), out var property) + ? property + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDeclaredComplexProperties() + => _complexProperties.Values; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetDerivedComplexProperties() + => _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : GetDerivedTypes().SelectMany(et => et.GetDeclaredComplexProperties()); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindDerivedComplexProperties(string propertyName) + { + Check.NotNull(propertyName, nameof(propertyName)); + + return _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : (IEnumerable)GetDerivedTypes() + .Select(et => et.FindDeclaredComplexProperty(propertyName)).Where(p => p != null); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindDerivedComplexPropertiesInclusive(string propertyName) + => _directlyDerivedTypes.Count == 0 + ? ToEnumerable(FindDeclaredComplexProperty(propertyName)) + : ToEnumerable(FindDeclaredComplexProperty(propertyName)).Concat(FindDerivedComplexProperties(propertyName)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable FindComplexPropertiesInHierarchy(string propertyName) + => _directlyDerivedTypes.Count == 0 + ? ToEnumerable(FindComplexProperty(propertyName)) + : ToEnumerable(FindComplexProperty(propertyName)).Concat(FindDerivedComplexProperties(propertyName)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? RemoveComplexProperty(string name) + { + Check.NotEmpty(name, nameof(name)); + + var property = FindDeclaredComplexProperty(name); + return property == null + ? null + : RemoveComplexProperty(property); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ComplexProperty? RemoveComplexProperty(ComplexProperty property) + { + Check.NotNull(property, nameof(property)); + Check.DebugAssert(IsInModel, "The entity type has been removed from the model"); + EnsureMutable(); + + if (property.DeclaringType != this) + { + throw new InvalidOperationException( + CoreStrings.PropertyWrongType( + property.Name, + DisplayName(), + property.DeclaringType.DisplayName())); + } + + CheckPropertyNotInUse(property); + var removed = _complexProperties.Remove(property.Name); + Check.DebugAssert(removed, "removed is false"); + + property.SetRemovedFromModel(); + + return (ComplexProperty?)Model.ConventionDispatcher.OnComplexPropertyRemoved(BaseBuilder, property); + } + + private void CheckPropertyNotInUse(ComplexProperty property) + { + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IEnumerable GetComplexProperties() + => BaseType != null + ? BaseType.GetComplexProperties().Concat(_complexProperties.Values) + : _complexProperties.Values; + + #endregion + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual PropertyAccessMode GetPropertyAccessMode() + => (PropertyAccessMode?)this[CoreAnnotationNames.PropertyAccessMode] + ?? Model.GetPropertyAccessMode(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual PropertyAccessMode? SetPropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + ConfigurationSource configurationSource) + => (PropertyAccessMode?)SetOrRemoveAnnotation( + CoreAnnotationNames.PropertyAccessMode, propertyAccessMode, configurationSource)?.Value; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public virtual ChangeTrackingStrategy GetChangeTrackingStrategy() + => _changeTrackingStrategy ?? Model.GetChangeTrackingStrategy(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ChangeTrackingStrategy? SetChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + ConfigurationSource configurationSource) + { + EnsureMutable(); + + if (changeTrackingStrategy != null) + { + var requireFullNotifications = + (bool?)Model[CoreAnnotationNames.FullChangeTrackingNotificationsRequired] == true; + var errorMessage = CheckChangeTrackingStrategy(this, changeTrackingStrategy.Value, requireFullNotifications); + if (errorMessage != null) + { + throw new InvalidOperationException(errorMessage); + } + } + + _changeTrackingStrategy = changeTrackingStrategy; + + _changeTrackingStrategyConfigurationSource = _changeTrackingStrategy == null + ? null + : configurationSource.Max(_changeTrackingStrategyConfigurationSource); + + return changeTrackingStrategy; + } + + /// + /// 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? CheckChangeTrackingStrategy( + IReadOnlyTypeBase typeBase, + ChangeTrackingStrategy value, + bool requireFullNotifications) + { + if (requireFullNotifications) + { + if (value != ChangeTrackingStrategy.ChangingAndChangedNotifications + && value != ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues) + { + return CoreStrings.FullChangeTrackingRequired( + typeBase.DisplayName(), value, nameof(ChangeTrackingStrategy.ChangingAndChangedNotifications), + nameof(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues)); + } + } + else + { + if (value != ChangeTrackingStrategy.Snapshot + && !typeof(INotifyPropertyChanged).IsAssignableFrom(typeBase.ClrType)) + { + return CoreStrings.ChangeTrackingInterfaceMissing(typeBase.DisplayName(), value, nameof(INotifyPropertyChanged)); + } + + if ((value == ChangeTrackingStrategy.ChangingAndChangedNotifications + || value == ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues) + && !typeof(INotifyPropertyChanging).IsAssignableFrom(typeBase.ClrType)) + { + return CoreStrings.ChangeTrackingInterfaceMissing(typeBase.DisplayName(), value, nameof(INotifyPropertyChanging)); + } + } + + return null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetChangeTrackingStrategyConfigurationSource() + => _changeTrackingStrategyConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? AddIgnored(string name, ConfigurationSource configurationSource) + { + Check.NotNull(name, nameof(name)); + EnsureMutable(); + + if (_ignoredMembers.TryGetValue(name, out var existingIgnoredConfigurationSource)) + { + _ignoredMembers[name] = configurationSource.Max(existingIgnoredConfigurationSource); + return name; + } + + _ignoredMembers[name] = configurationSource; + + return OnTypeMemberIgnored(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. + /// + public abstract string? OnTypeMemberIgnored(string 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. + /// + public virtual IEnumerable GetIgnoredMembers() + => _ignoredMembers.Keys; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? FindDeclaredIgnoredConfigurationSource(string name) + => _ignoredMembers.TryGetValue(Check.NotEmpty(name, nameof(name)), out var ignoredConfigurationSource) + ? ignoredConfigurationSource + : null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? FindIgnoredConfigurationSource(string name) + { + var ignoredSource = FindDeclaredIgnoredConfigurationSource(name); + + return BaseType == null ? ignoredSource : BaseType.FindIgnoredConfigurationSource(name).Max(ignoredSource); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool IsIgnored(string name) + => FindIgnoredConfigurationSource(name) != null; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string? RemoveIgnored(string name) + { + Check.NotNull(name, nameof(name)); + EnsureMutable(); + + return _ignoredMembers.Remove(name) ? name : null; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + private string DisplayName() + => ((IReadOnlyTypeBase)this).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 static IEnumerable ToEnumerable(T? element) + where T : class + => element == null + ? Enumerable.Empty() + : new[] { element }; + + /// + /// 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. + /// + IReadOnlyModel IReadOnlyTypeBase.Model + { + [DebuggerStepThrough] + get => 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. + /// + IMutableModel IMutableTypeBase.Model + { + [DebuggerStepThrough] + get => 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. + /// + IConventionModel IConventionTypeBase.Model + { + [DebuggerStepThrough] + get => 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. + /// + IModel ITypeBase.Model + { + [DebuggerStepThrough] + get => 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. + /// + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] + Type IReadOnlyTypeBase.ClrType + { + [DebuggerStepThrough] + get => ClrType; + } + + /// + /// 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. + /// + IConventionTypeBaseBuilder IConventionTypeBase.Builder + { + [DebuggerStepThrough] + get => BaseBuilder; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + string? IMutableTypeBase.AddIgnored(string name) + => AddIgnored(name, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + string? IConventionTypeBase.AddIgnored(string name, bool fromDataAnnotation) + => AddIgnored(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableProperty IMutableTypeBase.AddProperty(string name) + => AddProperty(name, ConfigurationSource.Explicit)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionProperty? IConventionTypeBase.AddProperty(string name, bool fromDataAnnotation) + => AddProperty( + name, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableProperty IMutableTypeBase.AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType) + => AddProperty( + name, + propertyType, + ConfigurationSource.Explicit, + ConfigurationSource.Explicit)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionProperty? IConventionTypeBase.AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + bool setTypeConfigurationSource, + bool fromDataAnnotation) + => AddProperty( + name, + propertyType, + setTypeConfigurationSource + ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention + : null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableProperty IMutableTypeBase.AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo memberInfo) + => AddProperty( + name, propertyType, memberInfo, + ConfigurationSource.Explicit, ConfigurationSource.Explicit)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionProperty? IConventionTypeBase.AddProperty( + string name, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, + MemberInfo? memberInfo, + bool setTypeConfigurationSource, + bool fromDataAnnotation) + => AddProperty( + name, + propertyType, + memberInfo, + setTypeConfigurationSource + ? fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention + : null, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IReadOnlyProperty? IReadOnlyTypeBase.FindDeclaredProperty(string name) + => FindDeclaredProperty(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. + /// + [DebuggerStepThrough] + IProperty? ITypeBase.FindDeclaredProperty(string name) + => FindDeclaredProperty(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. + /// + [DebuggerStepThrough] + IReadOnlyList? IReadOnlyTypeBase.FindProperties(IReadOnlyList propertyNames) + => FindProperties(propertyNames); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IReadOnlyProperty? IReadOnlyTypeBase.FindProperty(string name) + => FindProperty(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. + /// + [DebuggerStepThrough] + IMutableProperty? IMutableTypeBase.FindProperty(string name) + => FindProperty(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. + /// + [DebuggerStepThrough] + IConventionProperty? IConventionTypeBase.FindProperty(string name) + => FindProperty(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. + /// + [DebuggerStepThrough] + IProperty? ITypeBase.FindProperty(string name) + => FindProperty(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. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDeclaredProperties() + => GetDeclaredProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IMutableTypeBase.GetDeclaredProperties() + => GetDeclaredProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IConventionTypeBase.GetDeclaredProperties() + => GetDeclaredProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetDeclaredProperties() + => GetDeclaredProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDerivedProperties() + => GetDerivedProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetProperties() + => GetProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IMutableTypeBase.GetProperties() + => GetProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IConventionTypeBase.GetProperties() + => GetProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetProperties() + => GetProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableProperty? IMutableTypeBase.RemoveProperty(string name) + => RemoveProperty(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. + /// + [DebuggerStepThrough] + IConventionProperty? IConventionTypeBase.RemoveProperty(string name) + => RemoveProperty(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. + /// + [DebuggerStepThrough] + IMutableProperty? IMutableTypeBase.RemoveProperty(IReadOnlyProperty property) + => RemoveProperty((Property)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. + /// + [DebuggerStepThrough] + IConventionProperty? IConventionTypeBase.RemoveProperty(IReadOnlyProperty property) + => RemoveProperty((Property)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. + /// + [DebuggerStepThrough] + IMutableComplexProperty IMutableTypeBase.AddComplexProperty(string name, bool collection) + => AddComplexProperty(name, collection, ConfigurationSource.Explicit)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionTypeBase.AddComplexProperty(string name, bool collection, bool fromDataAnnotation) + => AddComplexProperty( + name, + collection, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableComplexProperty IMutableTypeBase.AddComplexProperty( + string name, Type propertyType, Type targetType, bool collection) + => AddComplexProperty(name, propertyType, targetType, collection, ConfigurationSource.Explicit)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionTypeBase.AddComplexProperty( + string name, Type propertyType, Type targetType, bool collection, bool fromDataAnnotation) + => AddComplexProperty(name, propertyType, targetType, collection, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableComplexProperty IMutableTypeBase.AddComplexProperty( + string name, Type propertyType, MemberInfo memberInfo, Type targetType, bool collection) + => AddComplexProperty(name, propertyType, memberInfo, targetType, collection, ConfigurationSource.Explicit)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionTypeBase.AddComplexProperty( + string name, Type propertyType, MemberInfo memberInfo, Type targetType, bool collection, bool fromDataAnnotation) + => AddComplexProperty(name, propertyType, memberInfo, targetType, collection, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IReadOnlyComplexProperty? IReadOnlyTypeBase.FindComplexProperty(string name) + => FindComplexProperty(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. + /// + [DebuggerStepThrough] + IMutableComplexProperty? IMutableTypeBase.FindComplexProperty(string name) + => FindComplexProperty(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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionTypeBase.FindComplexProperty(string name) + => FindComplexProperty(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. + /// + [DebuggerStepThrough] + IComplexProperty? ITypeBase.FindComplexProperty(string name) + => FindComplexProperty(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. + /// + [DebuggerStepThrough] + IReadOnlyComplexProperty? IReadOnlyTypeBase.FindDeclaredComplexProperty(string name) + => FindDeclaredComplexProperty(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. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetComplexProperties() + => GetComplexProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IMutableTypeBase.GetComplexProperties() + => GetComplexProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IConventionTypeBase.GetComplexProperties() + => GetComplexProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetComplexProperties() + => GetComplexProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IMutableTypeBase.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IConventionTypeBase.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDerivedComplexProperties() + => GetDerivedComplexProperties(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + IMutableComplexProperty? IMutableTypeBase.RemoveComplexProperty(string name) + => RemoveComplexProperty(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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionTypeBase.RemoveComplexProperty(string name) + => RemoveComplexProperty(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. + /// + [DebuggerStepThrough] + IMutableComplexProperty? IMutableTypeBase.RemoveComplexProperty(IReadOnlyProperty property) + => RemoveComplexProperty((ComplexProperty)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. + /// + [DebuggerStepThrough] + IConventionComplexProperty? IConventionTypeBase.RemoveComplexProperty(IConventionComplexProperty property) + => RemoveComplexProperty((ComplexProperty)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. + /// + [DebuggerStepThrough] + void IMutableTypeBase.SetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy) + => SetChangeTrackingStrategy(changeTrackingStrategy, ConfigurationSource.Explicit); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + ChangeTrackingStrategy? IConventionTypeBase.SetChangeTrackingStrategy( + ChangeTrackingStrategy? changeTrackingStrategy, + bool fromDataAnnotation) + => SetChangeTrackingStrategy( + changeTrackingStrategy, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs b/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs index 972ef62545d..6f35fb8f09c 100644 --- a/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs +++ b/src/EFCore/Metadata/Internal/TypeBaseExtensions.cs @@ -11,6 +11,34 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// public static class TypeBaseExtensions { + /// + /// 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 DisplayName(this TypeBase entityType) + => ((IReadOnlyTypeBase)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. + /// + public static string ShortName(this TypeBase entityType) + => ((IReadOnlyTypeBase)entityType).ShortName(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [DebuggerStepThrough] + public static string GetOwnedName(this IReadOnlyTypeBase type, string simpleName, string ownershipNavigation) + => type.Name + "." + ownershipNavigation + "#" + simpleName; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/Internal/TypeConfigurationType.cs b/src/EFCore/Metadata/Internal/TypeConfigurationType.cs index 60ad541dc3e..0f1313bae28 100644 --- a/src/EFCore/Metadata/Internal/TypeConfigurationType.cs +++ b/src/EFCore/Metadata/Internal/TypeConfigurationType.cs @@ -43,6 +43,14 @@ public enum TypeConfigurationType /// OwnedEntityType, + /// + /// 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. + /// + ComplexType, + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/KeyComparer.cs b/src/EFCore/Metadata/KeyComparer.cs index fe94c47a6ab..f71356b02eb 100644 --- a/src/EFCore/Metadata/KeyComparer.cs +++ b/src/EFCore/Metadata/KeyComparer.cs @@ -39,7 +39,7 @@ private KeyComparer() public int Compare(IReadOnlyKey? x, IReadOnlyKey? y) { var result = PropertyListComparer.Instance.Compare(x?.Properties, y?.Properties); - return result != 0 ? result : EntityTypeFullNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); + return result != 0 ? result : TypeBaseNameComparer.Instance.Compare(x?.DeclaringEntityType, y?.DeclaringEntityType); } /// @@ -60,7 +60,7 @@ public int GetHashCode(IReadOnlyKey obj) { var hashCode = new HashCode(); hashCode.Add(obj.Properties, PropertyListComparer.Instance); - hashCode.Add(obj.DeclaringEntityType, EntityTypeFullNameComparer.Instance); + hashCode.Add(obj.DeclaringEntityType, TypeBaseNameComparer.Instance); return hashCode.ToHashCode(); } } diff --git a/src/EFCore/Metadata/RuntimeComplexProperty.cs b/src/EFCore/Metadata/RuntimeComplexProperty.cs new file mode 100644 index 00000000000..60378ab7d6b --- /dev/null +++ b/src/EFCore/Metadata/RuntimeComplexProperty.cs @@ -0,0 +1,129 @@ +// 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; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of an entity type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class RuntimeComplexProperty : RuntimePropertyBase, IComplexProperty +{ + private readonly bool _isNullable; + private readonly bool _isCollection; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public RuntimeComplexProperty( + string name, + Type clrType, + string targetTypeName, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type targetType, + PropertyInfo? propertyInfo, + FieldInfo? fieldInfo, + RuntimeTypeBase declaringType, + PropertyAccessMode propertyAccessMode, + bool nullable, + bool collection, + ChangeTrackingStrategy changeTrackingStrategy, + PropertyInfo? indexerPropertyInfo, + bool propertyBag) + : base(name, propertyInfo, fieldInfo, propertyAccessMode) + { + DeclaringType = declaringType; + ClrType = clrType; + _isNullable = nullable; + _isCollection = collection; + ComplexType = new RuntimeComplexType( + targetTypeName, targetType, this, changeTrackingStrategy, indexerPropertyInfo, propertyBag); + } + + /// + /// Gets the type of value that this property-like object holds. + /// + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] + protected override Type ClrType { get; } + + /// + /// Gets the type that this property belongs to. + /// + public override RuntimeTypeBase DeclaringType { get; } + + /// + /// Gets the type of value that this property-like object holds. + /// + public virtual RuntimeComplexType ComplexType { get; } + + /// + public override object? Sentinel => null; + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + => ((IProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public virtual DebugView DebugView + => new( + () => ((IProperty)this).ToDebugString(), + () => ((IProperty)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + IReadOnlyTypeBase IReadOnlyPropertyBase.DeclaringType + { + [DebuggerStepThrough] + get => DeclaringType; + } + + /// + ITypeBase IPropertyBase.DeclaringType + { + [DebuggerStepThrough] + get => DeclaringType; + } + /// + + IComplexType IComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + IReadOnlyComplexType IReadOnlyComplexProperty.ComplexType + { + [DebuggerStepThrough] + get => ComplexType; + } + + /// + bool IReadOnlyComplexProperty.IsNullable + { + [DebuggerStepThrough] + get => _isNullable; + } + + /// + bool IReadOnlyComplexProperty.IsCollection + { + [DebuggerStepThrough] + get => _isCollection; + } +} diff --git a/src/EFCore/Metadata/RuntimeComplexType.cs b/src/EFCore/Metadata/RuntimeComplexType.cs new file mode 100644 index 00000000000..598f29369a4 --- /dev/null +++ b/src/EFCore/Metadata/RuntimeComplexType.cs @@ -0,0 +1,178 @@ +// 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 Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents an entity type in a model. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class RuntimeComplexType : RuntimeTypeBase, IRuntimeComplexType +{ + private InstantiationBinding? _constructorBinding; + private InstantiationBinding? _serviceOnlyConstructorBinding; + + // Warning: Never access these fields directly as access needs to be thread-safe + private PropertyCounts? _counts; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public RuntimeComplexType( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + RuntimeComplexProperty complexProperty, + ChangeTrackingStrategy changeTrackingStrategy, + PropertyInfo? indexerPropertyInfo, + bool propertyBag) + : base(name, type, complexProperty.DeclaringType.Model, null, changeTrackingStrategy, indexerPropertyInfo, propertyBag) + { + ComplexProperty = complexProperty; + FundamentalEntityType = complexProperty.DeclaringType switch + { + RuntimeEntityType entityType => entityType, + RuntimeComplexType declaringComplexType => declaringComplexType.FundamentalEntityType, + _ => throw new NotImplementedException() + }; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual RuntimeComplexProperty ComplexProperty { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + private RuntimeEntityType FundamentalEntityType { get; } + + /// + /// Gets or sets the for the preferred constructor. + /// + public virtual InstantiationBinding? ConstructorBinding + { + get => !ClrType.IsAbstract + ? NonCapturingLazyInitializer.EnsureInitialized( + ref _constructorBinding, this, static complexType => + { + ((IModel)complexType.Model).GetModelDependencies().ConstructorBindingFactory.GetBindings( + complexType, + out complexType._constructorBinding, + out complexType._serviceOnlyConstructorBinding); + }) + : _constructorBinding; + + [DebuggerStepThrough] + set => _constructorBinding = value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public virtual InstantiationBinding? ServiceOnlyConstructorBinding + { + [DebuggerStepThrough] + get => _serviceOnlyConstructorBinding; + + [DebuggerStepThrough] + set => _serviceOnlyConstructorBinding = value; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override PropertyCounts Counts + => NonCapturingLazyInitializer.EnsureInitialized(ref _counts, this, static complexType => complexType.CalculateCounts()); + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + => ((IReadOnlyEntityType)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public virtual DebugView DebugView + => new( + () => ((IReadOnlyComplexType)this).ToDebugString(), + () => ((IReadOnlyComplexType)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + + /// + bool IReadOnlyTypeBase.HasSharedClrType + { + [DebuggerStepThrough] + get => true; + } + + /// + IReadOnlyModel IReadOnlyTypeBase.Model + { + [DebuggerStepThrough] + get => Model; + } + + /// + IModel ITypeBase.Model + { + [DebuggerStepThrough] + get => Model; + } + + /// + IReadOnlyComplexProperty IReadOnlyComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + IComplexProperty IComplexType.ComplexProperty + { + [DebuggerStepThrough] + get => ComplexProperty; + } + + /// + IReadOnlyEntityType IReadOnlyTypeBase.FundamentalEntityType + { + [DebuggerStepThrough] + get => FundamentalEntityType; + } + + /// + IEntityType ITypeBase.FundamentalEntityType + { + [DebuggerStepThrough] + get => FundamentalEntityType; + } +} diff --git a/src/EFCore/Metadata/RuntimeEntityType.cs b/src/EFCore/Metadata/RuntimeEntityType.cs index 4aedc345306..161697c4a9a 100644 --- a/src/EFCore/Metadata/RuntimeEntityType.cs +++ b/src/EFCore/Metadata/RuntimeEntityType.cs @@ -5,7 +5,6 @@ using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage.Json; namespace Microsoft.EntityFrameworkCore.Metadata; @@ -15,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// See Modeling entity types and relationships for more information and examples. /// -public class RuntimeEntityType : AnnotatableBase, IRuntimeEntityType +public class RuntimeEntityType : RuntimeTypeBase, IRuntimeEntityType { private readonly List _foreignKeys = new(); @@ -28,8 +27,6 @@ private readonly SortedDictionary _skipNavigation private readonly SortedDictionary _serviceProperties = new(StringComparer.Ordinal); - private readonly SortedDictionary _properties; - private readonly SortedDictionary, RuntimeIndex> _unnamedIndexes = new(PropertyListComparer.Instance); @@ -45,16 +42,8 @@ private readonly SortedDictionary _triggers private RuntimeKey? _primaryKey; private readonly bool _hasSharedClrType; - [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] - private readonly Type _clrType; - - private readonly RuntimeEntityType? _baseType; - private readonly SortedSet _directlyDerivedTypes = new(EntityTypeFullNameComparer.Instance); - private readonly ChangeTrackingStrategy _changeTrackingStrategy; private InstantiationBinding? _constructorBinding; private InstantiationBinding? _serviceOnlyConstructorBinding; - private readonly PropertyInfo? _indexerPropertyInfo; - private readonly bool _isPropertyBag; private readonly object? _discriminatorValue; private bool _hasServiceProperties; @@ -62,11 +51,6 @@ private readonly SortedDictionary _triggers private PropertyCounts? _counts; private Func? _relationshipSnapshotFactory; - private Func? _originalValuesFactory; - private Func? _temporaryValuesFactory; - private Func? _storeGeneratedValuesFactory; - private Func? _shadowValuesFactory; - private Func? _emptyShadowValuesFactory; private IProperty[]? _foreignKeyProperties; private IProperty[]? _valueGeneratingProperties; @@ -88,35 +72,15 @@ public RuntimeEntityType( PropertyInfo? indexerPropertyInfo, bool propertyBag, object? discriminatorValue) + : base(name, type, model, baseType, changeTrackingStrategy, indexerPropertyInfo, propertyBag) { - Name = name; - _clrType = type; _hasSharedClrType = sharedClrType; - Model = model; - if (baseType != null) - { - _baseType = baseType; - baseType._directlyDerivedTypes.Add(this); - } - _changeTrackingStrategy = changeTrackingStrategy; - _indexerPropertyInfo = indexerPropertyInfo; - _isPropertyBag = propertyBag; SetAnnotation(CoreAnnotationNames.DiscriminatorProperty, discriminatorProperty); _discriminatorValue = discriminatorValue; - - _properties = new SortedDictionary(new PropertyNameComparer(this)); } - /// - /// Gets the name of this type. - /// - public virtual string Name { [DebuggerStepThrough] get; } - - /// - /// Gets the model that this type belongs to. - /// - public virtual RuntimeModel Model { [DebuggerStepThrough] get; private set; } + private new RuntimeEntityType? BaseType => (RuntimeEntityType?)base.BaseType; /// /// Re-parents this entity type to the given model. @@ -125,30 +89,8 @@ public RuntimeEntityType( public virtual void Reparent(RuntimeModel model) => Model = model; - private IEnumerable GetDerivedTypes() - { - if (_directlyDerivedTypes.Count == 0) - { - return Enumerable.Empty(); - } - - var derivedTypes = new List(); - var type = this; - var currentTypeIndex = 0; - while (type != null) - { - derivedTypes.AddRange(type._directlyDerivedTypes); - type = derivedTypes.Count > currentTypeIndex - ? derivedTypes[currentTypeIndex] - : null; - currentTypeIndex++; - } - - return derivedTypes; - } - private RuntimeKey? FindPrimaryKey() - => _baseType?.FindPrimaryKey() ?? _primaryKey; + => BaseType?.FindPrimaryKey() ?? _primaryKey; /// /// Sets the primary key for this entity type. @@ -158,7 +100,7 @@ public virtual void SetPrimaryKey(RuntimeKey key) { foreach (var property in key.Properties) { - _properties.Remove(property.Name); + Properties.Remove(property.Name); property.PrimaryKey = key; } @@ -166,7 +108,7 @@ public virtual void SetPrimaryKey(RuntimeKey key) foreach (var property in key.Properties) { - _properties.Add(property.Name, property); + Properties.Add(property.Name, property); } } @@ -204,13 +146,13 @@ public virtual RuntimeKey AddKey(IReadOnlyList properties) public virtual RuntimeKey? FindKey(IReadOnlyList properties) => _keys.TryGetValue(properties, out var key) ? key - : _baseType?.FindKey(properties); + : BaseType?.FindKey(properties); private IEnumerable GetDeclaredKeys() => _keys.Values; private IEnumerable GetKeys() - => _baseType?.GetKeys().Concat(_keys.Values) ?? _keys.Values; + => BaseType?.GetKeys().Concat(_keys.Values) ?? _keys.Values; /// /// Adds a new relationship to this entity type. @@ -281,10 +223,10 @@ public virtual RuntimeForeignKey AddForeignKey( } private IEnumerable FindForeignKeys(IReadOnlyList properties) - => _baseType != null + => BaseType != null ? _foreignKeys.Count == 0 - ? _baseType.FindForeignKeys(properties) - : _baseType.FindForeignKeys(properties).Concat(FindDeclaredForeignKeys(properties)) + ? BaseType.FindForeignKeys(properties) + : BaseType.FindForeignKeys(properties).Concat(FindDeclaredForeignKeys(properties)) : FindDeclaredForeignKeys(properties); /// @@ -304,18 +246,18 @@ private IEnumerable FindForeignKeys(IReadOnlyList FindDeclaredForeignKey(properties, principalKey, principalEntityType) - ?? _baseType?.FindForeignKey(properties, principalKey, principalEntityType); + ?? BaseType?.FindForeignKey(properties, principalKey, principalEntityType); private IEnumerable GetDerivedForeignKeys() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et._foreignKeys); + : GetDerivedTypes().Cast().SelectMany(et => et._foreignKeys); private IEnumerable GetForeignKeys() - => _baseType != null + => BaseType != null ? _foreignKeys.Count == 0 - ? _baseType.GetForeignKeys() - : _baseType.GetForeignKeys().Concat(_foreignKeys) + ? BaseType.GetForeignKeys() + : BaseType.GetForeignKeys().Concat(_foreignKeys) : _foreignKeys; /// @@ -351,10 +293,10 @@ public virtual IEnumerable FindDeclaredForeignKeys(IReadOnlyL } private IEnumerable GetReferencingForeignKeys() - => _baseType != null + => BaseType != null ? (DeclaredReferencingForeignKeys?.Count ?? 0) == 0 - ? _baseType.GetReferencingForeignKeys() - : _baseType.GetReferencingForeignKeys().Concat(GetDeclaredReferencingForeignKeys()) + ? BaseType.GetReferencingForeignKeys() + : BaseType.GetReferencingForeignKeys().Concat(GetDeclaredReferencingForeignKeys()) : GetDeclaredReferencingForeignKeys(); private IEnumerable GetDeclaredReferencingForeignKeys() @@ -415,8 +357,8 @@ private IEnumerable GetDeclaredNavigations() => _navigations.Values; private IEnumerable GetNavigations() - => _baseType != null - ? _navigations.Count == 0 ? _baseType.GetNavigations() : _baseType.GetNavigations().Concat(_navigations.Values) + => BaseType != null + ? _navigations.Count == 0 ? BaseType.GetNavigations() : BaseType.GetNavigations().Concat(_navigations.Values) : _navigations.Values; /// @@ -474,7 +416,7 @@ public virtual RuntimeSkipNavigation AddSkipNavigation( /// The name of the navigation property on the entity class. /// The navigation property, or if none is found. public virtual RuntimeSkipNavigation? FindSkipNavigation(string name) - => FindDeclaredSkipNavigation(name) ?? _baseType?.FindSkipNavigation(name); + => FindDeclaredSkipNavigation(name) ?? BaseType?.FindSkipNavigation(name); private RuntimeSkipNavigation? FindSkipNavigation(MemberInfo memberInfo) => FindSkipNavigation(memberInfo.GetSimpleMemberName()); @@ -488,15 +430,15 @@ private IEnumerable GetDeclaredSkipNavigations() => _skipNavigations.Values; private IEnumerable GetDerivedSkipNavigations() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.GetDeclaredSkipNavigations()); + : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredSkipNavigations()); private IEnumerable GetSkipNavigations() - => _baseType != null + => BaseType != null ? _skipNavigations.Count == 0 - ? _baseType.GetSkipNavigations() - : _baseType.GetSkipNavigations().Concat(_skipNavigations.Values) + ? BaseType.GetSkipNavigations() + : BaseType.GetSkipNavigations().Concat(_skipNavigations.Values) : _skipNavigations.Values; /// @@ -547,7 +489,7 @@ public virtual RuntimeIndex AddIndex( public virtual RuntimeIndex? FindIndex(IReadOnlyList properties) => _unnamedIndexes.TryGetValue(properties, out var index) ? index - : _baseType?.FindIndex(properties); + : BaseType?.FindIndex(properties); /// /// Gets the index with the given name. Returns if no such index exists. @@ -557,7 +499,7 @@ public virtual RuntimeIndex AddIndex( public virtual RuntimeIndex? FindIndex(string name) => _namedIndexes.TryGetValue(name, out var index) ? index - : _baseType?.FindIndex(name); + : BaseType?.FindIndex(name); private IEnumerable GetDeclaredIndexes() => _namedIndexes.Count == 0 @@ -565,166 +507,17 @@ private IEnumerable GetDeclaredIndexes() : _unnamedIndexes.Values.Concat(_namedIndexes.Values); private IEnumerable GetDerivedIndexes() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.GetDeclaredIndexes()); + : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredIndexes()); private IEnumerable GetIndexes() - => _baseType != null + => BaseType != null ? _namedIndexes.Count == 0 && _unnamedIndexes.Count == 0 - ? _baseType.GetIndexes() - : _baseType.GetIndexes().Concat(GetDeclaredIndexes()) + ? BaseType.GetIndexes() + : BaseType.GetIndexes().Concat(GetDeclaredIndexes()) : GetDeclaredIndexes(); - /// - /// Adds a property to this entity type. - /// - /// The name of the property to add. - /// The type of value the property will hold. - /// The property value to use to consider the property not set. - /// The corresponding CLR property or for a shadow property. - /// The corresponding CLR field or for a shadow property. - /// The used for this property. - /// A value indicating whether this property can contain . - /// A value indicating whether this property is used as a concurrency token. - /// A value indicating when a value for this property will be generated by the database. - /// - /// A value indicating whether or not this property can be modified before the entity is saved to the database. - /// - /// - /// A value indicating whether or not this property can be modified after the entity is saved to the database. - /// - /// The maximum length of data that is allowed in this property. - /// A value indicating whether or not the property can persist Unicode characters. - /// The precision of data that is allowed in this property. - /// The scale of data that is allowed in this property. - /// - /// The type that the property value will be converted to before being sent to the database provider. - /// - /// The factory that has been set to generate values for this property, if any. - /// The custom set for this property. - /// The for this property. - /// The to use with keys for this property. - /// The to use for the provider values for this property. - /// The for this property. - /// The for this property. - /// The newly created property. - public virtual RuntimeProperty AddProperty( - string name, - Type clrType, - object? sentinel = null, - PropertyInfo? propertyInfo = null, - FieldInfo? fieldInfo = null, - PropertyAccessMode propertyAccessMode = Internal.Model.DefaultPropertyAccessMode, - bool nullable = false, - bool concurrencyToken = false, - ValueGenerated valueGenerated = ValueGenerated.Never, - PropertySaveBehavior beforeSaveBehavior = PropertySaveBehavior.Save, - PropertySaveBehavior afterSaveBehavior = PropertySaveBehavior.Save, - int? maxLength = null, - bool? unicode = null, - int? precision = null, - int? scale = null, - Type? providerPropertyType = null, - Func? valueGeneratorFactory = null, - ValueConverter? valueConverter = null, - ValueComparer? valueComparer = null, - ValueComparer? keyValueComparer = null, - ValueComparer? providerValueComparer = null, - JsonValueReaderWriter? jsonValueReaderWriter = null, - CoreTypeMapping? typeMapping = null) - { - var property = new RuntimeProperty( - name, - clrType, - sentinel, - propertyInfo, - fieldInfo, - this, - propertyAccessMode, - nullable, - concurrencyToken, - valueGenerated, - beforeSaveBehavior, - afterSaveBehavior, - maxLength, - unicode, - precision, - scale, - providerPropertyType, - valueGeneratorFactory, - valueConverter, - valueComparer, - keyValueComparer, - providerValueComparer, - jsonValueReaderWriter, - typeMapping); - - _properties.Add(property.Name, property); - - return property; - } - - /// - /// Gets the property with a given name. Returns if no property with the given name is defined. - /// - /// - /// This API only finds scalar properties and does not find navigation properties. Use - /// to find a navigation property. - /// - /// The name of the property. - /// The property, or if none is found. - public virtual RuntimeProperty? FindProperty(string name) - => FindDeclaredProperty(name) ?? _baseType?.FindProperty(name); - - private RuntimeProperty? FindDeclaredProperty(string name) - => _properties.TryGetValue(name, out var property) - ? property - : null; - - private IEnumerable GetDeclaredProperties() - => _properties.Values; - - private IEnumerable GetDerivedProperties() - => _directlyDerivedTypes.Count == 0 - ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.GetDeclaredProperties()); - - /// - /// Finds matching properties on the given entity type. Returns if any property is not found. - /// - /// - /// This API only finds scalar properties and does not find navigations or service properties. - /// - /// The property names. - /// The properties, or if any property is not found. - public virtual IReadOnlyList? FindProperties(IEnumerable propertyNames) - { - var properties = new List(); - foreach (var propertyName in propertyNames) - { - var property = FindProperty(propertyName); - if (property == null) - { - return null; - } - - properties.Add(property); - } - - return properties; - } - - private IEnumerable GetProperties() - => _baseType != null - ? _baseType.GetProperties().Concat(_properties.Values) - : _properties.Values; - - /// - [DebuggerStepThrough] - public virtual PropertyInfo? FindIndexerPropertyInfo() - => _indexerPropertyInfo; - /// /// Adds a service property to this entity type. /// @@ -765,7 +558,7 @@ public virtual RuntimeServiceProperty AddServiceProperty( /// The name of the service property. /// The service property, or if none is found. public virtual RuntimeServiceProperty? FindServiceProperty(string name) - => FindDeclaredServiceProperty(name) ?? _baseType?.FindServiceProperty(name); + => FindDeclaredServiceProperty(name) ?? BaseType?.FindServiceProperty(name); private RuntimeServiceProperty? FindDeclaredServiceProperty(string name) => _serviceProperties.TryGetValue(name, out var property) @@ -773,22 +566,22 @@ public virtual RuntimeServiceProperty AddServiceProperty( : null; private bool HasServiceProperties() - => _hasServiceProperties || _baseType != null && _baseType.HasServiceProperties(); + => _hasServiceProperties || BaseType != null && BaseType.HasServiceProperties(); private IEnumerable GetServiceProperties() - => _baseType != null + => BaseType != null ? _hasServiceProperties - ? _baseType.GetServiceProperties().Concat(_serviceProperties.Values) - : _baseType.GetServiceProperties() + ? BaseType.GetServiceProperties().Concat(_serviceProperties.Values) + : BaseType.GetServiceProperties() : _serviceProperties.Values; private IEnumerable GetDeclaredServiceProperties() => _serviceProperties.Values; private IEnumerable GetDerivedServiceProperties() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.GetDeclaredServiceProperties()); + : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredServiceProperties()); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -823,8 +616,8 @@ private IEnumerable GetDeclaredTriggers() => _triggers.Values; private IEnumerable GetTriggers() - => _baseType != null - ? _baseType.GetTriggers().Concat(GetDeclaredTriggers()) + => BaseType != null + ? BaseType.GetTriggers().Concat(GetDeclaredTriggers()) : GetDeclaredTriggers(); /// @@ -832,15 +625,15 @@ private IEnumerable GetTriggers() /// public virtual InstantiationBinding? ConstructorBinding { - get => !_clrType.IsAbstract + get => !base.ClrType.IsAbstract ? NonCapturingLazyInitializer.EnsureInitialized( - ref _constructorBinding, this, static entityType => + ref _constructorBinding, this, (Action)(entityType => { ((IModel)entityType.Model).GetModelDependencies().ConstructorBindingFactory.GetBindings( entityType, out entityType._constructorBinding, out entityType._serviceOnlyConstructorBinding); - }) + })) : _constructorBinding; [DebuggerStepThrough] @@ -864,14 +657,13 @@ public virtual InstantiationBinding? ServiceOnlyConstructorBinding } /// - /// Returns the default indexer property that takes a value if one exists. + /// 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 type to look for the indexer on. - /// An indexer property or . - public static PropertyInfo? FindIndexerProperty( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] - Type type) - => type.FindIndexerProperty(); + protected override PropertyCounts Counts + => NonCapturingLazyInitializer.EnsureInitialized(ref _counts, this, static entityType => entityType.CalculateCounts()); /// /// Returns a string that represents the current object. @@ -892,19 +684,6 @@ public virtual DebugView DebugView () => ((IReadOnlyEntityType)this).ToDebugString(), () => ((IReadOnlyEntityType)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); - /// - [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] - Type IReadOnlyTypeBase.ClrType - { - [DebuggerStepThrough] - get => _clrType; - } - - /// - [DebuggerStepThrough] - ChangeTrackingStrategy IReadOnlyEntityType.GetChangeTrackingStrategy() - => _changeTrackingStrategy; - /// [DebuggerStepThrough] LambdaExpression? IReadOnlyEntityType.GetQueryFilter() @@ -914,7 +693,7 @@ ChangeTrackingStrategy IReadOnlyEntityType.GetChangeTrackingStrategy() [DebuggerStepThrough] string? IReadOnlyEntityType.GetDiscriminatorPropertyName() { - if (_baseType != null) + if (BaseType != null) { return ((IReadOnlyEntityType)this).GetRootType().GetDiscriminatorPropertyName(); } @@ -934,13 +713,6 @@ bool IReadOnlyTypeBase.HasSharedClrType get => _hasSharedClrType; } - /// - bool IReadOnlyTypeBase.IsPropertyBag - { - [DebuggerStepThrough] - get => _isPropertyBag; - } - /// IReadOnlyModel IReadOnlyTypeBase.Model { @@ -959,36 +731,36 @@ IModel ITypeBase.Model IReadOnlyEntityType? IReadOnlyEntityType.BaseType { [DebuggerStepThrough] - get => _baseType; + get => BaseType; } /// IEntityType? IEntityType.BaseType { [DebuggerStepThrough] - get => _baseType; + get => BaseType; } /// [DebuggerStepThrough] IEnumerable IReadOnlyEntityType.GetDerivedTypes() - => GetDerivedTypes(); + => GetDerivedTypes().Cast(); /// IEnumerable IReadOnlyEntityType.GetDerivedTypesInclusive() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? new[] { this } - : new[] { this }.Concat(GetDerivedTypes()); + : new[] { this }.Concat(GetDerivedTypes().Cast()); /// [DebuggerStepThrough] IEnumerable IReadOnlyEntityType.GetDirectlyDerivedTypes() - => _directlyDerivedTypes; + => DirectlyDerivedTypes.Cast(); /// [DebuggerStepThrough] IEnumerable IEntityType.GetDirectlyDerivedTypes() - => _directlyDerivedTypes; + => DirectlyDerivedTypes.Cast(); /// [DebuggerStepThrough] @@ -1139,9 +911,9 @@ IEnumerable IEntityType.GetDeclaredNavigations() /// [DebuggerStepThrough] IEnumerable IReadOnlyEntityType.GetDerivedNavigations() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() - : GetDerivedTypes().SelectMany(et => et.GetDeclaredNavigations()); + : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredNavigations()); /// [DebuggerStepThrough] @@ -1258,56 +1030,6 @@ IEnumerable IReadOnlyEntityType.GetIndexes() IEnumerable IEntityType.GetIndexes() => GetIndexes(); - /// - [DebuggerStepThrough] - IReadOnlyProperty? IReadOnlyEntityType.FindDeclaredProperty(string name) - => FindDeclaredProperty(name); - - /// - [DebuggerStepThrough] - IProperty? IEntityType.FindDeclaredProperty(string name) - => FindDeclaredProperty(name); - - /// - [DebuggerStepThrough] - IReadOnlyList? IReadOnlyEntityType.FindProperties(IReadOnlyList propertyNames) - => FindProperties(propertyNames); - - /// - [DebuggerStepThrough] - IReadOnlyProperty? IReadOnlyEntityType.FindProperty(string name) - => FindProperty(name); - - /// - [DebuggerStepThrough] - IProperty? IEntityType.FindProperty(string name) - => FindProperty(name); - - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetDeclaredProperties() - => GetDeclaredProperties(); - - /// - [DebuggerStepThrough] - IEnumerable IEntityType.GetDeclaredProperties() - => GetDeclaredProperties(); - - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetDerivedProperties() - => GetDerivedProperties(); - - /// - [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetProperties() - => GetProperties(); - - /// - [DebuggerStepThrough] - IEnumerable IEntityType.GetProperties() - => GetProperties(); - /// [DebuggerStepThrough] IReadOnlyTrigger? IReadOnlyEntityType.FindDeclaredTrigger(string name) @@ -1328,46 +1050,12 @@ IEnumerable IReadOnlyEntityType.GetDeclaredTriggers() IEnumerable IEntityType.GetDeclaredTriggers() => GetDeclaredTriggers(); - /// - PropertyCounts IRuntimeEntityType.Counts - => NonCapturingLazyInitializer.EnsureInitialized(ref _counts, this, static entityType => entityType.CalculateCounts()); - /// Func IRuntimeEntityType.RelationshipSnapshotFactory => NonCapturingLazyInitializer.EnsureInitialized( ref _relationshipSnapshotFactory, this, static entityType => new RelationshipSnapshotFactoryFactory().Create(entityType)); - /// - Func IRuntimeEntityType.OriginalValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _originalValuesFactory, this, - static entityType => new OriginalValuesFactoryFactory().Create(entityType)); - - /// - Func IRuntimeEntityType.StoreGeneratedValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _storeGeneratedValuesFactory, this, - static entityType => new StoreGeneratedValuesFactoryFactory().CreateEmpty(entityType)); - - /// - Func IRuntimeEntityType.TemporaryValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _temporaryValuesFactory, this, - static entityType => new TemporaryValuesFactoryFactory().Create(entityType)); - - /// - Func IRuntimeEntityType.ShadowValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _shadowValuesFactory, this, - static entityType => new ShadowValuesFactoryFactory().Create(entityType)); - - /// - Func IRuntimeEntityType.EmptyShadowValuesFactory - => NonCapturingLazyInitializer.EnsureInitialized( - ref _emptyShadowValuesFactory, this, - static entityType => new EmptyShadowValuesFactoryFactory().CreateEmpty(entityType)); - /// [DebuggerStepThrough] IEnumerable IEntityType.GetForeignKeyProperties() @@ -1425,15 +1113,6 @@ IEnumerable IEntityType.GetServiceProperties() IEnumerable> IReadOnlyEntityType.GetSeedData(bool providerValues) => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); - PropertyAccessMode IReadOnlyTypeBase.GetPropertyAccessMode() - => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); - - PropertyAccessMode IReadOnlyTypeBase.GetNavigationAccessMode() - => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); - - ConfigurationSource? IRuntimeEntityType.GetConstructorBindingConfigurationSource() - => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); - - ConfigurationSource? IRuntimeEntityType.GetServiceOnlyConstructorBindingConfigurationSource() + PropertyAccessMode IReadOnlyEntityType.GetNavigationAccessMode() => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); } diff --git a/src/EFCore/Metadata/RuntimeKey.cs b/src/EFCore/Metadata/RuntimeKey.cs index 259794d024e..50ff0f8a1ef 100644 --- a/src/EFCore/Metadata/RuntimeKey.cs +++ b/src/EFCore/Metadata/RuntimeKey.cs @@ -44,7 +44,7 @@ public RuntimeKey(IReadOnlyList properties) public virtual RuntimeEntityType DeclaringEntityType { [DebuggerStepThrough] - get => Properties[0].DeclaringEntityType; + get => (RuntimeEntityType)Properties[0].DeclaringType; } /// diff --git a/src/EFCore/Metadata/RuntimeModel.cs b/src/EFCore/Metadata/RuntimeModel.cs index 60b1161c02a..8b3395e741b 100644 --- a/src/EFCore/Metadata/RuntimeModel.cs +++ b/src/EFCore/Metadata/RuntimeModel.cs @@ -54,7 +54,7 @@ public virtual void SetSkipDetectChanges(bool skipDetectChanges) /// Whether this entity type can share its ClrType with other entities. /// The base type of this entity type. /// The name of the property that will be used for storing a discriminator value. - /// The change tracking strategy for this entity type + /// The change tracking strategy for this entity type. /// The for the indexer on the associated CLR type if one exists. /// /// A value indicating whether this entity type has an indexer which is able to contain arbitrary properties @@ -93,7 +93,7 @@ public virtual RuntimeEntityType AddEntityType( } else { - var types = new SortedSet(EntityTypeFullNameComparer.Instance) { entityType }; + var types = new SortedSet(TypeBaseNameComparer.Instance) { entityType }; _sharedTypes.Add(type, types); } } diff --git a/src/EFCore/Metadata/RuntimeNavigation.cs b/src/EFCore/Metadata/RuntimeNavigation.cs index b6850fa1705..4153e9097bc 100644 --- a/src/EFCore/Metadata/RuntimeNavigation.cs +++ b/src/EFCore/Metadata/RuntimeNavigation.cs @@ -63,12 +63,16 @@ public RuntimeNavigation( /// /// Gets the entity type that this navigation property belongs to. /// - public override RuntimeEntityType DeclaringEntityType + public virtual RuntimeEntityType DeclaringEntityType { [DebuggerStepThrough] get => ((IReadOnlyNavigation)this).IsOnDependent ? ForeignKey.DeclaringEntityType : ForeignKey.PrincipalEntityType; } + /// + public override RuntimeTypeBase DeclaringType + => DeclaringEntityType; + /// public override object? Sentinel => null; diff --git a/src/EFCore/Metadata/RuntimeProperty.cs b/src/EFCore/Metadata/RuntimeProperty.cs index 983b00982c4..8998e43d0ba 100644 --- a/src/EFCore/Metadata/RuntimeProperty.cs +++ b/src/EFCore/Metadata/RuntimeProperty.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 Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.Json; @@ -10,7 +10,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Represents a scalar property of an entity type. +/// Represents a scalar property of an structural type. /// /// /// See Modeling entity types and relationships for more information and examples. @@ -23,7 +23,7 @@ public class RuntimeProperty : RuntimePropertyBase, IProperty private readonly object? _sentinel; private readonly PropertySaveBehavior _beforeSaveBehavior; private readonly PropertySaveBehavior _afterSaveBehavior; - private readonly Func? _valueGeneratorFactory; + private readonly Func? _valueGeneratorFactory; private readonly ValueConverter? _valueConverter; private ValueComparer? _valueComparer; private ValueComparer? _keyValueComparer; @@ -44,7 +44,7 @@ public RuntimeProperty( object? sentinel, PropertyInfo? propertyInfo, FieldInfo? fieldInfo, - RuntimeEntityType declaringEntityType, + RuntimeTypeBase declaringType, PropertyAccessMode propertyAccessMode, bool nullable, bool concurrencyToken, @@ -56,7 +56,7 @@ public RuntimeProperty( int? precision, int? scale, Type? providerClrType, - Func? valueGeneratorFactory, + Func? valueGeneratorFactory, ValueConverter? valueConverter, ValueComparer? valueComparer, ValueComparer? keyValueComparer, @@ -65,7 +65,7 @@ public RuntimeProperty( CoreTypeMapping? typeMapping) : base(name, propertyInfo, fieldInfo, propertyAccessMode) { - DeclaringEntityType = declaringEntityType; + DeclaringType = declaringType; ClrType = clrType; _sentinel = sentinel; _isNullable = nullable; @@ -114,10 +114,8 @@ public RuntimeProperty( [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] protected override Type ClrType { get; } - /// - /// Gets the type that this property belongs to. - /// - public override RuntimeEntityType DeclaringEntityType { get; } + /// + public override RuntimeTypeBase DeclaringType { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -173,7 +171,7 @@ public virtual CoreTypeMapping TypeMapping get => NonCapturingLazyInitializer.EnsureInitialized( ref _typeMapping, (IProperty)this, static property => - property.DeclaringEntityType.Model.GetModelDependencies().TypeMappingSource.FindMapping(property)!); + property.DeclaringType.Model.GetModelDependencies().TypeMappingSource.FindMapping(property)!); set => _typeMapping = value; } @@ -213,7 +211,7 @@ private ValueComparer GetKeyValueComparer() private ValueComparer? GetKeyValueComparer(HashSet? checkedProperties) { - if (_keyValueComparer != null) + if ( _keyValueComparer != null) { return _keyValueComparer; } @@ -237,35 +235,16 @@ private ValueComparer GetKeyValueComparer() return principal.GetKeyValueComparer(checkedProperties); } - /// - /// Gets the for this property, or if none is set. - /// - /// The reader/writer, or if none has been set. - public virtual JsonValueReaderWriter? GetJsonValueReaderWriter() - => _jsonValueReaderWriter; - /// public override object? Sentinel => _sentinel; /// - /// Returns a string that represents the current object. - /// - /// A string that represents the current object. - public override string ToString() - => ((IProperty)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); - - /// - /// 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. + /// Gets the for this property, or if none is set. /// - [EntityFrameworkInternal] - public virtual DebugView DebugView - => new( - () => ((IProperty)this).ToDebugString(), - () => ((IProperty)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + /// The reader/writer, or if none has been set. + public virtual JsonValueReaderWriter? GetJsonValueReaderWriter() + => _jsonValueReaderWriter; /// bool IReadOnlyProperty.IsNullable @@ -320,7 +299,7 @@ PropertySaveBehavior IReadOnlyProperty.GetAfterSaveBehavior() /// [DebuggerStepThrough] - Func? IReadOnlyProperty.GetValueGeneratorFactory() + Func? IReadOnlyProperty.GetValueGeneratorFactory() => _valueGeneratorFactory; /// @@ -333,20 +312,6 @@ PropertySaveBehavior IReadOnlyProperty.GetAfterSaveBehavior() Type? IReadOnlyProperty.GetProviderClrType() => (Type?)this[CoreAnnotationNames.ProviderClrType]; - /// - IReadOnlyEntityType IReadOnlyProperty.DeclaringEntityType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } - - /// - IEntityType IProperty.DeclaringEntityType - { - [DebuggerStepThrough] - get => DeclaringEntityType; - } - /// [DebuggerStepThrough] CoreTypeMapping? IReadOnlyProperty.FindTypeMapping() diff --git a/src/EFCore/Metadata/RuntimePropertyBase.cs b/src/EFCore/Metadata/RuntimePropertyBase.cs index 6f8b598d3b7..511aba6daf3 100644 --- a/src/EFCore/Metadata/RuntimePropertyBase.cs +++ b/src/EFCore/Metadata/RuntimePropertyBase.cs @@ -55,7 +55,7 @@ protected RuntimePropertyBase( /// /// Gets the type that this property-like object belongs to. /// - public abstract RuntimeEntityType DeclaringEntityType { get; } + public abstract RuntimeTypeBase DeclaringType { get; } /// /// Gets the type of value that this property-like object holds. @@ -89,7 +89,7 @@ PropertyAccessMode IReadOnlyPropertyBase.GetPropertyAccessMode() IReadOnlyTypeBase IReadOnlyPropertyBase.DeclaringType { [DebuggerStepThrough] - get => DeclaringEntityType; + get => DeclaringType; } /// @@ -116,7 +116,7 @@ PropertyIndexes IRuntimePropertyBase.PropertyIndexes ref _indexes, this, static property => { - var _ = ((IRuntimeEntityType)property.DeclaringEntityType).Counts; + var _ = ((IRuntimeTypeBase)property.DeclaringType).Counts; }); set => NonCapturingLazyInitializer.EnsureInitialized(ref _indexes, value); } diff --git a/src/EFCore/Metadata/RuntimeServiceProperty.cs b/src/EFCore/Metadata/RuntimeServiceProperty.cs index 993f359298d..7b4332617f5 100644 --- a/src/EFCore/Metadata/RuntimeServiceProperty.cs +++ b/src/EFCore/Metadata/RuntimeServiceProperty.cs @@ -42,7 +42,11 @@ public RuntimeServiceProperty( /// /// Gets the type that this property-like object belongs to. /// - public override RuntimeEntityType DeclaringEntityType { get; } + public virtual RuntimeEntityType DeclaringEntityType { get; } + + /// + public override RuntimeTypeBase DeclaringType + => DeclaringEntityType; /// /// Gets the type of value that this property-like object holds. diff --git a/src/EFCore/Metadata/RuntimeSkipNavigation.cs b/src/EFCore/Metadata/RuntimeSkipNavigation.cs index e57aacea091..baa73917429 100644 --- a/src/EFCore/Metadata/RuntimeSkipNavigation.cs +++ b/src/EFCore/Metadata/RuntimeSkipNavigation.cs @@ -81,7 +81,11 @@ public RuntimeSkipNavigation( /// /// Gets the type that this property belongs to. /// - public override RuntimeEntityType DeclaringEntityType { get; } + public virtual RuntimeEntityType DeclaringEntityType { get; } + + /// + public override RuntimeTypeBase DeclaringType + => DeclaringEntityType; /// /// Gets the entity type that this navigation property will hold an instance(s) of. diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs new file mode 100644 index 00000000000..485661fead5 --- /dev/null +++ b/src/EFCore/Metadata/RuntimeTypeBase.cs @@ -0,0 +1,557 @@ +// 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 Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage.Json; + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents an entity type in a model. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public abstract class RuntimeTypeBase : AnnotatableBase, IRuntimeTypeBase +{ + private RuntimeModel _model; + private readonly RuntimeTypeBase? _baseType; + private readonly SortedSet _directlyDerivedTypes = new(TypeBaseNameComparer.Instance); + private readonly SortedDictionary _properties; + + private readonly SortedDictionary _complexProperties = + new SortedDictionary(StringComparer.Ordinal); + + private readonly PropertyInfo? _indexerPropertyInfo; + private readonly bool _isPropertyBag; + private readonly ChangeTrackingStrategy _changeTrackingStrategy; + + // Warning: Never access these fields directly as access needs to be thread-safe + private Func? _originalValuesFactory; + private Func? _temporaryValuesFactory; + private Func? _storeGeneratedValuesFactory; + private Func? _shadowValuesFactory; + private Func? _emptyShadowValuesFactory; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public RuntimeTypeBase( + string name, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type, + RuntimeModel model, + RuntimeTypeBase? baseType, + ChangeTrackingStrategy changeTrackingStrategy, + PropertyInfo? indexerPropertyInfo, + bool propertyBag) + { + Name = name; + ClrType = type; + _model = model; + if (baseType != null) + { + _baseType = baseType; + baseType._directlyDerivedTypes.Add(this); + } + _changeTrackingStrategy = changeTrackingStrategy; + _indexerPropertyInfo = indexerPropertyInfo; + _isPropertyBag = propertyBag; + _properties = new SortedDictionary(new PropertyNameComparer(this)); + } + + /// + /// Gets the name of this type. + /// + public virtual string Name { [DebuggerStepThrough] get; } + + /// + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] + public virtual Type ClrType { get; } + + /// + /// Gets the model that this type belongs to. + /// + public virtual RuntimeModel Model { get => _model; set => _model = value; } + + /// + /// Gets the base type of this type. Returns if this is not a + /// derived type in an inheritance hierarchy. + /// + public virtual RuntimeTypeBase? BaseType => _baseType; + + /// + /// Gets all types in the model that directly derive from this type. + /// + /// The derived types. + public virtual SortedSet DirectlyDerivedTypes => _directlyDerivedTypes; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual IEnumerable GetDerivedTypes() + { + if (DirectlyDerivedTypes.Count == 0) + { + return Enumerable.Empty(); + } + + var derivedTypes = new List(); + var type = this; + var currentTypeIndex = 0; + while (type != null) + { + derivedTypes.AddRange(type.DirectlyDerivedTypes); + type = derivedTypes.Count > currentTypeIndex + ? derivedTypes[currentTypeIndex] + : null; + currentTypeIndex++; + } + + return derivedTypes; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected abstract PropertyCounts Counts { get; } + + /// + /// Adds a property to this entity type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The property value to use to consider the property not set. + /// The corresponding CLR property or for a shadow property. + /// The corresponding CLR field or for a shadow property. + /// The used for this property. + /// A value indicating whether this property can contain . + /// A value indicating whether this property is used as a concurrency token. + /// A value indicating when a value for this property will be generated by the database. + /// + /// A value indicating whether or not this property can be modified before the entity is saved to the database. + /// + /// + /// A value indicating whether or not this property can be modified after the entity is saved to the database. + /// + /// The maximum length of data that is allowed in this property. + /// A value indicating whether or not the property can persist Unicode characters. + /// The precision of data that is allowed in this property. + /// The scale of data that is allowed in this property. + /// + /// The type that the property value will be converted to before being sent to the database provider. + /// + /// The factory that has been set to generate values for this property, if any. + /// The custom set for this property. + /// The for this property. + /// The to use with keys for this property. + /// The to use for the provider values for this property. + /// The for this property. + /// The for this property. + /// The newly created property. + public virtual RuntimeProperty AddProperty( + string name, + Type clrType, + object? sentinel = null, + PropertyInfo? propertyInfo = null, + FieldInfo? fieldInfo = null, + PropertyAccessMode propertyAccessMode = Internal.Model.DefaultPropertyAccessMode, + bool nullable = false, + bool concurrencyToken = false, + ValueGenerated valueGenerated = ValueGenerated.Never, + PropertySaveBehavior beforeSaveBehavior = PropertySaveBehavior.Save, + PropertySaveBehavior afterSaveBehavior = PropertySaveBehavior.Save, + int? maxLength = null, + bool? unicode = null, + int? precision = null, + int? scale = null, + Type? providerPropertyType = null, + Func? valueGeneratorFactory = null, + ValueConverter? valueConverter = null, + ValueComparer? valueComparer = null, + ValueComparer? keyValueComparer = null, + ValueComparer? providerValueComparer = null, + JsonValueReaderWriter? jsonValueReaderWriter = null, + CoreTypeMapping? typeMapping = null) + { + var property = new RuntimeProperty( + name, + clrType, + sentinel, + propertyInfo, + fieldInfo, + this, + propertyAccessMode, + nullable, + concurrencyToken, + valueGenerated, + beforeSaveBehavior, + afterSaveBehavior, + maxLength, + unicode, + precision, + scale, + providerPropertyType, + valueGeneratorFactory, + valueConverter, + valueComparer, + keyValueComparer, + providerValueComparer, + jsonValueReaderWriter, + typeMapping); + + _properties.Add(property.Name, property); + + return property; + } + + /// + /// Gets the property with a given name. Returns if no property with the given name is defined. + /// + /// + /// This API only finds scalar properties and does not find navigation properties. + /// + /// The name of the property. + /// The property, or if none is found. + public virtual RuntimeProperty? FindProperty(string name) + => FindDeclaredProperty(name) ?? _baseType?.FindProperty(name); + + private RuntimeProperty? FindDeclaredProperty(string name) + => _properties.TryGetValue(name, out var property) + ? property + : null; + + private IEnumerable GetDeclaredProperties() + => _properties.Values; + + private IEnumerable GetDerivedProperties() + => _directlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : GetDerivedTypes().SelectMany(et => et.GetDeclaredProperties()); + + /// + /// Finds matching properties on the given entity type. Returns if any property is not found. + /// + /// + /// This API only finds scalar properties and does not find navigations or service properties. + /// + /// The property names. + /// The properties, or if any property is not found. + public virtual IReadOnlyList? FindProperties(IEnumerable propertyNames) + { + var properties = new List(); + foreach (var propertyName in propertyNames) + { + var property = FindProperty(propertyName); + if (property == null) + { + return null; + } + + properties.Add(property); + } + + return properties; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual IEnumerable GetProperties() + => _baseType != null + ? _baseType.GetProperties().Concat(_properties.Values) + : _properties.Values; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual SortedDictionary Properties => _properties; + + /// + [DebuggerStepThrough] + public virtual PropertyInfo? FindIndexerPropertyInfo() + => _indexerPropertyInfo; + + /// + /// Returns the default indexer property that takes a value if one exists. + /// + /// The type to look for the indexer on. + /// An indexer property or . + public static PropertyInfo? FindIndexerProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] + Type type) + => type.FindIndexerProperty(); + + /// + /// Adds a complex property to this entity type. + /// + /// The name of the property to add. + /// The type of value the property will hold. + /// The name of the complex type to be added. + /// The CLR type that is used to represent instances of this complex type. + /// The corresponding CLR property or for a shadow property. + /// The corresponding CLR field or for a shadow property. + /// The used for this property. + /// A value indicating whether this property can contain . + /// Indicates whether the property represents a collection. + /// The change tracking strategy for this complex type. + /// The for the indexer on the associated CLR type if one exists. + /// + /// A value indicating whether this entity type has an indexer which is able to contain arbitrary properties + /// and a method that can be used to determine whether a given indexer property contains a value. + /// + /// The newly created property. + public virtual RuntimeComplexProperty AddComplexProperty( + string name, + Type clrType, + string targetTypeName, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type targetType, + PropertyInfo? propertyInfo = null, + FieldInfo? fieldInfo = null, + PropertyAccessMode propertyAccessMode = Internal.Model.DefaultPropertyAccessMode, + bool nullable = false, + bool collection = false, + ChangeTrackingStrategy changeTrackingStrategy = ChangeTrackingStrategy.Snapshot, + PropertyInfo? indexerPropertyInfo = null, + bool propertyBag = false) + { + var property = new RuntimeComplexProperty( + name, + clrType, + targetTypeName, + targetType, + propertyInfo, + fieldInfo, + this, + propertyAccessMode, + nullable, + collection, + changeTrackingStrategy, + indexerPropertyInfo, + propertyBag); + + _complexProperties.Add(property.Name, property); + + return property; + } + + /// + /// Gets the complex property with a given name. Returns if no property with the given name is defined. + /// + /// The name of the property. + /// The property, or if none is found. + public virtual RuntimeComplexProperty? FindComplexProperty(string name) + => FindDeclaredComplexProperty(name) ?? BaseType?.FindComplexProperty(name); + + private RuntimeComplexProperty? FindDeclaredComplexProperty(string name) + => _complexProperties.TryGetValue(name, out var property) + ? property + : null; + + private IEnumerable GetDeclaredComplexProperties() + => _complexProperties.Values; + + private IEnumerable GetDerivedComplexProperties() + => DirectlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : GetDerivedTypes().Cast().SelectMany(et => et.GetDeclaredComplexProperties()); + + private IEnumerable GetComplexProperties() + => BaseType != null + ? BaseType.GetComplexProperties().Concat(_complexProperties.Values) + : _complexProperties.Values; + + /// + bool IReadOnlyTypeBase.HasSharedClrType + { + [DebuggerStepThrough] + get => true; + } + + /// + bool IReadOnlyTypeBase.IsPropertyBag + { + [DebuggerStepThrough] + get => _isPropertyBag; + } + + /// + IReadOnlyModel IReadOnlyTypeBase.Model + { + [DebuggerStepThrough] + get => Model; + } + + /// + IModel ITypeBase.Model + { + [DebuggerStepThrough] + get => Model; + } + + /// + [DebuggerStepThrough] + IReadOnlyProperty? IReadOnlyTypeBase.FindDeclaredProperty(string name) + => FindDeclaredProperty(name); + + /// + [DebuggerStepThrough] + IProperty? ITypeBase.FindDeclaredProperty(string name) + => FindDeclaredProperty(name); + + /// + [DebuggerStepThrough] + IReadOnlyList? IReadOnlyTypeBase.FindProperties(IReadOnlyList propertyNames) + => FindProperties(propertyNames); + + /// + [DebuggerStepThrough] + IReadOnlyProperty? IReadOnlyTypeBase.FindProperty(string name) + => FindProperty(name); + + /// + [DebuggerStepThrough] + IProperty? ITypeBase.FindProperty(string name) + => FindProperty(name); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDeclaredProperties() + => GetDeclaredProperties(); + + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetDeclaredProperties() + => GetDeclaredProperties(); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDerivedProperties() + => GetDerivedProperties(); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetProperties() + => GetProperties(); + + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetProperties() + => GetProperties(); + + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetComplexProperties() + => GetComplexProperties(); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetComplexProperties() + => GetComplexProperties(); + + /// + [DebuggerStepThrough] + IEnumerable ITypeBase.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyTypeBase.GetDerivedComplexProperties() + => GetDerivedComplexProperties(); + + /// + [DebuggerStepThrough] + IComplexProperty? ITypeBase.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + [DebuggerStepThrough] + IReadOnlyComplexProperty? IReadOnlyTypeBase.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + [DebuggerStepThrough] + IReadOnlyComplexProperty? IReadOnlyTypeBase.FindDeclaredComplexProperty(string name) + => FindDeclaredComplexProperty(name); + + /// + PropertyCounts IRuntimeTypeBase.Counts + { + [DebuggerStepThrough] + get => Counts; + } + + /// + Func IRuntimeTypeBase.OriginalValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _originalValuesFactory, this, + static complexType => new OriginalValuesFactoryFactory().Create(complexType)); + + /// + Func IRuntimeTypeBase.StoreGeneratedValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _storeGeneratedValuesFactory, this, + static complexType => new StoreGeneratedValuesFactoryFactory().CreateEmpty(complexType)); + + /// + Func IRuntimeTypeBase.TemporaryValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _temporaryValuesFactory, this, + static complexType => new TemporaryValuesFactoryFactory().Create(complexType)); + + /// + Func IRuntimeTypeBase.ShadowValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _shadowValuesFactory, this, + static complexType => new ShadowValuesFactoryFactory().Create(complexType)); + + /// + Func IRuntimeTypeBase.EmptyShadowValuesFactory + => NonCapturingLazyInitializer.EnsureInitialized( + ref _emptyShadowValuesFactory, this, + static complexType => new EmptyShadowValuesFactoryFactory().CreateEmpty(complexType)); + + /// + [DebuggerStepThrough] + ChangeTrackingStrategy IReadOnlyTypeBase.GetChangeTrackingStrategy() + => _changeTrackingStrategy; + + /// + PropertyAccessMode IReadOnlyTypeBase.GetPropertyAccessMode() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + + /// + ConfigurationSource? IRuntimeTypeBase.GetConstructorBindingConfigurationSource() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + + /// + ConfigurationSource? IRuntimeTypeBase.GetServiceOnlyConstructorBindingConfigurationSource() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); +} diff --git a/src/EFCore/Metadata/TypeBaseNameComparer.cs b/src/EFCore/Metadata/TypeBaseNameComparer.cs new file mode 100644 index 00000000000..1e85a22a074 --- /dev/null +++ b/src/EFCore/Metadata/TypeBaseNameComparer.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// +/// An implementation of and to compare +/// instances by name. +/// +/// +/// This type is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Implementation of database providers and extensions +/// for more information and examples. +/// +public sealed class TypeBaseNameComparer : IComparer, IEqualityComparer +{ + private TypeBaseNameComparer() + { + } + + /// + /// The singleton instance of the comparer to use. + /// + public static readonly TypeBaseNameComparer Instance = new(); + + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. + /// + /// The first object to compare. + /// The second object to compare. + /// A negative number if 'x' is less than 'y'; a positive number if 'x' is greater than 'y'; zero otherwise. + public int Compare(IReadOnlyTypeBase? x, IReadOnlyTypeBase? y) + { + if (ReferenceEquals(x, y)) + { + return 0; + } + + if (x == null) + { + return -1; + } + + if (y == null) + { + return 1; + } + + return StringComparer.Ordinal.Compare(x.Name, y.Name); + } + + /// + /// Determines whether the specified objects are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// if the specified objects are equal; otherwise, . + public bool Equals(IReadOnlyTypeBase? x, IReadOnlyTypeBase? y) + => Compare(x, y) == 0; + + /// + /// Returns a hash code for the specified object. + /// + /// The for which a hash code is to be returned. + /// A hash code for the specified object. + public int GetHashCode(IReadOnlyTypeBase obj) + => obj.Name.GetHashCode(); +} diff --git a/src/EFCore/Metadata/TypeBaseTypeComparer.cs b/src/EFCore/Metadata/TypeBaseTypeComparer.cs new file mode 100644 index 00000000000..3ee1c1424c9 --- /dev/null +++ b/src/EFCore/Metadata/TypeBaseTypeComparer.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// +/// An implementation of and to compare +/// instances by their CLR type. +/// +/// +/// This type is typically used by database providers (and other extensions). It is generally +/// not used in application code. +/// +/// +/// +/// See Implementation of database providers and extensions +/// for more information and examples. +/// +public sealed class TypeBaseTypeComparer : IComparer, IEqualityComparer +{ + private TypeBaseTypeComparer() + { + } + + /// + /// The singleton instance of the comparer to use. + /// + public static readonly TypeBaseTypeComparer Instance = new(); + + /// + /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. + /// + /// The first object to compare. + /// The second object to compare. + /// A negative number if 'x' is less than 'y'; a positive number if 'x' is greater than 'y'; zero otherwise. + public int Compare(IReadOnlyTypeBase? x, IReadOnlyTypeBase? y) + { + if (ReferenceEquals(x, y)) + { + return 0; + } + + if (x == null) + { + return -1; + } + + if (y == null) + { + return 1; + } + + return StringComparer.Ordinal.Compare(x.ClrType.FullName, y.ClrType.FullName); + } + + /// + /// Determines whether the specified objects are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// if the specified objects are equal; otherwise, . + public bool Equals(IReadOnlyTypeBase? x, IReadOnlyTypeBase? y) + => Compare(x, y) == 0; + + /// + /// Returns a hash code for the specified object. + /// + /// The for which a hash code is to be returned. + /// A hash code for the specified object. + public int GetHashCode(IReadOnlyTypeBase obj) + => obj.ClrType.GetHashCode(); +} diff --git a/src/EFCore/ModelConfigurationBuilder.cs b/src/EFCore/ModelConfigurationBuilder.cs index 95242191541..9fbc6127d84 100644 --- a/src/EFCore/ModelConfigurationBuilder.cs +++ b/src/EFCore/ModelConfigurationBuilder.cs @@ -322,6 +322,51 @@ public virtual ModelConfigurationBuilder DefaultTypeMapping( return this; } + /// + /// Marks the given and derived types as corresponding to complex properties. + /// + /// + /// + /// This can also be called on an interface to apply the configuration to all properties of implementing types. + /// + /// + /// See Pre-convention model building in EF Core for more information and + /// examples. + /// + /// + /// The property type to be configured. + /// An object that can be used to configure the properties. + public virtual ComplexPropertiesConfigurationBuilder ComplexProperties() + { + var property = _modelConfiguration.GetOrAddComplexProperty(typeof(TProperty)); + + return new ComplexPropertiesConfigurationBuilder(property); + } + + /// + /// Marks the given and derived types as corresponding to complex properties. + /// + /// + /// + /// This can also be called on an interface or an unbound generic type to apply the configuration to all + /// properties of implementing and constructed types. + /// + /// + /// See Pre-convention model building in EF Core for more information and + /// examples. + /// + /// + /// The property type to be configured. + /// An object that can be used to configure the property. + public virtual ComplexPropertiesConfigurationBuilder ComplexProperties(Type propertyType) + { + Check.NotNull(propertyType, nameof(propertyType)); + + var property = _modelConfiguration.GetOrAddComplexProperty(propertyType); + + return new ComplexPropertiesConfigurationBuilder(property); + } + /// /// Creates the configured used to create the model. This is done automatically when using /// ; this method allows it to be run diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 176d3c2d81e..12bd1a04d44 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -35,7 +35,7 @@ public static string AbstractLeafEntityType(object? entityType) entityType); /// - /// Cannot add an entity type with type '{typeName}' to the model as it is a dynamically-generated proxy type. + /// Cannot add type '{typeName}' to the model as it is a dynamically-generated proxy type. /// public static string AddingProxyTypeAsEntityType(object? typeName) => string.Format( @@ -51,7 +51,7 @@ public static string AmbiguousDependentEntity(object? entityType, object? target entityType, targetEntryCall); /// - /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be configured as having a required dependent since the dependent side cannot be determined. To identify the dependent side of the relationship, configure the foreign key property in 'OnModelCreating'. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details. + /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be configured as having a required dependent since the dependent side cannot be determined. To identify the dependent side of the relationship, configure the foreign key property in 'OnModelCreating'. See https://go.microsoft.com/fwlink/?LinkId=724062 for more details. /// public static string AmbiguousEndRequiredDependent(object? foreignKeyProperties, object? entityType) => string.Format( @@ -59,7 +59,7 @@ public static string AmbiguousEndRequiredDependent(object? foreignKeyProperties, foreignKeyProperties, entityType); /// - /// The navigation '{entityType}.{navigation}' cannot be configured as required since the dependent side of the underlying foreign key {foreignKeyProperties} cannot be determined. To identify the dependent side of the relationship, configure the foreign key property in 'OnModelCreating'. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details. + /// The navigation '{entityType}.{navigation}' cannot be configured as required since the dependent side of the underlying foreign key {foreignKeyProperties} cannot be determined. To identify the dependent side of the relationship, configure the foreign key property in 'OnModelCreating'. See https://go.microsoft.com/fwlink/?LinkId=724062 for more details. /// public static string AmbiguousEndRequiredDependentNavigation(object? entityType, object? navigation, object? foreignKeyProperties) => string.Format( @@ -67,7 +67,7 @@ public static string AmbiguousEndRequiredDependentNavigation(object? entityType, entityType, navigation, foreignKeyProperties); /// - /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be inverted to entity type '{principalEntityType}' since it was configured as required before the dependent side was configured. Configure the foreign key property or the principal key before configuring the foreign key as required. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details. + /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be inverted to entity type '{principalEntityType}' since it was configured as required before the dependent side was configured. Configure the foreign key property or the principal key before configuring the foreign key as required. See https://go.microsoft.com/fwlink/?LinkId=724062 for more details. /// public static string AmbiguousEndRequiredInverted(object? foreignKeyProperties, object? entityType, object? principalEntityType) => string.Format( @@ -83,7 +83,7 @@ public static string AmbiguousForeignKeyPropertyCandidates(object? firstDependen firstDependentToPrincipalNavigationSpecification, firstPrincipalToDependentNavigationSpecification, secondDependentToPrincipalNavigationSpecification, secondPrincipalToDependentNavigationSpecification, foreignKeyProperties); /// - /// The dependent side could not be determined for the one-to-one relationship between '{dependentToPrincipalNavigationSpecification}' and '{principalToDependentNavigationSpecification}'. To identify the dependent side of the relationship, configure the foreign key property. If these navigations should not be part of the same relationship, configure them independently via separate method chains in 'OnModelCreating'. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details. + /// The dependent side could not be determined for the one-to-one relationship between '{dependentToPrincipalNavigationSpecification}' and '{principalToDependentNavigationSpecification}'. To identify the dependent side of the relationship, configure the foreign key property. If these navigations should not be part of the same relationship, configure them independently via separate method chains in 'OnModelCreating'. See https://go.microsoft.com/fwlink/?LinkId=724062 for more details. /// public static string AmbiguousOneToOneRelationship(object? dependentToPrincipalNavigationSpecification, object? principalToDependentNavigationSpecification) => string.Format( @@ -122,6 +122,14 @@ public static string ArgumentPropertyNull(object? property, object? argument) GetString("ArgumentPropertyNull", nameof(property), nameof(argument)), property, argument); + /// + /// The [{attribute}] attribute may only be specified on entity type properties. Remove the attribute from '{type}.{propertyName}'. + /// + public static string AttributeNotOnEntityTypeProperty(object? attribute, object? type, object? propertyName) + => string.Format( + GetString("AttributeNotOnEntityTypeProperty", nameof(attribute), nameof(type), nameof(propertyName)), + attribute, type, propertyName); + /// /// Cycle detected while auto-including navigations: {cycleNavigations}. To fix this issue, either don't configure at least one navigation in the cycle as auto included in `OnModelCreating` or call 'IgnoreAutoInclude' method on the query. /// @@ -448,6 +456,22 @@ public static string CompiledQueryDifferentModel(object? queryExpression) GetString("CompiledQueryDifferentModel", nameof(queryExpression)), queryExpression); + /// + /// The collection complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not implement 'IEnumerable<{targetType}>'. Collection complex property must implement IEnumerable<> of the complex type. + /// + public static string ComplexCollectionWrongClrType(object? property, object? type, object? clrType, object? targetType) + => string.Format( + GetString("ComplexCollectionWrongClrType", nameof(property), nameof(type), nameof(clrType), nameof(targetType)), + property, type, clrType, targetType); + + /// + /// The complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not match the expected CLR type '{targetType}'. + /// + public static string ComplexPropertyWrongClrType(object? property, object? type, object? clrType, object? targetType) + => string.Format( + GetString("ComplexPropertyWrongClrType", nameof(property), nameof(type), nameof(clrType), nameof(targetType)), + property, type, clrType, targetType); + /// /// There are multiple properties with the [ForeignKey] attribute pointing to navigation '{1_entityType}.{0_navigation}'. To define a composite foreign key using data annotations, use the [ForeignKey] attribute on the navigation. /// @@ -495,12 +519,12 @@ public static string ConflictingKeylessAndPrimaryKeyAttributes(object? entity) entity); /// - /// The property or navigation '{member}' cannot be added to the entity type '{entityType}' because a property or navigation with the same name already exists on entity type '{conflictingEntityType}'. + /// The property or navigation '{member}' cannot be added to the '{type}' type because a property or navigation with the same name already exists on the '{conflictingType}' type. /// - public static string ConflictingPropertyOrNavigation(object? member, object? entityType, object? conflictingEntityType) + public static string ConflictingPropertyOrNavigation(object? member, object? type, object? conflictingType) => string.Format( - GetString("ConflictingPropertyOrNavigation", nameof(member), nameof(entityType), nameof(conflictingEntityType)), - member, entityType, conflictingEntityType); + GetString("ConflictingPropertyOrNavigation", nameof(member), nameof(type), nameof(conflictingType)), + member, type, conflictingType); /// /// Cannot create a relationship between '{newPrincipalNavigationSpecification}' and '{newDependentNavigationSpecification}' because a relationship already exists between '{existingPrincipalNavigationSpecification}' and '{existingDependentNavigationSpecification}'. Navigations can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation '{newDependentNavigationSpecification}' first in 'OnModelCreating'. @@ -629,16 +653,20 @@ public static string DefaultMethodInvoked => GetString("DefaultMethodInvoked"); /// - /// The [DeleteBehavior] attribute may only be specified on navigation properties, and is not supported not on properties making up the foreign key. + /// The [DeleteBehavior] attribute may only be specified on navigation properties, and is not supported on properties making up the foreign key. Remove the attribute from '{type}.{propertyName}'. /// - public static string DeleteBehaviorAttributeNotOnNavigationProperty - => GetString("DeleteBehaviorAttributeNotOnNavigationProperty"); + public static string DeleteBehaviorAttributeNotOnNavigationProperty(object? type, object? propertyName) + => string.Format( + GetString("DeleteBehaviorAttributeNotOnNavigationProperty", nameof(type), nameof(propertyName)), + type, propertyName); /// - /// The [DeleteBehavior] attribute may only be specified on dependent side of the relationship. + /// The [DeleteBehavior] attribute may only be specified on the dependent side of the relationship. Remove the attribute from '{entityType}.{navigationName}'. /// - public static string DeleteBehaviorAttributeOnPrincipalProperty - => GetString("DeleteBehaviorAttributeOnPrincipalProperty"); + public static string DeleteBehaviorAttributeOnPrincipalProperty(object? entityType, object? navigationName) + => string.Format( + GetString("DeleteBehaviorAttributeOnPrincipalProperty", nameof(entityType), nameof(navigationName)), + entityType, navigationName); /// /// You are configuring a relationship between '{dependentEntityType}' and '{principalEntityType}' but have specified a foreign key on '{entityType}'. The foreign key must be defined on a type that is part of the relationship. @@ -1224,12 +1252,12 @@ public static string IndexWrongType(object? index, object? entityType, object? o index, entityType, otherEntityType); /// - /// The property '{property}' cannot be ignored on entity type '{entityType}' because it's declared on the base entity type '{baseEntityType}'. To exclude this property from your model, use the [NotMapped] attribute or 'Ignore' on the base type in 'OnModelCreating'. + /// The property '{property}' cannot be ignored on type '{type}' because it's declared on the base type '{baseType}'. To exclude this property from your model, use the [NotMapped] attribute or 'Ignore' on the base type in 'OnModelCreating'. /// - public static string InheritedPropertyCannotBeIgnored(object? property, object? entityType, object? baseEntityType) + public static string InheritedPropertyCannotBeIgnored(object? property, object? type, object? baseType) => string.Format( - GetString("InheritedPropertyCannotBeIgnored", nameof(property), nameof(entityType), nameof(baseEntityType)), - property, entityType, baseEntityType); + GetString("InheritedPropertyCannotBeIgnored", nameof(property), nameof(type), nameof(baseType)), + property, type, baseType); /// /// The property '{entityType}.{navigation}' is of an interface type ('{propertyType}'). If it is a navigation, manually configure the relationship for this property by casting it to a mapped entity type. Otherwise, ignore the property using the [NotMapped] attribute or 'Ignore' in 'OnModelCreating'. @@ -1247,6 +1275,14 @@ public static string InvalidAlternateKeyValue(object? entityType, object? keyPro GetString("InvalidAlternateKeyValue", nameof(entityType), nameof(keyProperty)), entityType, keyProperty); + /// + /// The specified type '{type}' must be a non-interface type with a public constructor to be used as an entity type. + /// + public static string InvalidComplexType(object? type) + => string.Format( + GetString("InvalidComplexType", nameof(type)), + type); + /// /// A previous error has left the DbContext in an invalid state. Applications should not continue to use a DbContext instance after an InvalidOperationException has been thrown. /// @@ -1278,7 +1314,7 @@ public static string InvalidEnumValue(object? value, object? argumentName, objec value, argumentName, enumType); /// - /// The expression '{expression}' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393. + /// The expression '{expression}' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see https://go.microsoft.com/fwlink/?LinkID=746393. /// public static string InvalidIncludeExpression(object? expression) => string.Format( @@ -1590,7 +1626,7 @@ public static string ModelReadOnly => GetString("ModelReadOnly"); /// - /// The filters '{filter1}' and '{filter2}' have both been configured on the same included navigation. Only one unique filter per navigation is allowed. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393. + /// The filters '{filter1}' and '{filter2}' have both been configured on the same included navigation. Only one unique filter per navigation is allowed. For more information on including related data, see https://go.microsoft.com/fwlink/?LinkID=746393. /// public static string MultipleFilteredIncludesOnSameNavigation(object? filter1, object? filter2) => string.Format( @@ -1622,7 +1658,7 @@ public static string MultipleProvidersConfigured(object? storeNames) storeNames); /// - /// When called from '{0}', rewriting a node of type '{1}' must return a non-null value of the same type. Alternatively, override '{0}' and change it to not visit children of this type. + /// When called from '{caller}', rewriting a node of type '{type}' must return a non-null value of the same type. Alternatively, override '{caller}' and change it to not visit children of this type. /// public static string MustRewriteToSameNode(object? caller, object? type) => string.Format( @@ -1914,12 +1950,12 @@ public static string NoProperty(object? field, object? entity, object? propertyA field, entity, propertyAccessMode); /// - /// The property '{property}' cannot be added to the type '{entityType}' because no property type was specified and there is no corresponding CLR property or field. To add a shadow state property, the property type must be specified. + /// The property '{property}' cannot be added to the type '{type}' because no property type was specified and there is no corresponding CLR property or field. To add a shadow state property, the property type must be specified. /// - public static string NoPropertyType(object? property, object? entityType) + public static string NoPropertyType(object? property, object? type) => string.Format( - GetString("NoPropertyType", nameof(property), nameof(entityType)), - property, entityType); + GetString("NoPropertyType", nameof(property), nameof(type)), + property, type); /// /// No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext. @@ -2042,7 +2078,7 @@ public static string OwnershipToDependent(object? navigation, object? principalE navigation, principalEntityType, dependentEntityType); /// - /// The DbContext of type '{contextType}' cannot be pooled because it does not have a public constructor accepting a parameter of type DbContextOptions or has more than one constructor. + /// The DbContext of type '{contextType}' cannot be pooled because it does not have a public constructor accepting a single parameter of type DbContextOptions or has more than one constructor. /// public static string PoolingContextCtorError(object? contextType) => string.Format( @@ -2112,20 +2148,12 @@ public static string PrincipalOwnedType(object? referencingEntityTypeOrNavigatio referencingEntityTypeOrNavigation, referencedEntityTypeOrNavigation, ownedType); /// - /// '{property}' cannot be used as a property on entity type '{entityType}' because it is configured as a navigation. + /// The indexer property '{property}' cannot be added to the type '{type}' because the CLR type contains a member with the same name. Specify a different name or configure '{property}' as a non-indexer property. /// - public static string PropertyCalledOnNavigation(object? property, object? entityType) + public static string PropertyClashingNonIndexer(object? property, object? type) => string.Format( - GetString("PropertyCalledOnNavigation", nameof(property), nameof(entityType)), - property, entityType); - - /// - /// The indexer property '{property}' cannot be added to type '{entityType}' because the CLR type contains a member with the same name. Specify a different name or configure '{property}' as a non-indexer property. - /// - public static string PropertyClashingNonIndexer(object? property, object? entityType) - => string.Format( - GetString("PropertyClashingNonIndexer", nameof(property), nameof(entityType)), - property, entityType); + GetString("PropertyClashingNonIndexer", nameof(property), nameof(type)), + property, type); /// /// The property '{1_entityType}.{0_property}' contains null, but the property is marked as required. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values. @@ -2144,23 +2172,23 @@ public static string PropertyConceptualNullSensitive(object? property, object? e property, entityType, keyValue); /// - /// The property '{property}' belongs to entity type '{entityType}', but is being used with an instance of entity type '{expectedType}'. + /// The property '{property}' belongs to the type '{expectedType}', but is being used with an instance of type '{actualType}'. /// - public static string PropertyDoesNotBelong(object? property, object? entityType, object? expectedType) + public static string PropertyDoesNotBelong(object? property, object? expectedType, object? actualType) => string.Format( - GetString("PropertyDoesNotBelong", nameof(property), nameof(entityType), nameof(expectedType)), - property, entityType, expectedType); + GetString("PropertyDoesNotBelong", nameof(property), nameof(expectedType), nameof(actualType)), + property, expectedType, actualType); /// - /// The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the foreign key {foreignKeyProperties} on '{foreignKeyType}'. All containing foreign keys must be removed or redefined before the property can be removed. + /// The property '{property}' cannot be removed from the type '{type}' because it is being used in the foreign key {foreignKeyProperties} on '{foreignKeyType}'. All containing foreign keys must be removed or redefined before the property can be removed. /// - public static string PropertyInUseForeignKey(object? property, object? entityType, object? foreignKeyProperties, object? foreignKeyType) + public static string PropertyInUseForeignKey(object? property, object? type, object? foreignKeyProperties, object? foreignKeyType) => string.Format( - GetString("PropertyInUseForeignKey", nameof(property), nameof(entityType), nameof(foreignKeyProperties), nameof(foreignKeyType)), - property, entityType, foreignKeyProperties, foreignKeyType); + GetString("PropertyInUseForeignKey", nameof(property), nameof(type), nameof(foreignKeyProperties), nameof(foreignKeyType)), + property, type, foreignKeyProperties, foreignKeyType); /// - /// The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the index {index} on '{indexType}'. All containing indexes must be removed or redefined before the property can be removed. + /// The property '{property}' cannot be removed from the type '{entityType}' because it is being used in the index {index} on '{indexType}'. All containing indexes must be removed or redefined before the property can be removed. /// public static string PropertyInUseIndex(object? property, object? entityType, object? index, object? indexType) => string.Format( @@ -2168,21 +2196,13 @@ public static string PropertyInUseIndex(object? property, object? entityType, ob property, entityType, index, indexType); /// - /// The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the key {keyProperties}. All containing keys must be removed or redefined before the property can be removed. + /// The property '{property}' cannot be removed from the type '{entityType}' because it is being used in the key {keyProperties}. All containing keys must be removed or redefined before the property can be removed. /// public static string PropertyInUseKey(object? property, object? entityType, object? keyProperties) => string.Format( GetString("PropertyInUseKey", nameof(property), nameof(entityType), nameof(keyProperties)), property, entityType, keyProperties); - /// - /// The property '{1_entityType}.{0_property}' is being accessed using the '{propertyMethod}' method, but is defined in the model as a navigation. Use either the '{referenceMethod}' or '{collectionMethod}' method to access navigations. - /// - public static string PropertyIsNavigation(object? property, object? entityType, object? propertyMethod, object? referenceMethod, object? collectionMethod) - => string.Format( - GetString("PropertyIsNavigation", "0_property", "1_entityType", nameof(propertyMethod), nameof(referenceMethod), nameof(collectionMethod)), - property, entityType, propertyMethod, referenceMethod, collectionMethod); - /// /// The EF.Property<T> method may only be used within Entity Framework LINQ queries. /// @@ -2238,36 +2258,36 @@ public static string PropertyReadOnlyBeforeSave(object? property, object? entity property, entityType); /// - /// The property '{property}' cannot be added to type '{entityType}' because the type of the corresponding CLR property or field '{clrType}' does not match the specified type '{propertyType}'. + /// The property '{property}' cannot be added to the type '{type}' because the type of the corresponding CLR property or field '{clrType}' does not match the specified type '{propertyType}'. /// - public static string PropertyWrongClrType(object? property, object? entityType, object? clrType, object? propertyType) + public static string PropertyWrongClrType(object? property, object? type, object? clrType, object? propertyType) => string.Format( - GetString("PropertyWrongClrType", nameof(property), nameof(entityType), nameof(clrType), nameof(propertyType)), - property, entityType, clrType, propertyType); + GetString("PropertyWrongClrType", nameof(property), nameof(type), nameof(clrType), nameof(propertyType)), + property, type, clrType, propertyType); /// - /// The property '{property}' cannot be added to entity type '{entityType}' because it is declared on the CLR type '{clrType}'. + /// The property '{property}' cannot be added to the type '{type}' because it is declared on the CLR type '{clrType}'. /// - public static string PropertyWrongEntityClrType(object? property, object? entityType, object? clrType) + public static string PropertyWrongEntityClrType(object? property, object? type, object? clrType) => string.Format( - GetString("PropertyWrongEntityClrType", nameof(property), nameof(entityType), nameof(clrType)), - property, entityType, clrType); + GetString("PropertyWrongEntityClrType", nameof(property), nameof(type), nameof(clrType)), + property, type, clrType); /// - /// The property '{property}' cannot be added to entity type '{entityType}' because it doesn't match the name of the provided CLR property or field '{clrName}'. Use the same name or specify a different CLR member. + /// The property '{property}' cannot be added to the type '{type}' because it doesn't match the name of the provided CLR property or field '{clrName}'. Use the same name or specify a different CLR member. /// - public static string PropertyWrongName(object? property, object? entityType, object? clrName) + public static string PropertyWrongName(object? property, object? type, object? clrName) => string.Format( - GetString("PropertyWrongName", nameof(property), nameof(entityType), nameof(clrName)), - property, entityType, clrName); + GetString("PropertyWrongName", nameof(property), nameof(type), nameof(clrName)), + property, type, clrName); /// - /// The property '{property}' cannot be removed from the entity type '{entityType}' because it is declared on the entity type '{otherEntityType}'. + /// The property '{property}' cannot be removed from the type '{type}' because it is declared on the '{otherType}' type. /// - public static string PropertyWrongType(object? property, object? entityType, object? otherEntityType) + public static string PropertyWrongType(object? property, object? type, object? otherType) => string.Format( - GetString("PropertyWrongType", nameof(property), nameof(entityType), nameof(otherEntityType)), - property, entityType, otherEntityType); + GetString("PropertyWrongType", nameof(property), nameof(type), nameof(otherType)), + property, type, otherType); /// /// The materialization condition passed for entity shaper of entity type '{entityType}' is not of the correct shape. A materialization condition must be a 'LambdaExpression' of 'Func<ValueBuffer, IEntityType>'. @@ -2948,7 +2968,7 @@ private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.EntityFrameworkCore.Properties.CoreStrings", typeof(CoreResources).Assembly); /// - /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be configured as required since the dependent side cannot be determined. To identify the dependent side of the relationship, configure the foreign key property or the principal key before configuring the foreign key as required in 'OnModelCreating'. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details. + /// The foreign key {foreignKeyProperties} on entity type '{entityType}' cannot be configured as required since the dependent side cannot be determined. To identify the dependent side of the relationship, configure the foreign key property or the principal key before configuring the foreign key as required in 'OnModelCreating'. See https://go.microsoft.com/fwlink/?LinkId=724062 for more details. /// public static EventDefinition LogAmbiguousEndRequired(IDiagnosticsLogger logger) { @@ -3697,6 +3717,31 @@ public static EventDefinition LogManyServiceProvidersCreated(IDiagnosticsLogger return (EventDefinition)definition; } + /// + /// The complex property '{type}.{property}' was first mapped explicitly and then ignored. Consider not mapping the complex property in the first place. + /// + public static EventDefinition LogMappedComplexPropertyIgnored(IDiagnosticsLogger logger) + { + var definition = ((LoggingDefinitions)logger.Definitions).LogMappedComplexPropertyIgnored; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((LoggingDefinitions)logger.Definitions).LogMappedComplexPropertyIgnored, + logger, + static logger => new EventDefinition( + logger.Options, + CoreEventId.MappedComplexPropertyIgnoredWarning, + LogLevel.Warning, + "CoreEventId.MappedComplexPropertyIgnoredWarning", + level => LoggerMessage.Define( + level, + CoreEventId.MappedComplexPropertyIgnoredWarning, + _resourceManager.GetString("LogMappedComplexPropertyIgnored")!))); + } + + return (EventDefinition)definition; + } + /// /// The entity type '{entityType}' was first mapped explicitly and then ignored. Consider not mapping the entity type in the first place. /// @@ -3738,7 +3783,7 @@ public static EventDefinition LogMappedNavigationIgnored(IDiagno CoreEventId.MappedNavigationIgnoredWarning, LogLevel.Warning, "CoreEventId.MappedNavigationIgnoredWarning", - level => LoggerMessage.Define( + level => LoggerMessage.Define( level, CoreEventId.MappedNavigationIgnoredWarning, _resourceManager.GetString("LogMappedNavigationIgnored")!))); @@ -3763,7 +3808,7 @@ public static EventDefinition LogMappedPropertyIgnored(IDiagnost CoreEventId.MappedPropertyIgnoredWarning, LogLevel.Warning, "CoreEventId.MappedPropertyIgnoredWarning", - level => LoggerMessage.Define( + level => LoggerMessage.Define( level, CoreEventId.MappedPropertyIgnoredWarning, _resourceManager.GetString("LogMappedPropertyIgnored")!))); diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index c0b16e0eebb..a3871f1bb03 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -1,17 +1,17 @@  - @@ -121,7 +121,7 @@ The corresponding CLR type for entity type '{entityType}' cannot be instantiated, and there is no derived entity type in the model that corresponds to a concrete CLR type. - Cannot add an entity type with type '{typeName}' to the model as it is a dynamically-generated proxy type. + Cannot add type '{typeName}' to the model as it is a dynamically-generated proxy type. The entity type '{entityType}' uses a shared type and the supplied entity is currently referenced from several owner entities. To access the entry for a particular reference, call '{targetEntryCall}' on the owner entry. @@ -153,6 +153,9 @@ The property '{property}' of the argument '{argument}' cannot be null. + + The [{attribute}] attribute may only be specified on entity type properties. Remove the attribute from '{type}.{propertyName}'. + Cycle detected while auto-including navigations: {cycleNavigations}. To fix this issue, either don't configure at least one navigation in the cycle as auto included in `OnModelCreating` or call 'IgnoreAutoInclude' method on the query. @@ -276,6 +279,12 @@ The compiled query '{queryExpression}' was executed with a different model than it was compiled against. Compiled queries can only be used with a single model. + + The collection complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not implement 'IEnumerable<{targetType}>'. Collection complex property must implement IEnumerable<> of the complex type. + + + The complex property '{property}' cannot be added to the type '{type}' because its CLR type '{clrType}' does not match the expected CLR type '{targetType}'. + There are multiple properties with the [ForeignKey] attribute pointing to navigation '{1_entityType}.{0_navigation}'. To define a composite foreign key using data annotations, use the [ForeignKey] attribute on the navigation. @@ -295,7 +304,7 @@ The entity type '{entity}' has both [Keyless] and [PrimaryKey] attributes; one must be removed. - The property or navigation '{member}' cannot be added to the entity type '{entityType}' because a property or navigation with the same name already exists on entity type '{conflictingEntityType}'. + The property or navigation '{member}' cannot be added to the '{type}' type because a property or navigation with the same name already exists on the '{conflictingType}' type. Cannot create a relationship between '{newPrincipalNavigationSpecification}' and '{newDependentNavigationSpecification}' because a relationship already exists between '{existingPrincipalNavigationSpecification}' and '{existingDependentNavigationSpecification}'. Navigations can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation '{newDependentNavigationSpecification}' first in 'OnModelCreating'. @@ -349,10 +358,10 @@ The EF.Default<T> property may only be used within Entity Framework ExecuteUpdate method. - The [DeleteBehavior] attribute may only be specified on navigation properties, and is not supported not on properties making up the foreign key. + The [DeleteBehavior] attribute may only be specified on navigation properties, and is not supported on properties making up the foreign key. Remove the attribute from '{type}.{propertyName}'. - The [DeleteBehavior] attribute may only be specified on dependent side of the relationship. + The [DeleteBehavior] attribute may only be specified on the dependent side of the relationship. Remove the attribute from '{entityType}.{navigationName}'. You are configuring a relationship between '{dependentEntityType}' and '{principalEntityType}' but have specified a foreign key on '{entityType}'. The foreign key must be defined on a type that is part of the relationship. @@ -578,7 +587,7 @@ The index {index} cannot be removed from the entity type '{entityType}' because it is defined on the entity type '{otherEntityType}'. - The property '{property}' cannot be ignored on entity type '{entityType}' because it's declared on the base entity type '{baseEntityType}'. To exclude this property from your model, use the [NotMapped] attribute or 'Ignore' on the base type in 'OnModelCreating'. + The property '{property}' cannot be ignored on type '{type}' because it's declared on the base type '{baseType}'. To exclude this property from your model, use the [NotMapped] attribute or 'Ignore' on the base type in 'OnModelCreating'. The property '{entityType}.{navigation}' is of an interface type ('{propertyType}'). If it is a navigation, manually configure the relationship for this property by casting it to a mapped entity type. Otherwise, ignore the property using the [NotMapped] attribute or 'Ignore' in 'OnModelCreating'. @@ -586,6 +595,9 @@ Unable to track an entity of type '{entityType}' because alternate key property '{keyProperty}' is null. If the alternate key is not used in a relationship, then consider using a unique index instead. Unique indexes may contain nulls, while alternate keys may not. + + The specified type '{type}' must be a non-interface type with a public constructor to be used as a complex type. + A previous error has left the DbContext in an invalid state. Applications should not continue to use a DbContext instance after an InvalidOperationException has been thrown. @@ -823,17 +835,21 @@ More than twenty 'IServiceProvider' instances have been created for internal use by Entity Framework. This is commonly caused by injection of a new singleton service instance into every DbContext instance. For example, calling 'UseLoggerFactory' passing in a new instance each time--see https://go.microsoft.com/fwlink/?linkid=869049 for more details. This may lead to performance issues, consider reviewing calls on 'DbContextOptionsBuilder' that may require new service providers to be built. Warning CoreEventId.ManyServiceProvidersCreatedWarning + + The complex property '{type}.{property}' was first mapped explicitly and then ignored. Consider not mapping the complex property in the first place. + Warning CoreEventId.MappedComplexPropertyIgnoredWarning string string + The entity type '{entityType}' was first mapped explicitly and then ignored. Consider not mapping the entity type in the first place. - Warning CoreEventId.MappedEntityTypeIgnored string - - - The property '{entityType}.{property}' was first mapped explicitly and then ignored. Consider not mapping the property in the first place. - Warning CoreEventId.MappedPropertyIgnored string string + Warning CoreEventId.MappedEntityTypeIgnoredWarning string The navigation '{entityType}.{navigation}' was first mapped explicitly and then ignored. Consider not mapping the navigation in the first place. - Warning CoreEventId.MappedNavigationIgnored string string + Warning CoreEventId.MappedNavigationIgnoredWarning string string + + + The property '{entityType}.{property}' was first mapped explicitly and then ignored. Consider not mapping the property in the first place. + Warning CoreEventId.MappedPropertyIgnoredWarning string string There are multiple navigations ({navigations}) configured with [InverseProperty] attribute which point to the same inverse navigation '{inverseNavigation}' therefore no relationship was configured by convention. @@ -1035,7 +1051,7 @@ Services for database providers {storeNames} have been registered in the service provider. Only a single database provider can be registered in a service provider. If possible, ensure that Entity Framework is managing its service provider by removing the call to 'UseInternalServiceProvider'. Otherwise, consider conditionally registering the database provider, or maintaining one service provider per database provider. - When called from '{0}', rewriting a node of type '{1}' must return a non-null value of the same type. Alternatively, override '{0}' and change it to not visit children of this type. + When called from '{caller}', rewriting a node of type '{type}' must return a non-null value of the same type. Alternatively, override '{caller}' and change it to not visit children of this type. The property '{keyProperty}' cannot be configured as 'ValueGeneratedOnUpdate' or 'ValueGeneratedOnAddOrUpdate' because it's part of a key and its value cannot be changed after the entity has been added to the store. @@ -1146,7 +1162,7 @@ No property was associated with field '{field}' of entity type '{entity}'. Either configure a property or use a different '{propertyAccessMode}'. - The property '{property}' cannot be added to the type '{entityType}' because no property type was specified and there is no corresponding CLR property or field. To add a shadow state property, the property type must be specified. + The property '{property}' cannot be added to the type '{type}' because no property type was specified and there is no corresponding CLR property or field. To add a shadow state property, the property type must be specified. No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext. @@ -1223,11 +1239,8 @@ The relationship from '{referencingEntityTypeOrNavigation}' to '{referencedEntityTypeOrNavigation}' is not supported because the owned entity type '{ownedType}' cannot be on the principal side of a non-ownership relationship. Remove the relationship or configure the foreign key to be on '{ownedType}'. - - '{property}' cannot be used as a property on entity type '{entityType}' because it is configured as a navigation. - - The indexer property '{property}' cannot be added to type '{entityType}' because the CLR type contains a member with the same name. Specify a different name or configure '{property}' as a non-indexer property. + The indexer property '{property}' cannot be added to the type '{type}' because the CLR type contains a member with the same name. Specify a different name or configure '{property}' as a non-indexer property. The property '{1_entityType}.{0_property}' contains null, but the property is marked as required. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values. @@ -1236,19 +1249,16 @@ The property '{property}' contains null on entity '{entityType}' with the key value '{keyValue}', but the property is marked as required. - The property '{property}' belongs to entity type '{entityType}', but is being used with an instance of entity type '{expectedType}'. + The property '{property}' belongs to the type '{expectedType}', but is being used with an instance of type '{actualType}'. - The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the foreign key {foreignKeyProperties} on '{foreignKeyType}'. All containing foreign keys must be removed or redefined before the property can be removed. + The property '{property}' cannot be removed from the type '{type}' because it is being used in the foreign key {foreignKeyProperties} on '{foreignKeyType}'. All containing foreign keys must be removed or redefined before the property can be removed. - The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the index {index} on '{indexType}'. All containing indexes must be removed or redefined before the property can be removed. + The property '{property}' cannot be removed from the type '{entityType}' because it is being used in the index {index} on '{indexType}'. All containing indexes must be removed or redefined before the property can be removed. - The property '{property}' cannot be removed from entity type '{entityType}' because it is being used in the key {keyProperties}. All containing keys must be removed or redefined before the property can be removed. - - - The property '{1_entityType}.{0_property}' is being accessed using the '{propertyMethod}' method, but is defined in the model as a navigation. Use either the '{referenceMethod}' or '{collectionMethod}' method to access navigations. + The property '{property}' cannot be removed from the type '{entityType}' because it is being used in the key {keyProperties}. All containing keys must be removed or redefined before the property can be removed. The EF.Property<T> method may only be used within Entity Framework LINQ queries. @@ -1272,16 +1282,16 @@ The property '{1_entityType}.{0_property}' is defined as read-only before it has been saved, but its value has been set to something other than a temporary or default value. - The property '{property}' cannot be added to type '{entityType}' because the type of the corresponding CLR property or field '{clrType}' does not match the specified type '{propertyType}'. + The property '{property}' cannot be added to the type '{type}' because the type of the corresponding CLR property or field '{clrType}' does not match the specified type '{propertyType}'. - The property '{property}' cannot be added to entity type '{entityType}' because it is declared on the CLR type '{clrType}'. + The property '{property}' cannot be added to the type '{type}' because it is declared on the CLR type '{clrType}'. - The property '{property}' cannot be added to entity type '{entityType}' because it doesn't match the name of the provided CLR property or field '{clrName}'. Use the same name or specify a different CLR member. + The property '{property}' cannot be added to the type '{type}' because it doesn't match the name of the provided CLR property or field '{clrName}'. Use the same name or specify a different CLR member. - The property '{property}' cannot be removed from the entity type '{entityType}' because it is declared on the entity type '{otherEntityType}'. + The property '{property}' cannot be removed from the type '{type}' because it is declared on the '{otherType}' type. The materialization condition passed for entity shaper of entity type '{entityType}' is not of the correct shape. A materialization condition must be a 'LambdaExpression' of 'Func<ValueBuffer, IEntityType>'. @@ -1538,4 +1548,4 @@ Cannot start tracking the entry for entity type '{entityType}' because it was created by a different StateManager instance. - + \ No newline at end of file diff --git a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs index 0721a0a4c60..2ddfad8f5ed 100644 --- a/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs @@ -580,7 +580,7 @@ private BlockExpression CreateFullMaterializeExpression( concreteEntityType, "instance", _queryTrackingBehavior), materializationContextVariable); if (_queryStateManager - && concreteEntityType.ShadowPropertyCount() > 0) + && ((IRuntimeEntityType)concreteEntityType).ShadowPropertyCount > 0) { var valueBufferExpression = Expression.Call( materializationContextVariable, MaterializationContext.GetValueBufferMethod); diff --git a/src/EFCore/Storage/CoreTypeMapping.cs b/src/EFCore/Storage/CoreTypeMapping.cs index a5eb87daeec..1b0a7072914 100644 --- a/src/EFCore/Storage/CoreTypeMapping.cs +++ b/src/EFCore/Storage/CoreTypeMapping.cs @@ -46,7 +46,7 @@ public CoreTypeMappingParameters( ValueComparer? comparer = null, ValueComparer? keyComparer = null, ValueComparer? providerValueComparer = null, - Func? valueGeneratorFactory = null, + Func? valueGeneratorFactory = null, CoreTypeMapping? elementTypeMapping = null, JsonValueReaderWriter? jsonValueReaderWriter = null) { @@ -90,7 +90,7 @@ public CoreTypeMappingParameters( /// An optional factory for creating a specific to use with /// this mapping. /// - public Func? ValueGeneratorFactory { get; } + public Func? ValueGeneratorFactory { get; } /// /// If this type mapping represents a primitive collection, this holds the element's type mapping. @@ -228,7 +228,7 @@ public virtual ValueConverter? Converter /// An optional factory for creating a specific to use with /// this mapping. /// - public virtual Func? ValueGeneratorFactory { get; } + public virtual Func? ValueGeneratorFactory { get; } /// /// A adds custom value snapshotting and comparison for diff --git a/src/EFCore/Storage/ValueConversion/ConverterMappingHints.cs b/src/EFCore/Storage/ValueConversion/ConverterMappingHints.cs index 0b536bfcd23..1ecb1aea39d 100644 --- a/src/EFCore/Storage/ValueConversion/ConverterMappingHints.cs +++ b/src/EFCore/Storage/ValueConversion/ConverterMappingHints.cs @@ -28,7 +28,7 @@ public ConverterMappingHints( int? precision = null, int? scale = null, bool? unicode = null, - Func? valueGeneratorFactory = null) + Func? valueGeneratorFactory = null) { Size = size; Precision = precision; @@ -101,5 +101,5 @@ public virtual ConverterMappingHints Override(ConverterMappingHints? hints) /// An optional factory for creating a specific to use for model /// values when this converter is being used. /// - public virtual Func? ValueGeneratorFactory { get; } + public virtual Func? ValueGeneratorFactory { get; } } diff --git a/src/EFCore/ValueGeneration/DiscriminatorValueGeneratorFactory.cs b/src/EFCore/ValueGeneration/DiscriminatorValueGeneratorFactory.cs index 3eea090f1ee..0749aca8c92 100644 --- a/src/EFCore/ValueGeneration/DiscriminatorValueGeneratorFactory.cs +++ b/src/EFCore/ValueGeneration/DiscriminatorValueGeneratorFactory.cs @@ -15,6 +15,6 @@ namespace Microsoft.EntityFrameworkCore.ValueGeneration; public class DiscriminatorValueGeneratorFactory : ValueGeneratorFactory { /// - public override ValueGenerator Create(IProperty property, IEntityType entityType) + public override ValueGenerator Create(IProperty property, ITypeBase entityType) => new DiscriminatorValueGenerator(); } diff --git a/src/EFCore/ValueGeneration/IValueGeneratorCache.cs b/src/EFCore/ValueGeneration/IValueGeneratorCache.cs index 1d4f6c4b1f0..be89869d95c 100644 --- a/src/EFCore/ValueGeneration/IValueGeneratorCache.cs +++ b/src/EFCore/ValueGeneration/IValueGeneratorCache.cs @@ -30,7 +30,7 @@ public interface IValueGeneratorCache /// the cache. /// /// The property to get the value generator for. - /// + /// /// The entity type that the value generator will be used for. When called on inherited properties on derived entity types, /// this entity type may be different from the declared entity type on /// @@ -38,6 +38,6 @@ public interface IValueGeneratorCache /// The existing or newly created value generator. ValueGenerator GetOrAdd( IProperty property, - IEntityType entityType, - Func factory); + ITypeBase typeBase, + Func factory); } diff --git a/src/EFCore/ValueGeneration/IValueGeneratorSelector.cs b/src/EFCore/ValueGeneration/IValueGeneratorSelector.cs index 50a52c93390..f2d9b276f72 100644 --- a/src/EFCore/ValueGeneration/IValueGeneratorSelector.cs +++ b/src/EFCore/ValueGeneration/IValueGeneratorSelector.cs @@ -30,10 +30,10 @@ public interface IValueGeneratorSelector /// Selects the appropriate value generator for a given property. /// /// The property to get the value generator for. - /// - /// The entity type that the value generator will be used for. When called on inherited properties on derived entity types, - /// this entity type may be different from the declared entity type on + /// + /// The type that the value generator will be used for. When called on inherited properties on derived types, + /// this type may be different from the declaring type for /// /// The value generator to be used. - ValueGenerator Select(IProperty property, IEntityType entityType); + ValueGenerator Select(IProperty property, ITypeBase typeBase); } diff --git a/src/EFCore/ValueGeneration/TemporaryNumberValueGeneratorFactory.cs b/src/EFCore/ValueGeneration/TemporaryNumberValueGeneratorFactory.cs index 18550c75e18..c1771ad23cd 100644 --- a/src/EFCore/ValueGeneration/TemporaryNumberValueGeneratorFactory.cs +++ b/src/EFCore/ValueGeneration/TemporaryNumberValueGeneratorFactory.cs @@ -23,9 +23,9 @@ public class TemporaryNumberValueGeneratorFactory : ValueGeneratorFactory /// Creates a new value generator. /// /// The property to create the value generator for. - /// The entity type for which the value generator will be used. + /// The type for which the value generator will be used. /// The newly created value generator. - public override ValueGenerator Create(IProperty property, IEntityType entityType) + public override ValueGenerator Create(IProperty property, ITypeBase entityType) { var type = property.GetTypeMapping().ClrType.UnwrapEnumType(); @@ -47,7 +47,7 @@ public override ValueGenerator Create(IProperty property, IEntityType entityType throw new ArgumentException( CoreStrings.InvalidValueGeneratorFactoryProperty( - nameof(TemporaryNumberValueGeneratorFactory), property.Name, property.DeclaringEntityType.DisplayName())); + nameof(TemporaryNumberValueGeneratorFactory), property.Name, property.DeclaringType.DisplayName())); ValueGenerator? TryCreate() { diff --git a/src/EFCore/ValueGeneration/ValueGeneratorCache.cs b/src/EFCore/ValueGeneration/ValueGeneratorCache.cs index 04f196ad3ac..526fd631b22 100644 --- a/src/EFCore/ValueGeneration/ValueGeneratorCache.cs +++ b/src/EFCore/ValueGeneration/ValueGeneratorCache.cs @@ -45,24 +45,24 @@ public ValueGeneratorCache(ValueGeneratorCacheDependencies dependencies) private readonly struct CacheKey : IEquatable { - public CacheKey(IProperty property, IEntityType entityType) + public CacheKey(IProperty property, ITypeBase typeBase) { Property = property; - EntityType = entityType; + TypeBase = typeBase; } public IProperty Property { get; } - public IEntityType EntityType { get; } + public ITypeBase TypeBase { get; } public bool Equals(CacheKey other) - => Property.Equals(other.Property) && EntityType.Equals(other.EntityType); + => Property.Equals(other.Property) && TypeBase.Equals(other.TypeBase); public override bool Equals(object? obj) => obj is CacheKey cacheKey && Equals(cacheKey); public override int GetHashCode() - => HashCode.Combine(Property, EntityType); + => HashCode.Combine(Property, TypeBase); } /// @@ -70,7 +70,7 @@ public override int GetHashCode() /// the cache. /// /// The property to get the value generator for. - /// + /// /// The entity type that the value generator will be used for. When called on inherited properties on derived entity types, /// this entity type may be different from the declared entity type on /// @@ -78,7 +78,7 @@ public override int GetHashCode() /// The existing or newly created value generator. public virtual ValueGenerator GetOrAdd( IProperty property, - IEntityType entityType, - Func factory) - => _cache.GetOrAdd(new CacheKey(property, entityType), static (ck, f) => f(ck.Property, ck.EntityType), factory); + ITypeBase typeBase, + Func factory) + => _cache.GetOrAdd(new CacheKey(property, typeBase), static (ck, f) => f(ck.Property, ck.TypeBase), factory); } diff --git a/src/EFCore/ValueGeneration/ValueGeneratorFactory.cs b/src/EFCore/ValueGeneration/ValueGeneratorFactory.cs index 6f2e0b8099b..aff8ad1cfba 100644 --- a/src/EFCore/ValueGeneration/ValueGeneratorFactory.cs +++ b/src/EFCore/ValueGeneration/ValueGeneratorFactory.cs @@ -20,9 +20,9 @@ public abstract class ValueGeneratorFactory /// See EF Core value generation for more information and examples. /// /// The property to create the value generator for. - /// The entity type for which the value generator will be used. + /// The type for which the value generator will be used. /// The newly created value generator. - public abstract ValueGenerator Create(IProperty property, IEntityType entityType); + public abstract ValueGenerator Create(IProperty property, ITypeBase typeBase); internal const DynamicallyAccessedMemberTypes DynamicallyAccessedMemberTypes = System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors diff --git a/src/EFCore/ValueGeneration/ValueGeneratorSelector.cs b/src/EFCore/ValueGeneration/ValueGeneratorSelector.cs index d87c8dac522..d3c1af43f7a 100644 --- a/src/EFCore/ValueGeneration/ValueGeneratorSelector.cs +++ b/src/EFCore/ValueGeneration/ValueGeneratorSelector.cs @@ -50,18 +50,17 @@ public ValueGeneratorSelector(ValueGeneratorSelectorDependencies dependencies) /// Selects the appropriate value generator for a given property. /// /// The property to get the value generator for. - /// + /// /// The entity type that the value generator will be used for. When called on inherited properties on derived entity types, /// this entity type may be different from the declared entity type on /// /// The value generator to be used. - public virtual ValueGenerator Select(IProperty property, IEntityType entityType) - => Cache.GetOrAdd(property, entityType, (p, t) => CreateFromFactory(p, t) ?? Create(p, t)); + public virtual ValueGenerator Select(IProperty property, ITypeBase typeBase) + => Cache.GetOrAdd(property, typeBase, (p, t) => CreateFromFactory(p, t) ?? Create(p, t)); - private static ValueGenerator? CreateFromFactory(IProperty property, IEntityType entityType) + private static ValueGenerator? CreateFromFactory(IProperty property, ITypeBase entityType) { var factory = property.GetValueGeneratorFactory(); - if (factory == null) { var mapping = property.GetTypeMapping(); @@ -75,15 +74,15 @@ public virtual ValueGenerator Select(IProperty property, IEntityType entityType) /// Creates a new value generator for the given property. /// /// The property to get the value generator for. - /// + /// /// The entity type that the value generator will be used for. When called on inherited properties on derived entity types, /// this entity type may be different from the declared entity type on /// /// The newly created value generator. - public virtual ValueGenerator Create(IProperty property, IEntityType entityType) + public virtual ValueGenerator Create(IProperty property, ITypeBase typeBase) { var propertyType = property.ClrType.UnwrapNullableType().UnwrapEnumType(); - var generator = FindForType(property, entityType, propertyType); + var generator = FindForType(property, typeBase, propertyType); if (generator != null) { return generator; @@ -93,7 +92,7 @@ public virtual ValueGenerator Create(IProperty property, IEntityType entityType) if (converter != null && converter.ProviderClrType != propertyType) { - generator = FindForType(property, entityType, converter.ProviderClrType); + generator = FindForType(property, typeBase, converter.ProviderClrType); if (generator != null) { return generator.WithConverter(converter); @@ -101,20 +100,20 @@ public virtual ValueGenerator Create(IProperty property, IEntityType entityType) } throw new NotSupportedException( - CoreStrings.NoValueGenerator(property.Name, property.DeclaringEntityType.DisplayName(), propertyType.ShortDisplayName())); + CoreStrings.NoValueGenerator(property.Name, property.DeclaringType.DisplayName(), propertyType.ShortDisplayName())); } /// /// Creates a new value generator for the given property and type, where the property may have a . /// /// The property to get the value generator for. - /// + /// /// The entity type that the value generator will be used for. When called on inherited properties on derived entity types, /// this entity type may be different from the declared entity type on /// /// The type, which may be the provider type after conversion, rather than the property type. /// The newly created value generator. - protected virtual ValueGenerator? FindForType(IProperty property, IEntityType entityType, Type clrType) + protected virtual ValueGenerator? FindForType(IProperty property, ITypeBase typeBase, Type clrType) => clrType == typeof(Guid) ? new GuidValueGenerator() : clrType == typeof(string) diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs index 715ba2c595c..1d026fe4a58 100644 --- a/src/Shared/SharedTypeExtensions.cs +++ b/src/Shared/SharedTypeExtensions.cs @@ -44,7 +44,14 @@ public static bool IsNullableType(this Type type) => !type.IsValueType || type.IsNullableValueType(); public static bool IsValidEntityType(this Type type) - => type is { IsClass: true, IsArray: false }; + => type is { IsClass: true, IsArray: false } + && type != typeof(string); + + public static bool IsValidComplexType(this Type type) + => !type.IsArray + && !type.IsInterface + && type != typeof(string) + && !CommonTypeDictionary.ContainsKey(type); public static bool IsPropertyBagType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type) { diff --git a/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs b/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs index a9788f95152..222d01e4f51 100644 --- a/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/CosmosApiConsistencyTest.cs @@ -30,38 +30,44 @@ public class CosmosApiConsistencyFixture : ApiConsistencyFixtureBase }; public override - List<(Type Type, - Type ReadonlyExtensions, + Dictionary MetadataExtensionTypes { get; } + Type RuntimeExtensions)> MetadataExtensionTypes + { get; } = new() { - ( + { typeof(IReadOnlyModel), - typeof(CosmosModelExtensions), - typeof(CosmosModelExtensions), - typeof(CosmosModelExtensions), - typeof(CosmosModelBuilderExtensions), - null - ), - ( + ( + typeof(CosmosModelExtensions), + typeof(CosmosModelExtensions), + typeof(CosmosModelExtensions), + typeof(CosmosModelBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyEntityType), - typeof(CosmosEntityTypeExtensions), - typeof(CosmosEntityTypeExtensions), - typeof(CosmosEntityTypeExtensions), - typeof(CosmosEntityTypeBuilderExtensions), - null - ), - ( + ( + typeof(CosmosEntityTypeExtensions), + typeof(CosmosEntityTypeExtensions), + typeof(CosmosEntityTypeExtensions), + typeof(CosmosEntityTypeBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyProperty), - typeof(CosmosPropertyExtensions), - typeof(CosmosPropertyExtensions), - typeof(CosmosPropertyExtensions), - typeof(CosmosPropertyBuilderExtensions), - null - ) + ( + typeof(CosmosPropertyExtensions), + typeof(CosmosPropertyExtensions), + typeof(CosmosPropertyExtensions), + typeof(CosmosPropertyBuilderExtensions), + null + ) + } }; } } diff --git a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs index b0f632fb609..b2b9d76b638 100644 --- a/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/EndToEndCosmosTest.cs @@ -1760,10 +1760,25 @@ public async Task Can_have_non_string_property_named_Discriminator() using var context = new NonStringDiscriminatorContext(Fixture.CreateOptions()); context.Database.EnsureCreated(); - await context.AddAsync(new NonStringDiscriminator { Id = 1 }); + var entry = await context.AddAsync(new NonStringDiscriminator { Id = 1 }); await context.SaveChangesAsync(); - Assert.NotNull(await context.Set().OrderBy(e => e.Id).FirstOrDefaultAsync()); + var document = entry.Property("__jObject").CurrentValue; + Assert.NotNull(document); + Assert.Equal("0", document["Discriminator"]); + + Assert.NotNull(await context.Set() + .Where(e => e.Discriminator == EntityType.Base).OrderBy(e => e.Id).FirstOrDefaultAsync()); + + AssertSql( + context, +""" +SELECT c +FROM root c +WHERE (c["Discriminator"] = 0) +ORDER BY c["Id"] +OFFSET 0 LIMIT 1 +"""); } private class NonStringDiscriminator diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index a5c90589246..66927a9dcec 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -574,7 +574,7 @@ IEnumerable IReadOnlyEntityType.FindDeclaredForeignKeys(IRe IReadOnlyNavigation IReadOnlyEntityType.FindDeclaredNavigation(string name) => throw new NotImplementedException(); - IReadOnlyProperty IReadOnlyEntityType.FindDeclaredProperty(string name) + IReadOnlyProperty IReadOnlyTypeBase.FindDeclaredProperty(string name) => throw new NotImplementedException(); IReadOnlyForeignKey IReadOnlyEntityType.FindForeignKey( @@ -598,7 +598,7 @@ IReadOnlyKey IReadOnlyEntityType.FindKey(IReadOnlyList proper IReadOnlyKey IReadOnlyEntityType.FindPrimaryKey() => throw new NotImplementedException(); - IReadOnlyProperty IReadOnlyEntityType.FindProperty(string name) + IReadOnlyProperty IReadOnlyTypeBase.FindProperty(string name) => throw new NotImplementedException(); IReadOnlyServiceProperty IReadOnlyEntityType.FindServiceProperty(string name) @@ -619,7 +619,7 @@ IEnumerable IReadOnlyEntityType.GetDeclaredKeys() IEnumerable IReadOnlyEntityType.GetDeclaredNavigations() => throw new NotImplementedException(); - IEnumerable IReadOnlyEntityType.GetDeclaredProperties() + IEnumerable IReadOnlyTypeBase.GetDeclaredProperties() => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetDeclaredReferencingForeignKeys() @@ -649,7 +649,7 @@ IEnumerable IReadOnlyEntityType.GetKeys() IEnumerable IReadOnlyEntityType.GetNavigations() => throw new NotImplementedException(); - IEnumerable IReadOnlyEntityType.GetProperties() + IEnumerable IReadOnlyTypeBase.GetProperties() => throw new NotImplementedException(); IEnumerable IReadOnlyEntityType.GetReferencingForeignKeys() @@ -672,5 +672,29 @@ IEnumerable IReadOnlyEntityType.GetDeclaredTriggers() IEnumerable IEntityType.GetDeclaredTriggers() => throw new NotImplementedException(); + + public IComplexProperty FindComplexProperty(string name) + => throw new NotImplementedException(); + + public IEnumerable GetComplexProperties() + => throw new NotImplementedException(); + + public IEnumerable GetDeclaredComplexProperties() + => throw new NotImplementedException(); + + IReadOnlyComplexProperty IReadOnlyTypeBase.FindComplexProperty(string name) + => throw new NotImplementedException(); + + public IReadOnlyComplexProperty FindDeclaredComplexProperty(string name) + => throw new NotImplementedException(); + + IEnumerable IReadOnlyTypeBase.GetComplexProperties() + => throw new NotImplementedException(); + + IEnumerable IReadOnlyTypeBase.GetDeclaredComplexProperties() + => throw new NotImplementedException(); + + public IEnumerable GetDerivedComplexProperties() + => throw new NotImplementedException(); } } diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CustomPartitionKeyIdValueGeneratorFactory.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CustomPartitionKeyIdValueGeneratorFactory.cs index bf458d8d928..be2461de025 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CustomPartitionKeyIdValueGeneratorFactory.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CustomPartitionKeyIdValueGeneratorFactory.cs @@ -5,6 +5,6 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities; internal class CustomPartitionKeyIdValueGeneratorFactory : ValueGeneratorFactory { - public override ValueGenerator Create(IProperty property, IEntityType entityType) + public override ValueGenerator Create(IProperty property, ITypeBase typeBase) => new CustomPartitionKeyIdGenerator(); } diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs index d30276b6423..08af7fcd587 100644 --- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -247,6 +247,211 @@ protected override TestModelBuilder CreateModelBuilder(Action CreateTestModelBuilder(CosmosTestHelpers.Instance, configure); } + public class CosmosGenericComplexType : GenericComplexType + { + public override void Properties_can_have_provider_type_set_for_type() + { + var modelBuilder = CreateModelBuilder(c => c.Properties().HaveConversion()); + + modelBuilder + .Ignore() + .Entity( + b => + { + b.Property("__id").HasConversion(null); + b.ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down); + b.Property("Charm"); + b.Property("Strange"); + }); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty("Up").GetProviderClrType()); + Assert.Same(typeof(byte[]), complexType.FindProperty("Down").GetProviderClrType()); + Assert.Null(complexType.FindProperty("Charm").GetProviderClrType()); + Assert.Same(typeof(byte[]), complexType.FindProperty("Strange").GetProviderClrType()); + } + + [ConditionalFact] + public virtual void Partition_key_is_added_to_the_keys() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders) + .HasPartitionKey(b => b.AlternateKey) + .Property(b => b.AlternateKey).HasConversion(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Equal( + new[] { nameof(Customer.Id), nameof(Customer.AlternateKey) }, + entity.FindPrimaryKey().Properties.Select(p => p.Name)); + Assert.Equal( + new[] { StoreKeyConvention.DefaultIdPropertyName, nameof(Customer.AlternateKey) }, + entity.GetKeys().First(k => k != entity.FindPrimaryKey()).Properties.Select(p => p.Name)); + + var idProperty = entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName); + Assert.Single(idProperty.GetContainingKeys()); + Assert.NotNull(idProperty.GetValueGeneratorFactory()); + } + + [ConditionalFact] + public virtual void Partition_key_is_added_to_the_alternate_key_if_primary_key_contains_id() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().HasKey(StoreKeyConvention.DefaultIdPropertyName); + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders) + .HasPartitionKey(b => b.AlternateKey) + .Property(b => b.AlternateKey).HasConversion(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Equal( + new[] { StoreKeyConvention.DefaultIdPropertyName }, + entity.FindPrimaryKey().Properties.Select(p => p.Name)); + Assert.Equal( + new[] { StoreKeyConvention.DefaultIdPropertyName, nameof(Customer.AlternateKey) }, + entity.GetKeys().First(k => k != entity.FindPrimaryKey()).Properties.Select(p => p.Name)); + } + + [ConditionalFact] + public virtual void No_id_property_created_if_another_property_mapped_to_id() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity() + .Property(c => c.Name) + .ToJsonProperty(StoreKeyConvention.IdPropertyJsonName); + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName)); + Assert.Single(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); + + var idProperty = entity.GetDeclaredProperties() + .Single(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName); + Assert.Single(idProperty.GetContainingKeys()); + Assert.NotNull(idProperty.GetValueGeneratorFactory()); + } + + [ConditionalFact] + public virtual void No_id_property_created_if_another_property_mapped_to_id_in_pk() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity() + .Property(c => c.Name) + .ToJsonProperty(StoreKeyConvention.IdPropertyJsonName); + modelBuilder.Entity() + .Ignore(c => c.Details) + .Ignore(c => c.Orders) + .HasKey(c => c.Name); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Null(entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName)); + Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); + + var idProperty = entity.GetDeclaredProperties() + .Single(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyJsonName); + Assert.Single(idProperty.GetContainingKeys()); + Assert.Null(idProperty.GetValueGeneratorFactory()); + } + + [ConditionalFact] + public virtual void No_alternate_key_is_created_if_primary_key_contains_id() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().HasKey(StoreKeyConvention.DefaultIdPropertyName); + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Equal( + new[] { StoreKeyConvention.DefaultIdPropertyName }, + entity.FindPrimaryKey().Properties.Select(p => p.Name)); + Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); + + var idProperty = entity.FindProperty(StoreKeyConvention.DefaultIdPropertyName); + Assert.Single(idProperty.GetContainingKeys()); + Assert.Null(idProperty.GetValueGeneratorFactory()); + } + + [ConditionalFact] + public virtual void No_alternate_key_is_created_if_primary_key_contains_id_and_partition_key() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().HasKey(nameof(Customer.AlternateKey), StoreKeyConvention.DefaultIdPropertyName); + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders) + .HasPartitionKey(b => b.AlternateKey) + .Property(b => b.AlternateKey).HasConversion(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Equal( + new[] { nameof(Customer.AlternateKey), StoreKeyConvention.DefaultIdPropertyName }, + entity.FindPrimaryKey().Properties.Select(p => p.Name)); + Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); + } + + [ConditionalFact] + public virtual void No_alternate_key_is_created_if_id_is_partition_key() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().HasKey(nameof(Customer.AlternateKey)); + modelBuilder.Entity() + .Ignore(b => b.Details) + .Ignore(b => b.Orders) + .HasPartitionKey(b => b.AlternateKey) + .Property(b => b.AlternateKey).HasConversion().ToJsonProperty("id"); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer)); + + Assert.Equal( + new[] { nameof(Customer.AlternateKey) }, + entity.FindPrimaryKey().Properties.Select(p => p.Name)); + Assert.Empty(entity.GetKeys().Where(k => k != entity.FindPrimaryKey())); + } + + protected override TestModelBuilder CreateModelBuilder(Action configure = null) + => CreateTestModelBuilder(CosmosTestHelpers.Instance, configure); + } + public class CosmosGenericInheritance : GenericInheritance { public override void Base_type_can_be_discovered_after_creating_foreign_keys_on_derived() diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 82f6d78427c..1e6788fd8c1 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -3718,28 +3718,28 @@ public virtual void Shared_owned_types_are_stored_in_snapshot() { Assert.Equal(7, o.GetEntityTypes().Count()); - var order = o.FindEntityType(typeof(Order).FullName); - Assert.Equal(1, order.PropertyCount()); + var order = (IRuntimeEntityType)o.FindEntityType(typeof(Order).FullName); + Assert.Equal(1, order.PropertyCount); - var orderInfo = order.FindNavigation(nameof(Order.OrderInfo)).TargetEntityType; - Assert.Equal(1, orderInfo.PropertyCount()); + var orderInfo = (IRuntimeEntityType)order.FindNavigation(nameof(Order.OrderInfo)).TargetEntityType; + Assert.Equal(1, orderInfo.PropertyCount); - var orderInfoAddress = orderInfo.FindNavigation(nameof(OrderInfo.StreetAddress)).TargetEntityType; - Assert.Equal(2, orderInfoAddress.PropertyCount()); + var orderInfoAddress = (IRuntimeEntityType)orderInfo.FindNavigation(nameof(OrderInfo.StreetAddress)).TargetEntityType; + Assert.Equal(2, orderInfoAddress.PropertyCount); - var orderBillingDetails = order.FindNavigation(nameof(Order.OrderBillingDetails)).TargetEntityType; - Assert.Equal(1, orderBillingDetails.PropertyCount()); + var orderBillingDetails = (IRuntimeEntityType)order.FindNavigation(nameof(Order.OrderBillingDetails)).TargetEntityType; + Assert.Equal(1, orderBillingDetails.PropertyCount); var orderBillingDetailsAddress = - orderBillingDetails.FindNavigation(nameof(OrderDetails.StreetAddress)).TargetEntityType; - Assert.Equal(2, orderBillingDetailsAddress.PropertyCount()); + (IRuntimeEntityType)orderBillingDetails.FindNavigation(nameof(OrderDetails.StreetAddress)).TargetEntityType; + Assert.Equal(2, orderBillingDetailsAddress.PropertyCount); - var orderShippingDetails = order.FindNavigation(nameof(Order.OrderShippingDetails)).TargetEntityType; - Assert.Equal(1, orderShippingDetails.PropertyCount()); + var orderShippingDetails = (IRuntimeEntityType)order.FindNavigation(nameof(Order.OrderShippingDetails)).TargetEntityType; + Assert.Equal(1, orderShippingDetails.PropertyCount); var orderShippingDetailsAddress = - orderShippingDetails.FindNavigation(nameof(OrderDetails.StreetAddress)).TargetEntityType; - Assert.Equal(2, orderShippingDetailsAddress.PropertyCount()); + (IRuntimeEntityType)orderShippingDetails.FindNavigation(nameof(OrderDetails.StreetAddress)).TargetEntityType; + Assert.Equal(2, orderShippingDetailsAddress.PropertyCount); }); [ConditionalFact] @@ -6942,7 +6942,7 @@ static List getAllProperties(IModel model) => model .GetEntityTypes() .SelectMany(m => m.GetProperties()) - .OrderBy(p => p.DeclaringEntityType.Name) + .OrderBy(p => p.DeclaringType.Name) .ThenBy(p => p.Name) .ToList(); @@ -7330,7 +7330,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) var originalProperty = originalProperties[i]; var snapshotProperty = snapshotProperties[i]; - Assert.Equal(originalProperty.DeclaringEntityType.Name, snapshotProperty.DeclaringEntityType.Name); + Assert.Equal(originalProperty.DeclaringType.Name, snapshotProperty.DeclaringType.Name); Assert.Equal(originalProperty.Name, snapshotProperty.Name); Assert.Equal(originalProperty.GetColumnType(), snapshotProperty.GetColumnType()); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 8bdec2f1f41..e541ad867a0 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -3,6 +3,7 @@ using System.Collections; using System.ComponentModel; +using System.ComponentModel.DataAnnotations.Schema; using System.Data; using System.Text.Json; using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration.Internal; @@ -1977,7 +1978,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba "Details", typeof(string), propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Details", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("
k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("_details", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), propertyAccessMode: PropertyAccessMode.Field, nullable: true); @@ -2004,7 +2005,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var context = runtimeEntityType.AddServiceProperty( "Context", propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Context", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - serviceType: typeof(Microsoft.EntityFrameworkCore.DbContext)); + serviceType: typeof(DbContext)); var key = runtimeEntityType.AddKey( new[] { principalBaseId, principalBaseAlternateId }); @@ -2124,7 +2125,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba "Details", typeof(string), propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Details", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("
k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("_details", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), nullable: true); details.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); @@ -2139,7 +2140,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var context = runtimeEntityType.AddServiceProperty( "Context", propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Context", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - serviceType: typeof(Microsoft.EntityFrameworkCore.DbContext)); + serviceType: typeof(DbContext)); var key = runtimeEntityType.AddKey( new[] { principalDerivedId, principalDerivedAlternateId, id }); @@ -3765,7 +3766,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba "Details", typeof(string), propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Details", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("
k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("_details", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), propertyAccessMode: PropertyAccessMode.Field, nullable: true); details.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); @@ -3782,7 +3783,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var context = runtimeEntityType.AddServiceProperty( "Context", propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Context", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - serviceType: typeof(Microsoft.EntityFrameworkCore.DbContext)); + serviceType: typeof(DbContext)); var key = runtimeEntityType.AddKey( new[] { principalBaseId, principalBaseAlternateId }); @@ -3883,7 +3884,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba "Details", typeof(string), propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Details", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("
k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("_details", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), nullable: true); details.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); @@ -3898,7 +3899,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba var context = runtimeEntityType.AddServiceProperty( "Context", propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Context", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - serviceType: typeof(Microsoft.EntityFrameworkCore.DbContext)); + serviceType: typeof(DbContext)); var key = runtimeEntityType.AddKey( new[] { principalDerivedId, principalDerivedAlternateId, id }); @@ -4631,6 +4632,690 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) c.SaveChanges(); }); + [ConditionalFact] + [SqlServerConfiguredCondition] + public void ComplexTypes() + => Test( + new ComplexTypesContext(), + new CompiledModelCodeGenerationOptions { UseNullableReferenceTypes = true }, + code => + Assert.Collection( + code, + c => AssertFileContents( + "ComplexTypesContextModel.cs", + """ +// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + [DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.ComplexTypesContext))] + public partial class ComplexTypesContextModel : RuntimeModel + { + static ComplexTypesContextModel() + { + var model = new ComplexTypesContextModel(); + model.Initialize(); + model.Customize(); + _instance = model; + } + + private static ComplexTypesContextModel _instance; + public static IModel Instance => _instance; + + partial void Initialize(); + + partial void Customize(); + } +} +""", c), + c => AssertFileContents( + "ComplexTypesContextModelBuilder.cs", + """ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Migrations; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + public partial class ComplexTypesContextModel + { + partial void Initialize() + { + var principalBase = PrincipalBaseEntityType.Create(this); + var principalDerived = PrincipalDerivedEntityType.Create(this, principalBase); + + PrincipalBaseEntityType.CreateForeignKey1(principalBase, principalBase); + PrincipalBaseEntityType.CreateForeignKey2(principalBase, principalDerived); + + PrincipalBaseEntityType.CreateAnnotations(principalBase); + PrincipalDerivedEntityType.CreateAnnotations(principalDerived); + + AddAnnotation("Relational:MaxIdentifierLength", 128); + AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel()); + } + + private IRelationalModel CreateRelationalModel() + { + var relationalModel = new RelationalModel(this); + + var principalBase = FindEntityType("Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase")!; + + var defaultTableMappings = new List>(); + principalBase.SetRuntimeAnnotation("Relational:DefaultMappings", defaultTableMappings); + var microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase = new TableBase("Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", null, relationalModel); + var discriminatorColumnBase = new ColumnBase("Discriminator", "nvarchar(55)", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Discriminator", discriminatorColumnBase); + var enum1ColumnBase = new ColumnBase("Enum1", "int", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Enum1", enum1ColumnBase); + var enum2ColumnBase = new ColumnBase("Enum2", "int", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Enum2", enum2ColumnBase); + var flagsEnum1ColumnBase = new ColumnBase("FlagsEnum1", "int", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("FlagsEnum1", flagsEnum1ColumnBase); + var flagsEnum2ColumnBase = new ColumnBase("FlagsEnum2", "int", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("FlagsEnum2", flagsEnum2ColumnBase); + var idColumnBase = new ColumnBase("Id", "bigint", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("Id", idColumnBase); + var principalBaseIdColumnBase = new ColumnBase("PrincipalBaseId", "bigint", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("PrincipalBaseId", principalBaseIdColumnBase); + var principalDerivedDependentBasebyteIdColumnBase = new ColumnBase("PrincipalDerived>Id", "bigint", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase) + { + IsNullable = true + }; + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.Columns.Add("PrincipalDerived>Id", principalDerivedDependentBasebyteIdColumnBase); + relationalModel.DefaultTables.Add("Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase); + var microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase = new TableMappingBase(principalBase, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase, true); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.AddEntityTypeMapping(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase, false); + defaultTableMappings.Add(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("Id")!, principalBase.FindProperty("Id")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("Discriminator")!, principalBase.FindProperty("Discriminator")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("Enum1")!, principalBase.FindProperty("Enum1")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("Enum2")!, principalBase.FindProperty("Enum2")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("FlagsEnum1")!, principalBase.FindProperty("FlagsEnum1")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("FlagsEnum2")!, principalBase.FindProperty("FlagsEnum2")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("PrincipalBaseId")!, principalBase.FindProperty("PrincipalBaseId")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("PrincipalDerived>Id")!, principalBase.FindProperty("PrincipalDerivedId")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase); + + var tableMappings = new List(); + principalBase.SetRuntimeAnnotation("Relational:TableMappings", tableMappings); + var principalBaseTable = new Table("PrincipalBase", null, relationalModel); + var idColumn = new Column("Id", "bigint", principalBaseTable); + principalBaseTable.Columns.Add("Id", idColumn); + var discriminatorColumn = new Column("Discriminator", "nvarchar(55)", principalBaseTable); + principalBaseTable.Columns.Add("Discriminator", discriminatorColumn); + var enum1Column = new Column("Enum1", "int", principalBaseTable); + principalBaseTable.Columns.Add("Enum1", enum1Column); + var enum2Column = new Column("Enum2", "int", principalBaseTable) + { + IsNullable = true + }; + principalBaseTable.Columns.Add("Enum2", enum2Column); + var flagsEnum1Column = new Column("FlagsEnum1", "int", principalBaseTable); + principalBaseTable.Columns.Add("FlagsEnum1", flagsEnum1Column); + var flagsEnum2Column = new Column("FlagsEnum2", "int", principalBaseTable); + principalBaseTable.Columns.Add("FlagsEnum2", flagsEnum2Column); + var principalBaseIdColumn = new Column("PrincipalBaseId", "bigint", principalBaseTable) + { + IsNullable = true + }; + principalBaseTable.Columns.Add("PrincipalBaseId", principalBaseIdColumn); + var principalDerivedDependentBasebyteIdColumn = new Column("PrincipalDerived>Id", "bigint", principalBaseTable) + { + IsNullable = true + }; + principalBaseTable.Columns.Add("PrincipalDerived>Id", principalDerivedDependentBasebyteIdColumn); + var pK_PrincipalBase = new UniqueConstraint("PK_PrincipalBase", principalBaseTable, new[] { idColumn }); + principalBaseTable.PrimaryKey = pK_PrincipalBase; + var pK_PrincipalBaseUc = RelationalModel.GetKey(this, + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", + new[] { "Id" }); + pK_PrincipalBase.MappedKeys.Add(pK_PrincipalBaseUc); + RelationalModel.GetOrCreateUniqueConstraints(pK_PrincipalBaseUc).Add(pK_PrincipalBase); + principalBaseTable.UniqueConstraints.Add("PK_PrincipalBase", pK_PrincipalBase); + var iX_PrincipalBase_PrincipalBaseId = new TableIndex( + "IX_PrincipalBase_PrincipalBaseId", principalBaseTable, new[] { principalBaseIdColumn }, false); + var iX_PrincipalBase_PrincipalBaseIdIx = RelationalModel.GetIndex(this, + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", + new[] { "PrincipalBaseId" }); + iX_PrincipalBase_PrincipalBaseId.MappedIndexes.Add(iX_PrincipalBase_PrincipalBaseIdIx); + RelationalModel.GetOrCreateTableIndexes(iX_PrincipalBase_PrincipalBaseIdIx).Add(iX_PrincipalBase_PrincipalBaseId); + principalBaseTable.Indexes.Add("IX_PrincipalBase_PrincipalBaseId", iX_PrincipalBase_PrincipalBaseId); + var iX_PrincipalBase_PrincipalDerivedDependentBasebyteId = new TableIndex( + "IX_PrincipalBase_PrincipalDerived>Id", principalBaseTable, new[] { principalDerivedDependentBasebyteIdColumn }, false); + var iX_PrincipalBase_PrincipalDerivedDependentBasebyteIdIx = RelationalModel.GetIndex(this, + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", + new[] { "PrincipalDerivedId" }); + iX_PrincipalBase_PrincipalDerivedDependentBasebyteId.MappedIndexes.Add(iX_PrincipalBase_PrincipalDerivedDependentBasebyteIdIx); + RelationalModel.GetOrCreateTableIndexes(iX_PrincipalBase_PrincipalDerivedDependentBasebyteIdIx).Add(iX_PrincipalBase_PrincipalDerivedDependentBasebyteId); + principalBaseTable.Indexes.Add("IX_PrincipalBase_PrincipalDerived>Id", iX_PrincipalBase_PrincipalDerivedDependentBasebyteId); + relationalModel.Tables.Add(("PrincipalBase", null), principalBaseTable); + var principalBaseTableMapping = new TableMapping(principalBase, principalBaseTable, true) + { + IsSharedTablePrincipal = true, + }; + principalBaseTable.AddEntityTypeMapping(principalBaseTableMapping, false); + tableMappings.Add(principalBaseTableMapping); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Id")!, principalBase.FindProperty("Id")!, principalBaseTableMapping); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Discriminator")!, principalBase.FindProperty("Discriminator")!, principalBaseTableMapping); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Enum1")!, principalBase.FindProperty("Enum1")!, principalBaseTableMapping); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Enum2")!, principalBase.FindProperty("Enum2")!, principalBaseTableMapping); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("FlagsEnum1")!, principalBase.FindProperty("FlagsEnum1")!, principalBaseTableMapping); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("FlagsEnum2")!, principalBase.FindProperty("FlagsEnum2")!, principalBaseTableMapping); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("PrincipalBaseId")!, principalBase.FindProperty("PrincipalBaseId")!, principalBaseTableMapping); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("PrincipalDerived>Id")!, principalBase.FindProperty("PrincipalDerivedId")!, principalBaseTableMapping); + + var principalDerived = FindEntityType("Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>")!; + + var defaultTableMappings0 = new List>(); + principalDerived.SetRuntimeAnnotation("Relational:DefaultMappings", defaultTableMappings0); + var microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0 = new TableMappingBase(principalDerived, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase, true); + microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.AddEntityTypeMapping(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0, false); + defaultTableMappings0.Add(microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("Id")!, principalDerived.FindProperty("Id")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("Discriminator")!, principalDerived.FindProperty("Discriminator")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("Enum1")!, principalDerived.FindProperty("Enum1")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("Enum2")!, principalDerived.FindProperty("Enum2")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("FlagsEnum1")!, principalDerived.FindProperty("FlagsEnum1")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("FlagsEnum2")!, principalDerived.FindProperty("FlagsEnum2")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("PrincipalBaseId")!, principalDerived.FindProperty("PrincipalBaseId")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + RelationalModel.CreateColumnMapping((ColumnBase)microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseTableBase.FindColumn("PrincipalDerived>Id")!, principalDerived.FindProperty("PrincipalDerivedId")!, microsoftEntityFrameworkCoreScaffoldingInternalCSharpRuntimeModelCodeGeneratorTestPrincipalBaseMappingBase0); + + var tableMappings0 = new List(); + principalDerived.SetRuntimeAnnotation("Relational:TableMappings", tableMappings0); + var principalBaseTableMapping0 = new TableMapping(principalDerived, principalBaseTable, true) + { + IsSharedTablePrincipal = false, + }; + principalBaseTable.AddEntityTypeMapping(principalBaseTableMapping0, false); + tableMappings0.Add(principalBaseTableMapping0); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Id")!, principalDerived.FindProperty("Id")!, principalBaseTableMapping0); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Discriminator")!, principalDerived.FindProperty("Discriminator")!, principalBaseTableMapping0); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Enum1")!, principalDerived.FindProperty("Enum1")!, principalBaseTableMapping0); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("Enum2")!, principalDerived.FindProperty("Enum2")!, principalBaseTableMapping0); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("FlagsEnum1")!, principalDerived.FindProperty("FlagsEnum1")!, principalBaseTableMapping0); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("FlagsEnum2")!, principalDerived.FindProperty("FlagsEnum2")!, principalBaseTableMapping0); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("PrincipalBaseId")!, principalDerived.FindProperty("PrincipalBaseId")!, principalBaseTableMapping0); + RelationalModel.CreateColumnMapping(principalBaseTable.FindColumn("PrincipalDerived>Id")!, principalDerived.FindProperty("PrincipalDerivedId")!, principalBaseTableMapping0); + var fK_PrincipalBase_PrincipalBase_PrincipalBaseId = new ForeignKeyConstraint( + "FK_PrincipalBase_PrincipalBase_PrincipalBaseId", principalBaseTable, principalBaseTable, + new[] { principalBaseIdColumn }, + principalBaseTable.FindUniqueConstraint("PK_PrincipalBase")!, ReferentialAction.NoAction); + var fK_PrincipalBase_PrincipalBase_PrincipalBaseIdFk = RelationalModel.GetForeignKey(this, + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", + new[] { "PrincipalBaseId" }, + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", + new[] { "Id" }); + fK_PrincipalBase_PrincipalBase_PrincipalBaseId.MappedForeignKeys.Add(fK_PrincipalBase_PrincipalBase_PrincipalBaseIdFk); + RelationalModel.GetOrCreateForeignKeyConstraints(fK_PrincipalBase_PrincipalBase_PrincipalBaseIdFk).Add(fK_PrincipalBase_PrincipalBase_PrincipalBaseId); + principalBaseTable.ForeignKeyConstraints.Add(fK_PrincipalBase_PrincipalBase_PrincipalBaseId); + principalBaseTable.ReferencingForeignKeyConstraints.Add(fK_PrincipalBase_PrincipalBase_PrincipalBaseId); + var fK_PrincipalBase_PrincipalBase_PrincipalDerivedDependentBasebyteId = new ForeignKeyConstraint( + "FK_PrincipalBase_PrincipalBase_PrincipalDerived>Id", principalBaseTable, principalBaseTable, + new[] { principalDerivedDependentBasebyteIdColumn }, + principalBaseTable.FindUniqueConstraint("PK_PrincipalBase")!, ReferentialAction.NoAction); + var fK_PrincipalBase_PrincipalBase_PrincipalDerivedDependentBasebyteIdFk = RelationalModel.GetForeignKey(this, + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", + new[] { "PrincipalDerivedId" }, + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>", + new[] { "Id" }); + fK_PrincipalBase_PrincipalBase_PrincipalDerivedDependentBasebyteId.MappedForeignKeys.Add(fK_PrincipalBase_PrincipalBase_PrincipalDerivedDependentBasebyteIdFk); + RelationalModel.GetOrCreateForeignKeyConstraints(fK_PrincipalBase_PrincipalBase_PrincipalDerivedDependentBasebyteIdFk).Add(fK_PrincipalBase_PrincipalBase_PrincipalDerivedDependentBasebyteId); + principalBaseTable.ForeignKeyConstraints.Add(fK_PrincipalBase_PrincipalBase_PrincipalDerivedDependentBasebyteId); + principalBaseTable.ReferencingForeignKeyConstraints.Add(fK_PrincipalBase_PrincipalBase_PrincipalDerivedDependentBasebyteId); + return relationalModel.MakeReadOnly(); + } + } +} +""", c), + c => AssertFileContents( + "PrincipalBaseEntityType.cs", + """ +// +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; +using Microsoft.EntityFrameworkCore.ValueGeneration; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class PrincipalBaseEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase", + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), + baseEntityType, + discriminatorProperty: "Discriminator", + discriminatorValue: "PrincipalBase"); + + var id = runtimeEntityType.AddProperty( + "Id", + typeof(long?), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw); + id.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + var discriminator = runtimeEntityType.AddProperty( + "Discriminator", + typeof(string), + afterSaveBehavior: PropertySaveBehavior.Throw, + maxLength: 55, + valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); + discriminator.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var enum1 = runtimeEntityType.AddProperty( + "Enum1", + typeof(CSharpRuntimeModelCodeGeneratorTest.AnEnum), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Enum1", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + sentinel: (CSharpRuntimeModelCodeGeneratorTest.AnEnum)0); + enum1.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var enum2 = runtimeEntityType.AddProperty( + "Enum2", + typeof(CSharpRuntimeModelCodeGeneratorTest.AnEnum?), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Enum2", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + enum2.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var flagsEnum1 = runtimeEntityType.AddProperty( + "FlagsEnum1", + typeof(CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("FlagsEnum1", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + sentinel: (CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum)0); + flagsEnum1.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var flagsEnum2 = runtimeEntityType.AddProperty( + "FlagsEnum2", + typeof(CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("FlagsEnum2", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + sentinel: (CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum)0); + flagsEnum2.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var principalBaseId = runtimeEntityType.AddProperty( + "PrincipalBaseId", + typeof(long?), + nullable: true); + principalBaseId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var principalDerivedId = runtimeEntityType.AddProperty( + "PrincipalDerivedId", + typeof(long?), + nullable: true); + principalDerivedId.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + OwnedComplexProperty.Create(runtimeEntityType); + var key = runtimeEntityType.AddKey( + new[] { id }); + runtimeEntityType.SetPrimaryKey(key); + + var index = runtimeEntityType.AddIndex( + new[] { principalBaseId }); + + var index0 = runtimeEntityType.AddIndex( + new[] { principalDerivedId }); + + return runtimeEntityType; + } + + private static class OwnedComplexProperty + { + public static RuntimeComplexProperty Create(RuntimeEntityType declaringType) + { + var complexProperty = declaringType.AddComplexProperty("Owned", + typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase.Owned#OwnedType", + typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Owned", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("_ownedField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyAccessMode: PropertyAccessMode.Field, + changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); + + var complexType = complexProperty.ComplexType; + var details = complexType.AddProperty( + "Details", + typeof(string), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Details", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("_details", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyAccessMode: PropertyAccessMode.FieldDuringConstruction, + concurrencyToken: true, + valueGenerated: ValueGenerated.OnAddOrUpdate, + beforeSaveBehavior: PropertySaveBehavior.Ignore, + afterSaveBehavior: PropertySaveBehavior.Ignore, + maxLength: 64, + unicode: false, + precision: 3, + scale: 2, + sentinel: ""); + details.AddAnnotation("foo", "bar"); + details.AddAnnotation("Relational:ColumnName", "Deets"); + details.AddAnnotation("Relational:ColumnType", "varchar"); + details.AddAnnotation("Relational:DefaultValueSql", "null"); + details.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var number = complexType.AddProperty( + "Number", + typeof(int), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Number", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + sentinel: 0); + number.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + PrincipalComplexProperty.Create(complexType); + complexType.AddAnnotation("go", "brr"); + complexProperty.AddAnnotation("goo", "ber"); + return complexProperty; + } + + private static class PrincipalComplexProperty + { + public static RuntimeComplexProperty Create(RuntimeComplexType declaringType) + { + var complexProperty = declaringType.AddComplexProperty("Principal", + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase.Owned#OwnedType.Principal#PrincipalBase", + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty("Principal", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + + var complexType = complexProperty.ComplexType; + var enum1 = complexType.AddProperty( + "Enum1", + typeof(CSharpRuntimeModelCodeGeneratorTest.AnEnum), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Enum1", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + sentinel: (CSharpRuntimeModelCodeGeneratorTest.AnEnum)0); + enum1.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var enum2 = complexType.AddProperty( + "Enum2", + typeof(CSharpRuntimeModelCodeGeneratorTest.AnEnum?), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Enum2", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + enum2.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var flagsEnum1 = complexType.AddProperty( + "FlagsEnum1", + typeof(CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("FlagsEnum1", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + sentinel: (CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum)0); + flagsEnum1.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var flagsEnum2 = complexType.AddProperty( + "FlagsEnum2", + typeof(CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("FlagsEnum2", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + sentinel: (CSharpRuntimeModelCodeGeneratorTest.AFlagsEnum)0); + flagsEnum2.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + var id = complexType.AddProperty( + "Id", + typeof(long?), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + id.AddAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.None); + + return complexProperty; + } + } + } + + public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty("PrincipalBaseId")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty("Id")! })!, + principalEntityType); + + var deriveds = principalEntityType.AddNavigation("Deriveds", + runtimeForeignKey, + onDependent: false, + typeof(ICollection), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty("Deriveds", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + return runtimeForeignKey; + } + + public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) + { + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty("PrincipalDerivedId")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty("Id")! })!, + principalEntityType); + + var principals = principalEntityType.AddNavigation("Principals", + runtimeForeignKey, + onDependent: false, + typeof(ICollection), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetProperty("Principals", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + + return runtimeForeignKey; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:MappingStrategy", "TPH"); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "PrincipalBase"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +""", c), + c => AssertFileContents( + "PrincipalDerivedEntityType.cs", + """ +// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + internal partial class PrincipalDerivedEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>", + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), + baseEntityType, + discriminatorProperty: "Discriminator", + discriminatorValue: "PrincipalDerived>"); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "PrincipalBase"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} +""", c)), + model => + { + var principalBase = model.FindEntityType(typeof(PrincipalBase)); + + var complexProperty = principalBase.GetComplexProperties().Single(); + Assert.Equal( + new[] { "goo" }, + complexProperty.GetAnnotations().Select(a => a.Name)); + Assert.Equal(nameof(PrincipalBase.Owned), complexProperty.Name); + Assert.False(complexProperty.IsCollection); + Assert.False(complexProperty.IsNullable); + Assert.Equal(typeof(OwnedType), complexProperty.ClrType); + Assert.Equal("_ownedField", complexProperty.FieldInfo.Name); + Assert.Equal(nameof(PrincipalBase.Owned), complexProperty.PropertyInfo.Name); + Assert.Equal(principalBase, complexProperty.DeclaringType); + Assert.Equal(PropertyAccessMode.Field, complexProperty.GetPropertyAccessMode()); + Assert.Equal("ber", complexProperty["goo"]); + + var complexType = complexProperty.ComplexType; + Assert.Equal( + new[] { "go" }, + complexType.GetAnnotations().Select(a => a.Name)); + Assert.Equal(typeof(PrincipalBase).FullName + ".Owned#OwnedType", complexType.Name); + Assert.Equal(typeof(OwnedType), complexType.ClrType); + Assert.True(complexType.HasSharedClrType); + Assert.False(complexType.IsPropertyBag); + Assert.IsType(complexType.ConstructorBinding); + Assert.Null(complexType.FindIndexerPropertyInfo()); + Assert.Equal( + ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues, + complexType.GetChangeTrackingStrategy()); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => complexType.GetPropertyAccessMode()).Message); + Assert.Equal("brr", complexType["go"]); + + var detailsProperty = complexType.FindProperty(nameof(OwnedType.Details)); + Assert.Equal( + new[] { + CoreAnnotationNames.MaxLength, + CoreAnnotationNames.Precision, + RelationalAnnotationNames.ColumnName, + RelationalAnnotationNames.ColumnType, + RelationalAnnotationNames.DefaultValueSql, + CoreAnnotationNames.Scale, + SqlServerAnnotationNames.ValueGenerationStrategy, + CoreAnnotationNames.Unicode, + "foo" + }, + detailsProperty.GetAnnotations().Select(a => a.Name)); + Assert.Equal(typeof(string), detailsProperty.ClrType); + Assert.Equal(typeof(string), detailsProperty.PropertyInfo.PropertyType); + Assert.Equal(typeof(string), detailsProperty.FieldInfo.FieldType); + Assert.Equal("_details", detailsProperty.FieldInfo.Name); + Assert.False(detailsProperty.IsNullable); + Assert.Equal(ValueGenerated.OnAddOrUpdate, detailsProperty.ValueGenerated); + Assert.Equal(PropertySaveBehavior.Ignore, detailsProperty.GetAfterSaveBehavior()); + Assert.Equal(PropertySaveBehavior.Ignore, detailsProperty.GetBeforeSaveBehavior()); + Assert.Equal("Deets", detailsProperty.GetColumnName()); + Assert.Equal("varchar(64)", detailsProperty.GetColumnType()); + Assert.False(detailsProperty.IsUnicode()); + Assert.True(detailsProperty.IsConcurrencyToken); + Assert.Equal(64, detailsProperty.GetMaxLength()); + Assert.Null(detailsProperty.IsFixedLength()); + Assert.Equal(3, detailsProperty.GetPrecision()); + Assert.Equal(2, detailsProperty.GetScale()); + Assert.Equal("", detailsProperty.Sentinel); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, detailsProperty.GetPropertyAccessMode()); + Assert.Null(detailsProperty.GetValueConverter()); + Assert.NotNull(detailsProperty.GetValueComparer()); + Assert.NotNull(detailsProperty.GetKeyValueComparer()); + Assert.Equal(SqlServerValueGenerationStrategy.None, detailsProperty.GetValueGenerationStrategy()); + Assert.Equal("null", detailsProperty.GetDefaultValueSql()); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => detailsProperty.GetIdentitySeed()).Message); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => detailsProperty.GetIdentityIncrement()).Message); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => detailsProperty.IsSparse()).Message); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => detailsProperty.GetCollation()).Message); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => detailsProperty.GetComment()).Message); + Assert.Equal( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => detailsProperty.GetColumnOrder()).Message); + + var nestedComplexType = complexType.FindComplexProperty(nameof(OwnedType.Principal)).ComplexType; + + Assert.Equal(5, nestedComplexType.GetProperties().Count()); + + var principalTable = StoreObjectIdentifier.Create(complexType, StoreObjectType.Table).Value; + + Assert.Equal("Deets", detailsProperty.GetColumnName(principalTable)); + + var principalDerived = model.FindEntityType(typeof(PrincipalDerived>)); + Assert.Equal(principalBase, principalDerived.BaseType); + + Assert.Equal( + new[] + { + principalBase, + principalDerived + }, + model.GetEntityTypes()); + }, + null, + c => + { + c.Set>>().Add( + new PrincipalDerived> + { + Id = 1, + AlternateId = new Guid(), + Dependent = new DependentBase(1), + Owned = new OwnedType(c) + }); + + //c.SaveChanges(); + }); + public class BigContextWithJson : BigContext { public BigContextWithJson() @@ -4810,6 +5495,59 @@ protected override void OnConfiguring(DbContextOptionsBuilder options) } } + public class ComplexTypesContext : SqlServerContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity( + eb => + { + eb.ComplexProperty(e => e.Owned, eb => + { + eb.IsRequired() + .HasField("_ownedField") + .UsePropertyAccessMode(PropertyAccessMode.Field) + .HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues) + .HasPropertyAnnotation("goo", "ber") + .HasTypeAnnotation("go", "brr"); + eb.Property(c => c.Details) + .HasColumnName("Deets") + .HasColumnOrder(1) + .HasColumnType("varchar") + .IsUnicode(false) + .IsRequired() + .HasField("_details") + .HasSentinel("") + .UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction) + .IsSparse() + .UseCollation("Latin1_General_CI_AI") + .HasMaxLength(64) + .HasPrecision(3, 2) + .HasComment("Dt") + .HasDefaultValueSql("null") + .IsRowVersion() + .HasAnnotation("foo", "bar"); + + eb.ComplexProperty(o => o.Principal); + }); + }); + + modelBuilder.Entity>>( + eb => + { + //eb.OwnsMany(typeof(OwnedType).FullName, "ManyOwned"); + eb.Ignore(p => p.Dependent); + }); + } + + protected override void OnConfiguring(DbContextOptionsBuilder options) + { + SqlServerTestStore.Create("RuntimeModelTest" + GetType().Name).AddProviderOptions(options); + } + } + [ConditionalFact] public void TPC_model() => Test( @@ -6269,6 +7007,10 @@ public class OwnedType : INotifyPropertyChanged, INotifyPropertyChanging { private DbContext _context; + public OwnedType() + { + } + public OwnedType(DbContext context) { Context = context; @@ -6286,7 +7028,17 @@ public DbContext Context } public int Number { get; set; } - public string Details { get; set; } + + [NotMapped] + public PrincipalBase Principal { get; set; } + + + private string _details; + public string Details + { + get => _details; + set => _details = value; + } public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangingEventHandler PropertyChanging; diff --git a/test/EFCore.InMemory.FunctionalTests/CustomValueGeneratorTest.cs b/test/EFCore.InMemory.FunctionalTests/CustomValueGeneratorTest.cs index ed8a51a7e34..17029b003aa 100644 --- a/test/EFCore.InMemory.FunctionalTests/CustomValueGeneratorTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/CustomValueGeneratorTest.cs @@ -203,8 +203,8 @@ public CustomInMemoryValueGeneratorSelector( { } - public override ValueGenerator Create(IProperty property, IEntityType entityType) - => _factory.Create(property, entityType); + public override ValueGenerator Create(IProperty property, ITypeBase typeBase) + => _factory.Create(property, typeBase); } private class CustomGuidValueGenerator : ValueGenerator @@ -243,7 +243,7 @@ public override bool GeneratesTemporaryValues private class CustomValueGeneratorFactory : ValueGeneratorFactory { - public override ValueGenerator Create(IProperty property, IEntityType entityType) + public override ValueGenerator Create(IProperty property, ITypeBase typeBase) { if (property.ClrType == typeof(Guid)) { @@ -253,7 +253,7 @@ public override ValueGenerator Create(IProperty property, IEntityType entityType } return property.ClrType == typeof(string) - && property.DeclaringEntityType.ClrType == typeof(SomeEntity) + && property.DeclaringType.ClrType == typeof(SomeEntity) ? new SomeEntityStringValueGenerator() : null; } diff --git a/test/EFCore.InMemory.Tests/InMemoryValueGeneratorSelectorTest.cs b/test/EFCore.InMemory.Tests/InMemoryValueGeneratorSelectorTest.cs index bc9dea371c7..cd2b8c4b3e0 100644 --- a/test/EFCore.InMemory.Tests/InMemoryValueGeneratorSelectorTest.cs +++ b/test/EFCore.InMemory.Tests/InMemoryValueGeneratorSelectorTest.cs @@ -69,7 +69,7 @@ private static object CreateAndUseFactory(IProperty property) var selector = InMemoryTestHelpers.Instance.CreateContextServices(model).GetRequiredService(); - return selector.Select(property, property.DeclaringEntityType).Next(null); + return selector.Select(property, property.DeclaringType).Next(null); } [ConditionalFact] diff --git a/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs b/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs index 70004149bfb..291cb87beb0 100644 --- a/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs +++ b/test/EFCore.InMemory.Tests/ModelBuilding/InMemoryModelBuilderGenericTest.cs @@ -15,6 +15,12 @@ protected override TestModelBuilder CreateModelBuilder(Action CreateTestModelBuilder(InMemoryTestHelpers.Instance, configure); } + public class InMemoryGenericComplexTypeTestBase : GenericComplexType + { + protected override TestModelBuilder CreateModelBuilder(Action configure = null) + => CreateTestModelBuilder(InMemoryTestHelpers.Instance, configure); + } + public class InMemoryGenericInheritance : GenericInheritance { protected override TestModelBuilder CreateModelBuilder(Action configure = null) diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs index f12ded84343..52da907229d 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs @@ -291,7 +291,7 @@ public void Throws_when_setting_column_default_value_of_wrong_type() Assert.Equal( RelationalStrings.IncorrectDefaultValueType( - guid, typeof(Guid), property.Name, property.ClrType, property.DeclaringEntityType.DisplayName()), + guid, typeof(Guid), property.Name, property.ClrType, property.DeclaringType.DisplayName()), Assert.Throws(() => property.SetDefaultValue(guid)).Message); } diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs index ba7436d7b8d..6330c6f6984 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs @@ -125,7 +125,7 @@ public void CommentAttribute_does_not_override_configuration_from_explicit_sourc private void RunConvention(InternalPropertyBuilder propertyBuilder) { var context = new ConventionContext( - propertyBuilder.Metadata.DeclaringEntityType.Model.ConventionDispatcher); + propertyBuilder.Metadata.DeclaringType.Model.ConventionDispatcher); new RelationalColumnAttributeConvention(CreateDependencies(), CreateRelationalDependencies()) .ProcessPropertyAdded(propertyBuilder, context); diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index db4bfbd645b..a4daa2723b4 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -339,6 +339,335 @@ public virtual void Configuring_direction_on_RowsAffectedParameter_throws() } } + public abstract class RelationalComplexTypeTestBase : ComplexTypeTestBase + { + [ConditionalFact] + public virtual void Can_use_table_splitting() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.HasDefaultSchema("dbo"); + + modelBuilder.Entity().SplitToTable( + "OrderDetails", s => + { + s.ExcludeFromMigrations(); + var propertyBuilder = s.Property(o => o.CustomerId); + var columnBuilder = propertyBuilder.HasColumnName("id"); + if (columnBuilder is IInfrastructure> genericBuilder) + { + Assert.IsType>(genericBuilder.Instance.GetInfrastructure>()); + Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides); + } + else + { + var nonGenericBuilder = (IInfrastructure)columnBuilder; + Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); + Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides); + } + }); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.False(entity.IsTableExcludedFromMigrations()); + Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order", "dbo"))); + Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); + Assert.Same( + entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order", "dbo"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails", "dbo"))); + } + + [ConditionalFact] + public virtual void Can_use_table_splitting_with_schema() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity().ToTable("Order", "dbo") + .SplitToTable( + "OrderDetails", "sch", s => + s.ExcludeFromMigrations() + .Property(o => o.CustomerId).HasColumnName("id")); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.False(entity.IsTableExcludedFromMigrations()); + Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order", "dbo"))); + Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Same( + entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Equal( + RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"), + Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order"))) + .Message); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order", "dbo"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order"))); + } + + [ConditionalFact] + public virtual void Can_use_view_splitting() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity().ToView("Order") + .SplitToView( + "OrderDetails", s => + { + var propertyBuilder = s.Property(o => o.CustomerId); + var columnBuilder = propertyBuilder.HasColumnName("id"); + if (columnBuilder is IInfrastructure> genericBuilder) + { + Assert.IsType>(genericBuilder.Instance.GetInfrastructure>()); + Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides); + } + else + { + var nonGenericBuilder = (IInfrastructure)columnBuilder; + Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); + Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides); + } + }); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.Same(entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails"))); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.View("OrderDetails"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.View("OrderDetails"))); + } + + [ConditionalFact] + public virtual void Can_use_view_splitting_with_schema() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity().ToView("Order", "dbo") + .SplitToView( + "OrderDetails", "sch", s => + s.Property(o => o.CustomerId).HasColumnName("id")); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.Same( + entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails", "sch"))); + Assert.Equal( + RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"), + Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.View("Order"))) + .Message); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order", "dbo"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.View("OrderDetails", "sch"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.View("OrderDetails", "sch"))); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order"))); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_return_and_parameter_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedParameter() + .HasRowsAffectedReturnValue())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_return_and_result_column_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureRowsAffectedReturnConflictingParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedResultColumn() + .HasRowsAffectedReturnValue())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_parameter_and_return_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedReturnValue() + .HasRowsAffectedParameter())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_result_column_and_return_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedReturnValue() + .HasRowsAffectedResultColumn())) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_result_column_and_parameter_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedParameter() + .HasRowsAffectedResultColumn())) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_rows_affected_result_column_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedResultColumn()).Metadata.GetUpdateStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn("BookLabel_Update"), + Assert.Throws(() => sproc.AddRowsAffectedResultColumn()) + .Message); + } + + [ConditionalFact] + public virtual void Conflicting_sproc_rows_affected_parameter_and_result_column_throw() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), + Assert.Throws( + () => modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedResultColumn() + .HasRowsAffectedParameter())) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_rows_affected_parameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .UpdateUsingStoredProcedure( + s => s.HasRowsAffectedParameter()).Metadata.GetUpdateStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter("BookLabel_Update"), + Assert.Throws(() => sproc.AddRowsAffectedParameter()) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_parameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasParameter(b => b.Id)).Metadata.GetInsertStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateParameter("Id", "BookLabel_Insert"), + Assert.Throws(() => sproc.AddParameter("Id")) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_original_value_parameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasOriginalValueParameter(b => b.Id)).Metadata.GetInsertStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateOriginalValueParameter("Id", "BookLabel_Insert"), + Assert.Throws(() => sproc.AddOriginalValueParameter("Id")) + .Message); + } + + [ConditionalFact] + public virtual void Duplicate_sproc_result_column_throws() + { + var modelBuilder = CreateModelBuilder(); + + var sproc = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasResultColumn(b => b.Id)).Metadata.GetInsertStoredProcedure()!; + + Assert.Equal( + RelationalStrings.StoredProcedureDuplicateResultColumn("Id", "BookLabel_Insert"), + Assert.Throws(() => sproc.AddResultColumn("Id")) + .Message); + } + + [ConditionalFact] + public virtual void Configuring_direction_on_RowsAffectedParameter_throws() + { + var modelBuilder = CreateModelBuilder(); + + var param = modelBuilder.Entity() + .InsertUsingStoredProcedure( + s => s.HasRowsAffectedParameter()).Metadata.GetInsertStoredProcedure()!.Parameters.Single(); + + Assert.Equal( + RelationalStrings.StoredProcedureParameterInvalidConfiguration("Direction", "RowsAffected", "BookLabel_Insert"), + Assert.Throws(() => param.Direction = ParameterDirection.Input) + .Message); + } + } + public abstract class RelationalInheritanceTestBase : InheritanceTestBase { [ConditionalFact] diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs index 93bd17b494a..47811719b24 100644 --- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs +++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs @@ -143,6 +143,7 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(RelationalIndexBuilderExtensions), typeof(RelationalKeyBuilderExtensions), typeof(RelationalEntityTypeBuilderExtensions), + typeof(RelationalOwnedNavigationBuilderExtensions), typeof(DbFunctionBuilder), typeof(DbFunctionParameterBuilder), typeof(TableBuilder), @@ -184,63 +185,123 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(OperationBuilder<>) }; - public override - List<(Type Type, - Type ReadonlyExtensions, + public override Dictionary MetadataExtensionTypes { get; } - = new() + Type RuntimeExtensions)> MetadataExtensionTypes { get; } = new() { - ( + { typeof(IReadOnlyModel), - typeof(RelationalModelExtensions), - typeof(RelationalModelExtensions), - typeof(RelationalModelExtensions), - typeof(RelationalModelBuilderExtensions), - null - ), - ( + ( + typeof(RelationalModelExtensions), + typeof(RelationalModelExtensions), + typeof(RelationalModelExtensions), + typeof(RelationalModelBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyEntityType), - typeof(RelationalEntityTypeExtensions), - typeof(RelationalEntityTypeExtensions), - typeof(RelationalEntityTypeExtensions), - typeof(RelationalEntityTypeBuilderExtensions), - null - ), - ( + ( + + typeof(RelationalEntityTypeExtensions), + typeof(RelationalEntityTypeExtensions), + typeof(RelationalEntityTypeExtensions), + typeof(RelationalEntityTypeBuilderExtensions), + null + ) + }, + { + typeof(IReadOnlyComplexType), + ( + null, + null, + null, + null, + null + ) + }, + { + typeof(IReadOnlyTypeBase), + ( + null, + null, + null, + null, + null + ) + }, + { typeof(IReadOnlyKey), - typeof(RelationalKeyExtensions), - typeof(RelationalKeyExtensions), - typeof(RelationalKeyExtensions), - typeof(RelationalKeyBuilderExtensions), - null - ), - ( + ( + typeof(RelationalKeyExtensions), + typeof(RelationalKeyExtensions), + typeof(RelationalKeyExtensions), + typeof(RelationalKeyBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyForeignKey), - typeof(RelationalForeignKeyExtensions), - typeof(RelationalForeignKeyExtensions), - typeof(RelationalForeignKeyExtensions), - typeof(RelationalForeignKeyBuilderExtensions), - null - ), - ( + ( + typeof(RelationalForeignKeyExtensions), + typeof(RelationalForeignKeyExtensions), + typeof(RelationalForeignKeyExtensions), + typeof(RelationalForeignKeyBuilderExtensions), + null + ) + }, + { + typeof(IReadOnlyComplexProperty), + ( + null, + null, + null, + null, + null + ) + }, + { typeof(IReadOnlyProperty), - typeof(RelationalPropertyExtensions), - typeof(RelationalPropertyExtensions), - typeof(RelationalPropertyExtensions), - typeof(RelationalPropertyBuilderExtensions), - null - ), - ( + ( + typeof(RelationalPropertyExtensions), + typeof(RelationalPropertyExtensions), + typeof(RelationalPropertyExtensions), + typeof(RelationalPropertyBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyIndex), - typeof(RelationalIndexExtensions), - typeof(RelationalIndexExtensions), - typeof(RelationalIndexExtensions), - typeof(RelationalIndexBuilderExtensions), - null - ) + ( + typeof(RelationalIndexExtensions), + typeof(RelationalIndexExtensions), + typeof(RelationalIndexExtensions), + typeof(RelationalIndexBuilderExtensions), + null + ) + }, + { + typeof(IReadOnlyTrigger), + ( + typeof(RelationalTriggerExtensions), + typeof(RelationalTriggerExtensions), + typeof(RelationalTriggerExtensions), + typeof(RelationalTriggerBuilderExtensions), + null + ) + }, + { + typeof(IReadOnlyDbFunction), + ( + typeof(RelationalDbFunctionExtensions), + typeof(RelationalDbFunctionExtensions), + typeof(RelationalDbFunctionExtensions), + null, + null + ) + } }; public override HashSet NonVirtualMethods { get; } diff --git a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs index 2604d0bab6e..e0c6cbb387c 100644 --- a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs +++ b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs @@ -3,6 +3,7 @@ using System.CodeDom.Compiler; using System.Runtime.CompilerServices; +using System.Xml.Linq; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore; @@ -70,16 +71,24 @@ public void Generic_fluent_api_methods_should_return_generic_types() if (method.ReturnType == type.BaseType && !Fixture.UnmatchedMetadataMethods.Contains(method)) { - var parameters = method.GetParameters() - .Select(p => GetEquivalentGenericType(p.ParameterType, type.GetGenericArguments())).ToArray(); - var hidingMethod = type.GetMethod( - method.Name, - method.GetGenericArguments().Length, - PublicInstance | BindingFlags.DeclaredOnly, - null, - parameters, - null); - if (hidingMethod == null || hidingMethod.ReturnType != type) + var methodFound = false; + foreach (var hidingMethod in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + if (method.Name != hidingMethod.Name + || hidingMethod.GetGenericArguments().Length != method.GetGenericArguments().Length + || hidingMethod.ReturnType != type + || !hidingMethod.GetParameters().Select(p => p.ParameterType) + .SequenceEqual(method.GetParameters().Select( + p => GetEquivalentGenericType(p.ParameterType, hidingMethod.GetGenericArguments())))) + { + continue; + } + + methodFound = true; + break; + } + + if (!methodFound) { nonGenericMethods.Add((type.BaseType, method)); } @@ -131,17 +140,34 @@ public void Generic_fluent_api_methods_should_return_generic_types() "\r\n-- Non-generic fluent returns that aren't hidden --\r\n" + string.Join( Environment.NewLine, nonGenericMethods.Select( - m => $"{m.Method.ReturnType.ShortDisplayName()} {m.Type.Name}.{m.Method.Name}({Format(m.Method.GetParameters())})"))); + m => $"{m.Method.ReturnType.ShortDisplayName()} {m.Type.Name}.{m.Method.Name}{FormatGenericArguments(m.Method)}({Format(m.Method.GetParameters())})"))); + } + + public static string FormatGenericArguments(MethodInfo methodInfo) + { + var arguments = methodInfo.GetGenericArguments(); + return arguments.Length == 0 ? "" : $"`{arguments.Length}"; } protected Type GetEquivalentGenericType(Type parameterType, Type[] genericArguments) { if (parameterType.IsGenericType - && parameterType.GetGenericTypeDefinition() == typeof(Action<>) - && Fixture.GenericFluentApiTypes.TryGetValue(parameterType.GetGenericArguments()[0], out var genericBuilder) - && genericBuilder.GetGenericArguments().Length == genericArguments.Length) + && parameterType.GetGenericTypeDefinition() == typeof(Action<>)) { - return typeof(Action<>).MakeGenericType(genericBuilder.MakeGenericType(genericArguments)); + var builder = parameterType.GetGenericArguments()[0]; + if (Fixture.GenericFluentApiTypes.TryGetValue(builder, out var genericBuilder) + && genericBuilder.GetGenericArguments().Length == genericArguments.Length) + { + return typeof(Action<>).MakeGenericType(genericBuilder.MakeGenericType(genericArguments)); + } + else if (builder.IsGenericType) + { + var builderDefinition = builder.GetGenericTypeDefinition(); + if (builderDefinition.GetGenericArguments().Length == genericArguments.Length) + { + return typeof(Action<>).MakeGenericType(builderDefinition.MakeGenericType(genericArguments)); + } + } } return parameterType; @@ -159,7 +185,7 @@ public void Builders_have_matching_methods() { if (!Fixture.UnmatchedMetadataMethods.Contains(method)) { - MethodInfo hidingMethod = null; + MethodInfo matchingMethod = null; foreach (var targetMethod in tuple.Value.GetMethods(PublicInstance | BindingFlags.DeclaredOnly)) { if (targetMethod.Name == method.Name @@ -170,23 +196,23 @@ public void Builders_have_matching_methods() new ParameterTypeEqualityComparer(method, targetMethod, this))) { Check.DebugAssert( - hidingMethod == null, + matchingMethod == null, "There should only be one method with the expected signature. Found: " + Environment.NewLine - + Format(hidingMethod ?? targetMethod, tuple.Value) + + Format(matchingMethod ?? targetMethod, tuple.Value) + Environment.NewLine + Format(targetMethod, tuple.Value)); - hidingMethod = targetMethod; + matchingMethod = targetMethod; } } - if (hidingMethod == null) + if (matchingMethod == null) { unmatchedMethods.Add((tuple.Key, method)); } else if (method.ReturnType == tuple.Key - && hidingMethod.ReturnType != tuple.Value) + && matchingMethod.ReturnType != tuple.Value) { wrongReturnMethods.Add((tuple.Value, method)); } @@ -293,11 +319,14 @@ private string ValidateMetadata(KeyValuePair typ if (conventionBuilderType != null) { - var builderProperty = conventionType.GetProperty("Builder"); - if (builderProperty == null - || builderProperty.PropertyType != conventionBuilderType) + if (!conventionBuilderType.IsGenericType) { - return $"{conventionType.Name} expected to have a '{conventionBuilderType.Name} Builder' property"; + var builderProperty = conventionType.GetProperty("Builder"); + if (builderProperty == null + || builderProperty.PropertyType != conventionBuilderType) + { + return $"{conventionType.Name} expected to have a '{conventionBuilderType.Name} Builder' property"; + } } var metadataProperty = conventionBuilderType.GetProperty("Metadata"); @@ -319,6 +348,7 @@ public void Mutable_metadata_types_have_matching_methods() typeTuple => from readonlyMethod in typeTuple.ReadOnly where !Fixture.UnmatchedMetadataMethods.Contains(readonlyMethod) + where typeTuple.Mutable != null join mutableMethod in typeTuple.Mutable on readonlyMethod.Name equals mutableMethod.Name into mutableGroup from mutableMethod in mutableGroup.DefaultIfEmpty() @@ -527,33 +557,45 @@ private string ValidateConventionBuilderMethods(IReadOnlyList method var declaringType = methods[0].DeclaringType; var builderType = methods[0].IsStatic ? methods[0].GetParameters()[0].ParameterType : declaringType; - var methodLookup = new Dictionary(); + var methodLookup = new Dictionary(methods.Count); foreach (var method in methods) { methodLookup[Fixture.MetadataMethodNameTransformers.TryGetValue(method, out var name) ? name : method.Name] = method; } + foreach (var interfaceType in builderType.GetDeclaredInterfaces()) + { + foreach (var method in interfaceType.GetMethods()) + { + methodLookup[Fixture.MetadataMethodNameTransformers.TryGetValue(method, out var name) ? name : method.Name] = method; + } + } + foreach (var keyValuePair in methodLookup) { var method = keyValuePair.Value; var methodName = keyValuePair.Key; + if (Fixture.UnmatchedMetadataMethods.Contains(method) - || method.ReturnType != builderType) + || method.ReturnType != builderType + || methodName.StartsWith("get_", StringComparison.Ordinal)) { continue; } var expectedName = methodName.StartsWith("HasNo", StringComparison.Ordinal) ? "CanRemove" + methodName[5..] - : "CanSet" - + (methodName.StartsWith("Has", StringComparison.Ordinal) - || methodName.StartsWith("Use", StringComparison.Ordinal) - ? methodName[3..] - : methodName.StartsWith("To", StringComparison.Ordinal) - ? methodName[2..] - : methodName.StartsWith("With", StringComparison.Ordinal) - ? methodName[4..] - : methodName); + : methodName.StartsWith("Ignore", StringComparison.Ordinal) + ? methodName + : "CanSet" + + (methodName.StartsWith("Has", StringComparison.Ordinal) + || methodName.StartsWith("Use", StringComparison.Ordinal) + ? methodName[3..] + : methodName.StartsWith("To", StringComparison.Ordinal) + ? methodName[2..] + : methodName.StartsWith("With", StringComparison.Ordinal) + ? methodName[4..] + : methodName); if (!methodLookup.TryGetValue(expectedName, out var canSetMethod)) { @@ -584,6 +626,152 @@ private string ValidateConventionBuilderMethods(IReadOnlyList method return null; } + [ConditionalFact] + public void Convention_builder_methods_have_matching_returns() + { + var errors = + Fixture.MetadataTypes.Select(t => + ValidateConventionBuilderMethodReturns(t.Value.ConventionBuilder, t.Value.Convention)) + .Where(e => e != null) + .ToList(); + + Assert.False( + errors.Count > 0, + "\r\n-- Hiding methods missing: --\r\n" + + string.Join(Environment.NewLine, errors)); + } + + private string ValidateConventionBuilderMethodReturns(Type builderType, Type conventionType) + { + if (builderType == null) + { + return null; + } + + var extensionType = Fixture.MetadataExtensionTypes.GetValueOrDefault(builderType).ConventionBuilderExtensions; + var unmatchedMethods = new List<(Type Type, Type ReturnType, MethodInfo Method)>(); + foreach (var interfaceType in builderType.GetDeclaredInterfaces()) + { + var isInheritedInterface = false; + foreach (var otherInterfaceType in builderType.GetDeclaredInterfaces()) + { + if (otherInterfaceType == interfaceType) + { + continue; + } + + if (interfaceType.IsAssignableFrom(otherInterfaceType)) + { + isInheritedInterface = true; + } + } + + if (isInheritedInterface) + { + continue; + } + + var normalizedInterfaceType = interfaceType.IsGenericType + ? interfaceType.GetGenericTypeDefinition() + : interfaceType; + var readOnlyInterfaceType = Fixture.MetadataTypes + .FirstOrDefault(p => p.Value.ConventionBuilder == normalizedInterfaceType) + .Key; + + var methods = interfaceType.GetMethods(PublicInstance); + foreach (var method in methods) + { + if ((method.ReturnType != interfaceType + && method.ReturnType != Fixture.MetadataTypes[readOnlyInterfaceType].Convention) + || Fixture.UnmatchedMetadataMethods.Contains(method) + || Fixture.IsObsolete(method)) + { + continue; + } + + var expectedReturn = method.ReturnType == interfaceType + ? builderType.IsGenericType + ? builderType.GetGenericArguments()[0] + : builderType + : conventionType; + + var parameters = method.GetParameters() + .Select(p => GetEquivalentGenericType(p.ParameterType, builderType.GetGenericArguments())).ToArray(); + var hidingMethod = builderType.GetMethod( + method.Name, + method.GetGenericArguments().Length, + PublicInstance | BindingFlags.DeclaredOnly, + null, + parameters, + null); + if (hidingMethod == null + || hidingMethod.ReturnType != expectedReturn) + { + unmatchedMethods.Add((builderType, expectedReturn, method)); + } + } + + if (readOnlyInterfaceType == null) + { + continue; + } + + var interfaceExtensionType = Fixture.MetadataExtensionTypes.GetValueOrDefault(readOnlyInterfaceType) + .ConventionBuilderExtensions; + if (interfaceExtensionType == null) + { + continue; + } + + var conventionBuilderExtensionMethods = interfaceExtensionType + .GetMethods(BindingFlags.Public | BindingFlags.Static); + foreach (var method in conventionBuilderExtensionMethods) + { + var parameters = method.GetParameters(); + if (parameters.First().ParameterType != interfaceType + || method.ReturnType != interfaceType + || Fixture.UnmatchedMetadataMethods.Contains(method) + || Fixture.IsObsolete(method)) + { + continue; + } + + var methodFound = false; + var expectedReturn = method.ReturnType == interfaceType ? builderType : conventionType; + if (extensionType != null) + { + var expectedParameters = new[] { builderType }.Concat( + parameters + .Skip(1) + .Select(p => GetEquivalentGenericType(p.ParameterType, builderType.GetGenericArguments()))) + .ToArray(); + var hidingMethod = extensionType.GetMethod( + method.Name, + method.GetGenericArguments().Length, + BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly, + null, + expectedParameters, + null); + methodFound = hidingMethod != null && hidingMethod.ReturnType == expectedReturn; + } + + if (!methodFound) + { + unmatchedMethods.Add((extensionType ?? builderType, expectedReturn, method)); + } + } + } + + if (unmatchedMethods.Count == 0) + { + return null; + } + + return string.Join( + Environment.NewLine, unmatchedMethods.Select( + m => $"{m.ReturnType.ShortDisplayName()} {m.Type.Name}.{m.Method.Name}{FormatGenericArguments(m.Method)}({Format(m.Method.GetParameters())})")); + } + [ConditionalFact] public void Runtime_metadata_types_have_matching_methods() { @@ -1067,6 +1255,7 @@ protected ApiConsistencyFixtureBase() { typeof(DataBuilder), typeof(DataBuilder<>) }, { typeof(DiscriminatorBuilder), typeof(DiscriminatorBuilder<>) }, { typeof(EntityTypeBuilder), typeof(EntityTypeBuilder<>) }, + { typeof(ComplexPropertyBuilder), typeof(ComplexPropertyBuilder<>) }, { typeof(IndexBuilder), typeof(IndexBuilder<>) }, { typeof(KeyBuilder), typeof(KeyBuilder<>) }, { typeof(NavigationBuilder), typeof(NavigationBuilder<,>) }, @@ -1074,6 +1263,7 @@ protected ApiConsistencyFixtureBase() { typeof(OwnedEntityTypeBuilder), typeof(OwnedEntityTypeBuilder<>) }, { typeof(OwnershipBuilder), typeof(OwnershipBuilder<,>) }, { typeof(PropertyBuilder), typeof(PropertyBuilder<>) }, + { typeof(ComplexTypePropertyBuilder), typeof(ComplexTypePropertyBuilder<>) }, { typeof(ReferenceCollectionBuilder), typeof(ReferenceCollectionBuilder<,>) }, { typeof(ReferenceNavigationBuilder), typeof(ReferenceNavigationBuilder<,>) }, { typeof(ReferenceReferenceBuilder), typeof(ReferenceReferenceBuilder<,>) }, @@ -1127,10 +1317,22 @@ protected ApiConsistencyFixtureBase() typeof(IConventionEntityTypeBuilder), typeof(IEntityType)) }, + { + typeof(IReadOnlyComplexType), (typeof(IMutableComplexType), + typeof(IConventionComplexType), + typeof(IConventionComplexTypeBuilder), + typeof(IComplexType)) + }, + { + typeof(IReadOnlyComplexProperty), (typeof(IMutableComplexProperty), + typeof(IConventionComplexProperty), + typeof(IConventionComplexPropertyBuilder), + typeof(IComplexProperty)) + }, { typeof(IReadOnlyTypeBase), (typeof(IMutableTypeBase), typeof(IConventionTypeBase), - null, + typeof(IConventionTypeBaseBuilder), typeof(ITypeBase)) }, { @@ -1190,7 +1392,7 @@ protected ApiConsistencyFixtureBase() { typeof(IReadOnlyPropertyBase), (typeof(IMutablePropertyBase), typeof(IConventionPropertyBase), - null, + typeof(IConventionPropertyBaseBuilder<>), typeof(IPropertyBase)) } }; @@ -1199,8 +1401,7 @@ protected ApiConsistencyFixtureBase() public Dictionary ConventionMetadataTypes { get; } = new(); public virtual - List<(Type Type, - Type ReadonlyExtensions, + Dictionary !IsObsolete(m) && m.GetParameters().First().ParameterType == type).ToArray() @@ -1284,7 +1486,7 @@ protected void AddInstanceMethods(Dictionary Attribute.IsDefined(method, typeof(ObsoleteAttribute), inherit: false); } } diff --git a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs index 8cfa853c098..2fa8a0c28f2 100644 --- a/test/EFCore.Specification.Tests/JsonTypesTestBase.cs +++ b/test/EFCore.Specification.Tests/JsonTypesTestBase.cs @@ -63,7 +63,6 @@ public virtual void Can_read_write_long_JSON_values(long value, string json) [ConditionalTheory] [InlineData(byte.MinValue, "{\"Prop\":0}")] [InlineData(byte.MaxValue, "{\"Prop\":255}")] - [InlineData((byte)0, "{\"Prop\":0}")] [InlineData((byte)1, "{\"Prop\":1}")] public virtual void Can_read_write_byte_JSON_values(byte value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.UInt8)), value, json); @@ -71,7 +70,6 @@ public virtual void Can_read_write_byte_JSON_values(byte value, string json) [ConditionalTheory] [InlineData(ushort.MinValue, "{\"Prop\":0}")] [InlineData(ushort.MaxValue, "{\"Prop\":65535}")] - [InlineData((ushort)0, "{\"Prop\":0}")] [InlineData((ushort)1, "{\"Prop\":1}")] public virtual void Can_read_write_ushort_JSON_values(ushort value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.UInt16)), value, json); @@ -79,7 +77,6 @@ public virtual void Can_read_write_ushort_JSON_values(ushort value, string json) [ConditionalTheory] [InlineData(uint.MinValue, "{\"Prop\":0}")] [InlineData(uint.MaxValue, "{\"Prop\":4294967295}")] - [InlineData((uint)0, "{\"Prop\":0}")] [InlineData((uint)1, "{\"Prop\":1}")] public virtual void Can_read_write_uint_JSON_values(uint value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.UInt32)), value, json); @@ -87,7 +84,6 @@ public virtual void Can_read_write_uint_JSON_values(uint value, string json) [ConditionalTheory] [InlineData(ulong.MinValue, "{\"Prop\":0}")] [InlineData(ulong.MaxValue, "{\"Prop\":18446744073709551615}")] - [InlineData((ulong)0, "{\"Prop\":0}")] [InlineData((ulong)1, "{\"Prop\":1}")] public virtual void Can_read_write_ulong_JSON_values(ulong value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.UInt64)), value, json); @@ -119,7 +115,6 @@ public virtual void Can_read_write_decimal_JSON_values(decimal value, string jso [ConditionalTheory] [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("12/31/9999", "{\"Prop\":\"9999-12-31\"}")] - [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("5/29/2023", "{\"Prop\":\"2023-05-29\"}")] public virtual void Can_read_write_DateOnly_JSON_values(string value, string json) => Can_read_and_write_JSON_value( @@ -129,7 +124,6 @@ public virtual void Can_read_write_DateOnly_JSON_values(string value, string jso [ConditionalTheory] [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00.0000000\"}")] [InlineData("23:59:59.9999999", "{\"Prop\":\"23:59:59.9999999\"}")] - [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00.0000000\"}")] [InlineData("11:05:12.3456789", "{\"Prop\":\"11:05:12.3456789\"}")] public virtual void Can_read_write_TimeOnly_JSON_values(string value, string json) => Can_read_and_write_JSON_value( @@ -139,7 +133,6 @@ public virtual void Can_read_write_TimeOnly_JSON_values(string value, string jso [ConditionalTheory] [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01T00:00:00\"}")] [InlineData("9999-12-31T23:59:59.9999999", "{\"Prop\":\"9999-12-31T23:59:59.9999999\"}")] - [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01T00:00:00\"}")] [InlineData("2023-05-29T10:52:47.2064353", "{\"Prop\":\"2023-05-29T10:52:47.2064353\"}")] public virtual void Can_read_write_DateTime_JSON_values(string value, string json) => Can_read_and_write_JSON_value( @@ -182,7 +175,6 @@ public virtual void Can_read_write_char_JSON_values(char value, string json) [ConditionalTheory] [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", "{\"Prop\":\"ffffffff-ffff-ffff-ffff-ffffffffffff\"}")] - [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("8C44242F-8E3F-4A20-8BE8-98C7C1AADEBD", "{\"Prop\":\"8c44242f-8e3f-4a20-8be8-98c7c1aadebd\"}")] public virtual void Can_read_write_GUID_JSON_values(Guid value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.Guid)), value, json); @@ -223,7 +215,6 @@ public virtual void Can_read_write_URI_JSON_values(string value, string json) [InlineData("192.168.1.156", "{\"Prop\":\"192.168.1.156\"}")] [InlineData("::1", "{\"Prop\":\"::1\"}")] [InlineData("::", "{\"Prop\":\"::\"}")] - [InlineData("::", "{\"Prop\":\"::\"}")] [InlineData("2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577", "{\"Prop\":\"2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577\"}")] public virtual void Can_read_write_IP_address_JSON_values(string value, string json) => Can_read_and_write_JSON_value( @@ -272,7 +263,6 @@ public virtual void Can_read_write_long_enum_JSON_values(Enum64 value, string js [ConditionalTheory] [InlineData((byte)EnumU8.Min, "{\"Prop\":0}")] [InlineData((byte)EnumU8.Max, "{\"Prop\":255}")] - [InlineData((byte)EnumU8.Default, "{\"Prop\":0}")] [InlineData((byte)EnumU8.One, "{\"Prop\":1}")] public virtual void Can_read_write_byte_enum_JSON_values(EnumU8 value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.EnumU8)), value, json); @@ -280,7 +270,6 @@ public virtual void Can_read_write_byte_enum_JSON_values(EnumU8 value, string js [ConditionalTheory] [InlineData((ushort)EnumU16.Min, "{\"Prop\":0}")] [InlineData((ushort)EnumU16.Max, "{\"Prop\":65535}")] - [InlineData((ushort)EnumU16.Default, "{\"Prop\":0}")] [InlineData((ushort)EnumU16.One, "{\"Prop\":1}")] public virtual void Can_read_write_ushort_enum_JSON_values(EnumU16 value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.EnumU16)), value, json); @@ -288,7 +277,6 @@ public virtual void Can_read_write_ushort_enum_JSON_values(EnumU16 value, string [ConditionalTheory] [InlineData((uint)EnumU32.Min, "{\"Prop\":0}")] [InlineData((uint)EnumU32.Max, "{\"Prop\":4294967295}")] - [InlineData((uint)EnumU32.Default, "{\"Prop\":0}")] [InlineData((uint)EnumU32.One, "{\"Prop\":1}")] public virtual void Can_read_write_uint_enum_JSON_values(EnumU32 value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.EnumU32)), value, json); @@ -296,7 +284,6 @@ public virtual void Can_read_write_uint_enum_JSON_values(EnumU32 value, string j [ConditionalTheory] [InlineData((ulong)EnumU64.Min, "{\"Prop\":0}")] [InlineData((ulong)EnumU64.Max, "{\"Prop\":18446744073709551615}")] - [InlineData((ulong)EnumU64.Default, "{\"Prop\":0}")] [InlineData((ulong)EnumU64.One, "{\"Prop\":1}")] public virtual void Can_read_write_ulong_enum_JSON_values(EnumU64 value, string json) => Can_read_and_write_JSON_value(Fixture.EntityType().GetProperty(nameof(PrimitiveTypes.EnumU64)), value, json); @@ -344,7 +331,6 @@ public virtual void Can_read_write_nullable_long_JSON_values(long? value, string [ConditionalTheory] [InlineData(byte.MinValue, "{\"Prop\":0}")] [InlineData(byte.MaxValue, "{\"Prop\":255}")] - [InlineData((byte)0, "{\"Prop\":0}")] [InlineData((byte)1, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_byte_JSON_values(byte? value, string json) @@ -354,7 +340,6 @@ public virtual void Can_read_write_nullable_byte_JSON_values(byte? value, string [ConditionalTheory] [InlineData(ushort.MinValue, "{\"Prop\":0}")] [InlineData(ushort.MaxValue, "{\"Prop\":65535}")] - [InlineData((ushort)0, "{\"Prop\":0}")] [InlineData((ushort)1, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ushort_JSON_values(ushort? value, string json) @@ -364,7 +349,6 @@ public virtual void Can_read_write_nullable_ushort_JSON_values(ushort? value, st [ConditionalTheory] [InlineData(uint.MinValue, "{\"Prop\":0}")] [InlineData(uint.MaxValue, "{\"Prop\":4294967295}")] - [InlineData((uint)0, "{\"Prop\":0}")] [InlineData((uint)1, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_uint_JSON_values(uint? value, string json) @@ -374,7 +358,6 @@ public virtual void Can_read_write_nullable_uint_JSON_values(uint? value, string [ConditionalTheory] [InlineData(ulong.MinValue, "{\"Prop\":0}")] [InlineData(ulong.MaxValue, "{\"Prop\":18446744073709551615}")] - [InlineData((ulong)0, "{\"Prop\":0}")] [InlineData((ulong)1, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ulong_JSON_values(ulong? value, string json) @@ -415,7 +398,6 @@ public virtual void Can_read_write_nullable_decimal_JSON_values(string? value, s [ConditionalTheory] [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("12/31/9999", "{\"Prop\":\"9999-12-31\"}")] - [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("5/29/2023", "{\"Prop\":\"2023-05-29\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_DateOnly_JSON_values(string? value, string json) @@ -426,7 +408,6 @@ public virtual void Can_read_write_nullable_DateOnly_JSON_values(string? value, [ConditionalTheory] [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00.0000000\"}")] [InlineData("23:59:59.9999999", "{\"Prop\":\"23:59:59.9999999\"}")] - [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00.0000000\"}")] [InlineData("11:05:12.3456789", "{\"Prop\":\"11:05:12.3456789\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_TimeOnly_JSON_values(string? value, string json) @@ -437,7 +418,6 @@ public virtual void Can_read_write_nullable_TimeOnly_JSON_values(string? value, [ConditionalTheory] [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01T00:00:00\"}")] [InlineData("9999-12-31T23:59:59.9999999", "{\"Prop\":\"9999-12-31T23:59:59.9999999\"}")] - [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01T00:00:00\"}")] [InlineData("2023-05-29T10:52:47.2064353", "{\"Prop\":\"2023-05-29T10:52:47.2064353\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_DateTime_JSON_values(string? value, string json) @@ -488,7 +468,6 @@ public virtual void Can_read_write_nullable_char_JSON_values(char? value, string [ConditionalTheory] [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", "{\"Prop\":\"ffffffff-ffff-ffff-ffff-ffffffffffff\"}")] - [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("8C44242F-8E3F-4A20-8BE8-98C7C1AADEBD", "{\"Prop\":\"8c44242f-8e3f-4a20-8be8-98c7c1aadebd\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_GUID_JSON_values(string? value, string json) @@ -541,7 +520,6 @@ public virtual void Can_read_write_nullable_URI_JSON_values(string? value, strin [InlineData("192.168.1.156", "{\"Prop\":\"192.168.1.156\"}")] [InlineData("::1", "{\"Prop\":\"::1\"}")] [InlineData("::", "{\"Prop\":\"::\"}")] - [InlineData("::", "{\"Prop\":\"::\"}")] [InlineData("2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577", "{\"Prop\":\"2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_IP_address_JSON_values(string? value, string json) @@ -606,7 +584,6 @@ public virtual void Can_read_write_nullable_long_enum_JSON_values(object? value, [ConditionalTheory] [InlineData((byte)EnumU8.Min, "{\"Prop\":0}")] [InlineData((byte)EnumU8.Max, "{\"Prop\":255}")] - [InlineData((byte)EnumU8.Default, "{\"Prop\":0}")] [InlineData((byte)EnumU8.One, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_byte_enum_JSON_values(object? value, string json) @@ -617,7 +594,6 @@ public virtual void Can_read_write_nullable_byte_enum_JSON_values(object? value, [ConditionalTheory] [InlineData((ushort)EnumU16.Min, "{\"Prop\":0}")] [InlineData((ushort)EnumU16.Max, "{\"Prop\":65535}")] - [InlineData((ushort)EnumU16.Default, "{\"Prop\":0}")] [InlineData((ushort)EnumU16.One, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ushort_enum_JSON_values(object? value, string json) @@ -628,7 +604,6 @@ public virtual void Can_read_write_nullable_ushort_enum_JSON_values(object? valu [ConditionalTheory] [InlineData((uint)EnumU32.Min, "{\"Prop\":0}")] [InlineData((uint)EnumU32.Max, "{\"Prop\":4294967295}")] - [InlineData((uint)EnumU32.Default, "{\"Prop\":0}")] [InlineData((uint)EnumU32.One, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_uint_enum_JSON_values(object? value, string json) @@ -639,7 +614,6 @@ public virtual void Can_read_write_nullable_uint_enum_JSON_values(object? value, [ConditionalTheory] [InlineData((ulong)EnumU64.Min, "{\"Prop\":0}")] [InlineData((ulong)EnumU64.Max, "{\"Prop\":18446744073709551615}")] - [InlineData((ulong)EnumU64.Default, "{\"Prop\":0}")] [InlineData((ulong)EnumU64.One, "{\"Prop\":1}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ulong_enum_JSON_values(object? value, string json) @@ -690,7 +664,6 @@ public virtual void Can_read_write_nullable_long_as_string_JSON_values(long? val [ConditionalTheory] [InlineData(byte.MinValue, "{\"Prop\":\"0\"}")] [InlineData(byte.MaxValue, "{\"Prop\":\"255\"}")] - [InlineData((byte)0, "{\"Prop\":\"0\"}")] [InlineData((byte)1, "{\"Prop\":\"1\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_byte_as_string_JSON_values(byte? value, string json) @@ -700,7 +673,6 @@ public virtual void Can_read_write_nullable_byte_as_string_JSON_values(byte? val [ConditionalTheory] [InlineData(ushort.MinValue, "{\"Prop\":\"0\"}")] [InlineData(ushort.MaxValue, "{\"Prop\":\"65535\"}")] - [InlineData((ushort)0, "{\"Prop\":\"0\"}")] [InlineData((ushort)1, "{\"Prop\":\"1\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ushort_as_string_JSON_values(ushort? value, string json) @@ -710,7 +682,6 @@ public virtual void Can_read_write_nullable_ushort_as_string_JSON_values(ushort? [ConditionalTheory] [InlineData(uint.MinValue, "{\"Prop\":\"0\"}")] [InlineData(uint.MaxValue, "{\"Prop\":\"4294967295\"}")] - [InlineData((uint)0, "{\"Prop\":\"0\"}")] [InlineData((uint)1, "{\"Prop\":\"1\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_uint_as_string_JSON_values(uint? value, string json) @@ -720,7 +691,6 @@ public virtual void Can_read_write_nullable_uint_as_string_JSON_values(uint? val [ConditionalTheory] [InlineData(ulong.MinValue, "{\"Prop\":\"0\"}")] [InlineData(ulong.MaxValue, "{\"Prop\":\"18446744073709551615\"}")] - [InlineData((ulong)0, "{\"Prop\":\"0\"}")] [InlineData((ulong)1, "{\"Prop\":\"1\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_ulong_as_string_JSON_values(ulong? value, string json) @@ -761,7 +731,6 @@ public virtual void Can_read_write_nullable_decimal_as_string_JSON_values(string [ConditionalTheory] [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("12/31/9999", "{\"Prop\":\"9999-12-31\"}")] - [InlineData("1/1/0001", "{\"Prop\":\"0001-01-01\"}")] [InlineData("5/29/2023", "{\"Prop\":\"2023-05-29\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_DateOnly_as_string_JSON_values(string? value, string json) @@ -772,7 +741,6 @@ public virtual void Can_read_write_nullable_DateOnly_as_string_JSON_values(strin [ConditionalTheory] [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00\"}")] [InlineData("23:59:59.9999999", "{\"Prop\":\"23:59:59.9999999\"}")] - [InlineData("00:00:00.0000000", "{\"Prop\":\"00:00:00\"}")] [InlineData("11:05:12.3456789", "{\"Prop\":\"11:05:12.3456789\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_TimeOnly_as_string_JSON_values(string? value, string json) @@ -783,7 +751,6 @@ public virtual void Can_read_write_nullable_TimeOnly_as_string_JSON_values(strin [ConditionalTheory] [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01 00:00:00\"}")] [InlineData("9999-12-31T23:59:59.9999999", "{\"Prop\":\"9999-12-31 23:59:59.9999999\"}")] - [InlineData("0001-01-01T00:00:00.0000000", "{\"Prop\":\"0001-01-01 00:00:00\"}")] [InlineData("2023-05-29T10:52:47.2064353", "{\"Prop\":\"2023-05-29 10:52:47.2064353\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_DateTime_as_string_JSON_values(string? value, string json) @@ -834,7 +801,6 @@ public virtual void Can_read_write_nullable_char_as_string_JSON_values(char? val [ConditionalTheory] [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF", "{\"Prop\":\"ffffffff-ffff-ffff-ffff-ffffffffffff\"}")] - [InlineData("00000000-0000-0000-0000-000000000000", "{\"Prop\":\"00000000-0000-0000-0000-000000000000\"}")] [InlineData("8C44242F-8E3F-4A20-8BE8-98C7C1AADEBD", "{\"Prop\":\"8c44242f-8e3f-4a20-8be8-98c7c1aadebd\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_as_string_GUID_JSON_values(string? value, string json) @@ -887,7 +853,6 @@ public virtual void Can_read_write_nullable_URI_as_string_JSON_values(string? va [InlineData("192.168.1.156", "{\"Prop\":\"192.168.1.156\"}")] [InlineData("::1", "{\"Prop\":\"::1\"}")] [InlineData("::", "{\"Prop\":\"::\"}")] - [InlineData("::", "{\"Prop\":\"::\"}")] [InlineData("2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577", "{\"Prop\":\"2a00:23c7:c60f:4f01:ba43:6d5a:e648:7577\"}")] [InlineData(null, "{\"Prop\":null}")] public virtual void Can_read_write_nullable_IP_address_as_string_JSON_values(string? value, string json) @@ -956,7 +921,6 @@ public virtual void Can_read_write_nullable_long_enum_as_string_JSON_values(obje [ConditionalTheory] [InlineData((byte)EnumU8.Min, "{\"Prop\":\"Min\"}")] [InlineData((byte)EnumU8.Max, "{\"Prop\":\"Max\"}")] - [InlineData((byte)EnumU8.Default, "{\"Prop\":\"Min\"}")] [InlineData((byte)EnumU8.One, "{\"Prop\":\"One\"}")] [InlineData((byte)77, "{\"Prop\":\"77\"}")] [InlineData(null, "{\"Prop\":null}")] @@ -968,7 +932,6 @@ public virtual void Can_read_write_nullable_byte_enum_as_string_JSON_values(obje [ConditionalTheory] [InlineData((ushort)EnumU16.Min, "{\"Prop\":\"Min\"}")] [InlineData((ushort)EnumU16.Max, "{\"Prop\":\"Max\"}")] - [InlineData((ushort)EnumU16.Default, "{\"Prop\":\"Min\"}")] [InlineData((ushort)EnumU16.One, "{\"Prop\":\"One\"}")] [InlineData((ushort)77, "{\"Prop\":\"77\"}")] [InlineData(null, "{\"Prop\":null}")] @@ -980,7 +943,6 @@ public virtual void Can_read_write_nullable_ushort_enum_as_string_JSON_values(ob [ConditionalTheory] [InlineData((uint)EnumU32.Min, "{\"Prop\":\"Min\"}")] [InlineData((uint)EnumU32.Max, "{\"Prop\":\"Max\"}")] - [InlineData((uint)EnumU32.Default, "{\"Prop\":\"Min\"}")] [InlineData((uint)EnumU32.One, "{\"Prop\":\"One\"}")] [InlineData((uint)77, "{\"Prop\":\"77\"}")] [InlineData(null, "{\"Prop\":null}")] @@ -992,7 +954,6 @@ public virtual void Can_read_write_nullable_uint_enum_as_string_JSON_values(obje [ConditionalTheory] [InlineData((ulong)EnumU64.Min, "{\"Prop\":\"Min\"}")] [InlineData((ulong)EnumU64.Max, "{\"Prop\":\"Max\"}")] - [InlineData((ulong)EnumU64.Default, "{\"Prop\":\"Min\"}")] [InlineData((ulong)EnumU64.One, "{\"Prop\":\"One\"}")] [InlineData((ulong)77, "{\"Prop\":\"77\"}")] [InlineData(null, "{\"Prop\":null}")] diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs index 56afddf5ed8..60687254374 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs @@ -433,7 +433,7 @@ public void RemoveAllConventions() ConventionSet.EntityTypeAddedConventions.Clear(); ConventionSet.EntityTypeAnnotationChangedConventions.Clear(); ConventionSet.EntityTypeBaseTypeChangedConventions.Clear(); - ConventionSet.EntityTypeIgnoredConventions.Clear(); + ConventionSet.TypeIgnoredConventions.Clear(); ConventionSet.EntityTypeMemberIgnoredConventions.Clear(); ConventionSet.EntityTypePrimaryKeyChangedConventions.Clear(); ConventionSet.EntityTypeRemovedConventions.Clear(); diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs index f320b234e00..6634ca58efd 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs @@ -41,54 +41,68 @@ public class SqlServerApiConsistencyFixture : ApiConsistencyFixtureBase }; public override - List<(Type Type, - Type ReadonlyExtensions, + Dictionary MetadataExtensionTypes { get; } = new() { - ( + { + typeof(IReadOnlyModel), - typeof(SqlServerModelExtensions), - typeof(SqlServerModelExtensions), - typeof(SqlServerModelExtensions), - typeof(SqlServerModelBuilderExtensions), - null - ), - ( + ( + typeof(SqlServerModelExtensions), + typeof(SqlServerModelExtensions), + typeof(SqlServerModelExtensions), + typeof(SqlServerModelBuilderExtensions), + null + ) + }, + { + typeof(IReadOnlyEntityType), - typeof(SqlServerEntityTypeExtensions), - typeof(SqlServerEntityTypeExtensions), - typeof(SqlServerEntityTypeExtensions), - typeof(SqlServerEntityTypeBuilderExtensions), - null - ), - ( + ( + typeof(SqlServerEntityTypeExtensions), + typeof(SqlServerEntityTypeExtensions), + typeof(SqlServerEntityTypeExtensions), + typeof(SqlServerEntityTypeBuilderExtensions), + null + ) + }, + { typeof(IReadOnlyKey), - typeof(SqlServerKeyExtensions), - typeof(SqlServerKeyExtensions), - typeof(SqlServerKeyExtensions), - typeof(SqlServerKeyBuilderExtensions), - null - ), - ( + ( + typeof(SqlServerKeyExtensions), + typeof(SqlServerKeyExtensions), + typeof(SqlServerKeyExtensions), + typeof(SqlServerKeyBuilderExtensions), + null + ) + }, + { + typeof(IReadOnlyProperty), - typeof(SqlServerPropertyExtensions), - typeof(SqlServerPropertyExtensions), - typeof(SqlServerPropertyExtensions), - typeof(SqlServerPropertyBuilderExtensions), - null - ), - ( + ( + typeof(SqlServerPrimitivePropertyBaseExtensions), + typeof(SqlServerPrimitivePropertyBaseExtensions), + typeof(SqlServerPrimitivePropertyBaseExtensions), + typeof(SqlServerPropertyBuilderExtensions), + null + ) + }, + { + typeof(IReadOnlyIndex), - typeof(SqlServerIndexExtensions), - typeof(SqlServerIndexExtensions), - typeof(SqlServerIndexExtensions), - typeof(SqlServerIndexBuilderExtensions), - null - ) + ( + typeof(SqlServerIndexExtensions), + typeof(SqlServerIndexExtensions), + typeof(SqlServerIndexExtensions), + typeof(SqlServerIndexBuilderExtensions), + null + ) + } }; protected override void Initialize() diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs index 62e650279c5..f2a0dbde446 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs @@ -16,6 +16,14 @@ protected override TestModelBuilder CreateTestModelBuilder( => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); } + public class SqlServerGenericComplexTypeTestBase : SqlServerComplexType + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); + } + public class SqlServerGenericInheritance : SqlServerInheritance { protected override TestModelBuilder CreateTestModelBuilder( diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs index 8d304107b5e..53998ac3ac3 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs @@ -16,6 +16,14 @@ protected override TestModelBuilder CreateTestModelBuilder( => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); } + public class SqlServerNonGenericComplexTypeTestBase : SqlServerComplexType + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); + } + public class SqlServerNonGenericInheritance : SqlServerInheritance { protected override TestModelBuilder CreateTestModelBuilder( diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs index 55ba5b6a859..be6de05160c 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs @@ -198,6 +198,196 @@ protected override TestModelBuilder CreateModelBuilder(Action CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); } + public abstract class SqlServerComplexType : RelationalComplexTypeTestBase + { + [ConditionalFact] + public virtual void Index_has_a_filter_if_nonclustered_unique_with_nullable_properties() + { + var modelBuilder = CreateModelBuilder(); + var entityTypeBuilder = modelBuilder + .Entity(); + var indexBuilder = entityTypeBuilder + .HasIndex(ix => ix.Name) + .IsUnique(); + + var entityType = modelBuilder.Model.FindEntityType(typeof(Customer))!; + var index = entityType.GetIndexes().Single(); + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + indexBuilder.IsUnique(false); + + Assert.Null(index.GetFilter()); + + indexBuilder.IsUnique(); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + indexBuilder.IsClustered(); + + Assert.Null(index.GetFilter()); + + indexBuilder.IsClustered(false); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).IsRequired(); + + Assert.Null(index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).IsRequired(false); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName("RelationalName"); + + Assert.Equal("[RelationalName] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName("SqlServerName"); + + Assert.Equal("[SqlServerName] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName(null); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + indexBuilder.HasFilter("Foo"); + + Assert.Equal("Foo", index.GetFilter()); + + indexBuilder.HasFilter(null); + + Assert.Null(index.GetFilter()); + } + + [ConditionalFact] + public void Indexes_can_have_same_name_across_tables() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .HasIndex(e => e.Id, "Ix_Id") + .IsUnique(); + modelBuilder.Entity() + .HasIndex(e => e.CustomerId, "Ix_Id") + .IsUnique(); + + var model = modelBuilder.FinalizeModel(); + + var customerIndex = model.FindEntityType(typeof(Customer))!.GetIndexes().Single(); + Assert.Equal("Ix_Id", customerIndex.Name); + Assert.Equal("Ix_Id", customerIndex.GetDatabaseName()); + Assert.Equal( + "Ix_Id", customerIndex.GetDatabaseName( + StoreObjectIdentifier.Table("Customer"))); + + var detailsIndex = model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); + Assert.Equal("Ix_Id", detailsIndex.Name); + Assert.Equal("Ix_Id", detailsIndex.GetDatabaseName()); + Assert.Equal( + "Ix_Id", detailsIndex.GetDatabaseName( + StoreObjectIdentifier.Table("CustomerDetails"))); + } + + [ConditionalFact] + public virtual void Can_set_store_type_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveColumnType("smallint"); + c.Properties().HaveColumnType("nchar(max)"); + c.Properties(typeof(Nullable<>)).HavePrecision(2); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.Equal("smallint", entityType.FindProperty(Customer.IdProperty.Name)!.GetColumnType()); + Assert.Equal("smallint", entityType.FindProperty("Up")!.GetColumnType()); + Assert.Equal("nchar(max)", entityType.FindProperty("Down")!.GetColumnType()); + var charm = entityType.FindProperty("Charm")!; + Assert.Equal("smallint", charm.GetColumnType()); + Assert.Null(charm.GetPrecision()); + Assert.Equal("nchar(max)", entityType.FindProperty("Strange")!.GetColumnType()); + var top = entityType.FindProperty("Top")!; + Assert.Equal("smallint", top.GetColumnType()); + Assert.Equal(2, top.GetPrecision()); + Assert.Equal("nchar(max)", entityType.FindProperty("Bottom")!.GetColumnType()); + } + + [ConditionalFact] + public virtual void Can_set_fixed_length_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().AreFixedLength(false); + c.Properties().AreFixedLength(); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.False(entityType.FindProperty(Customer.IdProperty.Name)!.IsFixedLength()); + Assert.False(entityType.FindProperty("Up")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Down")!.IsFixedLength()); + Assert.False(entityType.FindProperty("Charm")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Strange")!.IsFixedLength()); + Assert.False(entityType.FindProperty("Top")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Bottom")!.IsFixedLength()); + } + + [ConditionalFact] + public virtual void Can_set_collation_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().UseCollation("Latin1_General_CS_AS_KS_WS"); + c.Properties().UseCollation("Latin1_General_BIN"); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty(Customer.IdProperty.Name)!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down")!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Charm")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Strange")!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Top")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Bottom")!.GetCollation()); + } + + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + } public abstract class SqlServerInheritance : RelationalInheritanceTestBase { [ConditionalFact] // #7240 diff --git a/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs b/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs index f822fbd8880..09e9b6c5f14 100644 --- a/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs +++ b/test/EFCore.SqlServer.Tests/Storage/SqlServerTypeMappingSourceTest.cs @@ -283,7 +283,7 @@ public void Does_key_SQL_Server_string_mapping(Type type, bool? unicode, bool? f property.IsNullable = false; property.SetIsUnicode(unicode); property.SetIsFixedLength(fixedLength); - property.DeclaringEntityType.SetPrimaryKey(property); + ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); @@ -312,9 +312,9 @@ public void Does_foreign_key_SQL_Server_string_mapping(Type type, bool? unicode, property.IsNullable = false; property.SetIsUnicode(unicode); property.SetIsFixedLength(fixedLength); - var fkProperty = property.DeclaringEntityType.AddProperty("FK", type); - var pk = property.DeclaringEntityType.SetPrimaryKey(property); - property.DeclaringEntityType.AddForeignKey(fkProperty, pk, property.DeclaringEntityType); + var fkProperty = ((IMutableEntityType)property.DeclaringType).AddProperty("FK", type); + var pk = ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); + ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); @@ -343,9 +343,9 @@ public void Does_required_foreign_key_SQL_Server_string_mapping(Type type, bool? property.IsNullable = false; property.SetIsUnicode(unicode); property.SetIsFixedLength(fixedLength); - var fkProperty = property.DeclaringEntityType.AddProperty("FK", type); - var pk = property.DeclaringEntityType.SetPrimaryKey(property); - property.DeclaringEntityType.AddForeignKey(fkProperty, pk, property.DeclaringEntityType); + var fkProperty = ((IMutableEntityType)property.DeclaringType).AddProperty("FK", type); + var pk = ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); + ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); fkProperty.IsNullable = false; var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); @@ -621,7 +621,7 @@ public void Does_key_SQL_Server_string_mapping_ansi(Type type, bool? fixedLength property.IsNullable = false; property.SetIsUnicode(false); property.SetIsFixedLength(fixedLength); - property.DeclaringEntityType.SetPrimaryKey(property); + ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); @@ -646,9 +646,9 @@ public void Does_foreign_key_SQL_Server_string_mapping_ansi(Type type, bool? fix property.SetIsUnicode(false); property.SetIsFixedLength(fixedLength); property.IsNullable = false; - var fkProperty = property.DeclaringEntityType.AddProperty("FK", type); - var pk = property.DeclaringEntityType.SetPrimaryKey(property); - property.DeclaringEntityType.AddForeignKey(fkProperty, pk, property.DeclaringEntityType); + var fkProperty = ((IMutableEntityType)property.DeclaringType).AddProperty("FK", type); + var pk = ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); + ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); @@ -673,9 +673,9 @@ public void Does_required_foreign_key_SQL_Server_string_mapping_ansi(Type type, property.SetIsUnicode(false); property.SetIsFixedLength(fixedLength); property.IsNullable = false; - var fkProperty = property.DeclaringEntityType.AddProperty("FK", type); - var pk = property.DeclaringEntityType.SetPrimaryKey(property); - property.DeclaringEntityType.AddForeignKey(fkProperty, pk, property.DeclaringEntityType); + var fkProperty = ((IMutableEntityType)property.DeclaringType).AddProperty("FK", type); + var pk = ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); + ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); fkProperty.IsNullable = false; var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); @@ -929,7 +929,7 @@ public void Does_key_SQL_Server_binary_mapping(bool? fixedLength) var property = CreateEntityType().AddProperty("MyProp", typeof(byte[])); property.IsNullable = false; property.SetIsFixedLength(fixedLength); - property.DeclaringEntityType.SetPrimaryKey(property); + ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)property); @@ -947,9 +947,9 @@ public void Does_foreign_key_SQL_Server_binary_mapping(bool? fixedLength) var property = CreateEntityType().AddProperty("MyProp", typeof(byte[])); property.IsNullable = false; property.SetIsFixedLength(fixedLength); - var fkProperty = property.DeclaringEntityType.AddProperty("FK", typeof(byte[])); - var pk = property.DeclaringEntityType.SetPrimaryKey(property); - property.DeclaringEntityType.AddForeignKey(fkProperty, pk, property.DeclaringEntityType); + var fkProperty = ((IMutableEntityType)property.DeclaringType).AddProperty("FK", typeof(byte[])); + var pk = ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); + ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); Assert.False(typeMapping.IsFixedLength); @@ -967,9 +967,9 @@ public void Does_required_foreign_key_SQL_Server_binary_mapping(bool? fixedLengt var property = CreateEntityType().AddProperty("MyProp", typeof(byte[])); property.IsNullable = false; property.SetIsFixedLength(fixedLength); - var fkProperty = property.DeclaringEntityType.AddProperty("FK", typeof(byte[])); - var pk = property.DeclaringEntityType.SetPrimaryKey(property); - property.DeclaringEntityType.AddForeignKey(fkProperty, pk, property.DeclaringEntityType); + var fkProperty = ((IMutableEntityType)property.DeclaringType).AddProperty("FK", typeof(byte[])); + var pk = ((IMutableEntityType)property.DeclaringType).SetPrimaryKey(property); + ((IMutableEntityType)property.DeclaringType).AddForeignKey(fkProperty, pk, ((IMutableEntityType)property.DeclaringType)); fkProperty.IsNullable = false; var typeMapping = CreateRelationalTypeMappingSource().GetMapping((IProperty)fkProperty); diff --git a/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs b/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs index 03c7069491d..f9f6ce90fcf 100644 --- a/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/SqliteApiConsistencyTest.cs @@ -29,22 +29,25 @@ public class SqliteApiConsistencyFixture : ApiConsistencyFixtureBase }; public override - List<(Type Type, - Type ReadonlyExtensions, + Dictionary MetadataExtensionTypes { get; } = new() { - ( + { + typeof(IReadOnlyProperty), - typeof(SqlitePropertyExtensions), - typeof(SqlitePropertyExtensions), - typeof(SqlitePropertyExtensions), - typeof(SqlitePropertyBuilderExtensions), - null - ) + ( + typeof(SqlitePropertyExtensions), + typeof(SqlitePropertyExtensions), + typeof(SqlitePropertyExtensions), + typeof(SqlitePropertyBuilderExtensions), + null + ) + } }; } } diff --git a/test/EFCore.Tests/ApiConsistencyTest.cs b/test/EFCore.Tests/ApiConsistencyTest.cs index bfbce84c6e0..56b43adee86 100644 --- a/test/EFCore.Tests/ApiConsistencyTest.cs +++ b/test/EFCore.Tests/ApiConsistencyTest.cs @@ -44,6 +44,8 @@ protected override void Initialize() typeof(DiscriminatorBuilder<>), typeof(EntityTypeBuilder), typeof(EntityTypeBuilder<>), + typeof(ComplexPropertyBuilder), + typeof(ComplexPropertyBuilder<>), typeof(IndexBuilder), typeof(IndexBuilder<>), typeof(TriggerBuilder), @@ -60,6 +62,8 @@ protected override void Initialize() typeof(OwnershipBuilder<,>), typeof(PropertyBuilder), typeof(PropertyBuilder<>), + typeof(ComplexTypePropertyBuilder), + typeof(ComplexTypePropertyBuilder<>), typeof(ReferenceCollectionBuilder), typeof(ReferenceCollectionBuilder<,>), typeof(ReferenceNavigationBuilder), @@ -112,6 +116,10 @@ protected override void Initialize() public override HashSet UnmatchedMetadataMethods { get; } = new() { + typeof(ComplexPropertyBuilder).GetMethod( + nameof(ComplexPropertyBuilder.ComplexProperty), 0, new[] { typeof(string) }), + typeof(ComplexPropertyBuilder).GetMethod( + nameof(ComplexPropertyBuilder.ComplexProperty), 0, new[] { typeof(Type), typeof(string) }), typeof(OwnedNavigationBuilder).GetMethod( nameof(OwnedNavigationBuilder.OwnsOne), 0, new[] { typeof(string), typeof(string) }), typeof(OwnedNavigationBuilder).GetMethod( @@ -155,10 +163,8 @@ protected override void Initialize() typeof(IReadOnlyNavigationBase).GetMethod("get_Inverse"), typeof(IConventionAnnotatableBuilder).GetMethod(nameof(IConventionAnnotatableBuilder.HasNonNullAnnotation)), typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.RemoveUnusedImplicitProperties)), - typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.Ignore)), + typeof(IConventionTypeBaseBuilder).GetMethod(nameof(IConventionTypeBaseBuilder.RemoveUnusedImplicitProperties)), typeof(IConventionEntityTypeBuilder).GetMethod(nameof(IConventionEntityTypeBuilder.GetTargetEntityTypeBuilder)), - typeof(IConventionModelBuilder).GetMethod(nameof(IConventionModelBuilder.Ignore), new[] { typeof(Type), typeof(bool) }), - typeof(IConventionModelBuilder).GetMethod(nameof(IConventionModelBuilder.Ignore), new[] { typeof(string), typeof(bool) }), typeof(IConventionPropertyBuilder).GetMethod( nameof(IConventionPropertyBuilder.HasField), new[] { typeof(string), typeof(bool) }), typeof(IConventionPropertyBuilder).GetMethod( diff --git a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs index 1436c53ce10..7fc2b4daa2b 100644 --- a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs @@ -895,7 +895,7 @@ private static void ResetValueGenerator(DbContext context, IProperty property, b var generator = (ResettableValueGenerator)cache.GetOrAdd( property, - property.DeclaringEntityType, + property.DeclaringType, (p, e) => new ResettableValueGenerator()); generator.Reset(generateTemporaryValues); diff --git a/test/EFCore.Tests/ChangeTracking/EntityEntryTest.cs b/test/EFCore.Tests/ChangeTracking/EntityEntryTest.cs index 2e39b0445be..0ee200bf1cd 100644 --- a/test/EFCore.Tests/ChangeTracking/EntityEntryTest.cs +++ b/test/EFCore.Tests/ChangeTracking/EntityEntryTest.cs @@ -420,34 +420,6 @@ public void Throws_when_wrong_property_name_is_used_while_getting_property_entry Assert.Throws(() => context.Entry(entity).Property("Chimp").Metadata.Name).Message); } - [ConditionalFact] - public void Throws_when_accessing_navigation_as_property() - { - using var context = new FreezerContext(); - var entity = context.Add(new Chunky()).Entity; - - Assert.Equal( - CoreStrings.PropertyIsNavigation( - "Garcia", entity.GetType().Name, - nameof(EntityEntry.Property), nameof(EntityEntry.Reference), nameof(EntityEntry.Collection)), - Assert.Throws(() => context.Entry(entity).Property("Garcia").Metadata.Name).Message); - Assert.Equal( - CoreStrings.PropertyIsNavigation( - "Garcia", entity.GetType().Name, - nameof(EntityEntry.Property), nameof(EntityEntry.Reference), nameof(EntityEntry.Collection)), - Assert.Throws(() => context.Entry((object)entity).Property("Garcia").Metadata.Name).Message); - Assert.Equal( - CoreStrings.PropertyIsNavigation( - "Garcia", entity.GetType().Name, - nameof(EntityEntry.Property), nameof(EntityEntry.Reference), nameof(EntityEntry.Collection)), - Assert.Throws(() => context.Entry(entity).Property("Garcia").Metadata.Name).Message); - Assert.Equal( - CoreStrings.PropertyIsNavigation( - "Garcia", entity.GetType().Name, - nameof(EntityEntry.Property), nameof(EntityEntry.Reference), nameof(EntityEntry.Collection)), - Assert.Throws(() => context.Entry(entity).Property(e => e.Garcia).Metadata.Name).Message); - } - [ConditionalFact] public void Can_get_reference_entry_by_name() { diff --git a/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs b/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs index dde6de1d94f..636665378b9 100644 --- a/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs +++ b/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs @@ -23,8 +23,10 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() var foreignKey = new ForeignKey(new[] { property }, otherKey, entityType, otherEntityType, ConfigurationSource.Convention); var navigation = new Navigation("N", propertyInfo, null, foreignKey); var skipNavigation = new SkipNavigation( - "SN", propertyInfo, null, entityType, otherEntityType, true, false, ConfigurationSource.Convention); + "SN", null, propertyInfo, null, entityType, otherEntityType, true, false, ConfigurationSource.Convention); var navigationBase = new FakeNavigationBase("FNB", ConfigurationSource.Convention, entityType); + var complexProperty = entityType.AddComplexProperty( + "C", typeof(object), typeof(object), false, ConfigurationSource.Convention); entityType.Model.FinalizeModel(); var options = new DbContextOptionsBuilder() @@ -44,11 +46,13 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() { typeof(IConventionEntityType), () => entityType }, { typeof(IKey), () => new Key(new[] { property }, ConfigurationSource.Convention) }, { typeof(IPropertyBase), () => property }, + { typeof(IProperty), () => property }, { typeof(IReadOnlyProperty), () => property }, + { typeof(IComplexProperty), () => complexProperty }, { typeof(IServiceProvider), () => new FakeServiceProvider() }, { typeof(ICollection), () => new List() }, { typeof(IReadOnlyList), () => new[] { property } }, - { typeof(IReadOnlyList), () => Array.Empty() }, + { typeof(IReadOnlyList), Array.Empty }, { typeof(Func, EventDefinition, ConcurrencyExceptionEventData>), @@ -58,7 +62,6 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() { typeof(IEnumerable>), () => new[] { new Tuple(propertyInfo, typeof(object)) } }, { typeof(MemberInfo), () => propertyInfo }, { typeof(IReadOnlyList), () => new[] { new Exception() } }, - { typeof(IProperty), () => property }, { typeof(INavigation), () => navigation }, { typeof(IReadOnlyNavigation), () => navigation }, { typeof(ISkipNavigation), () => skipNavigation }, diff --git a/test/EFCore.Tests/Metadata/Conventions/BackingFieldConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/BackingFieldConventionTest.cs index e0cecbb846d..8e1a147d572 100644 --- a/test/EFCore.Tests/Metadata/Conventions/BackingFieldConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/BackingFieldConventionTest.cs @@ -264,13 +264,13 @@ private void RunConvention(IMutableProperty property) => new BackingFieldConvention(CreateDependencies()) .ProcessPropertyAdded( ((Property)property).Builder, - new ConventionContext(((Model)property.DeclaringEntityType.Model).ConventionDispatcher)); + new ConventionContext(((Model)property.DeclaringType.Model).ConventionDispatcher)); private void Validate(IMutableProperty property) => new BackingFieldConvention(CreateDependencies()) .ProcessModelFinalizing( - ((Property)property).DeclaringEntityType.Model.Builder, - new ConventionContext(((Model)property.DeclaringEntityType.Model).ConventionDispatcher)); + ((Property)property).DeclaringType.Model.Builder, + new ConventionContext(((Model)property.DeclaringType.Model).ConventionDispatcher)); private ProviderConventionSetBuilderDependencies CreateDependencies() => InMemoryTestHelpers.Instance.CreateContextServices().GetRequiredService(); diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs index 6e7ec72499a..39d15972baf 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs @@ -35,7 +35,7 @@ private class InfinitePropertyAddedConvention : IPropertyAddedConvention public void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) - => propertyBuilder.Metadata.DeclaringEntityType.AddProperty("TempProperty" + _count++, typeof(int)); + => ((IMutableEntityType)propertyBuilder.Metadata.DeclaringType).AddProperty("TempProperty" + _count++, typeof(int)); } [InlineData(false)] @@ -326,9 +326,9 @@ public void OnEntityTypeIgnored_calls_conventions_in_order(bool useBuilder, bool var convention1 = new EntityTypeIgnoredConvention(terminate: false); var convention2 = new EntityTypeIgnoredConvention(terminate: true); var convention3 = new EntityTypeIgnoredConvention(terminate: false); - conventions.EntityTypeIgnoredConventions.Add(convention1); - conventions.EntityTypeIgnoredConventions.Add(convention2); - conventions.EntityTypeIgnoredConventions.Add(convention3); + conventions.TypeIgnoredConventions.Add(convention1); + conventions.TypeIgnoredConventions.Add(convention2); + conventions.TypeIgnoredConventions.Add(convention3); var convention4 = new EntityTypeRemovedConvention(terminate: false); var convention5 = new EntityTypeRemovedConvention(terminate: true); @@ -371,7 +371,7 @@ public void OnEntityTypeIgnored_calls_conventions_in_order(bool useBuilder, bool Assert.Equal(0, convention6.Calls); } - private class EntityTypeIgnoredConvention : IEntityTypeIgnoredConvention + private class EntityTypeIgnoredConvention : ITypeIgnoredConvention { private readonly bool _terminate; public int Calls; @@ -381,7 +381,7 @@ public EntityTypeIgnoredConvention(bool terminate) _terminate = terminate; } - public void ProcessEntityTypeIgnored( + public void ProcessTypeIgnored( IConventionModelBuilder modelBuilder, string name, Type type, @@ -2093,7 +2093,7 @@ public void OnSkipNavigationAdded_calls_conventions_in_order(bool useBuilder, bo else { var result = firstEntityBuilder.Metadata.AddSkipNavigation( - nameof(Order.Products), null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Order.Products), null, null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); Assert.Equal(!useScope, result == null); } @@ -2158,7 +2158,7 @@ public void OnSkipNavigationAnnotationChanged_calls_conventions_in_order(bool us var secondEntityBuilder = builder.Entity(typeof(Product), ConfigurationSource.Convention); var navigation = firstEntityBuilder.Metadata.AddSkipNavigation( - nameof(Order.Products), null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Order.Products), null, null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -2267,7 +2267,7 @@ public void OnSkipNavigationForeignKeyChanged_calls_conventions_in_order(bool us .IsUnique(false, ConfigurationSource.Convention) .Metadata; var navigation = firstEntityBuilder.Metadata.AddSkipNavigation( - nameof(Order.Products), null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Order.Products), null, null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -2358,9 +2358,9 @@ public void OnSkipNavigationInverseChanged_calls_conventions_in_order(bool useBu var secondEntityBuilder = builder.Entity(typeof(Product), ConfigurationSource.Convention); var navigation = firstEntityBuilder.Metadata.AddSkipNavigation( - nameof(Order.Products), null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Order.Products), null, null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); var inverse = secondEntityBuilder.Metadata.AddSkipNavigation( - nameof(Product.Orders), null, firstEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Product.Orders), null, null, firstEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -2449,7 +2449,7 @@ public void OnSkipNavigationRemoved_calls_conventions_in_order(bool useScope) var secondEntityBuilder = builder.Entity(typeof(Product), ConfigurationSource.Convention); var navigation = firstEntityBuilder.Metadata.AddSkipNavigation( - nameof(Order.Products), null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); + nameof(Order.Products), null, null, secondEntityBuilder.Metadata, true, false, ConfigurationSource.Convention); var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; @@ -3419,7 +3419,7 @@ public void ProcessPropertyAdded( if (_terminate) { - propertyBuilder.Metadata.DeclaringEntityType.RemoveProperty(propertyBuilder.Metadata.Name); + propertyBuilder.Metadata.DeclaringType.RemoveProperty(propertyBuilder.Metadata.Name); context.StopProcessing(); } } @@ -3820,11 +3820,923 @@ public PropertyRemovedConvention(bool terminate) } public void ProcessPropertyRemoved( - IConventionEntityTypeBuilder entityTypeBuilder, + IConventionTypeBaseBuilder typeBaseBuilder, IConventionProperty property, IConventionContext context) { - Assert.NotNull(entityTypeBuilder.Metadata.Builder); + Assert.NotNull(typeBaseBuilder.Metadata.Builder); + + Calls.Add(property); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypePropertyAdded_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new PropertyAddedConvention(terminate: false); + var convention2 = new PropertyAddedConvention(terminate: true); + var convention3 = new PropertyAddedConvention(terminate: false); + conventions.PropertyAddedConventions.Add(convention1); + conventions.PropertyAddedConventions.Add(convention2); + conventions.PropertyAddedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var complexBuilder = entityBuilder.ComplexProperty( + Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder; + var shadowPropertyName = "ShadowProperty"; + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = complexBuilder.Property(typeof(int), shadowPropertyName, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + else + { + var result = complexBuilder.Metadata.AddProperty( + shadowPropertyName, typeof(int), ConfigurationSource.Convention, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + Assert.Empty(convention3.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { shadowPropertyName }, convention1.Calls); + Assert.Equal(new[] { shadowPropertyName }, convention2.Calls); + Assert.Empty(convention3.Calls); + + scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = complexBuilder.Property(nameof(OrderDetails.Id), ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + else + { + var result = ((IMutableComplexType)complexBuilder.Metadata).AddProperty(nameof(OrderDetails.Id)); + + Assert.Equal(!useScope, result == null); + } + + if (useScope) + { + Assert.Equal(new[] { shadowPropertyName }, convention1.Calls); + Assert.Equal(new[] { shadowPropertyName }, convention2.Calls); + Assert.Empty(convention3.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { shadowPropertyName, nameof(OrderDetails.Id) }, convention1.Calls); + Assert.Equal(new[] { shadowPropertyName, nameof(OrderDetails.Id) }, convention2.Calls); + Assert.Empty(convention3.Calls); + + Assert.Empty(entityBuilder.Metadata.GetProperties()); + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypePropertyNullabilityChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new PropertyNullabilityChangedConvention(false); + var convention2 = new PropertyNullabilityChangedConvention(true); + var convention3 = new PropertyNullabilityChangedConvention(false); + conventions.PropertyNullabilityChangedConventions.Add(convention1); + conventions.PropertyNullabilityChangedConventions.Add(convention2); + conventions.PropertyNullabilityChangedConventions.Add(convention3); + + var model = new Model(conventions); + + var scope = useScope ? model.DelayConventions() : null; + + var propertyBuilder = model.Builder.Entity(typeof(Order), ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder + .Property(typeof(string), "Name", ConfigurationSource.Convention); + if (useBuilder) + { + propertyBuilder.IsRequired(true, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = false; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false }, convention1.Calls); + Assert.Equal(new bool?[] { false }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(false, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = true; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true }, convention1.Calls); + Assert.Equal(new bool?[] { false, true }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(false, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = true; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true }, convention1.Calls); + Assert.Equal(new bool?[] { false, true }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(true, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = false; + } + + scope?.Dispose(); + + if (useScope) + { + Assert.Equal(new bool?[] { false, false, false }, convention1.Calls); + Assert.Equal(new bool?[] { false, false, false }, convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true, false }, convention1.Calls); + Assert.Equal(new bool?[] { false, true, false }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypePropertyFieldChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new PropertyFieldChangedConvention(terminate: false); + var convention2 = new PropertyFieldChangedConvention(terminate: true); + var convention3 = new PropertyFieldChangedConvention(terminate: false); + conventions.PropertyFieldChangedConventions.Add(convention1); + conventions.PropertyFieldChangedConventions.Add(convention2); + conventions.PropertyFieldChangedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var propertyBuilder = entityBuilder + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder + .Property(nameof(OrderDetails.Id), ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField(nameof(OrderDetails.IntField), ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetField( + nameof(OrderDetails.IntField), + ConfigurationSource.Convention); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new string[] { null }, convention1.Calls); + Assert.Equal(new string[] { null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField(nameof(OrderDetails.IntField), ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetField( + nameof(OrderDetails.IntField), + ConfigurationSource.Convention); + } + + Assert.Equal(new string[] { null }, convention1.Calls); + Assert.Equal(new string[] { null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField((string)null, ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetFieldInfo( + null, + ConfigurationSource.Convention); + } + + Assert.Equal(new[] { null, nameof(Order.IntField) }, convention1.Calls); + Assert.Equal(new[] { null, nameof(Order.IntField) }, convention2.Calls); + Assert.Empty(convention3.Calls); + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypePropertyAnnotationChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new PropertyAnnotationChangedConvention(false); + var convention2 = new PropertyAnnotationChangedConvention(true); + var convention3 = new PropertyAnnotationChangedConvention(false); + conventions.PropertyAnnotationChangedConventions.Add(convention1); + conventions.PropertyAnnotationChangedConventions.Add(convention2); + conventions.PropertyAnnotationChangedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var propertyBuilder = builder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder + .Property(nameof(OrderDetails.Id), ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata["foo"] = "bar"; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata["foo"] = "bar"; + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", null, ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.RemoveAnnotation("foo"); + } + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + Assert.Equal(new[] { "bar", null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + propertyBuilder.Metadata[CoreAnnotationNames.AfterSaveBehavior] = PropertySaveBehavior.Ignore; + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + } + + [InlineData(false)] + [InlineData(true)] + [ConditionalTheory] + public void OnComplexTypePropertyRemoved_calls_conventions_in_order(bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new PropertyRemovedConvention(terminate: false); + var convention2 = new PropertyRemovedConvention(terminate: true); + var convention3 = new PropertyRemovedConvention(terminate: false); + conventions.PropertyRemovedConventions.Add(convention1); + conventions.PropertyRemovedConventions.Add(convention2); + conventions.PropertyRemovedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var shadowPropertyName = "ShadowProperty"; + var property = entityBuilder + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder.Metadata.AddProperty( + shadowPropertyName, typeof(int), ConfigurationSource.Convention, ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + var result = ((ComplexType)property.DeclaringType).RemoveProperty(property); + + if (useScope) + { + Assert.Same(property, result); + } + else + { + Assert.Null(result); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { property }, convention1.Calls); + Assert.Equal(new[] { property }, convention2.Calls); + Assert.Empty(convention3.Calls); + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexPropertyAdded_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexPropertyAddedConvention(terminate: false); + var convention2 = new ComplexPropertyAddedConvention(terminate: true); + var convention3 = new ComplexPropertyAddedConvention(terminate: false); + conventions.ComplexPropertyAddedConventions.Add(convention1); + conventions.ComplexPropertyAddedConventions.Add(convention2); + conventions.ComplexPropertyAddedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = entityBuilder.ComplexProperty( + Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + else + { + var result = entityBuilder.Metadata.AddComplexProperty( + Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { Order.OrderDetailsProperty.Name }, convention1.Calls); + Assert.Equal(new[] { Order.OrderDetailsProperty.Name }, convention2.Calls); + Assert.Empty(convention3.Calls); + + scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = entityBuilder.ComplexProperty( + Order.OtherOrderDetailsProperty, collection: false, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + else + { + var result = ((IMutableEntityType)entityBuilder.Metadata).AddComplexProperty( + Order.OtherOrderDetailsProperty, collection: false); + + Assert.Equal(!useScope, result == null); + } + + if (useScope) + { + Assert.Equal(new[] { Order.OrderDetailsProperty.Name }, convention1.Calls); + Assert.Equal(new[] { Order.OrderDetailsProperty.Name }, convention2.Calls); + Assert.Empty(convention3.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { Order.OrderDetailsProperty.Name, Order.OtherOrderDetailsProperty.Name }, convention1.Calls); + Assert.Equal(new[] { Order.OrderDetailsProperty.Name, Order.OtherOrderDetailsProperty.Name }, convention2.Calls); + Assert.Empty(convention3.Calls); + + Assert.Empty(entityBuilder.Metadata.GetComplexProperties()); + } + + private class ComplexPropertyAddedConvention : IComplexPropertyAddedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexPropertyAddedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexPropertyAdded( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + Assert.True(propertyBuilder.Metadata.IsInModel); + + Calls.Add(propertyBuilder.Metadata.Name); + + if (_terminate) + { + ((IConventionEntityType)propertyBuilder.Metadata.DeclaringType).RemoveComplexProperty(propertyBuilder.Metadata.Name); + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexPropertyNullabilityChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexPropertyNullabilityChangedConvention(false); + var convention2 = new ComplexPropertyNullabilityChangedConvention(true); + var convention3 = new ComplexPropertyNullabilityChangedConvention(false); + conventions.ComplexPropertyNullabilityChangedConventions.Add(convention1); + conventions.ComplexPropertyNullabilityChangedConventions.Add(convention2); + conventions.ComplexPropertyNullabilityChangedConventions.Add(convention3); + + var model = new Model(conventions); + + var scope = useScope ? model.DelayConventions() : null; + + var propertyBuilder = model.Builder.Entity(typeof(Order), ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + if (useBuilder) + { + Assert.NotNull(propertyBuilder.IsRequired(true, ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.IsNullable = false; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false }, convention1.Calls); + Assert.Equal(new bool?[] { false }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(false, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = true; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true }, convention1.Calls); + Assert.Equal(new bool?[] { false, true }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(false, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = true; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true }, convention1.Calls); + Assert.Equal(new bool?[] { false, true }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + propertyBuilder.IsRequired(true, ConfigurationSource.Convention); + } + else + { + propertyBuilder.Metadata.IsNullable = false; + } + + scope?.Dispose(); + + if (useScope) + { + Assert.Equal(new bool?[] { false, false, false }, convention1.Calls); + Assert.Equal(new bool?[] { false, false, false }, convention2.Calls); + } + else + { + Assert.Equal(new bool?[] { false, true, false }, convention1.Calls); + Assert.Equal(new bool?[] { false, true, false }, convention2.Calls); + } + + Assert.Empty(convention3.Calls); + } + + private class ComplexPropertyNullabilityChangedConvention : IComplexPropertyNullabilityChangedConvention + { + public readonly List Calls = new(); + private readonly bool _terminate; + + public ComplexPropertyNullabilityChangedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexPropertyNullabilityChanged( + IConventionComplexPropertyBuilder propertyBuilder, + IConventionContext context) + { + Calls.Add(propertyBuilder.Metadata.IsNullable); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexPropertyFieldChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexPropertyFieldChangedConvention(terminate: false); + var convention2 = new ComplexPropertyFieldChangedConvention(terminate: true); + var convention3 = new ComplexPropertyFieldChangedConvention(terminate: false); + conventions.ComplexPropertyFieldChangedConventions.Add(convention1); + conventions.ComplexPropertyFieldChangedConventions.Add(convention2); + conventions.ComplexPropertyFieldChangedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var propertyBuilder = entityBuilder + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField(nameof(Order.OrderDetailsField), ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetField( + nameof(Order.OrderDetailsField), + ConfigurationSource.Convention); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new string[] { null }, convention1.Calls); + Assert.Equal(new string[] { null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField(nameof(Order.OrderDetailsField), ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetField( + nameof(Order.OrderDetailsField), + ConfigurationSource.Convention); + } + + Assert.Equal(new string[] { null }, convention1.Calls); + Assert.Equal(new string[] { null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasField((string)null, ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.SetFieldInfo( + null, + ConfigurationSource.Convention); + } + + Assert.Equal(new[] { null, nameof(Order.OrderDetailsField) }, convention1.Calls); + Assert.Equal(new[] { null, nameof(Order.OrderDetailsField) }, convention2.Calls); + Assert.Empty(convention3.Calls); + } + + private class ComplexPropertyFieldChangedConvention : IComplexPropertyFieldChangedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexPropertyFieldChangedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexPropertyFieldChanged( + IConventionComplexPropertyBuilder propertyBuilder, + FieldInfo newFieldInfo, + FieldInfo oldFieldInfo, + IConventionContext context) + { + Assert.True(propertyBuilder.Metadata.IsInModel); + + Calls.Add(oldFieldInfo?.Name); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexPropertyAnnotationChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexPropertyAnnotationChangedConvention(false); + var convention2 = new ComplexPropertyAnnotationChangedConvention(true); + var convention3 = new ComplexPropertyAnnotationChangedConvention(false); + conventions.ComplexPropertyAnnotationChangedConventions.Add(convention1); + conventions.ComplexPropertyAnnotationChangedConventions.Add(convention2); + conventions.ComplexPropertyAnnotationChangedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var propertyBuilder = builder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention); + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata["foo"] = "bar"; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata["foo"] = "bar"; + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(propertyBuilder.HasAnnotation("foo", null, ConfigurationSource.Convention)); + } + else + { + propertyBuilder.Metadata.RemoveAnnotation("foo"); + } + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + Assert.Equal(new[] { "bar", null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + propertyBuilder.Metadata[CoreAnnotationNames.AfterSaveBehavior] = PropertySaveBehavior.Ignore; + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + } + + private class ComplexPropertyAnnotationChangedConvention : IComplexPropertyAnnotationChangedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexPropertyAnnotationChangedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexPropertyAnnotationChanged( + IConventionComplexPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation annotation, + IConventionAnnotation oldAnnotation, + IConventionContext context) + { + Assert.True(propertyBuilder.Metadata.IsInModel); + + Calls.Add(annotation?.Value); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false)] + [InlineData(true)] + [ConditionalTheory] + public void OnComplexPropertyRemoved_calls_conventions_in_order(bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexPropertyRemovedConvention(terminate: false); + var convention2 = new ComplexPropertyRemovedConvention(terminate: true); + var convention3 = new ComplexPropertyRemovedConvention(terminate: false); + conventions.ComplexPropertyRemovedConventions.Add(convention1); + conventions.ComplexPropertyRemovedConventions.Add(convention2); + conventions.ComplexPropertyRemovedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var property = entityBuilder + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .Metadata; + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + var result = ((EntityType)property.DeclaringType).RemoveComplexProperty(property); + + if (useScope) + { + Assert.Same(property, result); + } + else + { + Assert.Null(result); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { property }, convention1.Calls); + Assert.Equal(new[] { property }, convention2.Calls); + Assert.Empty(convention3.Calls); + } + + private class ComplexPropertyRemovedConvention : IComplexPropertyRemovedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexPropertyRemovedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexPropertyRemoved( + IConventionTypeBaseBuilder typeBaseBuilder, + IConventionComplexProperty property, + IConventionContext context) + { + Assert.NotNull(typeBaseBuilder.Metadata.Builder); Calls.Add(property); @@ -3835,12 +4747,217 @@ public void ProcessPropertyRemoved( } } + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypeAnnotationChanged_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexTypeAnnotationChangedConvention(false); + var convention2 = new ComplexTypeAnnotationChangedConvention(true); + var convention3 = new ComplexTypeAnnotationChangedConvention(false); + conventions.ComplexTypeAnnotationChangedConventions.Add(convention1); + conventions.ComplexTypeAnnotationChangedConventions.Add(convention2); + conventions.ComplexTypeAnnotationChangedConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var typeBuilder = builder.Entity(typeof(SpecialOrder), ConfigurationSource.Convention) + .ComplexProperty(Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder; + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + Assert.NotNull(typeBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + typeBuilder.Metadata["foo"] = "bar"; + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(typeBuilder.HasAnnotation("foo", "bar", ConfigurationSource.Convention)); + } + else + { + typeBuilder.Metadata["foo"] = "bar"; + } + + Assert.Equal(new[] { "bar" }, convention1.Calls); + Assert.Equal(new[] { "bar" }, convention2.Calls); + Assert.Empty(convention3.Calls); + + if (useBuilder) + { + Assert.NotNull(typeBuilder.HasAnnotation("foo", null, ConfigurationSource.Convention)); + } + else + { + typeBuilder.Metadata.RemoveAnnotation("foo"); + } + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + Assert.Equal(new[] { "bar", null }, convention2.Calls); + Assert.Empty(convention3.Calls); + + typeBuilder.Metadata[CoreAnnotationNames.AfterSaveBehavior] = PropertySaveBehavior.Ignore; + + Assert.Equal(new[] { "bar", null }, convention1.Calls); + } + + private class ComplexTypeAnnotationChangedConvention : IComplexTypeAnnotationChangedConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexTypeAnnotationChangedConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexTypeAnnotationChanged( + IConventionComplexTypeBuilder propertyBuilder, + string name, + IConventionAnnotation annotation, + IConventionAnnotation oldAnnotation, + IConventionContext context) + { + Assert.True(propertyBuilder.Metadata.IsInModel); + + Calls.Add(annotation?.Value); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + [ConditionalTheory] + public void OnComplexTypeMemberIgnored_calls_conventions_in_order(bool useBuilder, bool useScope) + { + var conventions = new ConventionSet(); + + var convention1 = new ComplexTypeMemberIgnoredConvention(terminate: false); + var convention2 = new ComplexTypeMemberIgnoredConvention(terminate: true); + var convention3 = new ComplexTypeMemberIgnoredConvention(terminate: false); + conventions.ComplexTypeMemberIgnoredConventions.Add(convention1); + conventions.ComplexTypeMemberIgnoredConventions.Add(convention2); + conventions.ComplexTypeMemberIgnoredConventions.Add(convention3); + + var builder = new InternalModelBuilder(new Model(conventions)); + var entityBuilder = builder.Entity(typeof(Order), ConfigurationSource.Convention); + var complexBuilder = entityBuilder.ComplexProperty( + Order.OrderDetailsProperty, collection: false, ConfigurationSource.Convention) + .ComplexTypeBuilder; + var shadowPropertyName = "ShadowProperty"; + + var scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = complexBuilder.Ignore(shadowPropertyName, ConfigurationSource.Convention); + + Assert.NotNull(result); + } + else + { + var result = complexBuilder.Metadata.AddIgnored(shadowPropertyName, ConfigurationSource.Convention); + + Assert.Equal(!useScope, result == null); + } + + if (useScope) + { + Assert.Empty(convention1.Calls); + Assert.Empty(convention2.Calls); + scope.Dispose(); + } + + Assert.Equal(new[] { shadowPropertyName }, convention1.Calls); + Assert.Equal(new[] { shadowPropertyName }, convention2.Calls); + Assert.Empty(convention3.Calls); + + scope = useScope ? builder.Metadata.ConventionDispatcher.DelayConventions() : null; + + if (useBuilder) + { + var result = complexBuilder.Ignore(shadowPropertyName, ConfigurationSource.Convention); + + Assert.NotNull(result); + } + else + { + var result = complexBuilder.Metadata.AddIgnored(shadowPropertyName, ConfigurationSource.Convention); + + Assert.NotNull(result); + } + + Assert.Equal(new[] { shadowPropertyName }, convention1.Calls); + Assert.Equal(new[] { shadowPropertyName }, convention2.Calls); + Assert.Empty(convention3.Calls); + if (useScope) + { + scope.Dispose(); + } + + Assert.Empty(entityBuilder.Metadata.GetIgnoredMembers()); + } + + private class ComplexTypeMemberIgnoredConvention : IComplexTypeMemberIgnoredConvention + { + private readonly bool _terminate; + public readonly List Calls = new(); + + public ComplexTypeMemberIgnoredConvention(bool terminate) + { + _terminate = terminate; + } + + public void ProcessComplexTypeMemberIgnored( + IConventionComplexTypeBuilder complexTypeBuilder, + string name, + IConventionContext context) + { + Assert.NotNull(complexTypeBuilder.Metadata.Builder); + + Calls.Add(name); + + if (_terminate) + { + context.StopProcessing(); + } + } + } + private class Order { public static readonly PropertyInfo OrderIdProperty = typeof(Order).GetProperty(nameof(OrderId)); public static readonly PropertyInfo OrderDetailsProperty = typeof(Order).GetProperty(nameof(OrderDetails)); + public static readonly PropertyInfo OtherOrderDetailsProperty = typeof(Order).GetProperty(nameof(OtherOrderDetails)); public readonly int IntField = 1; + public readonly OrderDetails OrderDetailsField = default; public int OrderId { get; set; } @@ -3858,6 +4975,7 @@ private class SpecialOrder : Order private class OrderDetails { public static readonly PropertyInfo OrderProperty = typeof(OrderDetails).GetProperty(nameof(Order)); + public readonly int IntField = 1; public int Id { get; set; } public virtual Order Order { get; set; } diff --git a/test/EFCore.Tests/Metadata/Conventions/DeleteBehaviorAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/DeleteBehaviorAttributeConventionTest.cs index 3edfac832dd..7504634a73a 100644 --- a/test/EFCore.Tests/Metadata/Conventions/DeleteBehaviorAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/DeleteBehaviorAttributeConventionTest.cs @@ -89,7 +89,8 @@ public void Throw_InvalidOperationException_if_attribute_was_set_on_one_of_forei var modelBuilder = CreateModelBuilder(); Assert.Equal( - CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty, + CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty( + nameof(Post_On_FK_Property), nameof(Post_On_FK_Property.Blog_On_FK_PropertyId)), Assert.Throws( () => modelBuilder.Entity() .Property(e => e.Blog_On_FK_PropertyId)).Message @@ -102,7 +103,8 @@ public void Throw_InvalidOperationException_if_attribute_was_set_on_random_prope var modelBuilder = CreateModelBuilder(); Assert.Equal( - CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty, + CoreStrings.DeleteBehaviorAttributeNotOnNavigationProperty( + nameof(Post_On_Property), nameof(Post_On_Property.Id)), Assert.Throws( () => modelBuilder.Entity() .Property(e => e.Blog_On_PropertyId)).Message @@ -118,8 +120,9 @@ public void Throw_InvalidOperationException_if_attribute_was_set_on_principal_na .Property(e => e.Blog_On_PrincipalId); Assert.Equal( - CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty, - Assert.Throws(() => modelBuilder.FinalizeModel()).Message + CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty( + nameof(Blog_On_Principal), nameof(Blog_On_Principal.Posts)), + Assert.Throws(modelBuilder.FinalizeModel).Message ); } @@ -132,8 +135,9 @@ public void Throw_InvalidOperationException_if_attribute_was_set_on_principal_on .Property(e => e.Blog_On_PrincipalId); Assert.Equal( - CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty, - Assert.Throws(() => modelBuilder.FinalizeModel()).Message + CoreStrings.DeleteBehaviorAttributeOnPrincipalProperty( + nameof(Blog_On_Principal_OneToOne), nameof(Blog_On_Principal_OneToOne.Post_On_Principal_OneToOne)), + Assert.Throws(modelBuilder.FinalizeModel).Message ); } diff --git a/test/EFCore.Tests/Metadata/Conventions/EntityTypeAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/EntityTypeAttributeConventionTest.cs index b4ce29e8681..8bc7a8f2f7c 100644 --- a/test/EFCore.Tests/Metadata/Conventions/EntityTypeAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/EntityTypeAttributeConventionTest.cs @@ -128,13 +128,13 @@ private void RunConvention(InternalEntityTypeBuilder entityTypeBuilder) { var context = new ConventionContext(entityTypeBuilder.Metadata.Model.ConventionDispatcher); - new NotMappedEntityTypeAttributeConvention(CreateDependencies()) + new NotMappedTypeAttributeConvention(CreateDependencies()) .ProcessEntityTypeAdded(entityTypeBuilder, context); - new OwnedEntityTypeAttributeConvention(CreateDependencies()) + new OwnedAttributeConvention(CreateDependencies()) .ProcessEntityTypeAdded(entityTypeBuilder, context); - new KeylessEntityTypeAttributeConvention(CreateDependencies()) + new KeylessAttributeConvention(CreateDependencies()) .ProcessEntityTypeAdded(entityTypeBuilder, context); } diff --git a/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs index 0a9d896bcdf..55c365c3089 100644 --- a/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/ForeignKeyPropertyDiscoveryConventionTest.cs @@ -383,10 +383,10 @@ public void Does_not_match_PK_name_properties() public void Does_not_match_PK_name_properties_if_subset_of_dependent_PK_and_contains_id() { var dependentTypeBuilder = DependentTypeWithCompositeKey.Builder; - var pkProperty1 = dependentTypeBuilder.Property( + var pkProperty1 = (Property)dependentTypeBuilder.Property( DependentEntityWithCompositeKey.IdProperty, ConfigurationSource.Convention) .Metadata; - var pkProperty2 = dependentTypeBuilder.Property( + var pkProperty2 = (Property)dependentTypeBuilder.Property( DependentEntityWithCompositeKey.NameProperty, ConfigurationSource.Convention) .IsRequired(true, ConfigurationSource.Convention) .Metadata; @@ -418,7 +418,7 @@ public void Does_not_match_PK_name_properties_if_subset_of_dependent_PK_and_cont public void Matches_PK_name_properties_if_subset_of_dependent_PK() { var dependentTypeBuilder = DependentType.Builder; - var pkProperty = dependentTypeBuilder.Property( + var pkProperty = (Property)dependentTypeBuilder.Property( DependentEntity.IDProperty, ConfigurationSource.Convention) .IsRequired(true, ConfigurationSource.Convention) .Metadata; @@ -1138,7 +1138,7 @@ private InternalPropertyBuilder RunConvention(InternalPropertyBuilder propertyBu { var convention = CreateForeignKeyPropertyDiscoveryConvention(); var context = new ConventionContext( - propertyBuilder.Metadata.DeclaringEntityType.Model.ConventionDispatcher); + propertyBuilder.Metadata.DeclaringType.Model.ConventionDispatcher); convention.ProcessPropertyAdded(propertyBuilder, context); if (context.ShouldStopProcessing()) { diff --git a/test/EFCore.Tests/Metadata/Conventions/KeyDiscoveryConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/KeyDiscoveryConventionTest.cs index dc6f2273ec1..c2e99c7450a 100644 --- a/test/EFCore.Tests/Metadata/Conventions/KeyDiscoveryConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/KeyDiscoveryConventionTest.cs @@ -148,7 +148,7 @@ private void RunConvention(InternalEntityTypeBuilder entityTypeBuilder) private void RunConvention(InternalPropertyBuilder propertyBuilder) { var context = new ConventionContext( - propertyBuilder.Metadata.DeclaringEntityType.Model.ConventionDispatcher); + propertyBuilder.Metadata.DeclaringType.Model.ConventionDispatcher); CreateKeyDiscoveryConvention().ProcessPropertyAdded(propertyBuilder, context); } diff --git a/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs index 4a77a94f124..539f8532f9a 100644 --- a/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/NavigationAttributeConventionTest.cs @@ -207,7 +207,9 @@ public void RequiredAttribute_does_not_configure_skip_navigations() var navigationBuilder = postEntityTypeBuilder.HasSkipNavigation( new MemberIdentity(nameof(Post.Blogs)), blogEntityTypeBuilder.Metadata, + null, new MemberIdentity(nameof(Blog.Posts)), + null, ConfigurationSource.Convention, collections: true, onDependent: false); diff --git a/test/EFCore.Tests/Metadata/Conventions/NonNullableReferencePropertyConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/NonNullableReferencePropertyConventionTest.cs index 28caf462f8f..e14810a40c1 100644 --- a/test/EFCore.Tests/Metadata/Conventions/NonNullableReferencePropertyConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/NonNullableReferencePropertyConventionTest.cs @@ -90,7 +90,7 @@ public void Dictionary_indexer_is_not_configured_as_non_nullable() private void RunConvention(InternalPropertyBuilder propertyBuilder) { var context = new ConventionContext( - propertyBuilder.Metadata.DeclaringEntityType.Model.ConventionDispatcher); + propertyBuilder.Metadata.DeclaringType.Model.ConventionDispatcher); new NonNullableReferencePropertyConvention(CreateDependencies()) .ProcessPropertyAdded(propertyBuilder, context); diff --git a/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs index 908d077f953..09feb65d9b8 100644 --- a/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/PropertyAttributeConventionTest.cs @@ -713,7 +713,7 @@ private static void RunConvention(IConventionPropertyBuilder propertyBuilder) { var dependencies = CreateDependencies(); var context = new ConventionContext( - ((Model)propertyBuilder.Metadata.DeclaringEntityType.Model).ConventionDispatcher); + ((Model)propertyBuilder.Metadata.DeclaringType.Model).ConventionDispatcher); new BackingFieldConvention(dependencies) .ProcessPropertyAdded(propertyBuilder, context); diff --git a/test/EFCore.Tests/Metadata/Conventions/PropertyDiscoveryConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/PropertyDiscoveryConventionTest.cs index d2d2437a7ef..3666af6779c 100644 --- a/test/EFCore.Tests/Metadata/Conventions/PropertyDiscoveryConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/PropertyDiscoveryConventionTest.cs @@ -56,9 +56,9 @@ public void Properties_with_private_setters_on_unmapped_base_types_are_discovere Assert.Single(model.GetEntityTypes()); - var entityType = model.FindEntityType(typeof(DerivedWithoutPrivates)); + var entityType = (IRuntimeEntityType)model.FindEntityType(typeof(DerivedWithoutPrivates)); - Assert.Equal(3, entityType.PropertyCount()); + Assert.Equal(3, entityType.PropertyCount); var idProperty = entityType.FindProperty(nameof(BaseWithPrivates.Id)); Assert.NotNull(idProperty.PropertyInfo); diff --git a/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs b/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs index 88baa1d27e3..b5efa968452 100644 --- a/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs +++ b/test/EFCore.Tests/Metadata/EntityTypeExtensionsTest.cs @@ -21,7 +21,7 @@ public void Can_get_all_properties_and_navigations() Assert.Equal( new IReadOnlyPropertyBase[] { pk.Properties.Single(), fkProp, principalToDependent, dependentToPrincipal }, - ((IEntityType)entityType).GetPropertiesAndNavigations().ToArray()); + ((IRuntimeEntityType)entityType).GetSnapshottableMembers().ToArray()); } [ConditionalFact] diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs index bc65fb04777..087a06dfcf6 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertyGetterFactoryTest.cs @@ -61,7 +61,7 @@ public PropertySaveBehavior GetBeforeSaveBehavior() public PropertySaveBehavior GetAfterSaveBehavior() => throw new NotImplementedException(); - public Func GetValueGeneratorFactory() + public Func GetValueGeneratorFactory() => throw new NotImplementedException(); public ValueConverter GetValueConverter() @@ -109,7 +109,6 @@ public PropertyAccessMode GetPropertyAccessMode() public string Name { get; } public ITypeBase DeclaringType { get; } public Type ClrType { get; } - public IEntityType DeclaringEntityType { get; } public bool IsNullable { get; } public ValueGenerated ValueGenerated { get; } public bool IsConcurrencyToken { get; } diff --git a/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs index 96a0412884c..73b8bf1a464 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrPropertySetterFactoryTest.cs @@ -24,7 +24,6 @@ private class FakeProperty : Annotatable, IProperty, IClrPropertySetter public string Name { get; } public ITypeBase DeclaringType { get; } public Type ClrType { get; } - public IEntityType DeclaringEntityType { get; } public bool IsNullable { get; } public bool IsReadOnlyBeforeSave { get; } public bool IsReadOnlyAfterSave { get; } @@ -80,7 +79,7 @@ public PropertySaveBehavior GetBeforeSaveBehavior() public PropertySaveBehavior GetAfterSaveBehavior() => throw new NotImplementedException(); - public Func GetValueGeneratorFactory() + public Func GetValueGeneratorFactory() => throw new NotImplementedException(); public ValueConverter GetValueConverter() diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index 371de95e294..d6cf05128c7 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -869,7 +869,7 @@ public void Removing_a_foreign_key_throws_if_referenced_from_skip_navigation() .AddForeignKey(new[] { orderIdProperty }, firstKey, firstEntity); var navigation = firstEntity.AddSkipNavigation( - nameof(Order.Products), null, secondEntity, true, false); + nameof(Order.Products), null, null, secondEntity, true, false); navigation.SetForeignKey(foreignKey); Assert.Equal( @@ -1249,7 +1249,7 @@ public void Can_add_and_remove_skip_navigation() var customerForeignKey = orderEntity .AddForeignKey(customerFkProperty, customerKey, customerEntity); var relatedNavigation = orderEntity.AddSkipNavigation( - nameof(Order.RelatedOrder), null, orderEntity, false, true); + nameof(Order.RelatedOrder), null, null, orderEntity, false, true); relatedNavigation.SetForeignKey(customerForeignKey); Assert.True(relatedNavigation.IsOnDependent); @@ -1261,7 +1261,7 @@ public void Can_add_and_remove_skip_navigation() .AddForeignKey(new[] { orderProductFkProperty }, orderKey, orderEntity); var productsNavigation = orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false); + nameof(Order.Products), null, null, productEntity, true, false); productsNavigation.SetForeignKey(orderProductForeignKey); Assert.Equal(new[] { productsNavigation, relatedNavigation }, orderEntity.GetSkipNavigations()); @@ -1298,7 +1298,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_another_skip_ .AddForeignKey(new[] { orderProductFkProperty }, orderKey, orderEntity); var navigation = orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false); + nameof(Order.Products), null, null, productEntity, true, false); navigation.SetForeignKey(orderProductForeignKey); Assert.Equal( @@ -1306,7 +1306,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_another_skip_ Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false)).Message); + nameof(Order.Products), null, null, productEntity, true, false)).Message); } [ConditionalFact] @@ -1332,7 +1332,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_navigation_ Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false)).Message); + nameof(Order.Products), null, null, productEntity, true, false)).Message); } [ConditionalFact] @@ -1355,7 +1355,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_property_th Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false)).Message); + nameof(Order.Products), null, null, productEntity, true, false)).Message); } [ConditionalFact] @@ -1378,7 +1378,7 @@ public void Adding_skip_navigation_with_a_name_that_conflicts_with_a_service_pro Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), null, productEntity, true, false)).Message); + nameof(Order.Products), null, null, productEntity, true, false)).Message); } [ConditionalFact] @@ -1399,7 +1399,7 @@ public void Adding_CLR_skip_navigation_targetting_a_shadow_entity_type_throws() nameof(Order.Products), nameof(Order), "ICollection", "Dictionary"), Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), Order.ProductsProperty, productEntity, true, false)).Message); + nameof(Order.Products), null, Order.ProductsProperty, productEntity, true, false)).Message); } [ConditionalFact] @@ -1419,7 +1419,7 @@ public void Adding_CLR_skip_navigation_to_a_mismatched_entity_type_throws() CoreStrings.NoClrNavigation(nameof(Order.Products), nameof(Product)), Assert.Throws( () => productEntity.AddSkipNavigation( - nameof(Order.Products), Order.ProductsProperty, productEntity, true, false)).Message); + nameof(Order.Products), null, Order.ProductsProperty, productEntity, true, false)).Message); } [ConditionalFact] @@ -1439,7 +1439,7 @@ public void Adding_CLR_collection_skip_navigation_with_mismatched_target_entity_ CoreStrings.NavigationCollectionWrongClrType(nameof(Order.Products), nameof(Order), "ICollection", nameof(Order)), Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), Order.ProductsProperty, orderEntity, true, false)).Message); + nameof(Order.Products), null, Order.ProductsProperty, orderEntity, true, false)).Message); } [ConditionalFact] @@ -1459,7 +1459,7 @@ public void Adding_CLR_reference_skip_navigation_with_mismatched_target_entity_t CoreStrings.NavigationSingleWrongClrType(nameof(Order.Products), nameof(Order), "ICollection", nameof(Order)), Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), Order.ProductsProperty, orderEntity, false, false)).Message); + nameof(Order.Products), null, Order.ProductsProperty, orderEntity, false, false)).Message); } [ConditionalFact] @@ -1480,7 +1480,7 @@ public void Adding_skip_navigation_with_a_mismatched_memberinfo_throws() Assert.Throws( () => orderEntity.AddSkipNavigation( - nameof(Order.Products), Order.RelatedOrderProperty, productEntity, true, false)).Message); + nameof(Order.Products), null, Order.RelatedOrderProperty, productEntity, true, false)).Message); } [ConditionalFact] @@ -1670,7 +1670,7 @@ public void Can_add_and_remove_properties() Assert.Equal("Id", property1.Name); Assert.Same(typeof(int), property1.ClrType); Assert.False(((IReadOnlyProperty)property1).IsConcurrencyToken); - Assert.Same(entityType, property1.DeclaringEntityType); + Assert.Same(entityType, property1.DeclaringType); var property2 = entityType.AddProperty("Name", typeof(string)); @@ -1701,7 +1701,7 @@ public void Can_add_new_properties_or_get_existing_properties_using_PropertyInfo Assert.False(idProperty.IsShadowProperty()); Assert.Equal("Id", idProperty.Name); Assert.Same(typeof(int), idProperty.ClrType); - Assert.Same(entityType, idProperty.DeclaringEntityType); + Assert.Same(entityType, idProperty.DeclaringType); Assert.Same(idProperty, entityType.FindProperty(Customer.IdProperty)); Assert.Same(idProperty, entityType.FindProperty("Id")); @@ -1712,7 +1712,7 @@ public void Can_add_new_properties_or_get_existing_properties_using_PropertyInfo Assert.False(nameProperty.IsShadowProperty()); Assert.Equal("Name", nameProperty.Name); Assert.Same(typeof(string), nameProperty.ClrType); - Assert.Same(entityType, nameProperty.DeclaringEntityType); + Assert.Same(entityType, nameProperty.DeclaringType); Assert.Same(nameProperty, entityType.FindProperty(Customer.NameProperty)); Assert.Same(nameProperty, entityType.FindProperty("Name")); @@ -1732,7 +1732,7 @@ public void Can_add_new_properties_using_name_of_property_in_base_class() Assert.False(property.IsShadowProperty()); Assert.Equal("Raisin", property.Name); Assert.Same(typeof(string), property.ClrType); - Assert.Same(entityType, property.DeclaringEntityType); + Assert.Same(entityType, property.DeclaringType); Assert.Same(HiddenFieldBase.RaisinProperty, property.PropertyInfo); Assert.Null(property.FieldInfo); } @@ -1748,7 +1748,7 @@ public void Can_add_new_properties_using_name_of_field_in_base_class() Assert.False(property.IsShadowProperty()); Assert.Equal("_date", property.Name); Assert.Same(typeof(string), property.ClrType); - Assert.Same(entityType, property.DeclaringEntityType); + Assert.Same(entityType, property.DeclaringType); Assert.Same(HiddenFieldBase.DateField, property.FieldInfo); Assert.Null(property.PropertyInfo); } @@ -2113,7 +2113,7 @@ public void Can_add_indexed_property() Assert.True(mutableProperty.IsIndexerProperty()); Assert.Equal("Nation", mutableProperty.Name); Assert.Same(typeof(string), mutableProperty.ClrType); - Assert.Same(mutatbleEntityType, mutableProperty.DeclaringEntityType); + Assert.Same(mutatbleEntityType, mutableProperty.DeclaringType); Assert.True(new[] { mutableProperty }.SequenceEqual(mutatbleEntityType.GetProperties())); @@ -2127,7 +2127,7 @@ public void Can_add_indexed_property() Assert.True(conventionProperty.IsIndexerProperty()); Assert.Equal("Country", conventionProperty.Name); Assert.Same(typeof(string), conventionProperty.ClrType); - Assert.Same(mutatbleEntityType, conventionProperty.DeclaringEntityType); + Assert.Same(mutatbleEntityType, conventionProperty.DeclaringType); Assert.True(new[] { conventionProperty }.SequenceEqual(conventionEntityType.GetProperties())); @@ -2196,7 +2196,7 @@ public void Can_get_property_indexes() eb.Property("Mane_"); }); - var entityType = modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)); + var entityType = (IRuntimeEntityType)modelBuilder.FinalizeModel().FindEntityType(typeof(Customer)); Assert.Equal(0, entityType.FindProperty("Id_").GetIndex()); Assert.Equal(1, entityType.FindProperty("Mane_").GetIndex()); @@ -2206,7 +2206,7 @@ public void Can_get_property_indexes() Assert.Equal(1, entityType.FindProperty("Mane_").GetShadowIndex()); Assert.Equal(-1, entityType.FindProperty("Name").GetShadowIndex()); - Assert.Equal(2, entityType.ShadowPropertyCount()); + Assert.Equal(2, entityType.ShadowPropertyCount); } [ConditionalFact] @@ -2228,7 +2228,7 @@ public void Attempting_to_set_store_generated_value_for_non_generated_property_t public void Indexes_for_derived_types_are_calculated_correctly() { using var context = new Levels(); - var type = context.Model.FindEntityType(typeof(Level1)); + var type = (IRuntimeEntityType)context.Model.FindEntityType(typeof(Level1)); Assert.Equal(0, type.FindProperty("Id").GetIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetIndex()); @@ -2256,14 +2256,14 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(-1, type.FindNavigation("Level1Collection").GetStoreGeneratedIndex()); Assert.Equal(-1, type.FindNavigation("Level1Reference").GetStoreGeneratedIndex()); - Assert.Equal(4, type.PropertyCount()); - Assert.Equal(2, type.NavigationCount()); - Assert.Equal(2, type.ShadowPropertyCount()); - Assert.Equal(4, type.OriginalValueCount()); - Assert.Equal(4, type.RelationshipPropertyCount()); - Assert.Equal(2, type.StoreGeneratedCount()); + Assert.Equal(4, type.PropertyCount); + Assert.Equal(2, type.NavigationCount); + Assert.Equal(2, type.ShadowPropertyCount); + Assert.Equal(4, type.OriginalValueCount); + Assert.Equal(4, type.RelationshipPropertyCount); + Assert.Equal(2, type.StoreGeneratedCount); - type = context.Model.FindEntityType(typeof(Level2)); + type = (IRuntimeEntityType)context.Model.FindEntityType(typeof(Level2)); Assert.Equal(0, type.FindProperty("Id").GetIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetIndex()); @@ -2307,14 +2307,14 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(-1, type.FindNavigation("Level2Collection").GetStoreGeneratedIndex()); Assert.Equal(-1, type.FindNavigation("Level2Reference").GetStoreGeneratedIndex()); - Assert.Equal(6, type.PropertyCount()); - Assert.Equal(4, type.NavigationCount()); - Assert.Equal(3, type.ShadowPropertyCount()); - Assert.Equal(6, type.OriginalValueCount()); - Assert.Equal(7, type.RelationshipPropertyCount()); - Assert.Equal(3, type.StoreGeneratedCount()); + Assert.Equal(6, type.PropertyCount); + Assert.Equal(4, type.NavigationCount); + Assert.Equal(3, type.ShadowPropertyCount); + Assert.Equal(6, type.OriginalValueCount); + Assert.Equal(7, type.RelationshipPropertyCount); + Assert.Equal(3, type.StoreGeneratedCount); - type = context.Model.FindEntityType(typeof(Level3)); + type = (IRuntimeEntityType)context.Model.FindEntityType(typeof(Level3)); Assert.Equal(0, type.FindProperty("Id").GetIndex()); Assert.Equal(1, type.FindProperty("Level1ReferenceId").GetIndex()); @@ -2374,12 +2374,12 @@ public void Indexes_for_derived_types_are_calculated_correctly() Assert.Equal(-1, type.FindNavigation("Level3Collection").GetStoreGeneratedIndex()); Assert.Equal(-1, type.FindNavigation("Level3Reference").GetStoreGeneratedIndex()); - Assert.Equal(8, type.PropertyCount()); - Assert.Equal(6, type.NavigationCount()); - Assert.Equal(4, type.ShadowPropertyCount()); - Assert.Equal(8, type.OriginalValueCount()); - Assert.Equal(10, type.RelationshipPropertyCount()); - Assert.Equal(4, type.StoreGeneratedCount()); + Assert.Equal(8, type.PropertyCount); + Assert.Equal(6, type.NavigationCount); + Assert.Equal(4, type.ShadowPropertyCount); + Assert.Equal(8, type.OriginalValueCount); + Assert.Equal(10, type.RelationshipPropertyCount); + Assert.Equal(4, type.StoreGeneratedCount); } private class Levels : DbContext @@ -2405,8 +2405,8 @@ public void Indexes_for_owned_collection_types_are_calculated_correctly() using var context = new SideBySide(); var model = context.Model; - var parent = model.FindEntityType(typeof(Parent1Entity)); - var indexes = GetIndexes(parent.GetPropertiesAndNavigations()); + var parent = (IRuntimeEntityType)model.FindEntityType(typeof(Parent1Entity)); + var indexes = GetIndexes(parent.GetSnapshottableMembers()); Assert.Equal(2, indexes.Count); // Order: Index, Shadow, Original, StoreGenerated, Relationship Assert.Equal((0, -1, 0, 0, 0), indexes[nameof(Parent1Entity.Id)]); @@ -2419,8 +2419,8 @@ public void Indexes_for_owned_collection_types_are_calculated_correctly() Assert.Equal((1, 1, 1, 1, 1), indexes["Id"]); Assert.Equal((2, -1, 2, -1, -1), indexes[nameof(ChildEntity.Name)]); - parent = model.FindEntityType(typeof(Parent2Entity)); - indexes = GetIndexes(parent.GetPropertiesAndNavigations()); + parent = (IRuntimeEntityType)model.FindEntityType(typeof(Parent2Entity)); + indexes = GetIndexes(parent.GetSnapshottableMembers()); Assert.Equal(2, indexes.Count); // Order: Index, Shadow, Original, StoreGenerated, Relationship Assert.Equal((0, -1, 0, 0, 0), indexes[nameof(Parent2Entity.Id)]); @@ -2433,8 +2433,8 @@ public void Indexes_for_owned_collection_types_are_calculated_correctly() Assert.Equal((1, 1, 1, 1, 1), indexes["Id"]); Assert.Equal((2, -1, 2, -1, -1), indexes[nameof(ChildEntity.Name)]); - parent = model.FindEntityType(typeof(Parent3Entity)); - indexes = GetIndexes(parent.GetPropertiesAndNavigations()); + parent = (IRuntimeEntityType)model.FindEntityType(typeof(Parent3Entity)); + indexes = GetIndexes(parent.GetSnapshottableMembers()); Assert.Equal(2, indexes.Count); // Order: Index, Shadow, Original, StoreGenerated, Relationship Assert.Equal((0, -1, 0, 0, 0), indexes[nameof(Parent3Entity.Id)]); @@ -2814,7 +2814,7 @@ public void All_properties_have_original_value_indexes_when_using_snapshot_chang var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetOriginalValueIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetOriginalValueIndex()); @@ -2823,7 +2823,7 @@ public void All_properties_have_original_value_indexes_when_using_snapshot_chang Assert.Equal(4, entityType.FindProperty("Token").GetOriginalValueIndex()); Assert.Equal(5, entityType.FindProperty("UniqueIndex").GetOriginalValueIndex()); - Assert.Equal(6, entityType.OriginalValueCount()); + Assert.Equal(6, entityType.OriginalValueCount); } [ConditionalFact] @@ -2832,7 +2832,7 @@ public void All_relationship_properties_have_relationship_indexes_when_using_sna var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetRelationshipIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetRelationshipIndex()); @@ -2843,7 +2843,7 @@ public void All_relationship_properties_have_relationship_indexes_when_using_sna Assert.Equal(2, entityType.FindNavigation("CollectionNav").GetRelationshipIndex()); Assert.Equal(3, entityType.FindNavigation("ReferenceNav").GetRelationshipIndex()); - Assert.Equal(4, entityType.RelationshipPropertyCount()); + Assert.Equal(4, entityType.RelationshipPropertyCount); } [ConditionalFact] @@ -2852,7 +2852,7 @@ public void All_properties_have_original_value_indexes_when_using_changed_only_t var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetOriginalValueIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetOriginalValueIndex()); @@ -2861,7 +2861,7 @@ public void All_properties_have_original_value_indexes_when_using_changed_only_t Assert.Equal(4, entityType.FindProperty("Token").GetOriginalValueIndex()); Assert.Equal(5, entityType.FindProperty("UniqueIndex").GetOriginalValueIndex()); - Assert.Equal(6, entityType.OriginalValueCount()); + Assert.Equal(6, entityType.OriginalValueCount); } [ConditionalFact] @@ -2870,7 +2870,7 @@ public void Collections_dont_have_relationship_indexes_when_using_changed_only_c var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetRelationshipIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetRelationshipIndex()); @@ -2881,7 +2881,7 @@ public void Collections_dont_have_relationship_indexes_when_using_changed_only_c Assert.Equal(-1, entityType.FindNavigation("CollectionNav").GetRelationshipIndex()); Assert.Equal(2, entityType.FindNavigation("ReferenceNav").GetRelationshipIndex()); - Assert.Equal(3, entityType.RelationshipPropertyCount()); + Assert.Equal(3, entityType.RelationshipPropertyCount); } [ConditionalFact] @@ -2890,7 +2890,7 @@ public void Only_concurrency_index_and_key_properties_have_original_value_indexe var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetOriginalValueIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetOriginalValueIndex()); @@ -2899,7 +2899,7 @@ public void Only_concurrency_index_and_key_properties_have_original_value_indexe Assert.Equal(2, entityType.FindProperty("Token").GetOriginalValueIndex()); Assert.Equal(3, entityType.FindProperty("UniqueIndex").GetOriginalValueIndex()); - Assert.Equal(4, entityType.OriginalValueCount()); + Assert.Equal(4, entityType.OriginalValueCount); } [ConditionalFact] @@ -2908,7 +2908,7 @@ public void Collections_dont_have_relationship_indexes_when_using_full_notificat var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetRelationshipIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetRelationshipIndex()); @@ -2919,7 +2919,7 @@ public void Collections_dont_have_relationship_indexes_when_using_full_notificat Assert.Equal(-1, entityType.FindNavigation("CollectionNav").GetRelationshipIndex()); Assert.Equal(2, entityType.FindNavigation("ReferenceNav").GetRelationshipIndex()); - Assert.Equal(3, entityType.RelationshipPropertyCount()); + Assert.Equal(3, entityType.RelationshipPropertyCount); } [ConditionalFact] @@ -2928,7 +2928,7 @@ public void All_properties_have_original_value_indexes_when_full_notifications_w var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetOriginalValueIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetOriginalValueIndex()); @@ -2937,7 +2937,7 @@ public void All_properties_have_original_value_indexes_when_full_notifications_w Assert.Equal(4, entityType.FindProperty("Token").GetOriginalValueIndex()); Assert.Equal(5, entityType.FindProperty("UniqueIndex").GetOriginalValueIndex()); - Assert.Equal(6, entityType.OriginalValueCount()); + Assert.Equal(6, entityType.OriginalValueCount); } [ConditionalFact] @@ -2946,7 +2946,7 @@ public void Collections_dont_have_relationship_indexes_when_full_notifications_w var model = BuildFullNotificationEntityModel(); model.FindEntityType(typeof(FullNotificationEntity)) .SetChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); - var entityType = model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); + var entityType = (IRuntimeEntityType)model.FinalizeModel().FindEntityType(typeof(FullNotificationEntity)); Assert.Equal(0, entityType.FindProperty("Id").GetRelationshipIndex()); Assert.Equal(1, entityType.FindProperty("AnotherEntityId").GetRelationshipIndex()); @@ -2957,7 +2957,7 @@ public void Collections_dont_have_relationship_indexes_when_full_notifications_w Assert.Equal(-1, entityType.FindNavigation("CollectionNav").GetRelationshipIndex()); Assert.Equal(2, entityType.FindNavigation("ReferenceNav").GetRelationshipIndex()); - Assert.Equal(3, entityType.RelationshipPropertyCount()); + Assert.Equal(3, entityType.RelationshipPropertyCount); } [ConditionalFact] diff --git a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs index 4b3e9fd000d..0d751e7c219 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalEntityTypeBuilderTest.cs @@ -493,8 +493,8 @@ public void Can_create_foreign_key_on_mix_of_inherited_properties() Assert.Same(derivedEntityBuilder.Metadata.GetDeclaredForeignKeys().Single(), relationshipBuilder.Metadata); Assert.Collection( relationshipBuilder.Metadata.Properties, - t1 => Assert.Same(entityBuilder.Metadata, t1.DeclaringEntityType), - t2 => Assert.Same(derivedEntityBuilder.Metadata, t2.DeclaringEntityType)); + t1 => Assert.Same(entityBuilder.Metadata, t1.DeclaringType), + t2 => Assert.Same(derivedEntityBuilder.Metadata, t2.DeclaringType)); } [ConditionalFact] @@ -922,7 +922,7 @@ public void Can_create_index_on_inherited_property() Assert.Empty(entityBuilder.Metadata.GetIndexes()); Assert.Same(derivedEntityBuilder.Metadata.GetDeclaredIndexes().Single(), indexBuilder.Metadata); - Assert.Same(entityBuilder.Metadata, indexBuilder.Metadata.Properties.First().DeclaringEntityType); + Assert.Same(entityBuilder.Metadata, indexBuilder.Metadata.Properties.First().DeclaringType); } [ConditionalFact] @@ -943,8 +943,8 @@ public void Can_create_index_on_mix_of_inherited_properties() Assert.Same(derivedEntityBuilder.Metadata.GetDeclaredIndexes().Single(), indexBuilder.Metadata); Assert.Collection( indexBuilder.Metadata.Properties, - t1 => Assert.Same(entityBuilder.Metadata, t1.DeclaringEntityType), - t2 => Assert.Same(derivedEntityBuilder.Metadata, t2.DeclaringEntityType)); + t1 => Assert.Same(entityBuilder.Metadata, t1.DeclaringType), + t2 => Assert.Same(derivedEntityBuilder.Metadata, t2.DeclaringType)); } [ConditionalFact] @@ -1739,7 +1739,8 @@ public void Property_throws_for_navigation() ConfigurationSource.Explicit); Assert.Equal( - CoreStrings.PropertyCalledOnNavigation(nameof(Order.Customer), nameof(Order)), + CoreStrings.ConflictingPropertyOrNavigation( + nameof(Order.Customer), nameof(Order), nameof(Order)), Assert.Throws( () => dependentEntityBuilder .Property(Order.CustomerProperty, ConfigurationSource.Explicit)).Message); @@ -1808,7 +1809,7 @@ public void Can_configure_inherited_property() propertyBuilder = derivedEntityBuilder.Property(typeof(string), nameof(SpecialOrder.Specialty), ConfigurationSource.Explicit); Assert.Same(typeof(string), propertyBuilder.Metadata.ClrType); - Assert.Same(entityBuilder.Metadata, propertyBuilder.Metadata.DeclaringEntityType); + Assert.Same(entityBuilder.Metadata, propertyBuilder.Metadata.DeclaringType); Assert.NotNull(entityBuilder.Metadata.FindPrimaryKey()); } @@ -2576,20 +2577,9 @@ private void VerifyOverrideMembers( } else { - var message = ""; - if (firstMemberType == MemberType.Navigation - && secondMemberType == MemberType.Property) - { - message = CoreStrings.PropertyCalledOnNavigation(nameof(Order.Products), nameof(SpecialOrder)); - } - else - { - message = CoreStrings.ConflictingPropertyOrNavigation( - nameof(Order.Products), nameof(SpecialOrder), firstEntityTypeBuilder.Metadata.DisplayName()); - } - Assert.Equal( - message, + CoreStrings.ConflictingPropertyOrNavigation( + nameof(Order.Products), nameof(SpecialOrder), firstEntityTypeBuilder.Metadata.DisplayName()), Assert.Throws( () => ConfigureMember(secondEntityTypeBuilder, secondMemberType, secondSource)).Message); @@ -2661,6 +2651,8 @@ private bool ConfigureMember( { case MemberType.Property: return entityTypeBuilder.Property(Order.ProductsProperty, configurationSource) != null; + case MemberType.ComplexProperty: + return entityTypeBuilder.ComplexProperty(Order.ProductsProperty, collection: true, configurationSource) != null; case MemberType.ServiceProperty: return entityTypeBuilder.ServiceProperty(Order.ProductsProperty, configurationSource) != null; case MemberType.Navigation: @@ -2689,6 +2681,9 @@ private void AssertDeclaringType( Assert.Same( memberType == MemberType.Property ? expectedDeclaringType : null, GetDeclaringType(entityTypeBuilder, MemberType.Property)); + Assert.Same( + memberType == MemberType.ComplexProperty ? expectedDeclaringType : null, + GetDeclaringType(entityTypeBuilder, MemberType.ComplexProperty)); Assert.Same( memberType == MemberType.ServiceProperty ? expectedDeclaringType : null, GetDeclaringType(entityTypeBuilder, MemberType.ServiceProperty)); @@ -2707,7 +2702,9 @@ private EntityType GetDeclaringType( switch (memberType) { case MemberType.Property: - return entityTypeBuilder.Metadata.FindProperty(nameof(Order.Products))?.DeclaringEntityType; + return (EntityType)entityTypeBuilder.Metadata.FindProperty(nameof(Order.Products))?.DeclaringType; + case MemberType.ComplexProperty: + return (EntityType)entityTypeBuilder.Metadata.FindComplexProperty(nameof(Order.Products))?.DeclaringType; case MemberType.ServiceProperty: return entityTypeBuilder.Metadata.FindServiceProperty(nameof(Order.Products))?.DeclaringEntityType; case MemberType.Navigation: @@ -3429,6 +3426,7 @@ private InternalModelBuilder CreateConventionalModelBuilder() public enum MemberType { Property, + ComplexProperty, ServiceProperty, Navigation, SkipNavigation diff --git a/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs b/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs index 0d6d73151b1..4acfec1d06e 100644 --- a/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/InternalPropertyBuilderTest.cs @@ -243,7 +243,7 @@ public void Can_only_override_lower_or_equal_source_CustomValueGenerator_factory [ConditionalFact] public void Can_only_override_existing_CustomValueGenerator_factory_explicitly() { - ValueGenerator factory(IReadOnlyProperty p, IReadOnlyEntityType e) + ValueGenerator factory(IReadOnlyProperty p, ITypeBase t) => new CustomValueGenerator1(); var metadata = CreateProperty(); @@ -275,7 +275,7 @@ public void Can_clear_CustomValueGenerator_factory() Assert.Null( builder.HasValueGenerator( - (Func)null, ConfigurationSource.Convention)); + (Func)null, ConfigurationSource.Convention)); Assert.IsType(metadata.GetValueGeneratorFactory()(null, null)); Assert.Equal(ValueGenerated.Never, metadata.ValueGenerated); @@ -283,7 +283,7 @@ public void Can_clear_CustomValueGenerator_factory() Assert.NotNull( builder.HasValueGenerator( - (Func)null, ConfigurationSource.Explicit)); + (Func)null, ConfigurationSource.Explicit)); Assert.Null(metadata.GetValueGeneratorFactory()); Assert.Equal(ValueGenerated.Never, metadata.ValueGenerated); @@ -352,7 +352,7 @@ public override bool GeneratesTemporaryValues private class CustomValueGeneratorFactory : ValueGeneratorFactory { - public override ValueGenerator Create(IProperty property, IEntityType entityType) + public override ValueGenerator Create(IProperty property, ITypeBase typeBase) => new CustomValueGenerator1(); } diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs index 5d3a18581ff..22d6ba37948 100644 --- a/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/PropertyTest.cs @@ -152,8 +152,7 @@ public void Adding_a_nullable_property_to_a_key_throws() Assert.Equal( CoreStrings.NullableKey(typeof(object).DisplayName(), stringProperty.Name), Assert.Throws( - () => - stringProperty.DeclaringEntityType.AddKey(stringProperty)).Message); + () => entityType.AddKey(stringProperty)).Message); } [ConditionalFact] @@ -173,7 +172,7 @@ public void Properties_which_are_part_of_primary_key_cannot_be_made_nullable() var entityType = CreateModel().AddEntityType(typeof(object)); var stringProperty = entityType.AddProperty("Name", typeof(string)); stringProperty.IsNullable = false; - stringProperty.DeclaringEntityType.SetPrimaryKey(stringProperty); + entityType.SetPrimaryKey(stringProperty); Assert.Equal( CoreStrings.CannotBeNullablePK("Name", "object"), @@ -282,13 +281,13 @@ public void Throws_when_ValueGeneratorFactory_is_invalid() private class NonDerivedValueGeneratorFactory { - public ValueGenerator Create(IProperty property, IEntityType entityType) + public ValueGenerator Create(IProperty property, ITypeBase typeBase) => null; } private abstract class AbstractValueGeneratorFactory : ValueGeneratorFactory { - public override ValueGenerator Create(IProperty property, IEntityType entityType) + public override ValueGenerator Create(IProperty property, ITypeBase typeBase) => null; } @@ -298,7 +297,7 @@ private StaticValueGeneratorFactory() { } - public override ValueGenerator Create(IProperty property, IEntityType entityType) + public override ValueGenerator Create(IProperty property, ITypeBase typeBase) => null; } @@ -308,7 +307,7 @@ private PrivateValueGeneratorFactory() { } - public override ValueGenerator Create(IProperty property, IEntityType entityType) + public override ValueGenerator Create(IProperty property, ITypeBase typeBase) => null; } @@ -318,7 +317,7 @@ public NonParameterlessValueGeneratorFactory(object _) { } - public override ValueGenerator Create(IProperty property, IEntityType entityType) + public override ValueGenerator Create(IProperty property, ITypeBase typeBase) => null; } diff --git a/test/EFCore.Tests/Metadata/Internal/SkipNavigationTest.cs b/test/EFCore.Tests/Metadata/Internal/SkipNavigationTest.cs index 41348cdb38d..9b85ae4c14e 100644 --- a/test/EFCore.Tests/Metadata/Internal/SkipNavigationTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/SkipNavigationTest.cs @@ -16,14 +16,14 @@ public void Throws_when_model_is_readonly() var joinEntityBuilder = model.AddEntityType(typeof(OrderProduct)); var orderIdProperty = joinEntityBuilder.AddProperty(OrderProduct.OrderIdProperty); - var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, secondEntity, true, false); + var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, null, secondEntity, true, false); model.FinalizeModel(); Assert.Equal( CoreStrings.ModelReadOnly, Assert.Throws( - () => firstEntity.AddSkipNavigation(nameof(Order.Products), null, secondEntity, true, false)).Message); + () => firstEntity.AddSkipNavigation(nameof(Order.Products), null, null, secondEntity, true, false)).Message); Assert.Equal( CoreStrings.ModelReadOnly, @@ -67,7 +67,7 @@ public void Gets_expected_default_values() var firstFk = joinEntityBuilder .AddForeignKey(new[] { orderIdProperty }, firstKey, firstEntity); - var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, secondEntity, true, false); + var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, null, secondEntity, true, false); navigation.SetForeignKey(firstFk); Assert.True(navigation.IsCollection); @@ -103,7 +103,7 @@ public void Can_set_foreign_key() var firstFk = joinEntityBuilder .AddForeignKey(new[] { orderIdProperty }, firstKey, firstEntity); - var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, secondEntity, true, false); + var navigation = firstEntity.AddSkipNavigation(nameof(Order.Products), null, null, secondEntity, true, false); Assert.Null(navigation.ForeignKey); Assert.Null(navigation.GetForeignKeyConfigurationSource()); @@ -131,7 +131,7 @@ public void Setting_foreign_key_to_skip_navigation_with_wrong_dependent_throws() var orderProductFkProperty = orderProductEntity.AddProperty(nameof(OrderProduct.OrderId), typeof(int)); var orderProductForeignKey = orderProductEntity.AddForeignKey(orderProductFkProperty, orderKey, orderEntity); - var navigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, productEntity, true, true); + var navigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, null, productEntity, true, true); Assert.Equal( CoreStrings.SkipNavigationForeignKeyWrongDependentType( @@ -152,7 +152,7 @@ public void Setting_foreign_key_to_skip_navigation_with_wrong_principal_throws() var orderProductForeignKey = orderProductEntity.AddForeignKey(orderProductFkProperty, orderKey, orderEntity); var navigation = orderProductEntity.AddSkipNavigation( - nameof(OrderProduct.Order), null, orderEntity, false, false); + nameof(OrderProduct.Order), null, null, orderEntity, false, false); Assert.Equal( CoreStrings.SkipNavigationForeignKeyWrongPrincipalType( @@ -178,9 +178,9 @@ public void Setting_foreign_key_with_wrong_inverse_throws() var productOrderForeignKey = productEntity .AddForeignKey(new[] { productFkProperty }, productKey, productEntity); - var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, productEntity, true, false); + var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, null, productEntity, true, false); - var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, orderEntity, true, false); + var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, null, orderEntity, true, false); ordersNavigation.SetForeignKey(productOrderForeignKey); productsNavigation.SetInverse(ordersNavigation); @@ -211,10 +211,10 @@ public void Can_set_inverse() var productOrderForeignKey = orderProductEntity .AddForeignKey(new[] { productOrderFkProperty }, productKey, productEntity); - var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, productEntity, true, false); + var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, null, productEntity, true, false); productsNavigation.SetForeignKey(orderProductForeignKey); - var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, orderEntity, true, false); + var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, null, orderEntity, true, false); ordersNavigation.SetForeignKey(productOrderForeignKey); productsNavigation.SetInverse(ordersNavigation); @@ -257,10 +257,10 @@ public void Setting_inverse_targetting_wrong_type_throws() var productOrderForeignKey = orderProductEntity .AddForeignKey(new[] { productOrderFkProperty }, productKey, productEntity); - var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, productEntity, true, false); + var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, null, productEntity, true, false); productsNavigation.SetForeignKey(orderProductForeignKey); - var ordersNavigation = orderProductEntity.AddSkipNavigation(nameof(OrderProduct.Product), null, productEntity, false, true); + var ordersNavigation = orderProductEntity.AddSkipNavigation(nameof(OrderProduct.Product), null, null, productEntity, false, true); ordersNavigation.SetForeignKey(productOrderForeignKey); Assert.Equal( @@ -287,10 +287,10 @@ public void Setting_inverse_with_wrong_join_type_throws() var productOrderForeignKey = productEntity .AddForeignKey(new[] { productFkProperty }, productKey, productEntity); - var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, productEntity, true, false); + var productsNavigation = orderEntity.AddSkipNavigation(nameof(Order.Products), null, null, productEntity, true, false); productsNavigation.SetForeignKey(orderProductForeignKey); - var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, orderEntity, true, false); + var ordersNavigation = productEntity.AddSkipNavigation(nameof(Product.Orders), null, null, orderEntity, true, false); ordersNavigation.SetForeignKey(productOrderForeignKey); Assert.Equal( diff --git a/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs new file mode 100644 index 00000000000..518091764e9 --- /dev/null +++ b/test/EFCore.Tests/ModelBuilding/ComplexTypeTestBase.cs @@ -0,0 +1,1452 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Dynamic; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory; + +// ReSharper disable InconsistentNaming +namespace Microsoft.EntityFrameworkCore.ModelBuilding; + +public abstract partial class ModelBuilderTest +{ + public abstract class ComplexTypeTestBase : ModelBuilderTestBase + { + [ConditionalFact] + public virtual void Can_set_complex_property_annotation() + { + var modelBuilder = CreateModelBuilder(); + + var complexPropertyBuilder = modelBuilder + .Entity() + .ComplexProperty(e => e.Customer) + .HasTypeAnnotation("foo", "bar") + .HasPropertyAnnotation("foo2", "bar2"); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); + + Assert.Equal("bar", complexProperty.ComplexType["foo"]); + Assert.Equal("bar2", complexProperty["foo2"]); + Assert.Equal(typeof(Customer).Name, complexProperty.Name); + } + + [ConditionalFact] + public virtual void Can_set_property_annotation() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder + .Entity() + .ComplexProperty(e => e.Customer) + .Property(c => c.Name).HasAnnotation("foo", "bar"); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); + var property = complexProperty.ComplexType.FindProperty(nameof(Customer.Name)); + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Can_set_property_annotation_when_no_clr_property() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder + .Entity() + .ComplexProperty(e => e.Customer) + .Property(Customer.NameProperty.Name).HasAnnotation("foo", "bar"); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); + var property = complexProperty.ComplexType.FindProperty(nameof(Customer.Name)); + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Can_set_property_annotation_by_type() + { + var modelBuilder = CreateModelBuilder(c => c.Properties().HaveAnnotation("foo", "bar")); + + modelBuilder.Ignore(); + var propertyBuilder = modelBuilder + .Entity() + .ComplexProperty(e => e.Customer) + .Property(c => c.Name).HasAnnotation("foo", "bar"); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); + var property = complexProperty.ComplexType.FindProperty(nameof(Customer.Name)); + + Assert.Equal("bar", property["foo"]); + } + + [ConditionalFact] + public virtual void Properties_are_required_by_default_only_if_CLR_type_is_nullable() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down); + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty("Up").IsNullable); + Assert.True(complexType.FindProperty("Down").IsNullable); + Assert.False(complexType.FindProperty("Charm").IsNullable); + Assert.True(complexType.FindProperty("Strange").IsNullable); + Assert.False(complexType.FindProperty("Top").IsNullable); + Assert.True(complexType.FindProperty("Bottom").IsNullable); + } + + [ConditionalFact] + public virtual void Properties_can_be_ignored() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Ignore(e => e.Up); + b.Ignore(e => e.Down); + b.Ignore("Charm"); + b.Ignore("Strange"); + b.Ignore("Top"); + b.Ignore("Bottom"); + b.Ignore("Shadow"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Contains(nameof(Quarks.Id), complexType.GetProperties().Select(p => p.Name)); + Assert.DoesNotContain(nameof(Quarks.Up), complexType.GetProperties().Select(p => p.Name)); + Assert.DoesNotContain(nameof(Quarks.Down), complexType.GetProperties().Select(p => p.Name)); + } + + [ConditionalFact] + public virtual void Properties_can_be_ignored_by_type() + { + var modelBuilder = CreateModelBuilder(c => c.IgnoreAny()); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Customer); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Null(complexType.FindProperty(nameof(Customer.AlternateKey))); + } + + [ConditionalFact] + public virtual void Can_ignore_shadow_properties_when_they_have_been_added_explicitly() + { + var modelBuilder = CreateModelBuilder(); + + var complexPropertyBuilder = modelBuilder + .Entity() + .ComplexProperty(e => e.Customer); + complexPropertyBuilder.Property("Shadow"); + complexPropertyBuilder.Ignore("Shadow"); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Null(complexType.FindProperty("Shadow")); + } + + [ConditionalFact] + public virtual void Can_add_shadow_properties_when_they_have_been_ignored() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder + .Entity() + .ComplexProperty(e => e.Customer, + b => + { + b.Ignore("Shadow"); + b.Property("Shadow"); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.NotNull(complexType.FindProperty("Shadow")); + } + + [ConditionalFact] + public virtual void Properties_can_be_made_required() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).IsRequired(); + b.Property(e => e.Down).IsRequired(); + b.Property("Charm").IsRequired(); + b.Property("Strange").IsRequired(); + b.Property("Top").IsRequired(); + b.Property("Bottom").IsRequired(); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty("Up").IsNullable); + Assert.False(complexType.FindProperty("Down").IsNullable); + Assert.False(complexType.FindProperty("Charm").IsNullable); + Assert.False(complexType.FindProperty("Strange").IsNullable); + Assert.False(complexType.FindProperty("Top").IsNullable); + Assert.False(complexType.FindProperty("Bottom").IsNullable); + } + + [ConditionalFact] + public virtual void Properties_can_be_made_optional() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Down).IsRequired(false); + b.Property("Strange").IsRequired(false); + b.Property("Bottom").IsRequired(false); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.True(complexType.FindProperty("Down").IsNullable); + Assert.True(complexType.FindProperty("Strange").IsNullable); + Assert.True(complexType.FindProperty("Bottom").IsNullable); + } + + [ConditionalFact] + public virtual void Non_nullable_properties_cannot_be_made_optional() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + Assert.Equal( + CoreStrings.CannotBeNullable("Up", "ComplexProperties.Quarks#Quarks", "int"), + Assert.Throws(() => b.Property(e => e.Up).IsRequired(false)).Message); + + Assert.Equal( + CoreStrings.CannotBeNullable("Charm", "ComplexProperties.Quarks#Quarks", "int"), + Assert.Throws(() => b.Property("Charm").IsRequired(false)).Message); + + Assert.Equal( + CoreStrings.CannotBeNullable("Top", "ComplexProperties.Quarks#Quarks", "int"), + Assert.Throws(() => b.Property("Top").IsRequired(false)).Message); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty("Up").IsNullable); + Assert.False(complexType.FindProperty("Charm").IsNullable); + Assert.False(complexType.FindProperty("Top").IsNullable); + } + + [ConditionalFact] + public virtual void Properties_specified_by_string_are_shadow_properties_unless_already_known_to_be_CLR_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Up"); + b.Property("Gluon"); + b.Property("Down"); + b.Property("Photon"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty("Up").IsShadowProperty()); + Assert.False(complexType.FindProperty("Down").IsShadowProperty()); + Assert.True(complexType.FindProperty("Gluon").IsShadowProperty()); + Assert.True(complexType.FindProperty("Photon").IsShadowProperty()); + + Assert.Equal(-1, complexType.FindProperty("Up").GetShadowIndex()); + Assert.Equal(-1, complexType.FindProperty("Down").GetShadowIndex()); + Assert.NotEqual(-1, complexType.FindProperty("Gluon").GetShadowIndex()); + Assert.NotEqual(-1, complexType.FindProperty("Photon").GetShadowIndex()); + Assert.NotEqual(complexType.FindProperty("Gluon").GetShadowIndex(), complexType.FindProperty("Photon").GetShadowIndex()); + } + + [ConditionalFact] + public virtual void Properties_can_be_made_concurrency_tokens() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).IsConcurrencyToken(); + b.Property(e => e.Down).IsConcurrencyToken(false); + b.Property("Charm").IsConcurrencyToken(); + b.Property("Strange").IsConcurrencyToken(false); + b.Property("Top").IsConcurrencyToken(); + b.Property("Bottom").IsConcurrencyToken(false); + b.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.False(complexType.FindProperty(Customer.IdProperty.Name).IsConcurrencyToken); + Assert.True(complexType.FindProperty("Up").IsConcurrencyToken); + Assert.False(complexType.FindProperty("Down").IsConcurrencyToken); + Assert.True(complexType.FindProperty("Charm").IsConcurrencyToken); + Assert.False(complexType.FindProperty("Strange").IsConcurrencyToken); + Assert.True(complexType.FindProperty("Top").IsConcurrencyToken); + Assert.False(complexType.FindProperty("Bottom").IsConcurrencyToken); + + Assert.Equal(-1, complexType.FindProperty(Customer.IdProperty.Name).GetOriginalValueIndex()); + Assert.Equal(2, complexType.FindProperty("Up").GetOriginalValueIndex()); + Assert.Equal(-1, complexType.FindProperty("Down").GetOriginalValueIndex()); + Assert.Equal(0, complexType.FindProperty("Charm").GetOriginalValueIndex()); + Assert.Equal(-1, complexType.FindProperty("Strange").GetOriginalValueIndex()); + Assert.Equal(1, complexType.FindProperty("Top").GetOriginalValueIndex()); + Assert.Equal(-1, complexType.FindProperty("Bottom").GetOriginalValueIndex()); + + Assert.Equal(ChangeTrackingStrategy.ChangingAndChangedNotifications, complexType.GetChangeTrackingStrategy()); + } + + [ConditionalFact] + public virtual void Properties_can_have_access_mode_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down).HasField("_forDown").UsePropertyAccessMode(PropertyAccessMode.Field); + b.Property("Charm").UsePropertyAccessMode(PropertyAccessMode.Property); + b.Property("Strange").UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(PropertyAccessMode.PreferField, complexType.FindProperty("Up").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Field, complexType.FindProperty("Down").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, complexType.FindProperty("Charm").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, complexType.FindProperty("Strange").GetPropertyAccessMode()); + } + + [ConditionalFact] + public virtual void Access_mode_can_be_overridden_at_entity_and_property_levels() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Customer); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction); + b.UseDefaultPropertyAccessMode(PropertyAccessMode.FieldDuringConstruction); + b.Property(e => e.Up).UsePropertyAccessMode(PropertyAccessMode.Property); + b.Property(e => e.Down).HasField("_forDown"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(ComplexProperties)); + Assert.Equal(PropertyAccessMode.Field, model.GetPropertyAccessMode()); + + var customerType = entityType.FindComplexProperty(nameof(ComplexProperties.Customer)).ComplexType; + Assert.Equal(PropertyAccessMode.Field, customerType.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Field, customerType.FindProperty("Id").GetPropertyAccessMode()); + + var quarksProperty = entityType.FindComplexProperty(nameof(ComplexProperties.Quarks)); + var quarksType = quarksProperty.ComplexType; + Assert.Equal(PropertyAccessMode.PreferFieldDuringConstruction, quarksProperty.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.FieldDuringConstruction, quarksType.FindProperty("Down").GetPropertyAccessMode()); + Assert.Equal(PropertyAccessMode.Property, quarksType.FindProperty("Up").GetPropertyAccessMode()); + } + + [ConditionalFact] + public virtual void Properties_can_have_provider_type_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down).HasConversion(); + b.Property("Charm").HasConversion>(); + b.Property("Strange").HasConversion( + new CustomValueComparer(), new CustomValueComparer()); + b.Property("Strange").HasConversion(null); + b.Property("Top").HasConversion(new CustomValueComparer()); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var up = complexType.FindProperty("Up"); + Assert.Null(up.GetProviderClrType()); + Assert.IsType>(up.GetValueComparer()); + + var down = complexType.FindProperty("Down"); + Assert.Same(typeof(byte[]), down.GetProviderClrType()); + Assert.IsType>(down.GetValueComparer()); + Assert.IsType>(down.GetProviderValueComparer()); + + var charm = complexType.FindProperty("Charm"); + Assert.Same(typeof(long), charm.GetProviderClrType()); + Assert.IsType>(charm.GetValueComparer()); + Assert.IsType>(charm.GetProviderValueComparer()); + + var strange = complexType.FindProperty("Strange"); + Assert.Null(strange.GetProviderClrType()); + Assert.IsType>(strange.GetValueComparer()); + Assert.IsType>(strange.GetProviderValueComparer()); + + var top = complexType.FindProperty("Top"); + Assert.Same(typeof(string), top.GetProviderClrType()); + Assert.IsType>(top.GetValueComparer()); + Assert.IsType>(top.GetProviderValueComparer()); + } + + [ConditionalFact] + public virtual void Properties_can_have_provider_type_set_for_type() + { + var modelBuilder = CreateModelBuilder(c => c.Properties().HaveConversion()); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down); + b.Property("Charm"); + b.Property("Strange"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty("Up").GetProviderClrType()); + Assert.Same(typeof(byte[]), complexType.FindProperty("Down").GetProviderClrType()); + Assert.Null(complexType.FindProperty("Charm").GetProviderClrType()); + Assert.Same(typeof(byte[]), complexType.FindProperty("Strange").GetProviderClrType()); + } + + [ConditionalFact] + public virtual void Properties_can_have_non_generic_value_converter_set() + { + var modelBuilder = CreateModelBuilder(); + + ValueConverter stringConverter = new StringToBytesConverter(Encoding.UTF8); + ValueConverter intConverter = new CastingConverter(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down).HasConversion(stringConverter); + b.Property("Charm").HasConversion(intConverter, null, new CustomValueComparer()); + b.Property("Strange").HasConversion(stringConverter); + b.Property("Strange").HasConversion(null); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty("Up").GetValueConverter()); + + var down = complexType.FindProperty("Down"); + Assert.Same(stringConverter, down.GetValueConverter()); + Assert.IsType>(down.GetValueComparer()); + Assert.IsType>(down.GetProviderValueComparer()); + + var charm = complexType.FindProperty("Charm"); + Assert.Same(intConverter, charm.GetValueConverter()); + Assert.IsType>(charm.GetValueComparer()); + Assert.IsType>(charm.GetProviderValueComparer()); + + Assert.Null(complexType.FindProperty("Strange").GetValueConverter()); + } + + [ConditionalFact] + public virtual void Properties_can_have_custom_type_value_converter_type_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasConversion>(); + b.Property(e => e.Down) + .HasConversion, CustomValueComparer>(); + b.Property("Charm").HasConversion, CustomValueComparer>(); + b.Property("Strange").HasConversion>(); + b.Property("Strange").HasConversion(null, null); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var up = complexType.FindProperty("Up"); + Assert.Equal(typeof(int), up.GetProviderClrType()); + Assert.Null(up.GetValueConverter()); + Assert.IsType>(up.GetValueComparer()); + Assert.IsType>(up.GetProviderValueComparer()); + + var down = complexType.FindProperty("Down"); + Assert.IsType(down.GetValueConverter()); + Assert.IsType>(down.GetValueComparer()); + Assert.IsType>(down.GetProviderValueComparer()); + + var charm = complexType.FindProperty("Charm"); + Assert.IsType>(charm.GetValueConverter()); + Assert.IsType>(charm.GetValueComparer()); + Assert.IsType>(charm.GetProviderValueComparer()); + + var strange = complexType.FindProperty("Strange"); + Assert.Null(strange.GetValueConverter()); + Assert.IsType>(strange.GetValueComparer()); + Assert.IsType>(strange.GetProviderValueComparer()); + } + + private class UTF8StringToBytesConverter : StringToBytesConverter + { + public UTF8StringToBytesConverter() + : base(Encoding.UTF8) + { + } + } + + private class CustomValueComparer : ValueComparer + { + public CustomValueComparer() + : base(false) + { + } + } + + [ConditionalFact] + public virtual void Properties_can_have_value_converter_set_inline() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down).HasConversion(v => int.Parse(v), v => v.ToString()); + b.Property("Charm").HasConversion(v => (long)v, v => (int)v, new CustomValueComparer()); + b.Property("Strange").HasConversion( + v => (double)v, v => (float)v, new CustomValueComparer(), new CustomValueComparer()); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var up = complexType.FindProperty("Up"); + Assert.Null(up.GetProviderClrType()); + Assert.Null(up.GetValueConverter()); + Assert.IsType>(up.GetValueComparer()); + Assert.IsType>(up.GetProviderValueComparer()); + + var down = complexType.FindProperty("Down"); + Assert.IsType>(down.GetValueConverter()); + Assert.IsType>(down.GetValueComparer()); + Assert.IsType>(down.GetProviderValueComparer()); + + var charm = complexType.FindProperty("Charm"); + Assert.IsType>(charm.GetValueConverter()); + Assert.IsType>(charm.GetValueComparer()); + Assert.IsType>(charm.GetProviderValueComparer()); + + var strange = complexType.FindProperty("Strange"); + Assert.IsType>(strange.GetValueConverter()); + Assert.IsType>(strange.GetValueComparer()); + Assert.IsType>(strange.GetProviderValueComparer()); + } + + [ConditionalFact] + public virtual void Properties_can_have_value_converter_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up); + b.Property(e => e.Down).HasConversion( + new ValueConverter(v => int.Parse(v), v => v.ToString())); + b.Property("Charm").HasConversion( + new ValueConverter(v => v, v => (int)v), new CustomValueComparer()); + b.Property("Strange").HasConversion( + new ValueConverter(v => v, v => (float)v), new CustomValueComparer(), + new CustomValueComparer()); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var up = complexType.FindProperty("Up"); + Assert.Null(up.GetProviderClrType()); + Assert.Null(up.GetValueConverter()); + Assert.IsType>(up.GetValueComparer()); + Assert.IsType>(up.GetProviderValueComparer()); + + var down = complexType.FindProperty("Down"); + Assert.IsType>(down.GetValueConverter()); + Assert.IsType>(down.GetValueComparer()); + Assert.IsType>(down.GetProviderValueComparer()); + + var charm = complexType.FindProperty("Charm"); + Assert.IsType>(charm.GetValueConverter()); + Assert.IsType>(charm.GetValueComparer()); + Assert.IsType>(charm.GetProviderValueComparer()); + + var strange = complexType.FindProperty("Strange"); + Assert.IsType>(strange.GetValueConverter()); + Assert.IsType>(strange.GetValueComparer()); + Assert.IsType>(strange.GetProviderValueComparer()); + } + + [ConditionalFact] + public virtual void IEnumerable_properties_with_value_converter_set_are_not_discovered_as_complex_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.DynamicProperty, + b => + { + b.Property(e => e.ExpandoObject).HasConversion( + v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)); + + var comparer = new ValueComparer( + (v1, v2) => v1.SequenceEqual(v2), + v => v.GetHashCode()); + + b.Property(e => e.ExpandoObject).Metadata.SetValueComparer(comparer); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.NotNull(complexType.FindProperty(nameof(DynamicProperty.ExpandoObject)).GetValueConverter()); + Assert.NotNull(complexType.FindProperty(nameof(DynamicProperty.ExpandoObject)).GetValueComparer()); + } + + private static ExpandoObject DeserializeExpandoObject(string value) + { + dynamic obj = new ExpandoObject(); + obj.Value = value; + + return obj; + } + + private class ExpandoObjectConverter : ValueConverter + { + public ExpandoObjectConverter() + : base(v => (string)((IDictionary)v)["Value"], v => DeserializeExpandoObject(v)) + { + } + } + + private class ExpandoObjectComparer : ValueComparer + { + public ExpandoObjectComparer() + : base((v1, v2) => v1.SequenceEqual(v2), v => v.GetHashCode()) + { + } + } + + [ConditionalFact] + public virtual void Properties_can_have_value_converter_configured_by_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties(typeof(IWrapped<>)).AreUnicode(false); + c.Properties().HaveMaxLength(20); + c.Properties().HaveConversion(typeof(WrappedStringToStringConverter)); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.WrappedStringEntity); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + var wrappedProperty = complexType.FindProperty(nameof(WrappedStringEntity.WrappedString)); + Assert.False(wrappedProperty.IsUnicode()); + Assert.Equal(20, wrappedProperty.GetMaxLength()); + Assert.IsType(wrappedProperty.GetValueConverter()); + Assert.IsType>(wrappedProperty.GetValueComparer()); + } + + [ConditionalFact] + public virtual void Value_converter_configured_on_non_nullable_type_is_applied() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveConversion, CustomValueComparer>(); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Wierd"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var id = complexType.FindProperty("Id"); + Assert.IsType>(id.GetValueConverter()); + Assert.IsType>(id.GetValueComparer()); + + var wierd = complexType.FindProperty("Wierd"); + Assert.IsType>(wierd.GetValueConverter()); + Assert.IsType>(wierd.GetValueComparer()); + } + + [ConditionalFact] + public virtual void Value_converter_configured_on_nullable_type_overrides_non_nullable() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveConversion, CustomValueComparer>(); + c.Properties() + .HaveConversion, CustomValueComparer, CustomValueComparer>(); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Wierd"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + var id = complexType.FindProperty("Id"); + Assert.IsType>(id.GetValueConverter()); + Assert.IsType>(id.GetValueComparer()); + Assert.IsType>(id.GetProviderValueComparer()); + + var wierd = complexType.FindProperty("Wierd"); + Assert.IsType>(wierd.GetValueConverter()); + Assert.IsType>(wierd.GetValueComparer()); + Assert.IsType>(wierd.GetProviderValueComparer()); + } + + private class WrappedStringToStringConverter : ValueConverter + { + public WrappedStringToStringConverter() + : base(v => v.Value, v => new WrappedString { Value = v }) + { + } + } + + [ConditionalFact] + public virtual void Value_converter_type_is_checked() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + Assert.Equal( + CoreStrings.ConverterPropertyMismatch("string", "ComplexProperties.Quarks#Quarks", "Up", "int"), + Assert.Throws( + () => b.Property(e => e.Up).HasConversion( + new StringToBytesConverter(Encoding.UTF8))).Message); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Null(complexType.FindProperty("Up").GetValueConverter()); + } + + [ConditionalFact] + public virtual void Properties_can_have_field_set() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Up").HasField("_forUp"); + b.Property(e => e.Down).HasField("_forDown"); + b.Property("_forWierd").HasField("_forWierd"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal("_forUp", complexType.FindProperty("Up").GetFieldName()); + Assert.Equal("_forDown", complexType.FindProperty("Down").GetFieldName()); + Assert.Equal("_forWierd", complexType.FindProperty("_forWierd").GetFieldName()); + } + + [ConditionalFact] + public virtual void HasField_throws_if_field_is_not_found() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + Assert.Equal( + CoreStrings.MissingBackingField("_notFound", nameof(Quarks.Down), "ComplexProperties.Quarks#Quarks"), + Assert.Throws(() => b.Property(e => e.Down).HasField("_notFound")).Message); + }); + } + + [ConditionalFact] + public virtual void HasField_throws_if_field_is_wrong_type() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + Assert.Equal( + CoreStrings.BadBackingFieldType("_forUp", "int", nameof(Quarks), nameof(Quarks.Down), "string"), + Assert.Throws(() => b.Property(e => e.Down).HasField("_forUp")).Message); + }); + } + + [ConditionalFact] + public virtual void Properties_can_be_set_to_generate_values_on_Add() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).ValueGeneratedOnAddOrUpdate(); + b.Property(e => e.Down).ValueGeneratedNever(); + b.Property("Charm").Metadata.ValueGenerated = ValueGenerated.OnUpdateSometimes; + b.Property("Strange").ValueGeneratedNever(); + b.Property("Top").ValueGeneratedOnAddOrUpdate(); + b.Property("Bottom").ValueGeneratedOnUpdate(); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + Assert.Equal(ValueGenerated.Never, complexType.FindProperty(Customer.IdProperty.Name).ValueGenerated); + Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Up").ValueGenerated); + Assert.Equal(ValueGenerated.Never, complexType.FindProperty("Down").ValueGenerated); + Assert.Equal(ValueGenerated.OnUpdateSometimes, complexType.FindProperty("Charm").ValueGenerated); + Assert.Equal(ValueGenerated.Never, complexType.FindProperty("Strange").ValueGenerated); + Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Top").ValueGenerated); + Assert.Equal(ValueGenerated.OnUpdate, complexType.FindProperty("Bottom").ValueGenerated); + } + + [ConditionalFact] + public virtual void Properties_can_set_row_version() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).IsRowVersion(); + b.Property(e => e.Down).ValueGeneratedNever(); + b.Property("Charm").IsRowVersion(); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Up").ValueGenerated); + Assert.Equal(ValueGenerated.Never, complexType.FindProperty("Down").ValueGenerated); + Assert.Equal(ValueGenerated.OnAddOrUpdate, complexType.FindProperty("Charm").ValueGenerated); + + Assert.True(complexType.FindProperty("Up").IsConcurrencyToken); + Assert.False(complexType.FindProperty("Down").IsConcurrencyToken); + Assert.True(complexType.FindProperty("Charm").IsConcurrencyToken); + } + + [ConditionalFact] + public virtual void Can_set_max_length_for_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasMaxLength(0); + b.Property(e => e.Down).HasMaxLength(100); + b.Property("Charm").HasMaxLength(0); + b.Property("Strange").HasMaxLength(-1); + b.Property("Top").HasMaxLength(0); + b.Property("Bottom").HasMaxLength(100); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Up").GetMaxLength()); + Assert.Equal(100, complexType.FindProperty("Down").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Charm").GetMaxLength()); + Assert.Equal(-1, complexType.FindProperty("Strange").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Top").GetMaxLength()); + Assert.Equal(100, complexType.FindProperty("Bottom").GetMaxLength()); + } + + [ConditionalFact] + public virtual void Can_set_max_length_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveMaxLength(0); + c.Properties().HaveMaxLength(100); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(0, complexType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Up").GetMaxLength()); + Assert.Equal(100, complexType.FindProperty("Down").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Charm").GetMaxLength()); + Assert.Equal(100, complexType.FindProperty("Strange").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Top").GetMaxLength()); + Assert.Equal(100, complexType.FindProperty("Bottom").GetMaxLength()); + } + + [ConditionalFact] + public virtual void Can_set_sentinel_for_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasSentinel(1); + b.Property(e => e.Down).HasSentinel("100"); + b.Property("Charm").HasSentinel(-1); + b.Property("Strange").HasSentinel("-1"); + b.Property("Top").HasSentinel(77); + b.Property("Bottom").HasSentinel("100"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(0, complexType.FindProperty(Customer.IdProperty.Name)!.Sentinel); + Assert.Equal(1, complexType.FindProperty("Up")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Down")!.Sentinel); + Assert.Equal(-1, complexType.FindProperty("Charm")!.Sentinel); + Assert.Equal("-1", complexType.FindProperty("Strange")!.Sentinel); + Assert.Equal(77, complexType.FindProperty("Top")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Bottom")!.Sentinel); + } + + [ConditionalFact] + public virtual void Can_set_sentinel_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveSentinel(-1); + c.Properties().HaveSentinel("100"); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(-1, complexType.FindProperty(Customer.IdProperty.Name)!.Sentinel); + Assert.Equal(-1, complexType.FindProperty("Up")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Down")!.Sentinel); + Assert.Equal(-1, complexType.FindProperty("Charm")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Strange")!.Sentinel); + Assert.Equal(-1, complexType.FindProperty("Top")!.Sentinel); + Assert.Equal("100", complexType.FindProperty("Bottom")!.Sentinel); + } + + [ConditionalFact] + public virtual void Can_set_unbounded_max_length_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveMaxLength(0); + c.Properties().HaveMaxLength(-1); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(0, complexType.FindProperty(Customer.IdProperty.Name).GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Up").GetMaxLength()); + Assert.Equal(-1, complexType.FindProperty("Down").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Charm").GetMaxLength()); + Assert.Equal(-1, complexType.FindProperty("Strange").GetMaxLength()); + Assert.Equal(0, complexType.FindProperty("Top").GetMaxLength()); + Assert.Equal(-1, complexType.FindProperty("Bottom").GetMaxLength()); + } + + [ConditionalFact] + public virtual void Can_set_precision_and_scale_for_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasPrecision(1, 0); + b.Property(e => e.Down).HasPrecision(100, 10); + b.Property("Charm").HasPrecision(1, 0); + b.Property("Strange").HasPrecision(100, 10); + b.Property("Top").HasPrecision(1, 0); + b.Property("Bottom").HasPrecision(100, 10); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetPrecision()); + Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetScale()); + Assert.Equal(1, complexType.FindProperty("Up").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Up").GetScale()); + Assert.Equal(100, complexType.FindProperty("Down").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Down").GetScale()); + Assert.Equal(1, complexType.FindProperty("Charm").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Charm").GetScale()); + Assert.Equal(100, complexType.FindProperty("Strange").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Strange").GetScale()); + Assert.Equal(1, complexType.FindProperty("Top").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Top").GetScale()); + Assert.Equal(100, complexType.FindProperty("Bottom").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Bottom").GetScale()); + } + + [ConditionalFact] + public virtual void Can_set_precision_and_scale_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HavePrecision(1, 0); + c.Properties().HavePrecision(100, 10); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal(1, complexType.FindProperty(Customer.IdProperty.Name).GetPrecision()); + Assert.Equal(0, complexType.FindProperty(Customer.IdProperty.Name).GetScale()); + Assert.Equal(1, complexType.FindProperty("Up").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Up").GetScale()); + Assert.Equal(100, complexType.FindProperty("Down").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Down").GetScale()); + Assert.Equal(1, complexType.FindProperty("Charm").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Charm").GetScale()); + Assert.Equal(100, complexType.FindProperty("Strange").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Strange").GetScale()); + Assert.Equal(1, complexType.FindProperty("Top").GetPrecision()); + Assert.Equal(0, complexType.FindProperty("Top").GetScale()); + Assert.Equal(100, complexType.FindProperty("Bottom").GetPrecision()); + Assert.Equal(10, complexType.FindProperty("Bottom").GetScale()); + } + + [ConditionalFact] + public virtual void Can_set_custom_value_generator_for_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasValueGenerator(); + b.Property(e => e.Down).HasValueGenerator(typeof(CustomValueGenerator)); + b.Property("Charm").HasValueGenerator((_, __) => new CustomValueGenerator()); + b.Property("Strange").HasValueGenerator(); + b.Property("Top").HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)); + b.Property("Bottom").HasValueGeneratorFactory(); + }); + + var model = modelBuilder.FinalizeModel(); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).GetValueGeneratorFactory()); + Assert.IsType(complexType.FindProperty("Up").GetValueGeneratorFactory()(null, null)); + Assert.IsType(complexType.FindProperty("Down").GetValueGeneratorFactory()(null, null)); + Assert.IsType(complexType.FindProperty("Charm").GetValueGeneratorFactory()(null, null)); + Assert.IsType(complexType.FindProperty("Strange").GetValueGeneratorFactory()(null, null)); + Assert.IsType(complexType.FindProperty("Top").GetValueGeneratorFactory()(null, null)); + Assert.IsType(complexType.FindProperty("Bottom").GetValueGeneratorFactory()(null, null)); + } + + private class CustomValueGenerator : ValueGenerator + { + public override int Next(EntityEntry entry) + => throw new NotImplementedException(); + + public override bool GeneratesTemporaryValues + => false; + } + + private class CustomValueGeneratorFactory : ValueGeneratorFactory + { + public override ValueGenerator Create(IProperty property, ITypeBase entityType) + => new CustomValueGenerator(); + } + + [ConditionalFact] + public virtual void Throws_for_bad_value_generator_type() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + Assert.Equal( + CoreStrings.BadValueGeneratorType(nameof(Random), nameof(ValueGenerator)), + Assert.Throws(() => b.Property(e => e.Down).HasValueGenerator(typeof(Random))).Message); + }); + } + + [ConditionalFact] + public virtual void Throws_for_value_generator_that_cannot_be_constructed() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).HasValueGenerator(); + b.Property(e => e.Down).HasValueGenerator(); + }); + + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Equal( + CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator1), "HasValueGenerator"), + Assert.Throws( + () => complexType.FindProperty("Up").GetValueGeneratorFactory()(null, null)).Message); + + Assert.Equal( + CoreStrings.CannotCreateValueGenerator(nameof(BadCustomValueGenerator2), "HasValueGenerator"), + Assert.Throws( + () => complexType.FindProperty("Down").GetValueGeneratorFactory()(null, null)).Message); + } + + private class BadCustomValueGenerator1 : CustomValueGenerator + { + public BadCustomValueGenerator1(string foo) + { + } + } + + private abstract class BadCustomValueGenerator2 : CustomValueGenerator + { + } + + protected class StringCollectionEntity + { + public ICollection Property { get; set; } + } + + [ConditionalFact] + public virtual void Can_set_unicode_for_properties() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property(e => e.Up).IsUnicode(); + b.Property(e => e.Down).IsUnicode(false); + b.Property("Charm").IsUnicode(); + b.Property("Strange").IsUnicode(false); + b.Property("Top").IsUnicode(); + b.Property("Bottom").IsUnicode(false); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.Null(complexType.FindProperty(Customer.IdProperty.Name).IsUnicode()); + Assert.True(complexType.FindProperty("Up").IsUnicode()); + Assert.False(complexType.FindProperty("Down").IsUnicode()); + Assert.True(complexType.FindProperty("Charm").IsUnicode()); + Assert.False(complexType.FindProperty("Strange").IsUnicode()); + Assert.True(complexType.FindProperty("Top").IsUnicode()); + Assert.False(complexType.FindProperty("Bottom").IsUnicode()); + } + + [ConditionalFact] + public virtual void Can_set_unicode_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().AreUnicode(); + c.Properties().AreUnicode(false); + }); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.Quarks, + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + + Assert.True(complexType.FindProperty(Customer.IdProperty.Name).IsUnicode()); + Assert.True(complexType.FindProperty("Up").IsUnicode()); + Assert.False(complexType.FindProperty("Down").IsUnicode()); + Assert.True(complexType.FindProperty("Charm").IsUnicode()); + Assert.False(complexType.FindProperty("Strange").IsUnicode()); + Assert.True(complexType.FindProperty("Top").IsUnicode()); + Assert.False(complexType.FindProperty("Bottom").IsUnicode()); + } + + [ConditionalFact] + public virtual void PropertyBuilder_methods_can_be_chained() + => CreateModelBuilder() + .Entity() + .ComplexProperty(e => e.Quarks) + .Property(e => e.Up) + .IsRequired() + .HasAnnotation("A", "V") + .IsConcurrencyToken() + .ValueGeneratedNever() + .ValueGeneratedOnAdd() + .ValueGeneratedOnAddOrUpdate() + .ValueGeneratedOnUpdate() + .IsUnicode() + .HasMaxLength(100) + .HasSentinel(null) + .HasPrecision(10, 1) + .HasValueGenerator() + .HasValueGenerator(typeof(CustomValueGenerator)) + .HasValueGeneratorFactory() + .HasValueGeneratorFactory(typeof(CustomValueGeneratorFactory)) + .HasValueGenerator((_, __) => null) + .IsRequired(); + + [ConditionalFact] + public virtual void Can_call_Property_on_a_field() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.EntityWithFields).Property(e => e.Id); + + var model = modelBuilder.FinalizeModel(); + var complexType = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single().ComplexType; + var properties = complexType.GetProperties(); + var property = Assert.Single(properties); + Assert.Equal(nameof(EntityWithFields.Id), property.Name); + Assert.Null(property.PropertyInfo); + Assert.NotNull(property.FieldInfo); + } + + [ConditionalFact] + public virtual void Can_ignore_a_field() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity() + .ComplexProperty(e => e.EntityWithFields, b => { + b.Property(e => e.Id); + b.Ignore(e => e.CompanyId); + }); + + var model = modelBuilder.FinalizeModel(); + var complexProperty = model.FindEntityType(typeof(ComplexProperties)).GetComplexProperties().Single(); + var property = Assert.Single(complexProperty.ComplexType.GetProperties()); + Assert.Equal(nameof(EntityWithFields.Id), property.Name); + } + } +} diff --git a/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs b/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs index 898748f19a6..535a8587f5a 100644 --- a/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs @@ -601,7 +601,7 @@ public virtual void Creates_both_navigations_and_creates_shadow_FK() Assert.Equal("BigMakId", fkProperty.Name); Assert.True(fkProperty.IsShadowProperty()); Assert.Same(typeof(int?), fkProperty.ClrType); - Assert.Same(dependentType, fkProperty.DeclaringEntityType); + Assert.Same(dependentType, fkProperty.DeclaringType); Assert.Equal("BigMak", dependentType.GetNavigations().Single().Name); Assert.Equal("Pickles", principalType.GetNavigations().Single().Name); @@ -637,7 +637,7 @@ public virtual void Creates_shadow_FK_with_navigation_to_principal() Assert.True(fkProperty.IsShadowProperty()); Assert.Same(typeof(int?), fkProperty.ClrType); - Assert.Same(dependentType, fkProperty.DeclaringEntityType); + Assert.Same(dependentType, fkProperty.DeclaringType); Assert.Equal(nameof(Pickle.BigMak), dependentType.GetNavigations().Single().Name); Assert.Same(fk, dependentType.GetNavigations().Single().ForeignKey); @@ -671,7 +671,7 @@ public virtual void Creates_shadow_FK_with_navigation_to_dependent() Assert.True(fkProperty.IsShadowProperty()); Assert.Same(typeof(int?), fkProperty.ClrType); - Assert.Same(dependentType, fkProperty.DeclaringEntityType); + Assert.Same(dependentType, fkProperty.DeclaringType); Assert.Equal(nameof(BigMak.Pickles), fk.PrincipalToDependent.Name); Assert.Null(fk.DependentToPrincipal); @@ -707,7 +707,7 @@ public virtual void Creates_shadow_FK_with_no_navigations_with() Assert.True(fkProperty.IsShadowProperty()); Assert.Same(typeof(int?), fkProperty.ClrType); - Assert.Same(dependentType, fkProperty.DeclaringEntityType); + Assert.Same(dependentType, fkProperty.DeclaringType); Assert.Empty(dependentType.GetNavigations().Where(nav => nav.ForeignKey != fk)); Assert.Empty(principalType.GetNavigations().Where(nav => nav.ForeignKey != fk)); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs index 136f94c24d5..0f3b385963a 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs @@ -3,6 +3,8 @@ #nullable enable +using System.Numerics; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; // ReSharper disable InconsistentNaming @@ -57,12 +59,36 @@ public virtual void Changing_propertyInfo_updates_Property() { var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity().Property(e => ((IReplacable)e).Property); + modelBuilder.Entity().Property(e => ((IReplaceable)e).Property); modelBuilder.FinalizeModel(); var property = modelBuilder.Model.FindEntityType(typeof(DoubleProperty))!.GetProperty("Property"); - Assert.EndsWith(typeof(IReplacable).Name + "." + nameof(IReplacable.Property), property.GetIdentifyingMemberInfo()!.Name); + Assert.EndsWith(typeof(IReplaceable).Name + "." + nameof(IReplaceable.Property), property.GetIdentifyingMemberInfo()!.Name); + } + } + + public class GenericComplexType : ComplexTypeTestBase + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new GenericTestModelBuilder(testHelpers, configure); + + [ConditionalFact] + public virtual void Changing_propertyInfo_updates_Property() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder + .Ignore() + .Entity().ComplexProperty(e => e.DoubleProperty).Property(e => ((IReplaceable)e).Property); + + modelBuilder.FinalizeModel(); + + var property = modelBuilder.Model.FindEntityType(typeof(ComplexProperties))!.FindComplexProperty(nameof(DoubleProperty))! + .ComplexType.FindProperty("Property")!; + Assert.EndsWith(typeof(IReplaceable).Name + "." + nameof(IReplaceable.Property), property.GetIdentifyingMemberInfo()!.Name); } } @@ -207,6 +233,29 @@ public override TestPropertyBuilder Property(string proper public override TestPropertyBuilder IndexerProperty(string propertyName) => Wrap(EntityTypeBuilder.IndexerProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression) + => new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyExpression)); + + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName)); + + public override TestEntityTypeBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + buildAction(new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyExpression))); + + return this; + } + + public override TestEntityTypeBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + buildAction(new GenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName))); + + return this; + } + public override TestNavigationBuilder Navigation( Expression> navigationExpression) where TNavigation : class @@ -404,6 +453,86 @@ public EntityTypeBuilder Instance => EntityTypeBuilder; } + protected class GenericTestComplexPropertyBuilder : + TestComplexPropertyBuilder, IInfrastructure> + { + public GenericTestComplexPropertyBuilder(ComplexPropertyBuilder complexPropertyBuilder) + { + PropertyBuilder = complexPropertyBuilder; + } + + protected ComplexPropertyBuilder PropertyBuilder { get; } + + public override IMutableComplexProperty Metadata + => PropertyBuilder.Metadata; + + protected virtual TestComplexPropertyBuilder Wrap(ComplexPropertyBuilder complexPropertyBuilder) + => new GenericTestComplexPropertyBuilder(complexPropertyBuilder); + + protected virtual TestComplexTypePropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) + => new GenericTestComplexTypePropertyBuilder(propertyBuilder); + + public override TestComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasPropertyAnnotation(annotation, value)); + + public override TestComplexPropertyBuilder HasTypeAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasTypeAnnotation(annotation, value)); + + public override TestComplexTypePropertyBuilder Property(Expression> propertyExpression) + where TProperty : default + => Wrap(PropertyBuilder.Property(propertyExpression)); + + public override TestComplexTypePropertyBuilder Property(string propertyName) + => Wrap(PropertyBuilder.Property(propertyName)); + + public override TestComplexTypePropertyBuilder IndexerProperty(string propertyName) + => Wrap(PropertyBuilder.IndexerProperty(propertyName)); + + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression) + => Wrap(PropertyBuilder.ComplexProperty(propertyExpression)); + + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => Wrap(PropertyBuilder.ComplexProperty(propertyName)); + + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + buildAction(Wrap(PropertyBuilder.ComplexProperty(propertyExpression))); + + return this; + } + + public override TestComplexPropertyBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + buildAction(Wrap(PropertyBuilder.ComplexProperty(propertyName))); + + return this; + } + + public override TestComplexPropertyBuilder Ignore(Expression> propertyExpression) + => Wrap(PropertyBuilder.Ignore(propertyExpression)); + + public override TestComplexPropertyBuilder Ignore(string propertyName) + => Wrap(PropertyBuilder.Ignore(propertyName)); + + public override TestComplexPropertyBuilder IsRequired(bool isRequired = true) + => Wrap(PropertyBuilder.IsRequired(isRequired)); + + public override TestComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy) + => Wrap(PropertyBuilder.HasChangeTrackingStrategy(changeTrackingStrategy)); + + public override TestComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UsePropertyAccessMode(propertyAccessMode)); + + public override TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UseDefaultPropertyAccessMode(propertyAccessMode)); + + public ComplexPropertyBuilder Instance + => PropertyBuilder; + } + protected class GenericTestDiscriminatorBuilder : TestDiscriminatorBuilder { public GenericTestDiscriminatorBuilder(DiscriminatorBuilder discriminatorBuilder) @@ -508,7 +637,7 @@ public override TestPropertyBuilder HasValueGenerator(Type valueGener => Wrap(PropertyBuilder.HasValueGenerator(valueGeneratorType)); public override TestPropertyBuilder HasValueGenerator( - Func factory) + Func factory) => Wrap(PropertyBuilder.HasValueGenerator(factory)); public override TestPropertyBuilder HasValueGeneratorFactory() @@ -600,6 +729,160 @@ PropertyBuilder IInfrastructure>.Instance => PropertyBuilder; } + protected class GenericTestComplexTypePropertyBuilder : + TestComplexTypePropertyBuilder, IInfrastructure> + { + public GenericTestComplexTypePropertyBuilder(ComplexTypePropertyBuilder propertyBuilder) + { + PropertyBuilder = propertyBuilder; + } + + protected ComplexTypePropertyBuilder PropertyBuilder { get; } + + public override IMutableProperty Metadata + => PropertyBuilder.Metadata; + + protected virtual TestComplexTypePropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) + => new GenericTestComplexTypePropertyBuilder(propertyBuilder); + + public override TestComplexTypePropertyBuilder HasAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasAnnotation(annotation, value)); + + public override TestComplexTypePropertyBuilder IsRequired(bool isRequired = true) + => Wrap(PropertyBuilder.IsRequired(isRequired)); + + public override TestComplexTypePropertyBuilder HasMaxLength(int maxLength) + => Wrap(PropertyBuilder.HasMaxLength(maxLength)); + + public override TestComplexTypePropertyBuilder HasSentinel(object? sentinel) + => Wrap(PropertyBuilder.HasSentinel(sentinel)); + + public override TestComplexTypePropertyBuilder HasPrecision(int precision) + => Wrap(PropertyBuilder.HasPrecision(precision)); + + public override TestComplexTypePropertyBuilder HasPrecision(int precision, int scale) + => Wrap(PropertyBuilder.HasPrecision(precision, scale)); + + public override TestComplexTypePropertyBuilder IsUnicode(bool unicode = true) + => Wrap(PropertyBuilder.IsUnicode(unicode)); + + public override TestComplexTypePropertyBuilder IsRowVersion() + => Wrap(PropertyBuilder.IsRowVersion()); + + public override TestComplexTypePropertyBuilder IsConcurrencyToken(bool isConcurrencyToken = true) + => Wrap(PropertyBuilder.IsConcurrencyToken(isConcurrencyToken)); + + public override TestComplexTypePropertyBuilder ValueGeneratedNever() + => Wrap(PropertyBuilder.ValueGeneratedNever()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnAdd() + => Wrap(PropertyBuilder.ValueGeneratedOnAdd()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnAddOrUpdate() + => Wrap(PropertyBuilder.ValueGeneratedOnAddOrUpdate()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnUpdate() + => Wrap(PropertyBuilder.ValueGeneratedOnUpdate()); + + public override TestComplexTypePropertyBuilder HasValueGenerator() + => Wrap(PropertyBuilder.HasValueGenerator()); + + public override TestComplexTypePropertyBuilder HasValueGenerator(Type valueGeneratorType) + => Wrap(PropertyBuilder.HasValueGenerator(valueGeneratorType)); + + public override TestComplexTypePropertyBuilder HasValueGenerator( + Func factory) + => Wrap(PropertyBuilder.HasValueGenerator(factory)); + + public override TestComplexTypePropertyBuilder HasValueGeneratorFactory() + => Wrap(PropertyBuilder.HasValueGeneratorFactory()); + + public override TestComplexTypePropertyBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType) + => Wrap(PropertyBuilder.HasValueGeneratorFactory(valueGeneratorFactoryType)); + + public override TestComplexTypePropertyBuilder HasField(string fieldName) + => Wrap(PropertyBuilder.HasField(fieldName)); + + public override TestComplexTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UsePropertyAccessMode(propertyAccessMode)); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + public override TestComplexTypePropertyBuilder HasConversion(ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression) + => Wrap( + PropertyBuilder.HasConversion( + convertToProviderExpression, + convertFromProviderExpression)); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer) + => Wrap( + PropertyBuilder.HasConversion( + convertToProviderExpression, + convertFromProviderExpression, + valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap( + PropertyBuilder.HasConversion( + convertToProviderExpression, + convertFromProviderExpression, + valueComparer, + providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + ComplexTypePropertyBuilder IInfrastructure>.Instance + => PropertyBuilder; + } + protected class GenericTestKeyBuilder : TestKeyBuilder, IInfrastructure> { public GenericTestKeyBuilder(KeyBuilder keyBuilder) diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index 5fa2ad3385d..ad3de464d75 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -3,6 +3,8 @@ #nullable enable +using Microsoft.EntityFrameworkCore.Metadata; + namespace Microsoft.EntityFrameworkCore.ModelBuilding; public class ModelBuilderNonGenericTest : ModelBuilderTest @@ -15,6 +17,14 @@ protected override TestModelBuilder CreateTestModelBuilder( => new NonGenericTestModelBuilder(testHelpers, configure); } + public class NonGenericComplexType : ComplexTypeTestBase + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new NonGenericTestModelBuilder(testHelpers, configure); + } + public class NonGenericInheritance : InheritanceTestBase { protected override TestModelBuilder CreateTestModelBuilder( @@ -252,6 +262,35 @@ public override TestPropertyBuilder Property(string proper public override TestPropertyBuilder IndexerProperty(string propertyName) => Wrap(EntityTypeBuilder.IndexerProperty(propertyName)); + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression) + { + var memberInfo = propertyExpression.GetMemberAccess(); + return new NonGenericTestComplexPropertyBuilder( + EntityTypeBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + } + + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => new NonGenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName)); + + public override TestEntityTypeBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + var memberInfo = propertyExpression.GetMemberAccess(); + buildAction(new NonGenericTestComplexPropertyBuilder( + EntityTypeBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()))); + + return this; + } + + public override TestEntityTypeBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + buildAction(new NonGenericTestComplexPropertyBuilder(EntityTypeBuilder.ComplexProperty(propertyName))); + + return this; + } + public override TestNavigationBuilder Navigation(Expression> navigationExpression) where TNavigation : class => new NonGenericTestNavigationBuilder( @@ -484,6 +523,92 @@ public EntityTypeBuilder Instance => EntityTypeBuilder; } + protected class NonGenericTestComplexPropertyBuilder : + TestComplexPropertyBuilder, IInfrastructure + { + public NonGenericTestComplexPropertyBuilder(ComplexPropertyBuilder complexPropertyBuilder) + { + PropertyBuilder = complexPropertyBuilder; + } + + protected ComplexPropertyBuilder PropertyBuilder { get; } + + public override IMutableComplexProperty Metadata + => PropertyBuilder.Metadata; + + protected virtual NonGenericTestComplexPropertyBuilder Wrap(ComplexPropertyBuilder entityTypeBuilder) + => new(entityTypeBuilder); + + protected virtual TestComplexTypePropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) + => new NonGenericTestComplexTypePropertyBuilder(propertyBuilder); + + public override TestComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasPropertyAnnotation(annotation, value)); + + public override TestComplexPropertyBuilder HasTypeAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasTypeAnnotation(annotation, value)); + + public override TestComplexTypePropertyBuilder Property(Expression> propertyExpression) + { + var memberInfo = propertyExpression.GetMemberAccess(); + return Wrap(PropertyBuilder.Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + } + + public override TestComplexTypePropertyBuilder Property(string propertyName) + => Wrap(PropertyBuilder.Property(propertyName)); + + public override TestComplexTypePropertyBuilder IndexerProperty(string propertyName) + => Wrap(PropertyBuilder.IndexerProperty(propertyName)); + + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression) + { + var memberInfo = propertyExpression.GetMemberAccess(); + return Wrap(PropertyBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName())); + } + + public override TestComplexPropertyBuilder ComplexProperty(string propertyName) + => Wrap(PropertyBuilder.ComplexProperty(propertyName)); + + public override TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction) + { + var memberInfo = propertyExpression.GetMemberAccess(); + buildAction(Wrap(PropertyBuilder.ComplexProperty(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName()))); + + return this; + } + + public override TestComplexPropertyBuilder ComplexProperty( + string propertyName, Action> buildAction) + { + buildAction(Wrap(PropertyBuilder.ComplexProperty(propertyName))); + + return this; + } + + public override TestComplexPropertyBuilder Ignore(Expression> propertyExpression) + => Wrap(PropertyBuilder.Ignore(propertyExpression.GetMemberAccess().GetSimpleMemberName())); + + public override TestComplexPropertyBuilder Ignore(string propertyName) + => Wrap(PropertyBuilder.Ignore(propertyName)); + + public override TestComplexPropertyBuilder IsRequired(bool isRequired = true) + => Wrap(PropertyBuilder.IsRequired(isRequired)); + + public override TestComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy) + => Wrap(PropertyBuilder.HasChangeTrackingStrategy(changeTrackingStrategy)); + + public override TestComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UsePropertyAccessMode(propertyAccessMode)); + + public override TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UseDefaultPropertyAccessMode(propertyAccessMode)); + + public ComplexPropertyBuilder Instance + => PropertyBuilder; + } + protected class NonGenericTestDiscriminatorBuilder : TestDiscriminatorBuilder { public NonGenericTestDiscriminatorBuilder(DiscriminatorBuilder discriminatorBuilder) @@ -588,7 +713,7 @@ public override TestPropertyBuilder HasValueGenerator(Type valueGener => Wrap(PropertyBuilder.HasValueGenerator(valueGeneratorType)); public override TestPropertyBuilder HasValueGenerator( - Func factory) + Func factory) => Wrap(PropertyBuilder.HasValueGenerator(factory)); public override TestPropertyBuilder HasValueGeneratorFactory() @@ -677,6 +802,157 @@ PropertyBuilder IInfrastructure.Instance => PropertyBuilder; } + protected class NonGenericTestComplexTypePropertyBuilder : + TestComplexTypePropertyBuilder, IInfrastructure + { + public NonGenericTestComplexTypePropertyBuilder(ComplexTypePropertyBuilder propertyBuilder) + { + PropertyBuilder = propertyBuilder; + } + + private ComplexTypePropertyBuilder PropertyBuilder { get; } + + public override IMutableProperty Metadata + => PropertyBuilder.Metadata; + + protected virtual TestComplexTypePropertyBuilder Wrap(ComplexTypePropertyBuilder propertyBuilder) + => new NonGenericTestComplexTypePropertyBuilder(propertyBuilder); + + public override TestComplexTypePropertyBuilder HasAnnotation(string annotation, object? value) + => Wrap(PropertyBuilder.HasAnnotation(annotation, value)); + + public override TestComplexTypePropertyBuilder IsRequired(bool isRequired = true) + => Wrap(PropertyBuilder.IsRequired(isRequired)); + + public override TestComplexTypePropertyBuilder HasMaxLength(int maxLength) + => Wrap(PropertyBuilder.HasMaxLength(maxLength)); + + public override TestComplexTypePropertyBuilder HasSentinel(object? sentinel) + => Wrap(PropertyBuilder.HasSentinel(sentinel)); + + public override TestComplexTypePropertyBuilder HasPrecision(int precision) + => Wrap(PropertyBuilder.HasPrecision(precision)); + + public override TestComplexTypePropertyBuilder HasPrecision(int precision, int scale) + => Wrap(PropertyBuilder.HasPrecision(precision, scale)); + + public override TestComplexTypePropertyBuilder IsUnicode(bool unicode = true) + => Wrap(PropertyBuilder.IsUnicode(unicode)); + + public override TestComplexTypePropertyBuilder IsRowVersion() + => Wrap(PropertyBuilder.IsRowVersion()); + + public override TestComplexTypePropertyBuilder IsConcurrencyToken(bool isConcurrencyToken = true) + => Wrap(PropertyBuilder.IsConcurrencyToken(isConcurrencyToken)); + + public override TestComplexTypePropertyBuilder ValueGeneratedNever() + => Wrap(PropertyBuilder.ValueGeneratedNever()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnAdd() + => Wrap(PropertyBuilder.ValueGeneratedOnAdd()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnAddOrUpdate() + => Wrap(PropertyBuilder.ValueGeneratedOnAddOrUpdate()); + + public override TestComplexTypePropertyBuilder ValueGeneratedOnUpdate() + => Wrap(PropertyBuilder.ValueGeneratedOnUpdate()); + + public override TestComplexTypePropertyBuilder HasValueGenerator() + => Wrap(PropertyBuilder.HasValueGenerator()); + + public override TestComplexTypePropertyBuilder HasValueGenerator(Type valueGeneratorType) + => Wrap(PropertyBuilder.HasValueGenerator(valueGeneratorType)); + + public override TestComplexTypePropertyBuilder HasValueGenerator( + Func factory) + => Wrap(PropertyBuilder.HasValueGenerator(factory)); + + public override TestComplexTypePropertyBuilder HasValueGeneratorFactory() + => Wrap(PropertyBuilder.HasValueGeneratorFactory()); + + public override TestComplexTypePropertyBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType) + => Wrap(PropertyBuilder.HasValueGeneratorFactory(valueGeneratorFactoryType)); + + public override TestComplexTypePropertyBuilder HasField(string fieldName) + => Wrap(PropertyBuilder.HasField(fieldName)); + + public override TestComplexTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode) + => Wrap(PropertyBuilder.UsePropertyAccessMode(propertyAccessMode)); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion(typeof(TConversion))); + + public override TestComplexTypePropertyBuilder HasConversion(ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(typeof(TConversion), valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(typeof(TConversion), valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression) + => Wrap( + PropertyBuilder.HasConversion( + new ValueConverter(convertToProviderExpression, convertFromProviderExpression))); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer) + => Wrap( + PropertyBuilder.HasConversion( + new ValueConverter(convertToProviderExpression, convertFromProviderExpression), + valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap( + PropertyBuilder.HasConversion( + new ValueConverter(convertToProviderExpression, convertFromProviderExpression), + valueComparer, + providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter) + => Wrap(PropertyBuilder.HasConversion(converter)); + + public override TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer)); + + public override TestComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType) + => Wrap(PropertyBuilder.HasConversion(converter, valueComparer, providerComparerType)); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + public override TestComplexTypePropertyBuilder HasConversion() + => Wrap(PropertyBuilder.HasConversion()); + + ComplexTypePropertyBuilder IInfrastructure.Instance + => PropertyBuilder; + } + protected class NonGenericTestNavigationBuilder : TestNavigationBuilder { public NonGenericTestNavigationBuilder(NavigationBuilder navigationBuilder) diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index 80c7aaf02dc..2f3c0030a6b 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -4,6 +4,8 @@ #nullable enable using Microsoft.EntityFrameworkCore.Diagnostics.Internal; +using Microsoft.EntityFrameworkCore.Metadata; +using static Microsoft.EntityFrameworkCore.ModelBuilding.ModelBuilderTest; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ModelBuilding; @@ -201,6 +203,17 @@ public abstract TestPropertyBuilder Property( public abstract TestPropertyBuilder Property(string propertyName); public abstract TestPropertyBuilder IndexerProperty(string propertyName); + public abstract TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression); + + public abstract TestComplexPropertyBuilder ComplexProperty(string propertyName); + + public abstract TestEntityTypeBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction); + + public abstract TestEntityTypeBuilder ComplexProperty( + string propertyName, Action> buildAction); + public abstract TestNavigationBuilder Navigation( Expression> navigationExpression) where TNavigation : class; @@ -334,6 +347,39 @@ public abstract TestDiscriminatorBuilder HasDiscriminator HasNoDiscriminator(); } + public abstract class TestComplexPropertyBuilder + { + public abstract IMutableComplexProperty Metadata { get; } + public abstract TestComplexPropertyBuilder HasTypeAnnotation(string annotation, object? value); + public abstract TestComplexPropertyBuilder HasPropertyAnnotation(string annotation, object? value); + + public abstract TestComplexTypePropertyBuilder Property( + Expression> propertyExpression); + + public abstract TestComplexTypePropertyBuilder Property(string propertyName); + public abstract TestComplexTypePropertyBuilder IndexerProperty(string propertyName); + + public abstract TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression); + + public abstract TestComplexPropertyBuilder ComplexProperty(string propertyName); + + public abstract TestComplexPropertyBuilder ComplexProperty( + Expression> propertyExpression, Action> buildAction); + + public abstract TestComplexPropertyBuilder ComplexProperty( + string propertyName, Action> buildAction); + + public abstract TestComplexPropertyBuilder Ignore( + Expression> propertyExpression); + + public abstract TestComplexPropertyBuilder Ignore(string propertyName); + public abstract TestComplexPropertyBuilder IsRequired(bool isRequired = true); + public abstract TestComplexPropertyBuilder HasChangeTrackingStrategy(ChangeTrackingStrategy changeTrackingStrategy); + public abstract TestComplexPropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode); + public abstract TestComplexPropertyBuilder UseDefaultPropertyAccessMode(PropertyAccessMode propertyAccessMode); + } + public abstract class TestDiscriminatorBuilder { public abstract TestDiscriminatorBuilder IsComplete(bool complete); @@ -392,7 +438,7 @@ public abstract TestPropertyBuilder HasValueGenerator() public abstract TestPropertyBuilder HasValueGenerator(Type valueGeneratorType); public abstract TestPropertyBuilder HasValueGenerator( - Func factory); + Func factory); public abstract TestPropertyBuilder HasValueGeneratorFactory() where TFactory : ValueGeneratorFactory; @@ -451,6 +497,89 @@ public abstract TestPropertyBuilder HasConversion + { + public abstract IMutableProperty Metadata { get; } + public abstract TestComplexTypePropertyBuilder HasAnnotation(string annotation, object? value); + public abstract TestComplexTypePropertyBuilder IsRequired(bool isRequired = true); + public abstract TestComplexTypePropertyBuilder HasMaxLength(int maxLength); + public abstract TestComplexTypePropertyBuilder HasSentinel(object? sentinel); + public abstract TestComplexTypePropertyBuilder HasPrecision(int precision); + public abstract TestComplexTypePropertyBuilder HasPrecision(int precision, int scale); + public abstract TestComplexTypePropertyBuilder IsUnicode(bool unicode = true); + public abstract TestComplexTypePropertyBuilder IsRowVersion(); + public abstract TestComplexTypePropertyBuilder IsConcurrencyToken(bool isConcurrencyToken = true); + + public abstract TestComplexTypePropertyBuilder ValueGeneratedNever(); + public abstract TestComplexTypePropertyBuilder ValueGeneratedOnAdd(); + public abstract TestComplexTypePropertyBuilder ValueGeneratedOnAddOrUpdate(); + public abstract TestComplexTypePropertyBuilder ValueGeneratedOnUpdate(); + + public abstract TestComplexTypePropertyBuilder HasValueGenerator() + where TGenerator : ValueGenerator; + + public abstract TestComplexTypePropertyBuilder HasValueGenerator(Type valueGeneratorType); + + public abstract TestComplexTypePropertyBuilder HasValueGenerator( + Func factory); + + public abstract TestComplexTypePropertyBuilder HasValueGeneratorFactory() + where TFactory : ValueGeneratorFactory; + + public abstract TestComplexTypePropertyBuilder HasValueGeneratorFactory(Type valueGeneratorFactoryType); + + public abstract TestComplexTypePropertyBuilder HasField(string fieldName); + public abstract TestComplexTypePropertyBuilder UsePropertyAccessMode(PropertyAccessMode propertyAccessMode); + + public abstract TestComplexTypePropertyBuilder HasConversion(); + public abstract TestComplexTypePropertyBuilder HasConversion(ValueComparer? valueComparer); + + public abstract TestComplexTypePropertyBuilder HasConversion( + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression); + + public abstract TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer); + + public abstract TestComplexTypePropertyBuilder HasConversion( + Expression> convertToProviderExpression, + Expression> convertFromProviderExpression, + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexTypePropertyBuilder HasConversion(ValueConverter converter); + + public abstract TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer); + + public abstract TestComplexTypePropertyBuilder HasConversion( + ValueConverter converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter); + public abstract TestComplexTypePropertyBuilder HasConversion(ValueConverter? converter, ValueComparer? valueComparer); + + public abstract TestComplexTypePropertyBuilder HasConversion( + ValueConverter? converter, + ValueComparer? valueComparer, + ValueComparer? providerComparerType); + + public abstract TestComplexTypePropertyBuilder HasConversion() + where TComparer : ValueComparer; + + public abstract TestComplexTypePropertyBuilder HasConversion() + where TComparer : ValueComparer + where TProviderComparer : ValueComparer; + } + public abstract class TestNavigationBuilder { public abstract TestNavigationBuilder HasAnnotation(string annotation, object? value); diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index edadba300ea..c10579089e5 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -1208,27 +1208,6 @@ public virtual void Value_converter_configured_on_base_type_is_not_applied() Assert.Throws(() => modelBuilder.FinalizeModel()).Message); } - private interface IWrapped - { - T Value { get; init; } - } - - private abstract class WrappedStringBase : IWrapped - { - public abstract string Value { get; init; } - } - - private class WrappedString : WrappedStringBase - { - public override string Value { get; init; } - } - - private class WrappedStringEntity - { - public int Id { get; set; } - public WrappedString WrappedString { get; set; } - } - private class WrappedStringToStringConverter : ValueConverter { public WrappedStringToStringConverter() @@ -1640,7 +1619,7 @@ public override bool GeneratesTemporaryValues private class CustomValueGeneratorFactory : ValueGeneratorFactory { - public override ValueGenerator Create(IProperty property, IEntityType entityType) + public override ValueGenerator Create(IProperty property, ITypeBase entityType) => new CustomValueGenerator(); } @@ -1792,6 +1771,21 @@ protected class ThreeDee public int[,,] Three { get; set; } } + [ConditionalFact] + public virtual void Private_property_is_not_discovered_by_convention() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + Assert.Empty( + model.FindEntityType(typeof(Gamma)).GetProperties() + .Where(p => p.Name == "PrivateProperty")); + } + [ConditionalFact] protected virtual void Throws_for_int_keyed_dictionary() { @@ -2019,7 +2013,7 @@ public virtual void Can_ignore_explicit_interface_implementation_property() [ConditionalFact] public virtual void Can_set_key_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().HasKey(e => e.Id); @@ -2036,7 +2030,7 @@ public virtual void Can_set_key_on_an_entity_with_fields() [ConditionalFact] public virtual void Can_set_composite_key_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().HasKey(e => new { e.TenantId, e.CompanyId }); @@ -2112,7 +2106,7 @@ public virtual void Can_call_Property_on_an_entity_with_fields() [ConditionalFact] public virtual void Can_set_index_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().HasNoKey().HasIndex(e => e.CompanyId); @@ -2127,7 +2121,7 @@ public virtual void Can_set_index_on_an_entity_with_fields() [ConditionalFact] public virtual void Can_set_composite_index_on_an_entity_with_fields() { - var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder(); + var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().HasNoKey().HasIndex(e => new { e.TenantId, e.CompanyId }); @@ -2227,21 +2221,6 @@ public virtual void Can_add_seed_data_anonymous_objects() Assert.Equal(-2, data.Last().Values.Single()); } - [ConditionalFact] - public virtual void Private_property_is_not_discovered_by_convention() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Ignore(); - modelBuilder.Entity(); - - var model = modelBuilder.FinalizeModel(); - - Assert.Empty( - model.FindEntityType(typeof(Gamma)).GetProperties() - .Where(p => p.Name == "PrivateProperty")); - } - [ConditionalFact] public virtual void Can_add_seed_data_objects_indexed_property() { diff --git a/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs b/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs index 6d3e7fe663d..aee52834238 100644 --- a/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs @@ -603,7 +603,7 @@ public virtual void Creates_both_navigations_and_creates_shadow_FK() Assert.Equal("BigMakId", fkProperty.Name); Assert.True(fkProperty.IsShadowProperty()); Assert.Same(typeof(int?), fkProperty.ClrType); - Assert.Same(dependentType, fkProperty.DeclaringEntityType); + Assert.Same(dependentType, fkProperty.DeclaringType); Assert.Equal("BigMak", dependentType.GetNavigations().Single().Name); Assert.Equal("Pickles", principalType.GetNavigations().Single().Name); @@ -641,7 +641,7 @@ public virtual void Creates_shadow_FK_with_navigation_to_dependent() Assert.True(fkProperty.IsShadowProperty()); Assert.Same(typeof(int?), fkProperty.ClrType); - Assert.Same(dependentType, fkProperty.DeclaringEntityType); + Assert.Same(dependentType, fkProperty.DeclaringType); Assert.Equal(nameof(BigMak.Pickles), fk.PrincipalToDependent.Name); Assert.Null(fk.DependentToPrincipal); @@ -676,7 +676,7 @@ public virtual void Creates_shadow_FK_with_navigation_to_principal() Assert.True(fkProperty.IsShadowProperty()); Assert.Same(typeof(int?), fkProperty.ClrType); - Assert.Same(dependentType, fkProperty.DeclaringEntityType); + Assert.Same(dependentType, fkProperty.DeclaringType); Assert.Equal(nameof(Pickle.BigMak), fk.DependentToPrincipal.Name); Assert.Null(fk.PrincipalToDependent); @@ -714,7 +714,7 @@ public virtual void Creates_shadow_FK_with_no_navigation() Assert.Equal("BigMakId1", fkProperty.Name); Assert.True(fkProperty.IsShadowProperty()); Assert.Same(typeof(int?), fkProperty.ClrType); - Assert.Same(dependentType, fkProperty.DeclaringEntityType); + Assert.Same(dependentType, fkProperty.DeclaringType); Assert.Empty(dependentType.GetNavigations().Where(nav => nav.ForeignKey != existingFk)); Assert.Empty(principalType.GetNavigations().Where(nav => nav.ForeignKey != existingFk)); diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index f0e81f97409..a9a821328ff 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -847,17 +847,54 @@ protected class OwnedTypeInheritance2 public string? Value { get; set; } } - protected interface IReplacable + protected interface IReplaceable { int Property { get; set; } } - protected class DoubleProperty : IReplacable + protected class ComplexProperties + { + public int Id { get; set; } + public Customer Customer { get; set; } = null!; + public DoubleProperty DoubleProperty { get; set; } = null!; + public IndexedClass IndexedClass { get; set; } = null!; + public Quarks Quarks { get; set; } = null!; + + [NotMapped] + public DynamicProperty DynamicProperty { get; set; } = null!; + [NotMapped] + public EntityWithFields EntityWithFields { get; set; } = null!; + [NotMapped] + public WrappedStringEntity WrappedStringEntity { get; set; } = null!; + } + + protected interface IWrapped + { + T? Value { get; init; } + } + + protected abstract class WrappedStringBase : IWrapped + { + public abstract string? Value { get; init; } + } + + protected class WrappedString : WrappedStringBase + { + public override string? Value { get; init; } + } + + protected class WrappedStringEntity + { + public int Id { get; set; } + public WrappedString WrappedString { get; set; } = new WrappedString(); + } + + protected class DoubleProperty : IReplaceable { public int Id { get; set; } public int Property { get; set; } - int IReplacable.Property + int IReplaceable.Property { get => Property; set => Property = value; diff --git a/test/EFCore.Tests/ValueGeneration/TemporaryNumberValueGeneratorFactoryTest.cs b/test/EFCore.Tests/ValueGeneration/TemporaryNumberValueGeneratorFactoryTest.cs index ed1d7f4d1f2..44e4130b443 100644 --- a/test/EFCore.Tests/ValueGeneration/TemporaryNumberValueGeneratorFactoryTest.cs +++ b/test/EFCore.Tests/ValueGeneration/TemporaryNumberValueGeneratorFactoryTest.cs @@ -39,7 +39,7 @@ public void Can_create_factories_for_all_integer_types() } private static object CreateAndUseFactory(IProperty property) - => new TemporaryNumberValueGeneratorFactory().Create(property, property.DeclaringEntityType).Next(null); + => new TemporaryNumberValueGeneratorFactory().Create(property, property.DeclaringType).Next(null); [ConditionalFact] public void Throws_for_non_integer_property() From 8a9ce16aac20b6e870252d127cb9a8ee848dc1ff Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 26 Jun 2023 16:29:01 -0700 Subject: [PATCH 2/4] Fix typos --- .../Conventions/StoreKeyConvention.cs | 4 +- .../Properties/DesignStrings.Designer.cs | 3 +- .../Properties/DesignStrings.resx | 8 +- .../Internal/CSharpDbContextGenerator.cs | 2 +- .../Internal/CSharpDbContextGenerator.tt | 2 +- .../Properties/InMemoryStrings.Designer.cs | 2 +- .../Properties/InMemoryStrings.resx | 2 +- .../Internal/EntityProjectionExpression.cs | 9 +- ...ons.cs => RelationalPropertyExtensions.cs} | 2 +- ...ons.cs => RelationalPropertyExtensions.cs} | 2 +- .../Properties/RelationalStrings.Designer.cs | 142 ++-- .../Properties/RelationalStrings.resx | 80 +- ...verComplexTypePropertyBuilderExtensions.cs | 2 - .../SqlServerPropertyBuilderExtensions.cs | 4 +- ...ions.cs => SqlServerPropertyExtensions.cs} | 2 +- .../SqlServerTransientExceptionDetector.cs | 6 +- .../Internal/SqliteAnnotationProvider.cs | 3 +- .../Properties/SqliteStrings.Designer.cs | 8 +- .../Properties/SqliteStrings.resx | 8 +- .../PropertyDiscoveryConvention.cs | 1 - .../TypeAttributeConventionBase.cs | 2 +- src/EFCore/Metadata/IMutableComplexType.cs | 2 - src/EFCore/Metadata/IReadOnlyProperty.cs | 757 +++++++++--------- .../Internal/InternalEntityTypeBuilder.cs | 2 - .../Metadata/Internal/InternalModelBuilder.cs | 1 - .../Internal/InternalPropertyBuilder.cs | 196 ++--- src/EFCore/Metadata/Internal/Reference.cs | 119 ++- src/EFCore/Metadata/RuntimeComplexProperty.cs | 2 +- src/EFCore/Metadata/RuntimeComplexType.cs | 3 +- src/EFCore/Metadata/RuntimeTypeBase.cs | 2 +- src/EFCore/Metadata/TypeBaseTypeComparer.cs | 73 -- .../SqlServerApiConsistencyTest.cs | 6 +- 32 files changed, 683 insertions(+), 774 deletions(-) rename src/EFCore.Relational/Infrastructure/{RelationalPrimitivePropertyBaseExtensions.cs => RelationalPropertyExtensions.cs} (96%) rename src/EFCore.Relational/Metadata/Internal/{RelationalPrimitivePropertyBaseExtensions.cs => RelationalPropertyExtensions.cs} (97%) rename src/EFCore.SqlServer/Extensions/{SqlServerPrimitivePropertyBaseExtensions.cs => SqlServerPropertyExtensions.cs} (99%) delete mode 100644 src/EFCore/Metadata/TypeBaseTypeComparer.cs diff --git a/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs b/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs index a62df3cf722..4dc7cc621e2 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/StoreKeyConvention.cs @@ -306,9 +306,7 @@ public virtual void ProcessPropertyAnnotationChanged( } } - ProcessIdProperty(((declaringType as IConventionEntityType) - ?? ((IConventionComplexType)declaringType).FundamentalEntityType) - .Builder); + ProcessIdProperty(declaringType.FundamentalEntityType.Builder); } } } diff --git a/src/EFCore.Design/Properties/DesignStrings.Designer.cs b/src/EFCore.Design/Properties/DesignStrings.Designer.cs index ebbade0a075..815834efe07 100644 --- a/src/EFCore.Design/Properties/DesignStrings.Designer.cs +++ b/src/EFCore.Design/Properties/DesignStrings.Designer.cs @@ -666,7 +666,7 @@ public static string RevertMigration(object? name) name); /// - /// To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263. + /// To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263. /// public static string SensitiveInformationWarning => GetString("SensitiveInformationWarning"); @@ -829,3 +829,4 @@ private static string GetString(string name, params string[] formatterNames) } } } + diff --git a/src/EFCore.Design/Properties/DesignStrings.resx b/src/EFCore.Design/Properties/DesignStrings.resx index 7a692251456..5da17b71cb6 100644 --- a/src/EFCore.Design/Properties/DesignStrings.resx +++ b/src/EFCore.Design/Properties/DesignStrings.resx @@ -127,10 +127,10 @@ Entity Framework Core Migrations Bundle - Unable to create a 'DbContext' of type '{contextType}'. The exception '{rootException}' was thrown while attempting to create an instance. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728 + Unable to create a 'DbContext' of type '{contextType}'. The exception '{rootException}' was thrown while attempting to create an instance. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728 - The exception '{rootException}' was thrown while attempting to find 'DbContext' types. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728 + The exception '{rootException}' was thrown while attempting to find 'DbContext' types. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728 Unable to find expected assembly attribute [DesignTimeProviderServices] in provider assembly '{runtimeProviderAssemblyName}'. This attribute is required to identify the class which acts as the design-time service provider factory for the provider. @@ -381,7 +381,7 @@ Change your target project to the migrations project by using the Package Manage The migration '{name}' has already been applied to the database. Revert it and try again. If the migration has been applied to other databases, consider reverting its changes using a new migration instead. - To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263. + To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263. Localize the URL if we have localized docs. @@ -441,4 +441,4 @@ Change your target project to the migrations project by using the Package Manage Writing model snapshot to '{file}'. - + \ No newline at end of file diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index 88fe47c7bab..79dd3634758 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -96,7 +96,7 @@ public virtual string TransformText() if (!Options.SuppressConnectionStringWarning) { - this.Write(@"#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263. + this.Write(@"#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263. "); } diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.tt b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.tt index 843dc271f94..80e3c9eca95 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.tt +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.tt @@ -75,7 +75,7 @@ public partial class <#= Options.ContextName #> : DbContext if (!Options.SuppressConnectionStringWarning) { #> -#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263. +#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263. <# } #> diff --git a/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs b/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs index 1b972d62a46..83f0de02464 100644 --- a/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs +++ b/src/EFCore.InMemory/Properties/InMemoryStrings.Designer.cs @@ -159,7 +159,7 @@ public static EventDefinition LogSavedChanges(IDiagnosticsLogger logger) } /// - /// Transactions are not supported by the in-memory store. See http://go.microsoft.com/fwlink/?LinkId=800142 + /// Transactions are not supported by the in-memory store. See https://go.microsoft.com/fwlink/?LinkId=800142 /// public static EventDefinition LogTransactionsNotSupported(IDiagnosticsLogger logger) { diff --git a/src/EFCore.InMemory/Properties/InMemoryStrings.resx b/src/EFCore.InMemory/Properties/InMemoryStrings.resx index a5fda0181c4..93e3f5cfd5d 100644 --- a/src/EFCore.InMemory/Properties/InMemoryStrings.resx +++ b/src/EFCore.InMemory/Properties/InMemoryStrings.resx @@ -131,7 +131,7 @@ Information InMemoryEventId.ChangesSaved int - Transactions are not supported by the in-memory store. See http://go.microsoft.com/fwlink/?LinkId=800142 + Transactions are not supported by the in-memory store. See https://go.microsoft.com/fwlink/?LinkId=800142 Warning InMemoryEventId.TransactionIgnoredWarning diff --git a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs index 5d3f38a628a..39587ddf366 100644 --- a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs @@ -74,13 +74,8 @@ public virtual EntityProjectionExpression UpdateEntityType(IEntityType derivedTy var readExpressionMap = new Dictionary(); foreach (var (property, methodCallExpression) in _readExpressionMap) { - if (property.DeclaringType is not IEntityType entityType) - { - continue; - } - - if (derivedType.IsAssignableFrom(entityType) - || entityType.IsAssignableFrom(derivedType)) + if (derivedType.IsAssignableFrom(property.DeclaringType) + || property.DeclaringType.IsAssignableFrom(derivedType)) { readExpressionMap[property] = methodCallExpression; } diff --git a/src/EFCore.Relational/Infrastructure/RelationalPrimitivePropertyBaseExtensions.cs b/src/EFCore.Relational/Infrastructure/RelationalPropertyExtensions.cs similarity index 96% rename from src/EFCore.Relational/Infrastructure/RelationalPrimitivePropertyBaseExtensions.cs rename to src/EFCore.Relational/Infrastructure/RelationalPropertyExtensions.cs index 4b1bb2f7589..07ed92a0245 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalPrimitivePropertyBaseExtensions.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalPropertyExtensions.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure; /// /// Relational extension methods for . /// -public static class RelationalPrimitivePropertyBaseExtensions +public static class RelationalPropertyExtensions { /// /// Creates a comma-separated list of column names. diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalPrimitivePropertyBaseExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyExtensions.cs similarity index 97% rename from src/EFCore.Relational/Metadata/Internal/RelationalPrimitivePropertyBaseExtensions.cs rename to src/EFCore.Relational/Metadata/Internal/RelationalPropertyExtensions.cs index 06c92487b28..c0cfebbde1b 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalPrimitivePropertyBaseExtensions.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyExtensions.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.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 static class RelationalPrimitivePropertyBaseExtensions +public static class RelationalPropertyExtensions { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index 7b7f43e2888..977720170fc 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -53,14 +53,6 @@ public static string BadSequenceType public static string CannotChangeWhenOpen => GetString("CannotChangeWhenOpen"); - /// - /// The query contained a new array expression containing non-constant elements, which could not be translated: '{newArrayExpression}'. - /// - public static string CannotTranslateNonConstantNewArrayExpression(object? newArrayExpression) - => string.Format( - GetString("CannotTranslateNonConstantNewArrayExpression", nameof(newArrayExpression)), - newArrayExpression); - /// /// 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. /// @@ -69,6 +61,14 @@ public static string CannotConfigureTriggerNonRootTphEntity(object? entityType, GetString("CannotConfigureTriggerNonRootTphEntity", nameof(entityType), nameof(rootEntityType)), entityType, rootEntityType); + /// + /// The query contained a new array expression containing non-constant elements, which could not be translated: '{newArrayExpression}'. + /// + public static string CannotTranslateNonConstantNewArrayExpression(object? newArrayExpression) + => string.Format( + GetString("CannotTranslateNonConstantNewArrayExpression", nameof(newArrayExpression)), + newArrayExpression); + /// /// Unable to translate the given 'GroupBy' pattern. Call 'AsEnumerable' before 'GroupBy' to evaluate it client-side. /// @@ -175,14 +175,6 @@ public static string ConflictingRowValuesSensitive(object? firstEntityType, obje GetString("ConflictingRowValuesSensitive", nameof(firstEntityType), nameof(secondEntityType), nameof(keyValue), nameof(firstConflictingValue), nameof(secondConflictingValue), nameof(column)), firstEntityType, secondEntityType, keyValue, firstConflictingValue, secondConflictingValue, column); - /// - /// Conflicting type mappings were inferred for column '{column}'. - /// - public static string ConflictingTypeMappingsInferredForColumn(object? column) - => string.Format( - GetString("ConflictingTypeMappingsInferredForColumn", nameof(column)), - column); - /// /// A seed entity for entity type '{entityType}' has the same key value as another seed entity mapped to the same table '{table}', but have different values for the column '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values. /// @@ -199,6 +191,14 @@ public static string ConflictingSeedValuesSensitive(object? entityType, object? GetString("ConflictingSeedValuesSensitive", nameof(entityType), nameof(keyValue), nameof(table), nameof(column), nameof(firstValue), nameof(secondValue)), entityType, keyValue, table, column, firstValue, secondValue); + /// + /// Conflicting type mappings were inferred for column '{column}'. + /// + public static string ConflictingTypeMappingsInferredForColumn(object? column) + => string.Format( + GetString("ConflictingTypeMappingsInferredForColumn", nameof(column)), + column); + /// /// {numSortOrderProperties} values were provided in CreateIndexOperations.IsDescending, but the operation has {numColumns} columns. /// @@ -630,15 +630,7 @@ public static string DuplicateSeedDataSensitive(object? entityType, object? keyV entityType, keyValue, table); /// - /// Exactly one of '{param1}', '{param2}' or '{param3}' must be set. - /// - public static string OneOfThreeValuesMustBeSet(object? param1, object? param2, object? param3) - => string.Format( - GetString("OneOfThreeValuesMustBeSet", nameof(param1), nameof(param2), nameof(param3)), - param1, param2, param3); - - /// - /// Empty collections are not supported as constant query roots. + /// Empty collections are not supported as inline query roots. /// public static string EmptyCollectionNotSupportedAsInlineQueryRoot => GetString("EmptyCollectionNotSupportedAsInlineQueryRoot"); @@ -1424,12 +1416,12 @@ public static string NullTypeMappingInSqlTree(object? sqlExpression) sqlExpression); /// - /// 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. + /// Exactly one of '{param1}', '{param2}' or '{param3}' must be set. /// - public static string OptionalDependentWithDependentWithoutIdentifyingProperty(object? entityType) + public static string OneOfThreeValuesMustBeSet(object? param1, object? param2, object? param3) => string.Format( - GetString("OptionalDependentWithDependentWithoutIdentifyingProperty", nameof(entityType)), - entityType); + GetString("OneOfThreeValuesMustBeSet", nameof(param1), nameof(param2), nameof(param3)), + param1, param2, param3); /// /// Only constants are supported inside inline collection query roots. @@ -1437,6 +1429,14 @@ public static string OptionalDependentWithDependentWithoutIdentifyingProperty(ob 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. + /// + public static string OptionalDependentWithDependentWithoutIdentifyingProperty(object? entityType) + => string.Format( + GetString("OptionalDependentWithDependentWithoutIdentifyingProperty", nameof(entityType)), + entityType); + /// /// Cannot use the value provided for parameter '{parameter}' because it isn't assignable to type object[]. /// @@ -1535,6 +1535,14 @@ public static string SqlQueryUnmappedType(object? elementType) GetString("SqlQueryUnmappedType", nameof(elementType)), elementType); + /// + /// The foreign key column '{fkColumnName}' has '{fkColumnType}' values which cannot be compared to the '{pkColumnType}' values of the associated principal key column '{pkColumnName}'. To use 'SaveChanges` or 'SaveChangesAsync', foreign key column types must be comparable with principal key column types. + /// + public static string StoredKeyTypesNotConvertable(object? fkColumnName, object? fkColumnType, object? pkColumnType, object? pkColumnName) + => string.Format( + GetString("StoredKeyTypesNotConvertable", nameof(fkColumnName), nameof(fkColumnType), nameof(pkColumnType), nameof(pkColumnName)), + fkColumnName, fkColumnType, pkColumnType, pkColumnName); + /// /// Current value parameter '{parameter}' is not allowed on delete stored procedure '{sproc}'. Use HasOriginalValueParameter() instead. /// @@ -1744,19 +1752,19 @@ public static string StoredProcedureResultColumnParameterConflict(object? entity entityType, property, sproc); /// - /// Stored procedure '{sproc}' was configured with a rows affected output parameter or return value, but a valid value was not found when executing the procedure. + /// A rows affected parameter, result column or return value cannot be configured on stored procedure '{sproc}' because it is used for insertion. Rows affected values are only allowed on stored procedures performing updating or deletion. /// - public static string StoredProcedureRowsAffectedNotPopulated(object? sproc) + public static string StoredProcedureRowsAffectedForInsert(object? sproc) => string.Format( - GetString("StoredProcedureRowsAffectedNotPopulated", nameof(sproc)), + GetString("StoredProcedureRowsAffectedForInsert", nameof(sproc)), sproc); /// - /// A rows affected parameter, result column or return value cannot be configured on stored procedure '{sproc}' because it is used for insertion. Rows affected values are only allowed on stored procedures performing updating or deletion. + /// Stored procedure '{sproc}' was configured with a rows affected output parameter or return value, but a valid value was not found when executing the procedure. /// - public static string StoredProcedureRowsAffectedForInsert(object? sproc) + public static string StoredProcedureRowsAffectedNotPopulated(object? sproc) => string.Format( - GetString("StoredProcedureRowsAffectedForInsert", nameof(sproc)), + GetString("StoredProcedureRowsAffectedNotPopulated", nameof(sproc)), sproc); /// @@ -1799,14 +1807,6 @@ public static string StoredProcedureUnmapped(object? entityType) GetString("StoredProcedureUnmapped", nameof(entityType)), entityType); - /// - /// The foreign key column '{fkColumnName}' has '{fkColumnType}' values which cannot be compared to the '{pkColumnType}' values of the associated principal key column '{pkColumnName}'. To use 'SaveChanges` or 'SaveChangesAsync', foreign key column types must be comparable with principal key column types. - /// - public static string StoredKeyTypesNotConvertable(object? fkColumnName, object? fkColumnType, object? pkColumnType, object? pkColumnName) - => string.Format( - GetString("StoredKeyTypesNotConvertable", nameof(fkColumnName), nameof(fkColumnType), nameof(pkColumnType), nameof(pkColumnName)), - fkColumnName, fkColumnType, pkColumnType, pkColumnName); - /// /// Expression type '{expressionType}' does not implement proper cloning logic. Every expression derived from '{tableExpressionBase}' must implement '{clonableTableExpressionBase}' interface or have it's cloning logic added to the '{cloningExpressionVisitor}' inside '{selectExpression}'. /// @@ -1994,7 +1994,7 @@ public static string UnsupportedOperatorForSqlExpression(object? nodeType, objec nodeType, expressionType); /// - /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'. + /// No relational type mapping can be found for property '{entity}.{property}' and the current provider doesn't specify a default store type for the properties of type '{clrType}'. /// public static string UnsupportedPropertyType(object? entity, object? property, object? clrType) => string.Format( @@ -2018,7 +2018,7 @@ public static string UnsupportedType(object? clrType) clrType); /// - /// The database operation was expected to affect {expectedRows} row(s), but actually affected {actualRows} row(s); data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. + /// The database operation was expected to affect {expectedRows} row(s), but actually affected {actualRows} row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. /// public static string UpdateConcurrencyException(object? expectedRows, object? actualRows) => string.Format( @@ -2120,7 +2120,7 @@ private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.EntityFrameworkCore.Properties.RelationalStrings", typeof(RelationalResources).Assembly); /// - /// An ambient transaction has been detected, but the current provider does not support ambient transactions. See http://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) { @@ -3535,31 +3535,6 @@ public static EventDefinition LogOptionalDependentWithoutIdentifyingProp return (EventDefinition)definition; } - /// - /// The entity type '{entityType}' is mapped to the stored procedure '{sproc}', but the concurrency token '{token}' is not mapped to any original value parameter. - /// - public static EventDefinition LogStoredProcedureConcurrencyTokenNotMapped(IDiagnosticsLogger logger) - { - var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogStoredProcedureConcurrencyTokenNotMapped; - if (definition == null) - { - definition = NonCapturingLazyInitializer.EnsureInitialized( - ref ((RelationalLoggingDefinitions)logger.Definitions).LogStoredProcedureConcurrencyTokenNotMapped, - logger, - static logger => new EventDefinition( - logger.Options, - RelationalEventId.StoredProcedureConcurrencyTokenNotMapped, - LogLevel.Warning, - "RelationalEventId.StoredProcedureConcurrencyTokenNotMapped", - level => LoggerMessage.Define( - level, - RelationalEventId.StoredProcedureConcurrencyTokenNotMapped, - _resourceManager.GetString("LogStoredProcedureConcurrencyTokenNotMapped")!))); - } - - return (EventDefinition)definition; - } - /// /// Possible unintended use of method 'Equals' for arguments '{left}' and '{right}' of different types in a query. This comparison will always return false. /// @@ -3760,6 +3735,31 @@ public static EventDefinition LogRollingBackTransaction(IDiagnosticsLogger logge return (EventDefinition)definition; } + /// + /// The entity type '{entityType}' is mapped to the stored procedure '{sproc}', but the concurrency token '{token}' is not mapped to any original value parameter. + /// + public static EventDefinition LogStoredProcedureConcurrencyTokenNotMapped(IDiagnosticsLogger logger) + { + var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogStoredProcedureConcurrencyTokenNotMapped; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((RelationalLoggingDefinitions)logger.Definitions).LogStoredProcedureConcurrencyTokenNotMapped, + logger, + static logger => new EventDefinition( + logger.Options, + RelationalEventId.StoredProcedureConcurrencyTokenNotMapped, + LogLevel.Warning, + "RelationalEventId.StoredProcedureConcurrencyTokenNotMapped", + level => LoggerMessage.Define( + level, + RelationalEventId.StoredProcedureConcurrencyTokenNotMapped, + _resourceManager.GetString("LogStoredProcedureConcurrencyTokenNotMapped")!))); + } + + return (EventDefinition)definition; + } + /// /// The entity type '{entityType}' is using the table per concrete type mapping strategy, but property '{property}' is configured with an incompatible database-generated default. Configure a compatible value generation strategy if available, or use non-generated key values. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index d212c1beb9e..00203a4f7eb 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -1,17 +1,17 @@  - @@ -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. - - The query contained a new array expression containing non-constant elements, which could not be translated: '{newArrayExpression}'. - 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. + + The query contained a new array expression containing non-constant elements, which could not be translated: '{newArrayExpression}'. + Unable to translate the given 'GroupBy' pattern. Call 'AsEnumerable' before 'GroupBy' to evaluate it client-side. @@ -178,15 +178,15 @@ Instances of entity types '{firstEntityType}' and '{secondEntityType}' are mapped to the same row with the key value '{keyValue}', but have different property values '{firstConflictingValue}' and '{secondConflictingValue}' for the column '{column}'. - - Conflicting type mappings were inferred for column '{column}'. - A seed entity for entity type '{entityType}' has the same key value as another seed entity mapped to the same table '{table}', but have different values for the column '{column}'. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting values. A seed entity for entity type '{entityType}' has the same key value {keyValue} as another seed entity mapped to the same table '{table}', but have different values for the column '{column}' - '{firstValue}', '{secondValue}'. + + Conflicting type mappings were inferred for column '{column}'. + {numSortOrderProperties} values were provided in CreateIndexOperations.IsDescending, but the operation has {numColumns} columns. @@ -349,9 +349,6 @@ A seed entity for entity type '{entityType}' has the same key value {keyValue} as another seed entity mapped to the same table '{table}'. Key values should be unique across seed entities. - - Exactly one of '{param1}', '{param2}' or '{param3}' must be set. - Empty collections are not supported as inline query roots. @@ -560,7 +557,7 @@ Queries performing '{method}' operation must have a deterministic sort order. Rewrite the query to apply an 'OrderBy' operation on the sequence before calling '{method}'. - An ambient transaction has been detected, but the current provider does not support ambient transactions. See http://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 Warning RelationalEventId.AmbientTransactionWarning @@ -956,12 +953,15 @@ Expression '{sqlExpression}' in the SQL tree does not have a type mapping assigned. - - 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. + + Exactly one of '{param1}', '{param2}' or '{param3}' must be set. Only constants are supported inside inline collection query roots. + + 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. + Cannot use the value provided for parameter '{parameter}' because it isn't assignable to type object[]. @@ -1188,7 +1188,7 @@ The current provider doesn't have a store type mapping for properties of type '{clrType}'. - The database operation was expected to affect {expectedRows} row(s), but actually affected {actualRows} row(s); data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. + The database operation was expected to affect {expectedRows} row(s), but actually affected {actualRows} row(s); data may have been modified or deleted since entities were loaded. See https://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions. The number of key column types ({typesCount}) doesn't match the number of key columns ({columnsCount}) for the data modification operation on '{table}'. Provide the same number of key column types and key columns. diff --git a/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePropertyBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePropertyBuilderExtensions.cs index 14c4dc1e768..3e55e93207a 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePropertyBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePropertyBuilderExtensions.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; - // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore; diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs index f2aa1a664ca..11d8e694a1c 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerPropertyBuilderExtensions.cs @@ -696,7 +696,7 @@ public static bool CanSetValueGenerationStrategy( SqlServerValueGenerationStrategy? valueGenerationStrategy, bool fromDataAnnotation = false) => (valueGenerationStrategy == null - || SqlServerPrimitivePropertyBaseExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) + || SqlServerPropertyExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) && propertyBuilder.CanSetAnnotation( SqlServerAnnotationNames.ValueGenerationStrategy, valueGenerationStrategy, fromDataAnnotation); @@ -719,7 +719,7 @@ public static bool CanSetValueGenerationStrategy( in StoreObjectIdentifier storeObject, bool fromDataAnnotation = false) => (valueGenerationStrategy == null - || SqlServerPrimitivePropertyBaseExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) + || SqlServerPropertyExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) && (propertyBuilder.Metadata.FindOverrides(storeObject)?.Builder .CanSetAnnotation( SqlServerAnnotationNames.ValueGenerationStrategy, diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPrimitivePropertyBaseExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs similarity index 99% rename from src/EFCore.SqlServer/Extensions/SqlServerPrimitivePropertyBaseExtensions.cs rename to src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs index 43d2017a3a2..5251dbdabbf 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerPrimitivePropertyBaseExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore; /// Accessing SQL Server and SQL Azure databases with EF Core /// for more information and examples. /// -public static class SqlServerPrimitivePropertyBaseExtensions +public static class SqlServerPropertyExtensions { /// /// Returns the name to use for the hi-lo sequence. diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs index 911d23d5077..92d26598bce 100644 --- a/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs +++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerTransientExceptionDetector.cs @@ -78,16 +78,16 @@ public static bool ShouldRetryOn(Exception? ex) case 14355: // SQL Error Code: 10936 // Resource ID : %d. The request limit for the elastic pool is %d and has been reached. - // See 'http://go.microsoft.com/fwlink/?LinkId=267637' for assistance. + // See 'https://go.microsoft.com/fwlink/?LinkId=267637' for assistance. case 10936: // SQL Error Code: 10929 // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. // However, the server is currently too busy to support requests greater than %d for this database. - // For more information, see http://go.microsoft.com/fwlink/?LinkId=267637. Otherwise, please try again. + // For more information, see https://go.microsoft.com/fwlink/?LinkId=267637. Otherwise, please try again. case 10929: // SQL Error Code: 10928 // Resource ID: %d. The %s limit for the database is %d and has been reached. For more information, - // see http://go.microsoft.com/fwlink/?LinkId=267637. + // see https://go.microsoft.com/fwlink/?LinkId=267637. case 10928: // SQL Error Code: 10922 // %ls failed. Rerun the statement. diff --git a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs index b0878379a99..8aec3df3a1c 100644 --- a/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs +++ b/src/EFCore.Sqlite.Core/Metadata/Internal/SqliteAnnotationProvider.cs @@ -67,8 +67,7 @@ public override IEnumerable For(IColumn column, bool designTime) // Model validation ensures that these facets are the same on all mapped properties var property = column.PropertyMappings.First().Property; // Only return auto increment for integer single column primary key - var primaryKey = ((property.DeclaringType as IEntityType) ?? ((IComplexType)property.DeclaringType).FundamentalEntityType) - .FindPrimaryKey(); + var primaryKey = property.DeclaringType.FundamentalEntityType.FindPrimaryKey(); if (primaryKey is { Properties.Count: 1 } && primaryKey.Properties[0] == property && property.ValueGenerated == ValueGenerated.OnAdd diff --git a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs index 8794b7b9b1b..7d8d5beba71 100644 --- a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs +++ b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.Designer.cs @@ -60,7 +60,7 @@ public static string IncompatibleSqlReturningClauseMismatch(object? table, objec table, entityType, otherEntityType, entityTypeWithSqlReturningClause, entityTypeWithoutSqlReturningClause); /// - /// SQLite does not support this migration operation ('{operation}'). See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. + /// SQLite does not support this migration operation ('{operation}'). See https://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. /// public static string InvalidMigrationOperation(object? operation) => string.Format( @@ -68,7 +68,7 @@ public static string InvalidMigrationOperation(object? operation) operation); /// - /// Generating idempotent scripts for migrations is not currently supported for SQLite. See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. + /// Generating idempotent scripts for migrations is not currently supported for SQLite. See https://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. /// public static string MigrationScriptGenerationNotSupported => GetString("MigrationScriptGenerationNotSupported"); @@ -82,13 +82,13 @@ public static string OrderByNotSupported(object? type) type); /// - /// SQLite does not support sequences. See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. + /// SQLite does not support sequences. See https://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. /// public static string SequencesNotSupported => GetString("SequencesNotSupported"); /// - /// SQLite does not support stored procedures, but one has been configured on entity type '{entityType}'. See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. + /// SQLite does not support stored procedures, but one has been configured on entity type '{entityType}'. See https://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. /// public static string StoredProceduresNotSupported(object? entityType) => string.Format( diff --git a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx index aa3444f088b..bf8895f442b 100644 --- a/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx +++ b/src/EFCore.Sqlite.Core/Properties/SqliteStrings.resx @@ -133,7 +133,7 @@ Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and entity type '{entityTypeWithSqlReturningClause}' is configured to use the SQL RETURNING clause, but entity type '{entityTypeWithoutSqlReturningClause}' is not. - SQLite does not support this migration operation ('{operation}'). See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. + SQLite does not support this migration operation ('{operation}'). See https://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. The entity type '{entityType}' has composite key '{key}' which is configured to use generated values. SQLite does not support generated values on composite keys. @@ -208,15 +208,15 @@ Warning SqliteEventId.SchemasNotSupportedWarning - Generating idempotent scripts for migrations is not currently supported for SQLite. See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. + Generating idempotent scripts for migrations is not currently supported for SQLite. See https://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. SQLite does not support expressions of type '{type}' in ORDER BY clauses. Convert the values to a supported type, or use LINQ to Objects to order the results on the client side. - SQLite does not support sequences. See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. + SQLite does not support sequences. See https://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. - SQLite does not support stored procedures, but one has been configured on entity type '{entityType}'. See http://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. + SQLite does not support stored procedures, but one has been configured on entity type '{entityType}'. See https://go.microsoft.com/fwlink/?LinkId=723262 for more information and examples. \ No newline at end of file diff --git a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs index 4a73cb154ca..a0ed902da1e 100644 --- a/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/PropertyDiscoveryConvention.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; diff --git a/src/EFCore/Metadata/Conventions/TypeAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/TypeAttributeConventionBase.cs index 4bf7904f143..5b041314cd9 100644 --- a/src/EFCore/Metadata/Conventions/TypeAttributeConventionBase.cs +++ b/src/EFCore/Metadata/Conventions/TypeAttributeConventionBase.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// -/// A base type for conventions that perform configuration based on an attribute specified on an entity type. +/// A base type for conventions that perform configuration based on an attribute specified on a structural type. /// /// /// See Model building conventions for more information and examples. diff --git a/src/EFCore/Metadata/IMutableComplexType.cs b/src/EFCore/Metadata/IMutableComplexType.cs index d2a465f6b9a..4e9056ba685 100644 --- a/src/EFCore/Metadata/IMutableComplexType.cs +++ b/src/EFCore/Metadata/IMutableComplexType.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.Diagnostics.CodeAnalysis; - namespace Microsoft.EntityFrameworkCore.Metadata; /// diff --git a/src/EFCore/Metadata/IReadOnlyProperty.cs b/src/EFCore/Metadata/IReadOnlyProperty.cs index e48674fd83d..79b18681f02 100644 --- a/src/EFCore/Metadata/IReadOnlyProperty.cs +++ b/src/EFCore/Metadata/IReadOnlyProperty.cs @@ -5,436 +5,435 @@ using Microsoft.EntityFrameworkCore.Storage.Json; using System.Text; -namespace Microsoft.EntityFrameworkCore.Metadata +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a scalar property of a structural type. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IReadOnlyProperty : IReadOnlyPropertyBase { /// - /// Represents a scalar property of a structural type. + /// Gets the entity type that this property belongs to. /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - public interface IReadOnlyProperty : IReadOnlyPropertyBase + [Obsolete("Use DeclaringType and cast to IReadOnlyEntityType or IReadOnlyComplexType")] + IReadOnlyEntityType DeclaringEntityType => (IReadOnlyEntityType)DeclaringType; + + /// + /// Gets a value indicating whether this property can contain . + /// + bool IsNullable { get; } + + /// + /// Gets a value indicating when a value for this property will be generated by the database. Even when the + /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than + /// having one generated by the database) when the entity is added and a value is assigned, or the property is + /// marked as modified for an existing entity. See + /// and for more information and examples. + /// + ValueGenerated ValueGenerated { get; } + + /// + /// Gets a value indicating whether this property is used as a concurrency token. When a property is configured + /// as a concurrency token the value in the database will be checked when an instance of this entity type + /// is updated or deleted during to ensure it has not changed since + /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the + /// changes will not be applied to the database. + /// + bool IsConcurrencyToken { get; } + + /// + /// Returns the for the given property from a finalized model. + /// + /// The type mapping. + CoreTypeMapping GetTypeMapping() { - /// - /// Gets the entity type that this property belongs to. - /// - [Obsolete("Use DeclaringType and cast to IReadOnlyEntityType or IReadOnlyComplexType")] - IReadOnlyEntityType DeclaringEntityType => (IReadOnlyEntityType)DeclaringType; - - /// - /// Gets a value indicating whether this property can contain . - /// - bool IsNullable { get; } - - /// - /// Gets a value indicating when a value for this property will be generated by the database. Even when the - /// property is set to be generated by the database, EF may still attempt to save a specific value (rather than - /// having one generated by the database) when the entity is added and a value is assigned, or the property is - /// marked as modified for an existing entity. See - /// and for more information and examples. - /// - ValueGenerated ValueGenerated { get; } - - /// - /// Gets a value indicating whether this property is used as a concurrency token. When a property is configured - /// as a concurrency token the value in the database will be checked when an instance of this entity type - /// is updated or deleted during to ensure it has not changed since - /// the instance was retrieved from the database. If it has changed, an exception will be thrown and the - /// changes will not be applied to the database. - /// - bool IsConcurrencyToken { get; } - - /// - /// Returns the for the given property from a finalized model. - /// - /// The type mapping. - CoreTypeMapping GetTypeMapping() + var mapping = FindTypeMapping(); + if (mapping == null) { - var mapping = FindTypeMapping(); - if (mapping == null) - { - throw new InvalidOperationException(CoreStrings.ModelNotFinalized(nameof(GetTypeMapping))); - } - - return mapping; + throw new InvalidOperationException(CoreStrings.ModelNotFinalized(nameof(GetTypeMapping))); } - /// - /// Returns the type mapping for this property. - /// - /// The type mapping, or if none was found. - CoreTypeMapping? FindTypeMapping(); - - /// - /// Gets the maximum length of data that is allowed in this property. For example, if the property is a - /// then this is the maximum number of characters. - /// - /// - /// The maximum length, -1 if the property has no maximum length, or if the maximum length hasn't been - /// set. - /// - int? GetMaxLength(); - - /// - /// Gets the precision of data that is allowed in this property. - /// For example, if the property is a then this is the maximum number of digits. - /// - /// The precision, or if none is defined. - int? GetPrecision(); - - /// - /// Gets the scale of data that is allowed in this property. - /// For example, if the property is a then this is the maximum number of decimal places. - /// - /// The scale, or if none is defined. - int? GetScale(); - - /// - /// Gets a value indicating whether or not the property can persist Unicode characters. - /// - /// The Unicode setting, or if none is defined. - bool? IsUnicode(); - - /// - /// Gets a value indicating whether or not this property can be modified before the entity is - /// saved to the database. - /// - /// - /// - /// If , then an exception - /// will be thrown if a value is assigned to this property when it is in - /// the state. - /// - /// - /// If , then any value - /// set will be ignored when it is in the state. - /// - /// - /// The before save behavior for this property. - PropertySaveBehavior GetBeforeSaveBehavior(); - - /// - /// Gets a value indicating whether or not this property can be modified after the entity is - /// saved to the database. - /// - /// - /// - /// If , then an exception - /// will be thrown if a new value is assigned to this property after the entity exists in the database. - /// - /// - /// If , then any modification to the - /// property value of an entity that already exists in the database will be ignored. - /// - /// - /// The after save behavior for this property. - PropertySaveBehavior GetAfterSaveBehavior(); - - /// - /// Gets the factory that has been set to generate values for this property, if any. - /// - /// The factory, or if no factory has been set. - Func? GetValueGeneratorFactory(); - - /// - /// Gets the custom set for this property. - /// - /// The converter, or if none has been set. - ValueConverter? GetValueConverter(); - - /// - /// Gets the type that the property value will be converted to before being sent to the database provider. - /// - /// The provider type, or if none has been set. - Type? GetProviderClrType(); - - /// - /// Gets the for this property, or if none is set. - /// - /// The comparer, or if none has been set. - ValueComparer? GetValueComparer(); - - /// - /// Gets the to use with keys for this property, or if none is set. - /// - /// The comparer, or if none has been set. - ValueComparer? GetKeyValueComparer(); - - /// - /// Gets the to use for the provider values for this property. - /// - /// The comparer, or if none has been set. - ValueComparer? GetProviderValueComparer(); - - /// - /// Gets the for this property, or if none is set. - /// - /// The reader/writer, or if none has been set. - JsonValueReaderWriter? GetJsonValueReaderWriter(); - - /// - /// Finds the first principal property that the given property is constrained by - /// if the given property is part of a foreign key. - /// - /// The first associated principal property, or if none exists. - IReadOnlyProperty? FindFirstPrincipal() + return mapping; + } + + /// + /// Returns the type mapping for this property. + /// + /// The type mapping, or if none was found. + CoreTypeMapping? FindTypeMapping(); + + /// + /// Gets the maximum length of data that is allowed in this property. For example, if the property is a + /// then this is the maximum number of characters. + /// + /// + /// The maximum length, -1 if the property has no maximum length, or if the maximum length hasn't been + /// set. + /// + int? GetMaxLength(); + + /// + /// Gets the precision of data that is allowed in this property. + /// For example, if the property is a then this is the maximum number of digits. + /// + /// The precision, or if none is defined. + int? GetPrecision(); + + /// + /// Gets the scale of data that is allowed in this property. + /// For example, if the property is a then this is the maximum number of decimal places. + /// + /// The scale, or if none is defined. + int? GetScale(); + + /// + /// Gets a value indicating whether or not the property can persist Unicode characters. + /// + /// The Unicode setting, or if none is defined. + bool? IsUnicode(); + + /// + /// Gets a value indicating whether or not this property can be modified before the entity is + /// saved to the database. + /// + /// + /// + /// If , then an exception + /// will be thrown if a value is assigned to this property when it is in + /// the state. + /// + /// + /// If , then any value + /// set will be ignored when it is in the state. + /// + /// + /// The before save behavior for this property. + PropertySaveBehavior GetBeforeSaveBehavior(); + + /// + /// Gets a value indicating whether or not this property can be modified after the entity is + /// saved to the database. + /// + /// + /// + /// If , then an exception + /// will be thrown if a new value is assigned to this property after the entity exists in the database. + /// + /// + /// If , then any modification to the + /// property value of an entity that already exists in the database will be ignored. + /// + /// + /// The after save behavior for this property. + PropertySaveBehavior GetAfterSaveBehavior(); + + /// + /// Gets the factory that has been set to generate values for this property, if any. + /// + /// The factory, or if no factory has been set. + Func? GetValueGeneratorFactory(); + + /// + /// Gets the custom set for this property. + /// + /// The converter, or if none has been set. + ValueConverter? GetValueConverter(); + + /// + /// Gets the type that the property value will be converted to before being sent to the database provider. + /// + /// The provider type, or if none has been set. + Type? GetProviderClrType(); + + /// + /// Gets the for this property, or if none is set. + /// + /// The comparer, or if none has been set. + ValueComparer? GetValueComparer(); + + /// + /// Gets the to use with keys for this property, or if none is set. + /// + /// The comparer, or if none has been set. + ValueComparer? GetKeyValueComparer(); + + /// + /// Gets the to use for the provider values for this property. + /// + /// The comparer, or if none has been set. + ValueComparer? GetProviderValueComparer(); + + /// + /// Gets the for this property, or if none is set. + /// + /// The reader/writer, or if none has been set. + JsonValueReaderWriter? GetJsonValueReaderWriter(); + + /// + /// Finds the first principal property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The first associated principal property, or if none exists. + IReadOnlyProperty? FindFirstPrincipal() + { + foreach (var foreignKey in GetContainingForeignKeys()) { - foreach (var foreignKey in GetContainingForeignKeys()) + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) { - for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + if (this == foreignKey.Properties[propertyIndex]) { - if (this == foreignKey.Properties[propertyIndex]) - { - return foreignKey.PrincipalKey.Properties[propertyIndex]; - } + return foreignKey.PrincipalKey.Properties[propertyIndex]; } } - - return null; } - /// - /// Finds the list of principal properties including the given property that the given property is constrained by - /// if the given property is part of a foreign key. - /// - /// The list of all associated principal properties including the given property. - IReadOnlyList GetPrincipals() - => GetPrincipals(); - - /// - /// Finds the list of principal properties including the given property that the given property is constrained by - /// if the given property is part of a foreign key. - /// - /// The list of all associated principal properties including the given property. - IReadOnlyList GetPrincipals() - where T : IReadOnlyProperty - { - var principals = new List { (T)this }; - AddPrincipals((T)this, principals); - return principals; - } + return null; + } - private static void AddPrincipals(T property, List visited) - where T : IReadOnlyProperty + /// + /// Finds the list of principal properties including the given property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The list of all associated principal properties including the given property. + IReadOnlyList GetPrincipals() + => GetPrincipals(); + + /// + /// Finds the list of principal properties including the given property that the given property is constrained by + /// if the given property is part of a foreign key. + /// + /// The list of all associated principal properties including the given property. + IReadOnlyList GetPrincipals() + where T : IReadOnlyProperty + { + var principals = new List { (T)this }; + AddPrincipals((T)this, principals); + return principals; + } + + private static void AddPrincipals(T property, List visited) + where T : IReadOnlyProperty + { + foreach (var foreignKey in property.GetContainingForeignKeys()) { - foreach (var foreignKey in property.GetContainingForeignKeys()) + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) { - for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + if (ReferenceEquals(property, foreignKey.Properties[propertyIndex])) { - if (ReferenceEquals(property, foreignKey.Properties[propertyIndex])) + var principal = (T)foreignKey.PrincipalKey.Properties[propertyIndex]; + if (!visited.Contains(principal)) { - var principal = (T)foreignKey.PrincipalKey.Properties[propertyIndex]; - if (!visited.Contains(principal)) - { - visited.Add(principal); + visited.Add(principal); - AddPrincipals(principal, visited); - } + AddPrincipals(principal, visited); } } } } + } - /// - /// Gets a value indicating whether this property is used as a foreign key (or part of a composite foreign key). - /// - /// if the property is used as a foreign key, otherwise . - bool IsForeignKey(); - - /// - /// Gets all foreign keys that use this property (including composite foreign keys in which this property - /// is included). - /// - /// The foreign keys that use this property. - IEnumerable GetContainingForeignKeys(); - - /// - /// Gets a value indicating whether this property is used as an index (or part of a composite index). - /// - /// if the property is used as an index, otherwise . - bool IsIndex(); - - /// - /// Gets a value indicating whether this property is used as a unique index (or part of a unique composite index). - /// - /// if the property is used as an unique index, otherwise . - bool IsUniqueIndex() - => GetContainingIndexes().Any(e => e.IsUnique); - - /// - /// Gets all indexes that use this property (including composite indexes in which this property - /// is included). - /// - /// The indexes that use this property. - IEnumerable GetContainingIndexes(); - - /// - /// Gets a value indicating whether this property is used as the primary key (or part of a composite primary key). - /// - /// if the property is used as the primary key, otherwise . - bool IsPrimaryKey() - => FindContainingPrimaryKey() != null; - - /// - /// Gets the primary key that uses this property (including a composite primary key in which this property - /// is included). - /// - /// The primary that use this property, or if it is not part of the primary key. - IReadOnlyKey? FindContainingPrimaryKey(); - - /// - /// Gets a value indicating whether this property is used as the primary key or alternate key - /// (or part of a composite primary or alternate key). - /// - /// if the property is used as a key, otherwise . - bool IsKey(); - - /// - /// Gets all primary or alternate keys that use this property (including composite keys in which this property - /// is included). - /// - /// The primary and alternate keys that use this property. - IEnumerable GetContainingKeys(); - - /// - /// - /// Creates a human-readable representation of the given metadata. - /// - /// - /// Warning: Do not rely on the format of the returned string. - /// It is designed for debugging only and may change arbitrarily between releases. - /// - /// - /// Options for generating the string. - /// The number of indent spaces to use before each new line. - /// A human-readable representation. - string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) - { - var builder = new StringBuilder(); - var indentString = new string(' ', indent); + /// + /// Gets a value indicating whether this property is used as a foreign key (or part of a composite foreign key). + /// + /// if the property is used as a foreign key, otherwise . + bool IsForeignKey(); - try - { - builder.Append(indentString); + /// + /// Gets all foreign keys that use this property (including composite foreign keys in which this property + /// is included). + /// + /// The foreign keys that use this property. + IEnumerable GetContainingForeignKeys(); - var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; - if (singleLine) - { - builder.Append($"Property: {DeclaringType.DisplayName()}."); - } + /// + /// Gets a value indicating whether this property is used as an index (or part of a composite index). + /// + /// if the property is used as an index, otherwise . + bool IsIndex(); - builder.Append(Name).Append(" ("); + /// + /// Gets a value indicating whether this property is used as a unique index (or part of a unique composite index). + /// + /// if the property is used as an unique index, otherwise . + bool IsUniqueIndex() + => GetContainingIndexes().Any(e => e.IsUnique); - var field = GetFieldName(); - if (field == null) - { - builder.Append("no field, "); - } - else if (!field.EndsWith(">k__BackingField", StringComparison.Ordinal)) - { - builder.Append(field).Append(", "); - } + /// + /// Gets all indexes that use this property (including composite indexes in which this property + /// is included). + /// + /// The indexes that use this property. + IEnumerable GetContainingIndexes(); - builder.Append(ClrType.ShortDisplayName()).Append(')'); + /// + /// Gets a value indicating whether this property is used as the primary key (or part of a composite primary key). + /// + /// if the property is used as the primary key, otherwise . + bool IsPrimaryKey() + => FindContainingPrimaryKey() != null; - if (IsShadowProperty()) - { - builder.Append(" Shadow"); - } + /// + /// Gets the primary key that uses this property (including a composite primary key in which this property + /// is included). + /// + /// The primary that use this property, or if it is not part of the primary key. + IReadOnlyKey? FindContainingPrimaryKey(); - if (IsIndexerProperty()) - { - builder.Append(" Indexer"); - } + /// + /// Gets a value indicating whether this property is used as the primary key or alternate key + /// (or part of a composite primary or alternate key). + /// + /// if the property is used as a key, otherwise . + bool IsKey(); - if (!IsNullable) - { - builder.Append(" Required"); - } + /// + /// Gets all primary or alternate keys that use this property (including composite keys in which this property + /// is included). + /// + /// The primary and alternate keys that use this property. + IEnumerable GetContainingKeys(); - if (IsPrimaryKey()) - { - builder.Append(" PK"); - } + /// + /// + /// Creates a human-readable representation of the given metadata. + /// + /// + /// Warning: Do not rely on the format of the returned string. + /// It is designed for debugging only and may change arbitrarily between releases. + /// + /// + /// Options for generating the string. + /// The number of indent spaces to use before each new line. + /// A human-readable representation. + string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOptions.ShortDefault, int indent = 0) + { + var builder = new StringBuilder(); + var indentString = new string(' ', indent); - if (IsForeignKey()) - { - builder.Append(" FK"); - } + try + { + builder.Append(indentString); - if (IsKey() - && !IsPrimaryKey()) - { - builder.Append(" AlternateKey"); - } + var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; + if (singleLine) + { + builder.Append($"Property: {DeclaringType.DisplayName()}."); + } - if (IsIndex()) - { - builder.Append(" Index"); - } + builder.Append(Name).Append(" ("); - if (IsConcurrencyToken) - { - builder.Append(" Concurrency"); - } + var field = GetFieldName(); + if (field == null) + { + builder.Append("no field, "); + } + else if (!field.EndsWith(">k__BackingField", StringComparison.Ordinal)) + { + builder.Append(field).Append(", "); + } - if (Sentinel != null && !Equals(Sentinel, ClrType.GetDefaultValue())) - { - builder.Append(" Sentinel:").Append(Sentinel); - } + builder.Append(ClrType.ShortDisplayName()).Append(')'); - if (GetBeforeSaveBehavior() != PropertySaveBehavior.Save) - { - builder.Append(" BeforeSave:").Append(GetBeforeSaveBehavior()); - } + if (IsShadowProperty()) + { + builder.Append(" Shadow"); + } - if (GetAfterSaveBehavior() != PropertySaveBehavior.Save) - { - builder.Append(" AfterSave:").Append(GetAfterSaveBehavior()); - } + if (IsIndexerProperty()) + { + builder.Append(" Indexer"); + } - if (ValueGenerated != ValueGenerated.Never) - { - builder.Append(" ValueGenerated.").Append(ValueGenerated); - } + if (!IsNullable) + { + builder.Append(" Required"); + } - if (GetMaxLength() != null) - { - builder.Append(" MaxLength(").Append(GetMaxLength()).Append(')'); - } + if (IsPrimaryKey()) + { + builder.Append(" PK"); + } - if (IsUnicode() == false) - { - builder.Append(" ANSI"); - } + if (IsForeignKey()) + { + builder.Append(" FK"); + } - if (GetPropertyAccessMode() != PropertyAccessMode.PreferField) - { - builder.Append(" PropertyAccessMode.").Append(GetPropertyAccessMode()); - } + if (IsKey() + && !IsPrimaryKey()) + { + builder.Append(" AlternateKey"); + } - if ((options & MetadataDebugStringOptions.IncludePropertyIndexes) != 0 - && ((AnnotatableBase)this).IsReadOnly) - { - var indexes = ((IProperty)this).GetPropertyIndexes(); - builder.Append(' ').Append(indexes.Index); - builder.Append(' ').Append(indexes.OriginalValueIndex); - builder.Append(' ').Append(indexes.RelationshipIndex); - builder.Append(' ').Append(indexes.ShadowIndex); - builder.Append(' ').Append(indexes.StoreGenerationIndex); - } + if (IsIndex()) + { + builder.Append(" Index"); + } - if (!singleLine && (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) - { - builder.Append(AnnotationsToDebugString(indent + 2)); - } + if (IsConcurrencyToken) + { + builder.Append(" Concurrency"); } - catch (Exception exception) + + if (Sentinel != null && !Equals(Sentinel, ClrType.GetDefaultValue())) { - builder.AppendLine().AppendLine(CoreStrings.DebugViewError(exception.Message)); + builder.Append(" Sentinel:").Append(Sentinel); } - return builder.ToString(); + if (GetBeforeSaveBehavior() != PropertySaveBehavior.Save) + { + builder.Append(" BeforeSave:").Append(GetBeforeSaveBehavior()); + } + + if (GetAfterSaveBehavior() != PropertySaveBehavior.Save) + { + builder.Append(" AfterSave:").Append(GetAfterSaveBehavior()); + } + + if (ValueGenerated != ValueGenerated.Never) + { + builder.Append(" ValueGenerated.").Append(ValueGenerated); + } + + if (GetMaxLength() != null) + { + builder.Append(" MaxLength(").Append(GetMaxLength()).Append(')'); + } + + if (IsUnicode() == false) + { + builder.Append(" ANSI"); + } + + if (GetPropertyAccessMode() != PropertyAccessMode.PreferField) + { + builder.Append(" PropertyAccessMode.").Append(GetPropertyAccessMode()); + } + + if ((options & MetadataDebugStringOptions.IncludePropertyIndexes) != 0 + && ((AnnotatableBase)this).IsReadOnly) + { + var indexes = ((IProperty)this).GetPropertyIndexes(); + builder.Append(' ').Append(indexes.Index); + builder.Append(' ').Append(indexes.OriginalValueIndex); + builder.Append(' ').Append(indexes.RelationshipIndex); + builder.Append(' ').Append(indexes.ShadowIndex); + builder.Append(' ').Append(indexes.StoreGenerationIndex); + } + + if (!singleLine && (options & MetadataDebugStringOptions.IncludeAnnotations) != 0) + { + builder.Append(AnnotationsToDebugString(indent + 2)); + } } + catch (Exception exception) + { + builder.AppendLine().AppendLine(CoreStrings.DebugViewError(exception.Message)); + } + + return builder.ToString(); } } diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 69bb2e5ddb8..97fd4cd99e8 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using System.Globalization; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; -using static System.Environment; namespace Microsoft.EntityFrameworkCore.Metadata.Internal; diff --git a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs index 2e75e045c57..9531edcdb58 100644 --- a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; -using System.Security.AccessControl; namespace Microsoft.EntityFrameworkCore.Metadata.Internal; diff --git a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs index ccf46dd0d86..882374d2ac4 100644 --- a/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalPropertyBuilder.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// -/// this is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// 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. @@ -15,7 +15,7 @@ public class InternalPropertyBuilder : InternalPropertyBaseBuilder, IConventionPropertyBuilder { /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -26,7 +26,7 @@ public InternalPropertyBuilder(Property property, InternalModelBuilder modelBuil } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -34,7 +34,7 @@ public InternalPropertyBuilder(Property property, InternalModelBuilder modelBuil protected override IConventionPropertyBuilder This => this; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -77,7 +77,7 @@ public InternalPropertyBuilder(Property property, InternalModelBuilder modelBuil } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -91,7 +91,7 @@ public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? config && Metadata.GetContainingKeys().All(k => configurationSource.Overrides(k.GetConfigurationSource())))); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -109,7 +109,7 @@ public virtual bool CanSetIsRequired(bool? required, ConfigurationSource? config } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -119,7 +119,7 @@ public virtual bool CanSetValueGenerated(ValueGenerated? valueGenerated, Configu || Metadata.ValueGenerated == valueGenerated; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -136,7 +136,7 @@ public virtual bool CanSetValueGenerated(ValueGenerated? valueGenerated, Configu } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -146,7 +146,7 @@ public virtual bool CanSetIsConcurrencyToken(bool? concurrencyToken, Configurati || Metadata.IsConcurrencyToken == concurrencyToken; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -164,7 +164,7 @@ public virtual bool CanSetIsConcurrencyToken(bool? concurrencyToken, Configurati } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -174,7 +174,7 @@ public virtual bool CanSetSentinel(object? sentinel, ConfigurationSource? config || Equals(Metadata.Sentinel, sentinel); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -185,7 +185,7 @@ public virtual bool CanSetSentinel(object? sentinel, ConfigurationSource? config : this; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -196,7 +196,7 @@ public virtual bool CanSetSentinel(object? sentinel, ConfigurationSource? config : this; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -209,7 +209,7 @@ public virtual bool CanSetSentinel(object? sentinel, ConfigurationSource? config : this; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -227,7 +227,7 @@ public virtual bool CanSetSentinel(object? sentinel, ConfigurationSource? config } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -237,7 +237,7 @@ public virtual bool CanSetMaxLength(int? maxLength, ConfigurationSource? configu || Metadata.GetMaxLength() == maxLength; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -255,7 +255,7 @@ public virtual bool CanSetMaxLength(int? maxLength, ConfigurationSource? configu } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -265,7 +265,7 @@ public virtual bool CanSetPrecision(int? precision, ConfigurationSource? configu || Metadata.GetPrecision() == precision; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -283,7 +283,7 @@ public virtual bool CanSetPrecision(int? precision, ConfigurationSource? configu } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -293,7 +293,7 @@ public virtual bool CanSetScale(int? scale, ConfigurationSource? configurationSo || Metadata.GetScale() == scale; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -311,7 +311,7 @@ public virtual bool CanSetScale(int? scale, ConfigurationSource? configurationSo } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -321,7 +321,7 @@ public virtual bool CanSetIsUnicode(bool? unicode, ConfigurationSource? configur || Metadata.IsUnicode() == unicode; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -339,7 +339,7 @@ public virtual bool CanSetIsUnicode(bool? unicode, ConfigurationSource? configur } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -349,7 +349,7 @@ public virtual bool CanSetBeforeSave(PropertySaveBehavior? behavior, Configurati || Metadata.GetBeforeSaveBehavior() == behavior; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -367,7 +367,7 @@ public virtual bool CanSetBeforeSave(PropertySaveBehavior? behavior, Configurati } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -379,7 +379,7 @@ public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, Configuratio || Metadata.GetAfterSaveBehavior() == behavior; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -417,7 +417,7 @@ public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, Configuratio } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -437,7 +437,7 @@ public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, Configuratio } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -457,7 +457,7 @@ public virtual bool CanSetAfterSave(PropertySaveBehavior? behavior, Configuratio } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -470,7 +470,7 @@ public virtual bool CanSetValueGenerator( && (Func?)Metadata[CoreAnnotationNames.ValueGeneratorFactory] == factory); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -483,7 +483,7 @@ public virtual bool CanSetValueGeneratorFactory( && (Type?)Metadata[CoreAnnotationNames.ValueGeneratorFactoryType] == factory); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -502,7 +502,7 @@ public virtual bool CanSetValueGeneratorFactory( } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -518,7 +518,7 @@ public virtual bool CanSetConversion( && configurationSource.Overrides(Metadata.GetProviderClrTypeConfigurationSource()); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -537,7 +537,7 @@ public virtual bool CanSetConversion( } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -548,7 +548,7 @@ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? && configurationSource.Overrides(Metadata.GetValueConverterConfigurationSource()); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -569,7 +569,7 @@ public virtual bool CanSetConversion(Type? providerClrType, ConfigurationSource? } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -582,7 +582,7 @@ public virtual bool CanSetConverter( && (Type?)Metadata[CoreAnnotationNames.ValueConverterType] == converterType); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -602,7 +602,7 @@ public virtual bool CanSetConverter( } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -612,7 +612,7 @@ public virtual bool CanSetTypeMapping(CoreTypeMapping? typeMapping, Configuratio || Metadata.TypeMapping == typeMapping; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -632,7 +632,7 @@ public virtual bool CanSetTypeMapping(CoreTypeMapping? typeMapping, Configuratio } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -660,7 +660,7 @@ public virtual bool CanSetValueComparer(ValueComparer? comparer, ConfigurationSo } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -680,7 +680,7 @@ public virtual bool CanSetValueComparer(ValueComparer? comparer, ConfigurationSo } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -691,7 +691,7 @@ public virtual bool CanSetValueComparer(Type? comparerType, ConfigurationSource? && (Type?)Metadata[CoreAnnotationNames.ValueComparerType] == comparerType); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -711,7 +711,7 @@ public virtual bool CanSetValueComparer(Type? comparerType, ConfigurationSource? } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -728,7 +728,7 @@ public virtual bool CanSetProviderValueComparer(ValueComparer? comparer, Configu } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -748,7 +748,7 @@ public virtual bool CanSetProviderValueComparer(ValueComparer? comparer, Configu } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -762,7 +762,7 @@ public virtual bool CanSetProviderValueComparer( /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -872,7 +872,7 @@ public virtual bool CanSetProviderValueComparer( } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -884,7 +884,7 @@ IConventionPropertyBase IConventionPropertyBaseBuilder - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -896,7 +896,7 @@ IConventionProperty IConventionPropertyBuilder.Metadata } /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -908,7 +908,7 @@ IConventionProperty IConventionPropertyBuilder.Metadata == null ? null : this; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -920,7 +920,7 @@ IConventionProperty IConventionPropertyBuilder.Metadata == null ? null : this; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -932,7 +932,7 @@ IConventionProperty IConventionPropertyBuilder.Metadata == null ? null : this; /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -941,7 +941,7 @@ IConventionProperty IConventionPropertyBuilder.Metadata => IsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -950,7 +950,7 @@ bool IConventionPropertyBuilder.CanSetIsRequired(bool? required, bool fromDataAn => CanSetIsRequired(required, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -959,7 +959,7 @@ bool IConventionPropertyBuilder.CanSetIsRequired(bool? required, bool fromDataAn => ValueGenerated(valueGenerated, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -969,7 +969,7 @@ bool IConventionPropertyBuilder.CanSetValueGenerated(ValueGenerated? valueGenera valueGenerated, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -979,7 +979,7 @@ bool IConventionPropertyBuilder.CanSetValueGenerated(ValueGenerated? valueGenera concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -989,7 +989,7 @@ bool IConventionPropertyBuilder.CanSetIsConcurrencyToken(bool? concurrencyToken, concurrencyToken, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -998,7 +998,7 @@ bool IConventionPropertyBuilder.CanSetIsConcurrencyToken(bool? concurrencyToken, => HasSentinel(sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1007,7 +1007,7 @@ bool IConventionPropertyBuilder.CanSetSentinel(object? sentinel, bool fromDataAn => CanSetSentinel(sentinel, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1016,7 +1016,7 @@ bool IConventionPropertyBuilder.CanSetSentinel(object? sentinel, bool fromDataAn => HasField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1025,7 +1025,7 @@ bool IConventionPropertyBuilder.CanSetSentinel(object? sentinel, bool fromDataAn => HasField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1034,7 +1034,7 @@ bool IConventionPropertyBaseBuilder.CanSetField(stri => CanSetField(fieldName, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1043,7 +1043,7 @@ bool IConventionPropertyBaseBuilder.CanSetField(Fiel => CanSetField(fieldInfo, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1055,7 +1055,7 @@ bool IConventionPropertyBaseBuilder.CanSetField(Fiel propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1065,7 +1065,7 @@ bool IConventionPropertyBaseBuilder.CanSetPropertyAc propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1074,7 +1074,7 @@ bool IConventionPropertyBaseBuilder.CanSetPropertyAc => HasMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1083,7 +1083,7 @@ bool IConventionPropertyBuilder.CanSetMaxLength(int? maxLength, bool fromDataAnn => CanSetMaxLength(maxLength, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1092,7 +1092,7 @@ bool IConventionPropertyBuilder.CanSetMaxLength(int? maxLength, bool fromDataAnn => IsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1101,7 +1101,7 @@ bool IConventionPropertyBuilder.CanSetIsUnicode(bool? unicode, bool fromDataAnno => CanSetIsUnicode(unicode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1110,7 +1110,7 @@ bool IConventionPropertyBuilder.CanSetIsUnicode(bool? unicode, bool fromDataAnno => HasPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1119,7 +1119,7 @@ bool IConventionPropertyBuilder.CanSetPrecision(int? precision, bool fromDataAnn => CanSetPrecision(precision, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1128,7 +1128,7 @@ bool IConventionPropertyBuilder.CanSetPrecision(int? precision, bool fromDataAnn => HasScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1137,7 +1137,7 @@ bool IConventionPropertyBuilder.CanSetScale(int? scale, bool fromDataAnnotation) => CanSetScale(scale, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1146,7 +1146,7 @@ bool IConventionPropertyBuilder.CanSetScale(int? scale, bool fromDataAnnotation) => BeforeSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1155,7 +1155,7 @@ bool IConventionPropertyBuilder.CanSetBeforeSave(PropertySaveBehavior? behavior, => CanSetBeforeSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1164,7 +1164,7 @@ bool IConventionPropertyBuilder.CanSetBeforeSave(PropertySaveBehavior? behavior, => AfterSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1173,7 +1173,7 @@ bool IConventionPropertyBuilder.CanSetAfterSave(PropertySaveBehavior? behavior, => CanSetAfterSave(behavior, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1185,7 +1185,7 @@ bool IConventionPropertyBuilder.CanSetAfterSave(PropertySaveBehavior? behavior, valueGeneratorType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1196,7 +1196,7 @@ bool IConventionPropertyBuilder.CanSetAfterSave(PropertySaveBehavior? behavior, => HasValueGenerator(factory, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1205,7 +1205,7 @@ bool IConventionPropertyBuilder.CanSetValueGenerator(Func CanSetValueGenerator(factory, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1218,7 +1218,7 @@ bool IConventionPropertyBuilder.CanSetValueGenerator(Func - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1231,7 +1231,7 @@ bool IConventionPropertyBuilder.CanSetValueGeneratorFactory( fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1240,7 +1240,7 @@ bool IConventionPropertyBuilder.CanSetValueGeneratorFactory( => HasConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1249,7 +1249,7 @@ bool IConventionPropertyBuilder.CanSetConversion(ValueConverter? converter, bool => CanSetConversion(converter, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1260,7 +1260,7 @@ bool IConventionPropertyBuilder.CanSetConversion(ValueConverter? converter, bool => HasConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1271,7 +1271,7 @@ bool IConventionPropertyBuilder.CanSetConverter( => CanSetConverter(converterType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1280,7 +1280,7 @@ bool IConventionPropertyBuilder.CanSetConverter( => HasConversion(providerClrType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1297,7 +1297,7 @@ bool IConventionPropertyBuilder.CanSetTypeMapping(CoreTypeMapping typeMapping, b => CanSetTypeMapping(typeMapping, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1306,7 +1306,7 @@ bool IConventionPropertyBuilder.CanSetTypeMapping(CoreTypeMapping typeMapping, b => HasValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1315,7 +1315,7 @@ bool IConventionPropertyBuilder.CanSetValueComparer(ValueComparer? comparer, boo => CanSetValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1326,7 +1326,7 @@ bool IConventionPropertyBuilder.CanSetValueComparer(ValueComparer? comparer, boo => HasValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1337,7 +1337,7 @@ bool IConventionPropertyBuilder.CanSetValueComparer( => CanSetValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1346,7 +1346,7 @@ bool IConventionPropertyBuilder.CanSetValueComparer( => HasProviderValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1355,7 +1355,7 @@ bool IConventionPropertyBuilder.CanSetProviderValueComparer(ValueComparer? compa => CanSetProviderValueComparer(comparer, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. @@ -1366,7 +1366,7 @@ bool IConventionPropertyBuilder.CanSetProviderValueComparer(ValueComparer? compa => HasProviderValueComparer(comparerType, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// - /// this is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// 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. diff --git a/src/EFCore/Metadata/Internal/Reference.cs b/src/EFCore/Metadata/Internal/Reference.cs index afe767589a3..f8917878b58 100644 --- a/src/EFCore/Metadata/Internal/Reference.cs +++ b/src/EFCore/Metadata/Internal/Reference.cs @@ -1,79 +1,78 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Metadata.Internal +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// 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 class Reference : IMetadataReference { + private T _object; + private readonly IReferenceRoot? _root; + private int _referenceCount = 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. /// - public class Reference : IMetadataReference + public Reference(T @object) + : this(@object, null) { - private T _object; - private readonly IReferenceRoot? _root; - private int _referenceCount = 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. - /// - public Reference(T @object) - : this(@object, null) - { - } + } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public Reference(T @object, IReferenceRoot? root) - { - _object = @object; - _root = root; - } + /// + /// 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 Reference(T @object, IReferenceRoot? root) + { + _object = @object; + _root = root; + } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual T Object - { - get => _object; - set => _object = value; - } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual T Object + { + get => _object; + set => _object = value; + } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual void Dispose() + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void Dispose() + { + if (_referenceCount-- == 1) { - if (_referenceCount-- == 1) - { - _root?.Release(this); - _object = default!; - } + _root?.Release(this); + _object = default!; } + } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual void IncreaseReferenceCount() - { - _referenceCount++; - } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void IncreaseReferenceCount() + { + _referenceCount++; } } diff --git a/src/EFCore/Metadata/RuntimeComplexProperty.cs b/src/EFCore/Metadata/RuntimeComplexProperty.cs index 60378ab7d6b..bbe78d3fdbe 100644 --- a/src/EFCore/Metadata/RuntimeComplexProperty.cs +++ b/src/EFCore/Metadata/RuntimeComplexProperty.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Represents a scalar property of an entity type. +/// Represents a complex property of a structural type. /// /// /// See Modeling entity types and relationships for more information and examples. diff --git a/src/EFCore/Metadata/RuntimeComplexType.cs b/src/EFCore/Metadata/RuntimeComplexType.cs index 598f29369a4..a4822c2743f 100644 --- a/src/EFCore/Metadata/RuntimeComplexType.cs +++ b/src/EFCore/Metadata/RuntimeComplexType.cs @@ -4,12 +4,11 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage.Json; namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Represents an entity type in a model. +/// Represents the type of a complex property of a structural type. /// /// /// See Modeling entity types and relationships for more information and examples. diff --git a/src/EFCore/Metadata/RuntimeTypeBase.cs b/src/EFCore/Metadata/RuntimeTypeBase.cs index 485661fead5..3941bcee2a9 100644 --- a/src/EFCore/Metadata/RuntimeTypeBase.cs +++ b/src/EFCore/Metadata/RuntimeTypeBase.cs @@ -10,7 +10,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// -/// Represents an entity type in a model. +/// Represents a structural type in a model. /// /// /// See Modeling entity types and relationships for more information and examples. diff --git a/src/EFCore/Metadata/TypeBaseTypeComparer.cs b/src/EFCore/Metadata/TypeBaseTypeComparer.cs deleted file mode 100644 index 3ee1c1424c9..00000000000 --- a/src/EFCore/Metadata/TypeBaseTypeComparer.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Metadata; - -/// -/// -/// An implementation of and to compare -/// instances by their CLR type. -/// -/// -/// This type is typically used by database providers (and other extensions). It is generally -/// not used in application code. -/// -/// -/// -/// See Implementation of database providers and extensions -/// for more information and examples. -/// -public sealed class TypeBaseTypeComparer : IComparer, IEqualityComparer -{ - private TypeBaseTypeComparer() - { - } - - /// - /// The singleton instance of the comparer to use. - /// - public static readonly TypeBaseTypeComparer Instance = new(); - - /// - /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. - /// - /// The first object to compare. - /// The second object to compare. - /// A negative number if 'x' is less than 'y'; a positive number if 'x' is greater than 'y'; zero otherwise. - public int Compare(IReadOnlyTypeBase? x, IReadOnlyTypeBase? y) - { - if (ReferenceEquals(x, y)) - { - return 0; - } - - if (x == null) - { - return -1; - } - - if (y == null) - { - return 1; - } - - return StringComparer.Ordinal.Compare(x.ClrType.FullName, y.ClrType.FullName); - } - - /// - /// Determines whether the specified objects are equal. - /// - /// The first object to compare. - /// The second object to compare. - /// if the specified objects are equal; otherwise, . - public bool Equals(IReadOnlyTypeBase? x, IReadOnlyTypeBase? y) - => Compare(x, y) == 0; - - /// - /// Returns a hash code for the specified object. - /// - /// The for which a hash code is to be returned. - /// A hash code for the specified object. - public int GetHashCode(IReadOnlyTypeBase obj) - => obj.ClrType.GetHashCode(); -} diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs index 6634ca58efd..1ecaf5677d3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs @@ -85,9 +85,9 @@ public override typeof(IReadOnlyProperty), ( - typeof(SqlServerPrimitivePropertyBaseExtensions), - typeof(SqlServerPrimitivePropertyBaseExtensions), - typeof(SqlServerPrimitivePropertyBaseExtensions), + typeof(SqlServerPropertyExtensions), + typeof(SqlServerPropertyExtensions), + typeof(SqlServerPropertyExtensions), typeof(SqlServerPropertyBuilderExtensions), null ) From e6087e2630cbb037b08b7c97da1d6c6663fed6fa Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 26 Jun 2023 18:05:50 -0700 Subject: [PATCH 3/4] Fix test --- .../CSharpDbContextGeneratorTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/EFCore.SqlServer.HierarchyId.Tests/CSharpDbContextGeneratorTest.cs b/test/EFCore.SqlServer.HierarchyId.Tests/CSharpDbContextGeneratorTest.cs index 31b6636f1b9..19e7e3e67b1 100644 --- a/test/EFCore.SqlServer.HierarchyId.Tests/CSharpDbContextGeneratorTest.cs +++ b/test/EFCore.SqlServer.HierarchyId.Tests/CSharpDbContextGeneratorTest.cs @@ -46,7 +46,7 @@ public TestDbContext(DbContextOptions options) public virtual DbSet Patriarch { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) -#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263. +#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263. => optionsBuilder.UseSqlServer(""Initial Catalog=TestDatabase"", x => x.UseHierarchyId()); protected override void OnModelCreating(ModelBuilder modelBuilder) From 08963c4f90450d7f8ec945a20ef637d4c28270ca Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 26 Jun 2023 18:08:27 -0700 Subject: [PATCH 4/4] Update solution filter --- EFCore.Runtime.slnf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/EFCore.Runtime.slnf b/EFCore.Runtime.slnf index 504bb19e161..d0953712259 100644 --- a/EFCore.Runtime.slnf +++ b/EFCore.Runtime.slnf @@ -9,6 +9,8 @@ "src\\EFCore.InMemory\\EFCore.InMemory.csproj", "src\\EFCore.Proxies\\EFCore.Proxies.csproj", "src\\EFCore.Relational\\EFCore.Relational.csproj", + "src\\EFCore.SqlServer.Abstractions\\EFCore.SqlServer.Abstractions.csproj", + "src\\EFCore.SqlServer.HierarchyId\\EFCore.SqlServer.HierarchyId.csproj", "src\\EFCore.SqlServer.NTS\\EFCore.SqlServer.NTS.csproj", "src\\EFCore.SqlServer\\EFCore.SqlServer.csproj", "src\\EFCore.Sqlite.Core\\EFCore.Sqlite.Core.csproj", @@ -33,6 +35,7 @@ "test\\EFCore.Relational.Tests\\EFCore.Relational.Tests.csproj", "test\\EFCore.Specification.Tests\\EFCore.Specification.Tests.csproj", "test\\EFCore.SqlServer.FunctionalTests\\EFCore.SqlServer.FunctionalTests.csproj", + "test\\EFCore.SqlServer.HierarchyId.Tests\\EFCore.SqlServer.HierarchyId.Tests.csproj", "test\\EFCore.SqlServer.Tests\\EFCore.SqlServer.Tests.csproj", "test\\EFCore.Sqlite.FunctionalTests\\EFCore.Sqlite.FunctionalTests.csproj", "test\\EFCore.Sqlite.Tests\\EFCore.Sqlite.Tests.csproj",