From ed851d9628b0d4dc125e1994d52a72fd7b773882 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Fri, 16 Jun 2023 19:31:19 -0700 Subject: [PATCH] Add complex types to Metadata Fixes #13947 --- .../CosmosValueGenerationConvention.cs | 5 +- .../CSharpRuntimeModelCodeGenerator.cs | 224 +- .../RelationalScaffoldingModelFactory.cs | 4 +- .../InMemoryValueGeneratorSelector.cs | 10 +- ...nalComplexTypePropertyBuilderExtensions.cs | 1058 ++++++++++ ...s.cs => RelationalDbFunctionExtensions.cs} | 2 +- .../RelationalEntityTypeExtensions.cs | 88 +- ...ationalPrimitivePropertyBaseExtensions.cs} | 186 +- .../RelationalTypeBaseExtensions.cs | 372 ++++ ...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 + .../RelationalColumnAttributeConvention.cs | 31 +- ...ationalColumnCommentAttributeConvention.cs | 21 +- ...ertyJsonPropertyNameAttributeConvention.cs | 13 + .../RelationalTableAttributeConvention.cs | 2 +- ...lationalTableCommentAttributeConvention.cs | 2 +- .../RelationalValueGenerationConvention.cs | 47 +- .../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 +- .../Metadata/StoreObjectIdentifier.cs | 27 +- .../Query/Internal/CollateTranslator.cs | 2 +- .../RelationalEvaluatableExpressionFilter.cs | 2 +- ...verCSharpRuntimeAnnotationCodeGenerator.cs | 19 + ...verComplexTypePropertyBuilderExtensions.cs | 773 +++++++ ...lServerPrimitivePropertyBaseExtensions.cs} | 28 +- .../SqlServerPropertyBuilderExtensions.cs | 4 +- .../SqlServerValueGenerationConvention.cs | 11 +- .../Internal/SqlServerValueGeneratorCache.cs | 2 +- .../SqlServerValueGeneratorSelector.cs | 6 +- ...iteCSharpRuntimeAnnotationCodeGenerator.cs | 12 + 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 | 339 +++ .../Builders/IConventionEntityTypeBuilder.cs | 266 ++- .../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 | 55 +- .../IConventionServicePropertyBuilder.cs | 37 +- .../IConventionSkipNavigationBuilder.cs | 37 +- .../Builders/IConventionTriggerBuilder.cs | 39 + .../Builders/IConventionTypeBaseBuilder.cs | 100 + .../Metadata/Builders/PropertyBuilder.cs | 2 +- .../Metadata/Builders/PropertyBuilder`.cs | 2 +- .../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 | 2 +- ...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 | 50 + .../Metadata/IConstructorBindingFactory.cs | 12 + .../Metadata/IConventionComplexProperty.cs | 49 + src/EFCore/Metadata/IConventionComplexType.cs | 163 ++ src/EFCore/Metadata/IConventionEntityType.cs | 161 +- src/EFCore/Metadata/IConventionProperty.cs | 55 +- src/EFCore/Metadata/IConventionTypeBase.cs | 201 ++ src/EFCore/Metadata/IEntityType.cs | 104 +- .../Metadata/IMutableComplexProperty.cs | 34 + src/EFCore/Metadata/IMutableComplexType.cs | 148 ++ src/EFCore/Metadata/IMutableEntityType.cs | 221 +- src/EFCore/Metadata/IMutableProperty.cs | 26 +- src/EFCore/Metadata/IMutableTypeBase.cs | 176 +- src/EFCore/Metadata/IProperty.cs | 13 +- .../IPropertyParameterBindingFactory.cs | 12 + .../Metadata/IReadOnlyComplexProperty.cs | 126 ++ src/EFCore/Metadata/IReadOnlyComplexType.cs | 126 ++ src/EFCore/Metadata/IReadOnlyEntityType.cs | 109 +- src/EFCore/Metadata/IReadOnlyIndex.cs | 2 +- src/EFCore/Metadata/IReadOnlyKey.cs | 2 +- src/EFCore/Metadata/IReadOnlyNavigation.cs | 11 +- src/EFCore/Metadata/IReadOnlyProperty.cs | 743 +++---- .../Metadata/IReadOnlySkipNavigation.cs | 18 +- src/EFCore/Metadata/IReadOnlyTypeBase.cs | 105 +- src/EFCore/Metadata/ITypeBase.cs | 85 + 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 | 234 +++ src/EFCore/Metadata/Internal/ComplexType.cs | 962 +++++++++ .../Internal/ComplexTypeExtensions.cs | 87 + .../Internal/ConstructorBindingFactory.cs | 92 +- src/EFCore/Metadata/Internal/EntityType.cs | 1177 +++-------- .../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 | 318 +++ .../Internal/InternalComplexTypeBuilder.cs | 1162 +++++++++++ .../Internal/InternalEntityTypeBuilder.cs | 1838 +++++++++-------- .../Internal/InternalForeignKeyBuilder.cs | 104 +- .../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 | 369 ++-- .../InternalServicePropertyBuilder.cs | 67 +- .../Internal/InternalSkipNavigationBuilder.cs | 90 +- .../Internal/InternalTriggerBuilder.cs | 18 + .../Internal/InternalTypeBaseBuilder.cs | 807 ++++++++ src/EFCore/Metadata/Internal/Key.cs | 2 +- src/EFCore/Metadata/Internal/Model.cs | 109 +- .../Metadata/Internal/ModelConfiguration.cs | 71 +- .../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 | 12 +- .../Internal/SkipNavigationComparer.cs | 2 +- src/EFCore/Metadata/Internal/TypeBase.cs | 1541 +++++++++++++- .../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 | 320 +++ src/EFCore/Metadata/RuntimeEntityType.cs | 476 ++--- 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 | 383 ++++ src/EFCore/Metadata/TypeBaseNameComparer.cs | 73 + src/EFCore/Metadata/TypeBaseTypeComparer.cs | 73 + src/EFCore/ModelConfigurationBuilder.cs | 45 + src/EFCore/Properties/CoreStrings.Designer.cs | 193 +- src/EFCore/Properties/CoreStrings.resx | 117 +- .../ShapedQueryCompilingExpressionVisitor.cs | 2 +- src/EFCore/Storage/CoreTypeMapping.cs | 2 +- .../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 | 24 + .../CosmosModelBuilderGenericTest.cs | 205 ++ .../Migrations/ModelSnapshotSqlServerTest.cs | 34 +- .../CSharpRuntimeModelCodeGeneratorTest.cs | 767 ++++++- .../InMemoryModelBuilderGenericTest.cs | 6 + ...lationalPropertyAttributeConventionTest.cs | 2 +- .../RelationalModelBuilderTest.cs | 329 +++ .../RelationalApiConsistencyTest.cs | 183 +- .../ApiConsistencyTestBase.cs | 288 ++- .../JsonTypesTestBase.cs | 39 - .../TestUtilities/TestHelpers.cs | 2 +- .../SqlServerApiConsistencyTest.cs | 88 +- .../SqlServerModelBuilderGenericTest.cs | 8 + .../SqlServerModelBuilderNonGenericTest.cs | 8 + .../SqlServerModelBuilderTestBase.cs | 190 ++ .../SqliteApiConsistencyTest.cs | 21 +- test/EFCore.Tests/ApiConsistencyTest.cs | 11 +- .../Infrastructure/CoreEventIdTest.cs | 8 +- .../Conventions/BackingFieldConventionTest.cs | 2 +- .../Conventions/ConventionDispatcherTest.cs | 1148 +++++++++- .../DeleteBehaviorAttributeConventionTest.cs | 16 +- .../EntityTypeAttributeConventionTest.cs | 6 +- ...reignKeyPropertyDiscoveryConventionTest.cs | 8 +- .../Conventions/KeyDiscoveryConventionTest.cs | 2 +- .../NavigationAttributeConventionTest.cs | 2 + ...NullableReferencePropertyConventionTest.cs | 2 +- .../PropertyDiscoveryConventionTest.cs | 4 +- .../Metadata/EntityTypeExtensionsTest.cs | 2 +- .../Metadata/Internal/EntityTypeTest.cs | 130 +- .../Internal/InternalEntityTypeBuilderTest.cs | 14 +- .../Metadata/Internal/SkipNavigationTest.cs | 28 +- .../ModelBuilding/ComplexTypeTestBase.cs | 1452 +++++++++++++ .../ModelBuilding/ModelBuilderGenericTest.cs | 287 ++- .../ModelBuilderNonGenericTest.cs | 274 +++ .../ModelBuilding/ModelBuilderTestBase.cs | 128 ++ .../ModelBuilding/NonRelationshipTestBase.cs | 61 +- .../ModelBuilding/OneToManyTestBase.cs | 8 +- test/EFCore.Tests/ModelBuilding/TestModel.cs | 43 +- 281 files changed, 25619 insertions(+), 5670 deletions(-) create mode 100644 src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs rename src/EFCore.Relational/Extensions/{RelationalDbFunctionsExtensions.cs => RelationalDbFunctionExtensions.cs} (97%) rename src/EFCore.Relational/Extensions/{RelationalPropertyExtensions.cs => RelationalPrimitivePropertyBaseExtensions.cs} (93%) 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/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.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/ValueGeneration/Internal/InMemoryValueGeneratorSelector.cs b/src/EFCore.InMemory/ValueGeneration/Internal/InMemoryValueGeneratorSelector.cs index 8b98f3f12a3..ca611cc9080 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,7 +58,7 @@ 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) @@ -132,8 +132,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/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs new file mode 100644 index 00000000000..32b690db049 --- /dev/null +++ b/src/EFCore.Relational/Extensions/RelationalComplexTypePropertyBuilderExtensions.cs @@ -0,0 +1,1058 @@ +// 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 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. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasColumnName( + this IConventionPropertyBuilder propertyBuilder, + string? name, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetColumnName(name, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetColumnName(name, fromDataAnnotation); + return propertyBuilder; + } + + /// + /// Configures the column that the property maps to in a particular table-like store object. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the column. + /// The identifier of the store object. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasColumnName( + this IConventionPropertyBuilder propertyBuilder, + string? name, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetColumnName(name, storeObject, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetColumnName(name, storeObject, fromDataAnnotation); + return propertyBuilder; + } + + /// + /// Returns a value indicating whether the given column can be set for the property. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the column. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be mapped to the given column. + public static bool CanSetColumnName( + this IConventionPropertyBuilder propertyBuilder, + string? name, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation(RelationalAnnotationNames.ColumnName, name, fromDataAnnotation); + + /// + /// Returns a value indicating whether the given column for a particular table-like store object can be set for the property. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the property being configured. + /// The name of the column. + /// The identifier of the store object. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be mapped to the given column. + public static bool CanSetColumnName( + this IConventionPropertyBuilder propertyBuilder, + string? name, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + { + var overrides = (IConventionRelationalPropertyOverrides?)RelationalPropertyOverrides.Find( + propertyBuilder.Metadata, storeObject); + return overrides == null + || (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) + .Overrides(overrides.GetColumnNameConfigurationSource()) + || overrides.ColumnName == 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 order of the column the property is mapped to. + /// + /// The builder of the property being configured. + /// The column order. + /// A value indicating whether the configuration was specified using a data annotation. + /// The same builder instance if the configuration was applied, otherwise. + public static IConventionPropertyBuilder? HasColumnOrder( + this IConventionPropertyBuilder propertyBuilder, + int? order, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetColumnOrder(order, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetColumnOrder(order, fromDataAnnotation); + + return propertyBuilder; + } + + /// + /// Gets a value indicating whether the given column order can be set for the property. + /// + /// The builder of the property being configured. + /// The column order. + /// A value indicating whether the configuration was specified using a data annotation. + /// if the column order can be set for the property. + public static bool CanSetColumnOrder(this IConventionPropertyBuilder propertyBuilder, int? order, bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation(RelationalAnnotationNames.ColumnOrder, order, fromDataAnnotation); + + /// + /// 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 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. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasColumnType( + this IConventionPropertyBuilder propertyBuilder, + string? typeName, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetColumnType(typeName, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetColumnType(typeName, fromDataAnnotation); + return propertyBuilder; + } + + /// + /// Returns a value indicating whether the given data type can be set for the property. + /// + /// + /// 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. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given data type can be set for the property. + public static bool CanSetColumnType( + this IConventionPropertyBuilder propertyBuilder, + string? typeName, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation(RelationalAnnotationNames.ColumnType, typeName, fromDataAnnotation); + + /// + /// 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 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. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? IsFixedLength( + this IConventionPropertyBuilder propertyBuilder, + bool? fixedLength, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetIsFixedLength(fixedLength, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetIsFixedLength(fixedLength, fromDataAnnotation); + return propertyBuilder; + } + + /// + /// Returns a value indicating whether the property can be configured as being fixed length or not. + /// + /// + /// 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. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be configured as being fixed length or not. + public static bool CanSetIsFixedLength( + this IConventionPropertyBuilder propertyBuilder, + bool? fixedLength, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation(RelationalAnnotationNames.IsFixedLength, fixedLength, fromDataAnnotation); + + /// + /// 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 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. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasDefaultValueSql( + this IConventionPropertyBuilder propertyBuilder, + string? sql, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetDefaultValueSql(sql, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetDefaultValueSql(sql, fromDataAnnotation); + return propertyBuilder; + } + + /// + /// Returns a value indicating whether the given default value expression can be set for the column. + /// + /// + /// 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. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given default value expression can be set for the column. + public static bool CanSetDefaultValueSql( + this IConventionPropertyBuilder propertyBuilder, + string? sql, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation( + RelationalAnnotationNames.DefaultValueSql, + sql, + fromDataAnnotation); + + /// + /// 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 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. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasComputedColumnSql( + this IConventionPropertyBuilder propertyBuilder, + string? sql, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetComputedColumnSql(sql, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetComputedColumnSql(sql, fromDataAnnotation); + return propertyBuilder; + } + + /// + /// Configures the property to map to a computed column of the given type when targeting a relational database. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// + /// 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. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + public static IConventionPropertyBuilder? IsStoredComputedColumn( + this IConventionPropertyBuilder propertyBuilder, + bool? stored, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetIsStoredComputedColumn(stored, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetIsStored(stored, fromDataAnnotation); + return propertyBuilder; + } + + /// + /// Returns a value indicating whether the given computed value SQL expression can be set for the column. + /// + /// + /// 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. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given computed value SQL expression can be set for the column. + public static bool CanSetComputedColumnSql( + this IConventionPropertyBuilder propertyBuilder, + string? sql, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation( + RelationalAnnotationNames.ComputedColumnSql, + sql, + fromDataAnnotation); + + /// + /// Returns a value indicating whether the given computed column type can be set for the column. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// + /// 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. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// if the given computed column type can be set for the column. + public static bool CanSetIsStoredComputedColumn( + this IConventionPropertyBuilder propertyBuilder, + bool? stored, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation( + RelationalAnnotationNames.IsStored, + stored, + fromDataAnnotation); + + /// + /// 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 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. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasDefaultValue( + this IConventionPropertyBuilder propertyBuilder, + object? value, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetDefaultValue(value, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetDefaultValue(value, fromDataAnnotation); + return propertyBuilder; + } + + /// + /// Returns a value indicating whether the given value can be set as default for the column. + /// + /// + /// See Database default values for more information and examples. + /// + /// The builder for the property being configured. + /// The default value of the column. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as default for the column. + public static bool CanSetDefaultValue( + this IConventionPropertyBuilder propertyBuilder, + object? value, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation( + RelationalAnnotationNames.DefaultValue, + value, + fromDataAnnotation); + + /// + /// 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 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. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasComment( + this IConventionPropertyBuilder propertyBuilder, + string? comment, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetComment(comment, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetComment(comment, fromDataAnnotation); + return propertyBuilder; + } + + /// + /// Returns a value indicating whether the given value can be set as comment for 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. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as default for the column. + public static bool CanSetComment( + this IConventionPropertyBuilder propertyBuilder, + string? comment, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation( + RelationalAnnotationNames.Comment, + comment, + fromDataAnnotation); + + /// + /// 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 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. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? UseCollation( + this IConventionPropertyBuilder propertyBuilder, + string? collation, + bool fromDataAnnotation = false) + { + if (propertyBuilder.CanSetCollation(collation, fromDataAnnotation)) + { + propertyBuilder.Metadata.SetCollation(collation, fromDataAnnotation); + + return propertyBuilder; + } + + return null; + } + + /// + /// Returns a value indicating whether the given value can be set as the collation. + /// + /// + /// See Database collations for more information and examples. + /// + /// The builder for the property being configured. + /// The collation. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as default for the column. + public static bool CanSetCollation( + this IConventionPropertyBuilder propertyBuilder, + string? collation, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation(RelationalAnnotationNames.Collation, collation, fromDataAnnotation); + + /// + /// 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); + + /// + /// 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. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasJsonPropertyName( + this IConventionPropertyBuilder propertyBuilder, + string? name, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetJsonPropertyName(name, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetJsonPropertyName(name, fromDataAnnotation); + + return propertyBuilder; + } + + /// + /// Returns a value indicating whether the given value can be used as a JSON property name for a given entity property. + /// + /// The builder for the property being configured. + /// JSON property name to be used. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as JSON property name for this entity property. + public static bool CanSetJsonPropertyName( + this IConventionPropertyBuilder propertyBuilder, + string? name, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation(RelationalAnnotationNames.JsonPropertyName, name, fromDataAnnotation); +} 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/RelationalPrimitivePropertyBaseExtensions.cs similarity index 93% rename from src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs rename to src/EFCore.Relational/Extensions/RelationalPrimitivePropertyBaseExtensions.cs index 7b100aed1cb..cd51141d84d 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPrimitivePropertyBaseExtensions.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore; /// /// See Modeling entity types and relationships for more information and examples. /// -public static class RelationalPropertyExtensions +public static class RelationalPrimitivePropertyBaseExtensions { private static readonly MethodInfo GetFieldValueMethod = typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetFieldValue), new[] { typeof(int) })!; @@ -24,7 +24,7 @@ public static class RelationalPropertyExtensions typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.IsDBNull), new[] { typeof(int) })!; private static readonly MethodInfo ThrowReadValueExceptionMethod - = typeof(RelationalPropertyExtensions).GetTypeInfo().GetDeclaredMethod(nameof(ThrowReadValueException))!; + = typeof(RelationalPrimitivePropertyBaseExtensions).GetTypeInfo().GetDeclaredMethod(nameof(ThrowReadValueException))!; /// /// Returns the base name of the column to which the property would be mapped. @@ -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,58 @@ 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 as IReadOnlyEntityType + ?? ((IReadOnlyComplexType)property.DeclaringType).FundametalEntityType; + 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 +186,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 +197,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 +214,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 +266,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 +982,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 +1145,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 +1175,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 +1429,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,10 +1441,14 @@ 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; + var rootProperty = property as IReadOnlyProperty; + if (rootProperty == null) + { + return null; + } // 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 @@ -1456,13 +1486,13 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope return null; } - var principalProperty = property; + var principalProperty = property as IReadOnlyProperty; // 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 + var linkingRelationship = principalProperty?.DeclaringEntityType .FindRowInternalForeignKeys(storeObject).FirstOrDefault(); if (linkingRelationship == null) @@ -1485,12 +1515,13 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope return null; } - var principalProperty = property; + var principalProperty = property as IReadOnlyProperty; + // 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 + var linkingRelationship = principalProperty?.DeclaringEntityType .FindRowInternalForeignKeys(storeObject).FirstOrDefault(); if (linkingRelationship == null) { @@ -1729,7 +1760,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) @@ -1755,13 +1786,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; + } } } } @@ -1940,7 +1974,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..b027a550f84 --- /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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IEntityType ?? ((IComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IEntityType ?? ((IComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IEntityType ?? ((IComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IEntityType ?? ((IComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IEntityType ?? ((IComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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 as IEntityType ?? ((IComplexType)typeBase).FundametalEntityType).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 as IReadOnlyEntityType ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType).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/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/Conventions/RelationalColumnAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalColumnAttributeConvention.cs index cdfed28f535..10dd58e9622 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalColumnAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalColumnAttributeConvention.cs @@ -31,13 +31,30 @@ 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, + MemberInfo clrMember, + IConventionContext context) + { + if (!string.IsNullOrWhiteSpace(attribute.Name)) + { + propertyBuilder.HasColumnName(attribute.Name, fromDataAnnotation: true); + } + + if (!string.IsNullOrWhiteSpace(attribute.TypeName)) + { + propertyBuilder.HasColumnType(attribute.TypeName, fromDataAnnotation: true); + } + + if (attribute.Order >= 0) + { + propertyBuilder.HasColumnOrder(attribute.Order, fromDataAnnotation: true); + } + } + + /// 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..2c083dc15c1 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalColumnCommentAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalColumnCommentAttributeConvention.cs @@ -29,13 +29,20 @@ 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, + MemberInfo clrMember, + IConventionContext context) + { + if (!string.IsNullOrWhiteSpace(attribute.Comment)) + { + propertyBuilder.HasComment(attribute.Comment, fromDataAnnotation: true); + } + } + + /// protected override void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, CommentAttribute attribute, diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalPropertyJsonPropertyNameAttributeConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalPropertyJsonPropertyNameAttributeConvention.cs index 33f2b1ab240..cb470c2e39a 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalPropertyJsonPropertyNameAttributeConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalPropertyJsonPropertyNameAttributeConvention.cs @@ -43,4 +43,17 @@ protected override void ProcessPropertyAdded( propertyBuilder.HasJsonPropertyName(attribute.Name, fromDataAnnotation: true); } } + + /// + protected override void ProcessPropertyAdded( + IConventionPropertyBuilder propertyBuilder, + JsonPropertyNameAttribute attribute, + MemberInfo clrMember, + IConventionContext context) + { + if (!string.IsNullOrWhiteSpace(attribute.Name)) + { + propertyBuilder.HasJsonPropertyName(attribute.Name, fromDataAnnotation: true); + } + } } 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..febd0d47b1f 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalValueGenerationConvention.cs @@ -17,6 +17,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; public class RelationalValueGenerationConvention : ValueGenerationConvention, IPropertyAnnotationChangedConvention, + IPropertyAnnotationChangedConvention, IEntityTypeAnnotationChangedConvention { /// @@ -37,14 +38,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, @@ -75,6 +69,37 @@ public virtual void ProcessPropertyAnnotationChanged( } } + /// + public void ProcessComplexTypePropertyAnnotationChanged( + IConventionPropertyBuilder propertyBuilder, + string name, + IConventionAnnotation? annotation, + IConventionAnnotation? oldAnnotation, + IConventionContext context) + { + var property = propertyBuilder.Metadata; + switch (name) + { + case RelationalAnnotationNames.DefaultValue: +#pragma warning disable EF1001 // Internal EF Core API usage. + if ((((IProperty)property).TryGetMemberInfo(forMaterialization: false, forSet: false, out var member, out _) + ? member!.GetMemberType() + : property.ClrType) +#pragma warning restore EF1001 // Internal EF Core API usage. + == typeof(bool) + && Equals(true, property.GetDefaultValue())) + { + propertyBuilder.HasSentinel(annotation != null ? true : null); + } + + goto case RelationalAnnotationNames.DefaultValueSql; + case RelationalAnnotationNames.DefaultValueSql: + case RelationalAnnotationNames.ComputedColumnSql: + propertyBuilder.ValueGenerated(GetValueGenerated(property)); + break; + } + } + /// /// Called after an annotation is changed on an entity type. /// @@ -193,13 +218,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/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/StoreObjectIdentifier.cs b/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs index 070aa49b81c..dc939c80900 100644 --- a/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs +++ b/src/EFCore.Relational/Metadata/StoreObjectIdentifier.cs @@ -21,39 +21,40 @@ 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 as IReadOnlyEntityType) + ?? ((IReadOnlyComplexType)typeBase).FundametalEntityType); 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/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/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.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs index 793eb75ca00..87eed4267c5 100644 --- a/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.SqlServer/Design/Internal/SqlServerCSharpRuntimeAnnotationCodeGenerator.cs @@ -76,6 +76,25 @@ public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGen base.Generate(property, parameters); } + /// + public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + if (!parameters.IsRuntime) + { + var annotations = parameters.Annotations; + annotations.Remove(SqlServerAnnotationNames.IdentityIncrement); + annotations.Remove(SqlServerAnnotationNames.IdentitySeed); + annotations.Remove(SqlServerAnnotationNames.Sparse); + + if (!annotations.ContainsKey(SqlServerAnnotationNames.ValueGenerationStrategy)) + { + annotations[SqlServerAnnotationNames.ValueGenerationStrategy] = property.GetValueGenerationStrategy(); + } + } + + base.Generate(property, parameters); + } + /// public override void Generate(IColumn column, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { diff --git a/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePropertyBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePropertyBuilderExtensions.cs new file mode 100644 index 00000000000..aba2eb5bc1b --- /dev/null +++ b/src/EFCore.SqlServer/Extensions/SqlServerComplexTypePropertyBuilderExtensions.cs @@ -0,0 +1,773 @@ +// 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 database sequence used for the hi-lo pattern to generate values for the key property, + /// 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. + /// + /// The builder for the property being configured. + /// The name of the sequence. + /// The schema of the sequence. + /// Indicates whether the configuration was specified using a data annotation. + /// A builder to further configure the sequence. + public static IConventionSequenceBuilder? HasHiLoSequence( + this IConventionPropertyBuilder propertyBuilder, + string? name, + string? schema, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetHiLoSequence(name, schema, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetHiLoSequenceName(name, fromDataAnnotation); + propertyBuilder.Metadata.SetHiLoSequenceSchema(schema, fromDataAnnotation); + + return name == null + ? null + : propertyBuilder.Metadata.DeclaringType.Model.Builder.HasSequence(name, schema, fromDataAnnotation); + } + + /// + /// Returns a value indicating whether the given name and schema can be set for the hi-lo sequence. + /// + /// + /// 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. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given name and schema can be set for the hi-lo sequence. + public static bool CanSetHiLoSequence( + this IConventionPropertyBuilder propertyBuilder, + string? name, + string? schema, + bool fromDataAnnotation = false) + { + Check.NullButNotEmpty(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + return propertyBuilder.CanSetAnnotation(SqlServerAnnotationNames.HiLoSequenceName, name, fromDataAnnotation) + && propertyBuilder.CanSetAnnotation(SqlServerAnnotationNames.HiLoSequenceSchema, schema, fromDataAnnotation); + } + + /// + /// 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 database sequence used for the key value generation pattern to generate values for the key property, + /// 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. + /// + /// The builder for the property being configured. + /// The name of the sequence. + /// The schema of the sequence. + /// Indicates whether the configuration was specified using a data annotation. + /// A builder to further configure the sequence. + public static IConventionSequenceBuilder? HasSequence( + this IConventionPropertyBuilder propertyBuilder, + string? name, + string? schema, + bool fromDataAnnotation = false) + { + if (!propertyBuilder.CanSetSequence(name, schema, fromDataAnnotation)) + { + return null; + } + + propertyBuilder.Metadata.SetSequenceName(name, fromDataAnnotation); + propertyBuilder.Metadata.SetSequenceSchema(schema, fromDataAnnotation); + + return name == null + ? null + : propertyBuilder.Metadata.DeclaringType.Model.Builder.HasSequence(name, schema, fromDataAnnotation); + } + + /// + /// Returns a value indicating whether the given name and schema can be set for the key value generation sequence. + /// + /// + /// 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. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given name and schema can be set for the key value generation sequence. + public static bool CanSetSequence( + this IConventionPropertyBuilder propertyBuilder, + string? name, + string? schema, + bool fromDataAnnotation = false) + { + Check.NullButNotEmpty(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + return propertyBuilder.CanSetAnnotation(SqlServerAnnotationNames.SequenceName, name, fromDataAnnotation) + && propertyBuilder.CanSetAnnotation(SqlServerAnnotationNames.SequenceSchema, schema, fromDataAnnotation); + } + + /// + /// 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 the seed for SQL Server IDENTITY. + /// + /// + /// 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. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasIdentityColumnSeed( + this IConventionPropertyBuilder propertyBuilder, + long? seed, + bool fromDataAnnotation = false) + { + if (propertyBuilder.CanSetIdentityColumnSeed(seed, fromDataAnnotation)) + { + propertyBuilder.Metadata.SetIdentitySeed(seed, fromDataAnnotation); + return propertyBuilder; + } + + return null; + } + + /// + /// Configures the seed for SQL Server IDENTITY for a particular table. + /// + /// + /// 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 table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasIdentityColumnSeed( + this IConventionPropertyBuilder propertyBuilder, + long? seed, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + { + if (propertyBuilder.CanSetIdentityColumnSeed(seed, storeObject, fromDataAnnotation)) + { + propertyBuilder.Metadata.SetIdentitySeed(seed, storeObject, fromDataAnnotation); + return propertyBuilder; + } + + return null; + } + + /// + /// Returns a value indicating whether the given value can be set as the seed for SQL Server IDENTITY. + /// + /// + /// 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. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as the seed for SQL Server IDENTITY. + public static bool CanSetIdentityColumnSeed( + this IConventionPropertyBuilder propertyBuilder, + long? seed, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation(SqlServerAnnotationNames.IdentitySeed, seed, fromDataAnnotation); + + /// + /// Returns a value indicating whether the given value can be set as the seed for SQL Server IDENTITY + /// for a particular table. + /// + /// + /// 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 table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as the seed for SQL Server IDENTITY. + public static bool CanSetIdentityColumnSeed( + this IConventionPropertyBuilder propertyBuilder, + long? seed, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => propertyBuilder.Metadata.FindOverrides(storeObject)?.Builder + .CanSetAnnotation( + SqlServerAnnotationNames.IdentitySeed, + seed, + fromDataAnnotation) + ?? true; + + /// + /// Configures the increment for SQL Server IDENTITY. + /// + /// + /// 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 incremental value that is added to the identity value of the previous row that was loaded. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasIdentityColumnIncrement( + this IConventionPropertyBuilder propertyBuilder, + int? increment, + bool fromDataAnnotation = false) + { + if (propertyBuilder.CanSetIdentityColumnIncrement(increment, fromDataAnnotation)) + { + propertyBuilder.Metadata.SetIdentityIncrement(increment, fromDataAnnotation); + return propertyBuilder; + } + + return null; + } + + /// + /// Configures the increment for SQL Server IDENTITY for a particular table. + /// + /// + /// 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 incremental value that is added to the identity value of the previous row that was loaded. + /// The table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasIdentityColumnIncrement( + this IConventionPropertyBuilder propertyBuilder, + int? increment, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + { + if (propertyBuilder.CanSetIdentityColumnIncrement(increment, storeObject, fromDataAnnotation)) + { + propertyBuilder.Metadata.SetIdentityIncrement(increment, storeObject, fromDataAnnotation); + return propertyBuilder; + } + + return null; + } + + /// + /// Returns a value indicating whether the given value can be set as the increment for SQL Server IDENTITY. + /// + /// + /// 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 incremental value that is added to the identity value of the previous row that was loaded. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as the default increment for SQL Server IDENTITY. + public static bool CanSetIdentityColumnIncrement( + this IConventionPropertyBuilder propertyBuilder, + int? increment, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation(SqlServerAnnotationNames.IdentityIncrement, increment, fromDataAnnotation); + + /// + /// Returns a value indicating whether the given value can be set as the increment for SQL Server IDENTITY + /// for a particular table. + /// + /// + /// 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 incremental value that is added to the identity value of the previous row that was loaded. + /// The table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as the default increment for SQL Server IDENTITY. + public static bool CanSetIdentityColumnIncrement( + this IConventionPropertyBuilder propertyBuilder, + int? increment, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => propertyBuilder.Metadata.FindOverrides(storeObject)?.Builder + .CanSetAnnotation( + SqlServerAnnotationNames.IdentityIncrement, + increment, + fromDataAnnotation) + ?? true; + + /// + /// Configures the value generation strategy for the key property, 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. + /// + /// The builder for the property being configured. + /// The value generation strategy. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasValueGenerationStrategy( + this IConventionPropertyBuilder propertyBuilder, + SqlServerValueGenerationStrategy? valueGenerationStrategy, + bool fromDataAnnotation = false) + { + if (propertyBuilder.CanSetAnnotation( + SqlServerAnnotationNames.ValueGenerationStrategy, valueGenerationStrategy, fromDataAnnotation)) + { + propertyBuilder.Metadata.SetValueGenerationStrategy(valueGenerationStrategy, fromDataAnnotation); + if (valueGenerationStrategy != SqlServerValueGenerationStrategy.IdentityColumn) + { + propertyBuilder.HasIdentityColumnSeed(null, fromDataAnnotation); + propertyBuilder.HasIdentityColumnIncrement(null, fromDataAnnotation); + propertyBuilder.HasSequence(null, null, fromDataAnnotation); + } + + if (valueGenerationStrategy != SqlServerValueGenerationStrategy.SequenceHiLo) + { + propertyBuilder.HasHiLoSequence(null, null, fromDataAnnotation); + propertyBuilder.HasSequence(null, null, fromDataAnnotation); + } + + if (valueGenerationStrategy != SqlServerValueGenerationStrategy.Sequence) + { + propertyBuilder.HasIdentityColumnSeed(null, fromDataAnnotation); + propertyBuilder.HasIdentityColumnIncrement(null, fromDataAnnotation); + propertyBuilder.HasHiLoSequence(null, null, fromDataAnnotation); + } + + return propertyBuilder; + } + + return null; + } + + /// + /// Configures the value generation strategy for the key property, when targeting SQL Server for a particular table. + /// + /// + /// 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 generation strategy. + /// The table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionPropertyBuilder? HasValueGenerationStrategy( + this IConventionPropertyBuilder propertyBuilder, + SqlServerValueGenerationStrategy? valueGenerationStrategy, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + { + if (propertyBuilder.CanSetValueGenerationStrategy(valueGenerationStrategy, storeObject, fromDataAnnotation)) + { + propertyBuilder.Metadata.SetValueGenerationStrategy(valueGenerationStrategy, storeObject, fromDataAnnotation); + if (valueGenerationStrategy != SqlServerValueGenerationStrategy.IdentityColumn) + { + propertyBuilder.HasIdentityColumnSeed(null, storeObject, fromDataAnnotation); + propertyBuilder.HasIdentityColumnIncrement(null, storeObject, fromDataAnnotation); + } + + return propertyBuilder; + } + + return null; + } + + /// + /// Returns a value indicating whether the given value can be set as the value generation strategy. + /// + /// + /// 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 generation strategy. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as the default value generation strategy. + public static bool CanSetValueGenerationStrategy( + this IConventionPropertyBuilder propertyBuilder, + SqlServerValueGenerationStrategy? valueGenerationStrategy, + bool fromDataAnnotation = false) + => (valueGenerationStrategy == null + || SqlServerPrimitivePropertyBaseExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) + && propertyBuilder.CanSetAnnotation( + SqlServerAnnotationNames.ValueGenerationStrategy, valueGenerationStrategy, fromDataAnnotation); + + /// + /// Returns a value indicating whether the given value can be set as the value generation strategy for a particular table. + /// + /// + /// 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 generation strategy. + /// The table identifier. + /// Indicates whether the configuration was specified using a data annotation. + /// if the given value can be set as the default value generation strategy. + public static bool CanSetValueGenerationStrategy( + this IConventionPropertyBuilder propertyBuilder, + SqlServerValueGenerationStrategy? valueGenerationStrategy, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => (valueGenerationStrategy == null + || SqlServerPrimitivePropertyBaseExtensions.IsCompatibleWithValueGeneration(propertyBuilder.Metadata)) + && (propertyBuilder.Metadata.FindOverrides(storeObject)?.Builder + .CanSetAnnotation( + SqlServerAnnotationNames.ValueGenerationStrategy, + valueGenerationStrategy, + fromDataAnnotation) + ?? true); + + /// + /// 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); + + /// + /// 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. + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder instance if the configuration was applied, otherwise. + public static IConventionPropertyBuilder? IsSparse( + this IConventionPropertyBuilder propertyBuilder, + bool? sparse, + bool fromDataAnnotation = false) + { + if (propertyBuilder.CanSetIsSparse(sparse, fromDataAnnotation)) + { + propertyBuilder.Metadata.SetIsSparse(sparse, fromDataAnnotation); + + return propertyBuilder; + } + + return null; + } + + /// + /// Returns a value indicating whether the property's column can be configured 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. + /// Indicates whether the configuration was specified using a data annotation. + /// The same builder instance if the configuration was applied, otherwise. + /// + /// if the property's column can be configured as sparse when targeting SQL Server. + /// + public static bool CanSetIsSparse( + this IConventionPropertyBuilder propertyBuilder, + bool? sparse, + bool fromDataAnnotation = false) + => propertyBuilder.CanSetAnnotation(SqlServerAnnotationNames.Sparse, sparse, fromDataAnnotation); +} 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..cdb87750c4b 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 - || 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/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/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..f5bdc53ef1e 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())); } /// diff --git a/src/EFCore.Sqlite.Core/Design/Internal/SqliteCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Sqlite.Core/Design/Internal/SqliteCSharpRuntimeAnnotationCodeGenerator.cs index 226ec885c1e..2f944106107 100644 --- a/src/EFCore.Sqlite.Core/Design/Internal/SqliteCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Sqlite.Core/Design/Internal/SqliteCSharpRuntimeAnnotationCodeGenerator.cs @@ -52,6 +52,18 @@ public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGen base.Generate(property, parameters); } + /// + public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var annotations = parameters.Annotations; + if (!parameters.IsRuntime) + { + annotations.Remove(SqliteAnnotationNames.Srid); + } + + base.Generate(property, parameters); + } + /// public override void Generate(IColumn column, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { 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 4c98d57152b..5ff48d6357d 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..9b2be672c1d --- /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..2f727d38603 --- /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..295bb6351e7 --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionComplexTypeBuilder.cs @@ -0,0 +1,339 @@ +// 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); + + /// + /// 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 complex 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 complex type, + /// otherwise. + /// + IConventionPropertyBuilder? Property(MemberInfo memberInfo, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given property can be added to this complex 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 complex 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 complex type, + /// otherwise. + /// + IConventionPropertyBuilder? IndexerProperty( + Type complexType, + string propertyName, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given indexer property can be added to this complex 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 complexType, + string propertyName, + bool fromDataAnnotation = false); + + /// + /// 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. + /// + IConventionComplexTypeBuilder? HasNoProperty(IConventionProperty property, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the property can be removed from this complex type. + /// + /// The property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be removed from this complex 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 target complex 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. + /// + /// An object that can be used to configure the property if it exists on the complex 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 target complex type. + /// 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 complex 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 complex 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 complex 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 complex type, + /// otherwise. + /// + IConventionComplexPropertyBuilder? ComplexIndexerProperty( + Type propertyType, + string propertyName, + Type? complexType = null, + bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the given complex indexer property can be added to this complex 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 = null, + 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. + /// + IConventionComplexTypeBuilder? HasNoComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the complex property can be removed from this complex 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 complex type. + bool CanRemoveComplexProperty(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. + /// + IConventionComplexTypeBuilder? 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 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. + /// + IConventionComplexTypeBuilder? 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/IConventionEntityTypeBuilder.cs b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs index da8fa4ea64a..3eed4a1fa2d 100644 --- a/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Builders/IConventionEntityTypeBuilder.cs @@ -15,13 +15,52 @@ 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. /// new IConventionEntityType 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 IConventionEntityTypeBuilder? 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 IConventionEntityTypeBuilder? 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 IConventionEntityTypeBuilder? HasNoAnnotation(string name, bool fromDataAnnotation = false); + /// /// Sets the base type of this entity type in an inheritance hierarchy. /// @@ -159,6 +198,136 @@ bool CanHaveIndexerProperty( /// The properties to remove. IConventionEntityTypeBuilder RemoveUnusedImplicitProperties(IReadOnlyList properties); + /// + /// Removes a property from this entity 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. + /// + IConventionEntityTypeBuilder? HasNoProperty(IConventionProperty property, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the property can be removed from this entity type. + /// + /// The property to be removed. + /// Indicates whether the configuration was specified using a data annotation. + /// if the property can be removed from this entity 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 entity 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 entity 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 entity 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 entity 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 entity 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 entity 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 entity 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. + /// + IConventionEntityTypeBuilder? HasNoComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation = false); + + /// + /// Returns a value indicating whether the complex property can be removed from this entity 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 entity type. + bool CanRemoveComplexProperty(IConventionComplexProperty complexProperty, bool fromDataAnnotation = false); + /// /// Returns an object that can be used to configure the service property with the given member info. /// If no matching property exists, then a new property will be added. @@ -198,41 +367,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 +906,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 +924,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 +1013,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 +1026,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. /// 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..7f8151f077d 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. /// 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..abf8b8bdb13 --- /dev/null +++ b/src/EFCore/Metadata/Builders/IConventionTypeBaseBuilder.cs @@ -0,0 +1,100 @@ +// 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); + + /// + /// 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); +} diff --git a/src/EFCore/Metadata/Builders/PropertyBuilder.cs b/src/EFCore/Metadata/Builders/PropertyBuilder.cs index 4f9155f3c9d..1bb55229557 100644 --- a/src/EFCore/Metadata/Builders/PropertyBuilder.cs +++ b/src/EFCore/Metadata/Builders/PropertyBuilder.cs @@ -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..e855e4520d2 100644 --- a/src/EFCore/Metadata/Builders/PropertyBuilder`.cs +++ b/src/EFCore/Metadata/Builders/PropertyBuilder`.cs @@ -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..75355885b8b 100644 --- a/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs @@ -231,7 +231,7 @@ public virtual void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) { - TryConfigurePrimaryKey(propertyBuilder.Metadata.DeclaringEntityType.Builder); + TryConfigurePrimaryKey((IConventionEntityTypeBuilder)propertyBuilder.Metadata.DeclaringType.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..9b97eb49ad9 --- /dev/null +++ b/src/EFCore/Metadata/IComplexType.cs @@ -0,0 +1,50 @@ +// 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 witch the complex property chain is declared. + /// + new IEntityType FundametalEntityType + => (IEntityType)((IReadOnlyComplexType)this).FundametalEntityType; + + /// + /// Gets the for the preferred constructor. + /// + InstantiationBinding? ConstructorBinding { get; } + + /// + /// Gets a complex property on the given complex type. Returns if no property is found. + /// + /// The property on the complex type. + /// The property, or if none is found. + new IComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IComplexProperty?)((IReadOnlyComplexType)this).FindComplexProperty(memberInfo); + + /// + /// 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. + new IComplexProperty? FindComplexProperty(string name); + + /// + /// Gets the properties defined on this complex type. + /// + /// The properties defined on this complex type. + new IEnumerable GetComplexProperties(); +} 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..b1828e4ffa0 --- /dev/null +++ b/src/EFCore/Metadata/IConventionComplexType.cs @@ -0,0 +1,163 @@ +// 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 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; } + + /// + /// Gets the entity type on witch the complex property chain is declared. + /// + new IConventionEntityType FundametalEntityType { get; } + + /// + /// Adds a property to this complex type. + /// + /// The corresponding member on the complex 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 complex 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 complex 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 complex type. + /// + /// The name of the property to add. + /// The property type. + /// + /// + /// The corresponding CLR type member or for a shadow property. + /// + /// + /// 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 complex 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 type configuration source should be set. + /// 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 setTypeConfigurationSource = true, + 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, collection, fromDataAnnotation); + } + + /// + /// 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. + new IConventionComplexProperty? FindComplexProperty(string name); + + /// + /// Gets a complex property with the given member info. Returns if no property is found. + /// + /// The member on the entity class. + /// The property, or if none is found. + new IConventionComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IConventionComplexProperty?)((IReadOnlyComplexType)this).FindComplexProperty(memberInfo); + + /// + /// Gets the complex properties defined on this complex type. + /// + /// The complex properties defined on this complex type. + new IEnumerable GetComplexProperties(); + + /// + /// Removes a property from this complex type. + /// + /// The name of the property to remove. + /// The property that was removed. + IConventionComplexProperty? RemoveComplexProperty(string name); + + /// + /// Removes a property from this complex type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IConventionComplexProperty? RemoveComplexProperty(IConventionComplexProperty property); +} diff --git a/src/EFCore/Metadata/IConventionEntityType.cs b/src/EFCore/Metadata/IConventionEntityType.cs index bdef687dd02..1bfca534b13 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. /// @@ -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, @@ -782,72 +764,80 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// /// Adds a property to this entity type. /// - /// The corresponding member on the entity class. + /// The corresponding member on the entity 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")] - IConventionProperty? AddProperty(MemberInfo memberInfo, bool fromDataAnnotation = false) - => AddProperty( + IConventionComplexProperty? AddComplexProperty(MemberInfo memberInfo, bool collection = false, bool fromDataAnnotation = false) + => AddComplexProperty( memberInfo.GetSimpleMemberName(), memberInfo.GetMemberType(), - memberInfo, setTypeConfigurationSource: true, fromDataAnnotation); + memberInfo, memberInfo.GetMemberType(), collection, fromDataAnnotation); /// /// Adds a property to this entity 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. - IConventionProperty? AddProperty(string name, bool fromDataAnnotation = false); + IConventionComplexProperty? AddComplexProperty(string name, bool collection = false, 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. + /// 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. - IConventionProperty? AddProperty( + IConventionComplexProperty? AddComplexProperty( string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - bool setTypeConfigurationSource = true, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, 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 property type. /// /// - /// The corresponding CLR type member or for a shadow property. + /// 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. + /// 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. - IConventionProperty? AddProperty( + IConventionComplexProperty? AddComplexProperty( string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - MemberInfo? memberInfo, - bool setTypeConfigurationSource = true, + MemberInfo memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, 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. + /// 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. - IConventionProperty? AddIndexerProperty( + IConventionComplexProperty? AddComplexIndexerProperty( string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - bool setTypeConfigurationSource = true, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false, bool fromDataAnnotation = false) { var indexerPropertyInfo = FindIndexerPropertyInfo(); @@ -857,64 +847,23 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas CoreStrings.NonIndexerEntityType(name, DisplayName(), typeof(string).ShortDisplayName())); } - return AddProperty(name, propertyType, indexerPropertyInfo, setTypeConfigurationSource, fromDataAnnotation); + return AddComplexProperty(name, propertyType, indexerPropertyInfo, complexType, fromDataAnnotation); } /// - /// Gets the property with a given name. Returns if no property with the given name is defined. + /// Gets the complex 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(); + new IConventionComplexProperty? FindComplexProperty(string name); /// - /// Gets a property on the given entity type. Returns if no property is found. + /// Gets a complex 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 property on the entity class. + /// The member 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); + new IConventionComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IConventionComplexProperty?)((IReadOnlyEntityType)this).FindComplexProperty(memberInfo); /// /// Finds a property declared on the type with the given name. @@ -922,46 +871,46 @@ public interface IConventionEntityType : IReadOnlyEntityType, IConventionTypeBas /// /// The property name. /// The property, or if none is found. - new IConventionProperty? FindDeclaredProperty(string name) - => (IConventionProperty?)((IReadOnlyEntityType)this).FindDeclaredProperty(name); + new IConventionComplexProperty? FindDeclaredComplexProperty(string name) + => (IConventionComplexProperty?)((IReadOnlyEntityType)this).FindDeclaredComplexProperty(name); /// - /// Gets all non-navigation properties declared on this entity type. + /// Gets the complex properties defined 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(); + /// 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 all non-navigation properties declared on the types derived from this entity type. + /// Gets the complex 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 + /// 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 non-navigation properties. - new IEnumerable GetDerivedProperties() - => ((IReadOnlyEntityType)this).GetDerivedProperties().Cast(); + /// Derived complex properties. + new IEnumerable GetDerivedComplexProperties() + => ((IReadOnlyEntityType)this).GetDerivedComplexProperties().Cast(); /// /// Removes a property from this entity type. /// /// The name of the property to remove. /// The property that was removed. - IConventionProperty? RemoveProperty(string name); + IConventionComplexProperty? RemoveComplexProperty(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); + IConventionComplexProperty? RemoveComplexProperty(IConventionComplexProperty property); /// /// Adds a service property to this entity type. diff --git a/src/EFCore/Metadata/IConventionProperty.cs b/src/EFCore/Metadata/IConventionProperty.cs index 9cd79384d95..6f7323121e0 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. /// /// /// @@ -26,11 +26,6 @@ public interface IConventionProperty : IReadOnlyProperty, IConventionPropertyBas /// If the property has been removed from the model. new IConventionPropertyBuilder Builder { get; } - /// - /// Gets the type that this property belongs to. - /// - new IConventionEntityType DeclaringEntityType { get; } - /// /// Returns the configuration source for . /// @@ -96,26 +91,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 +113,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 +284,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 +311,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 +329,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 +355,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 +396,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 +423,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..b2afda788b4 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,12 @@ 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; } + /// /// Marks the given member name as ignored, preventing conventions from adding a matching property /// or navigation to the type. @@ -64,6 +71,200 @@ public interface IConventionTypeBase : IReadOnlyTypeBase, IConventionAnnotatable bool IsIgnored(string memberName) => FindIgnoredConfigurationSource(memberName) != null; + /// + /// 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. + /// + /// + /// 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 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); + + /// + /// 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. + ChangeTrackingStrategy? SetChangeTrackingStrategy(ChangeTrackingStrategy? changeTrackingStrategy, bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetChangeTrackingStrategyConfigurationSource(); + /// /// Sets the to use for properties of this type. /// diff --git a/src/EFCore/Metadata/IEntityType.cs b/src/EFCore/Metadata/IEntityType.cs index 31ec35acb13..4ee21d6df20 100644 --- a/src/EFCore/Metadata/IEntityType.cs +++ b/src/EFCore/Metadata/IEntityType.cs @@ -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(); @@ -424,51 +424,32 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase new IEnumerable GetIndexes(); /// - /// Gets a property on the given entity type. Returns if no property is found. + /// Returns the properties contained in foreign keys. /// - /// - /// 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); + /// The properties contained in foreign keys. + IEnumerable GetForeignKeyProperties(); /// - /// Gets the property with a given name. Returns if no property with the given name is defined. + /// Returns the properties that need a value to be generated when the entity entry transitions to the + /// state. /// - /// - /// 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); + /// The properties that need a value to be generated on add. + IEnumerable GetValueGeneratingProperties(); /// - /// Finds matching properties on the given entity type. Returns if any property is not found. + /// Gets the complex 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 property names. - /// The properties, or if any property is not found. - new IReadOnlyList? FindProperties( - IReadOnlyList propertyNames) - => (IReadOnlyList?)((IReadOnlyEntityType)this).FindProperties(propertyNames); + /// The name of the property. + /// The property, or if none is found. + new IComplexProperty? FindComplexProperty(string name); /// - /// Gets a property with the given name. + /// Gets a complex 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 property name. - /// The property. - new IProperty GetProperty(string name) - => (IProperty)((IReadOnlyEntityType)this).GetProperty(name); + /// 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. @@ -476,53 +457,32 @@ public interface IEntityType : IReadOnlyEntityType, ITypeBase /// /// The property name. /// The property, or if none is found. - new IProperty? FindDeclaredProperty(string name); + new IComplexProperty? FindDeclaredComplexProperty(string name) + => (IComplexProperty?)((IReadOnlyEntityType)this).FindDeclaredComplexProperty(name); /// - /// Gets all non-navigation properties declared on the given . + /// Gets the complex properties defined 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(); + /// The complex properties defined on this entity type. + new IEnumerable GetComplexProperties(); /// - /// Gets all non-navigation properties declared on the types derived from this entity type. + /// Gets the complex properties declared on 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(); + /// Declared complex properties. + new IEnumerable GetDeclaredComplexProperties(); /// - /// Gets the properties defined on this entity type. + /// Gets the complex properties declared on the types derived from this entity type. /// /// - /// This API only returns scalar properties and does not return navigation properties. Use - /// to get navigation properties. + /// 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. /// - /// The properties defined on this entity type. - new IEnumerable GetProperties(); - - /// - /// Returns the properties contained in foreign keys. - /// - /// The properties contained in foreign keys. - IEnumerable GetForeignKeyProperties(); - - /// - /// Returns the properties that need a value to be generated when the entity entry transitions to the - /// state. - /// - /// The properties that need a value to be generated on add. - IEnumerable GetValueGeneratingProperties(); + /// Derived complex properties. + new IEnumerable GetDerivedComplexProperties() + => ((IReadOnlyEntityType)this).GetDerivedComplexProperties().Cast(); /// /// Gets the service property with a given name. 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..6f4d0201ac5 --- /dev/null +++ b/src/EFCore/Metadata/IMutableComplexType.cs @@ -0,0 +1,148 @@ +// 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; } + + /// + /// Gets the entity type on witch the complex property chain is declared. + /// + new IMutableEntityType FundametalEntityType { get; } + + /// + /// Adds a complex property to this complex type. + /// + /// The corresponding member on the complex type. + /// 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(), memberInfo, + collection ? memberInfo.GetMemberType().GetSequenceType() : memberInfo.GetMemberType(), collection); + + /// + /// Adds a complex property to this complex 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); + + /// + /// Adds a complex property to this complex 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 complex 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 complex 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 complex type. Returns if no property is found. + /// + /// The property on the complex type. + /// The property, or if none is found. + new IMutableComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IMutableComplexProperty?)((IReadOnlyComplexType)this).FindComplexProperty(memberInfo); + + /// + /// 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. + new IMutableComplexProperty? FindComplexProperty(string name); + + /// + /// Gets the properties defined on this complex type. + /// + /// The properties defined on this complex type. + new IEnumerable GetComplexProperties(); + + /// + /// Removes a property from this complex 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 complex type. + /// + /// The property to remove. + /// The removed property, or if the property was not found. + IMutableComplexProperty? RemoveComplexProperty(IReadOnlyProperty property); +} diff --git a/src/EFCore/Metadata/IMutableEntityType.cs b/src/EFCore/Metadata/IMutableEntityType.cs index a1823a5b51f..e8e6dfd0f2a 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. /// @@ -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); /// @@ -665,26 +687,87 @@ IMutableIndex AddIndex(IMutableProperty property, string name) IMutableIndex? RemoveIndex(IReadOnlyIndex index); /// - /// Adds a property to this entity type. + /// Adds a complex property to this entity type. + /// + /// The corresponding member on the entity 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 entity 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); + + /// + /// Adds a complex property to this entity type. /// /// The name of the property to add. - /// The type of value the property will hold. + /// 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 entity type. + /// + /// The name of the property to add. + /// The property type. /// /// - /// The corresponding CLR type member or for a shadow property. + /// 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. - IMutableProperty AddProperty( + IMutableComplexProperty AddComplexProperty( string name, [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type propertyType, - MemberInfo? memberInfo); + MemberInfo memberInfo, + [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type complexType, + bool collection = false); /// - /// Gets a property on the given entity type. Returns if no property is found. + /// Adds a complex property backed up by an indexer to this entity 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 entity type. Returns if no property is found. /// /// /// This API only finds scalar properties and does not find navigation properties. Use @@ -692,11 +775,11 @@ IMutableProperty AddProperty( /// /// The property on the entity class. /// The property, or if none is found. - new IMutableProperty? FindProperty(MemberInfo memberInfo) - => (IMutableProperty?)((IReadOnlyEntityType)this).FindProperty(memberInfo); + new IMutableComplexProperty? FindComplexProperty(MemberInfo memberInfo) + => (IMutableComplexProperty?)((IReadOnlyEntityType)this).FindComplexProperty(memberInfo); /// - /// Gets the property with a given name. Returns if no property with the given name is defined. + /// Gets the complex 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 @@ -705,107 +788,39 @@ IMutableProperty AddProperty( /// /// The name of the property. /// The property, or if none is found. - new IMutableProperty? FindProperty(string name); + new IMutableComplexProperty? FindComplexProperty(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. + /// Finds a complex 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); - } + new IMutableComplexProperty? FindDeclaredComplexProperty(string name) + => (IMutableComplexProperty?)((IReadOnlyEntityType)this).FindDeclaredComplexProperty(name); /// - /// Gets all non-navigation properties declared on this entity type. + /// Gets all complex 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(); + /// Declared complex properties. + new IEnumerable GetDeclaredComplexProperties(); /// - /// Gets all non-navigation properties declared on the types derived from this entity type. + /// Gets all complex 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(); + /// Derived complex properties. + new IEnumerable GetDerivedComplexProperties() + => ((IReadOnlyEntityType)this).GetDerivedComplexProperties().Cast(); /// /// Gets the properties defined on this entity type. @@ -816,21 +831,21 @@ IMutableProperty AddIndexerProperty( /// properties. /// /// The properties defined on this entity type. - new IEnumerable GetProperties(); + new IEnumerable GetComplexProperties(); /// /// 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); + IMutableComplexProperty? RemoveComplexProperty(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); + IMutableComplexProperty? RemoveComplexProperty(IReadOnlyProperty property); /// /// Adds a service property to this entity type. diff --git a/src/EFCore/Metadata/IMutableProperty.cs b/src/EFCore/Metadata/IMutableProperty.cs index 936c2616a91..7a443edd240 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. /// /// /// @@ -21,11 +21,6 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// public interface IMutableProperty : IReadOnlyProperty, IMutablePropertyBase { - /// - /// Gets the type that this property belongs to. - /// - new IMutableEntityType DeclaringEntityType { get; } - /// /// Gets or sets a value indicating whether this property can contain . /// @@ -68,7 +63,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 +184,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 +198,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 +263,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..6da7b0f8609 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; @@ -53,8 +54,179 @@ public interface IMutableTypeBase : IReadOnlyTypeBase, IMutableAnnotatable /// The list of ignored member names. IEnumerable GetIgnoredMembers(); + + /// + /// 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 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. + /// + /// + /// This API only finds scalar properties and does not find navigation, complex or service properties. + /// + /// 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); + + /// + /// 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 entity type. + /// 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 @@ -65,7 +237,7 @@ void SetPropertyAccessMode(PropertyAccessMode? propertyAccessMode) => SetOrRemoveAnnotation(CoreAnnotationNames.PropertyAccessMode, propertyAccessMode); /// - /// Sets the to use for navigations of this entity type. + /// Sets the to use for navigations of this type. /// /// /// Note that individual navigations can override this access mode. The value set here will diff --git a/src/EFCore/Metadata/IProperty.cs b/src/EFCore/Metadata/IProperty.cs index 40ee5b4c195..e8b0085ffcf 100644 --- a/src/EFCore/Metadata/IProperty.cs +++ b/src/EFCore/Metadata/IProperty.cs @@ -1,24 +1,18 @@ // 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. /// public interface IProperty : IReadOnlyProperty, IPropertyBase { - /// - /// Gets the type that this property belongs to. - /// - new IEntityType DeclaringEntityType { get; } - /// /// Creates an for values of the given property type. /// @@ -41,7 +35,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 +92,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..b5b433ca86e --- /dev/null +++ b/src/EFCore/Metadata/IReadOnlyComplexType.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 System.Security.AccessControl; +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; } + + /// + /// Gets the entity type on witch the complex property chain is declared. + /// + IReadOnlyEntityType FundametalEntityType { get; } + + /// + /// 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. + IReadOnlyComplexProperty? FindComplexProperty(string name); + + /// + /// Gets a complex property with the given member info. Returns if no property is found. + /// + /// The member on the complex 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()); + + /// + /// Gets the complex properties defined on this complex type. + /// + /// The complex properties defined on this complex type. + IEnumerable GetComplexProperties(); + + /// + /// + /// 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..6f65d48f7fc 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. /// @@ -603,39 +596,21 @@ bool IsInOwnershipPath(IReadOnlyEntityType targetType) IEnumerable GetIndexes(); /// - /// Gets the property with a given name. Returns if no property with the given name is defined. + /// Gets the complex 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); + IReadOnlyComplexProperty? FindComplexProperty(string name); /// - /// Gets a property with the given member info. Returns if no property is found. + /// Gets a complex 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) + IReadOnlyComplexProperty? FindComplexProperty(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); + : FindComplexProperty(memberInfo.GetSimpleMemberName()); /// /// Finds a property declared on the type with the given name. @@ -643,70 +618,30 @@ bool IsInOwnershipPath(IReadOnlyEntityType targetType) /// /// The property name. /// The property, or if none is found. - IReadOnlyProperty? FindDeclaredProperty(string name); + IReadOnlyComplexProperty? FindDeclaredComplexProperty(string name); /// - /// Gets a property with the given name. + /// Gets the complex properties defined on this entity type. /// - /// - /// 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; - } + /// The complex properties defined on this entity type. + IEnumerable GetComplexProperties(); /// - /// Gets all non-navigation properties declared on this entity type. + /// Gets the complex 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(); + /// Declared complex properties. + IEnumerable GetDeclaredComplexProperties(); /// - /// Gets all non-navigation properties declared on the types derived from this entity type. + /// Gets the complex 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 + /// 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 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(); + /// Derived complex properties. + IEnumerable GetDerivedComplexProperties(); /// /// Gets the service property with a given name. @@ -855,6 +790,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..e41b851ffb5 100644 --- a/src/EFCore/Metadata/IReadOnlyProperty.cs +++ b/src/EFCore/Metadata/IReadOnlyProperty.cs @@ -1,428 +1,435 @@ // 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.Security.Principal; +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 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..31936ab6990 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. @@ -41,13 +41,13 @@ public interface IReadOnlyTypeBase : IReadOnlyAnnotatable 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 +55,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] @@ -149,6 +149,103 @@ string ShortName() return Name[(hashIndex + 1)..]; } + /// + /// 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. + 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 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, 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 entity type. + IEnumerable GetProperties(); + + /// + /// 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. /// diff --git a/src/EFCore/Metadata/ITypeBase.cs b/src/EFCore/Metadata/ITypeBase.cs index 2005c627d2d..bc023c4b01b 100644 --- a/src/EFCore/Metadata/ITypeBase.cs +++ b/src/EFCore/Metadata/ITypeBase.cs @@ -15,4 +15,89 @@ public interface ITypeBase : IReadOnlyTypeBase, IAnnotatable /// Gets the model that this type belongs to. /// new IModel Model { get; } + + /// + /// 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(); } 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..28c1f6765ef --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexPropertySnapshot.cs @@ -0,0 +1,234 @@ +// 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(InternalEntityTypeBuilder entityTypeBuilder) + { + var newProperty = entityTypeBuilder.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() + ? entityTypeBuilder.ComplexIndexerProperty( + ComplexProperty.ClrType, + ComplexProperty.Name, + ComplexType.ClrType, + ComplexProperty.IsCollection, + configurationSource) + : entityTypeBuilder.ComplexProperty( + ComplexProperty.ClrType, + ComplexProperty.Name, + ComplexProperty.GetIdentifyingMemberInfo(), + ComplexType.ClrType, + ComplexProperty.IsCollection, + configurationSource); + + if (complexPropertyBuilder is null) + { + return null; + } + } + + return MergeConfiguration(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. + /// + public virtual InternalComplexPropertyBuilder? Attach(InternalComplexTypeBuilder complexTypeBuilder) + { + var newProperty = complexTypeBuilder.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() + ? complexTypeBuilder.ComplexIndexerProperty( + ComplexProperty.ClrType, + ComplexProperty.Name, + ComplexType.ClrType, + ComplexProperty.IsCollection, + configurationSource) + : complexTypeBuilder.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..5ea8a4ae3f0 --- /dev/null +++ b/src/EFCore/Metadata/Internal/ComplexType.cs @@ -0,0 +1,962 @@ +// 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 IReadOnlyComplexType.FundametalEntityType + { + [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 IMutableComplexType.FundametalEntityType + { + [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 IConventionComplexType.FundametalEntityType + { + [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 IComplexType.FundametalEntityType + { + [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. + /// + [DebuggerStepThrough] + IMutableComplexProperty IMutableComplexType.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? IConventionComplexType.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 IMutableComplexType.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? IConventionComplexType.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 IMutableComplexType.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? IConventionComplexType.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] + IEnumerable IReadOnlyComplexType.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 IMutableComplexType.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 IConventionComplexType.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 IComplexType.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] + IReadOnlyComplexProperty? IReadOnlyComplexType.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? IMutableComplexType.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? IConventionComplexType.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? IComplexType.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? IMutableComplexType.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? IConventionComplexType.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? IMutableComplexType.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? IConventionComplexType.RemoveComplexProperty(IConventionComplexProperty property) + => RemoveComplexProperty((ComplexProperty)property); + + #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..a5fa0d7a859 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,36 @@ 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; + { + _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 +218,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 +229,7 @@ public virtual EntityType? BaseType /// public virtual bool IsKeyless { - get => RootType()._isKeyless ?? false; + get => GetRootType()._isKeyless ?? false; set => SetIsKeyless(value, ConfigurationSource.Explicit); } @@ -255,10 +287,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 +338,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) { @@ -365,14 +397,14 @@ private void UpdateIsKeylessConfigurationSource(ConfigurationSource configuratio CoreStrings.DuplicatePropertiesOnBase( DisplayName(), newBaseType.DisplayName(), - ((IReadOnlyTypeBase)conflictingMember.DeclaringType).DisplayName(), + conflictingMember.DeclaringType.DisplayName(), conflictingMember.Name, - ((IReadOnlyTypeBase)baseProperty.DeclaringType).DisplayName(), + baseProperty.DeclaringType.DisplayName(), baseProperty.Name)); } - _baseType = newBaseType; - _baseType._directlyDerivedTypes.Add(this); + base.BaseType = newBaseType; + newBaseType.DirectlyDerivedTypes.Add(this); } UpdateBaseTypeConfigurationSource(configurationSource); @@ -381,42 +413,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 +427,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 +434,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 +445,7 @@ public virtual IEnumerable GetDerivedTypesInclusive() /// [DebuggerStepThrough] public virtual IEnumerable GetForeignKeysInHierarchy() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? GetForeignKeys() : GetForeignKeys().Concat(GetDerivedForeignKeys()); @@ -506,7 +460,7 @@ private bool InheritsFrom(EntityType entityType) return true; } } - while ((et = et._baseType) != null); + while ((et = et.BaseType) != null); return false; } @@ -518,7 +472,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 +503,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 +516,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 +529,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 +563,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,32 +593,24 @@ public virtual IEnumerable FindMembersInHierarchy(string name) { foreach (var property in oldPrimaryKey.Properties) { - _properties.Remove(property.Name); property.PrimaryKey = null; } _primaryKey = null; - foreach (var property in oldPrimaryKey.Properties) - { - _properties.Add(property.Name, property); - } + ReorderProperties(oldPrimaryKey.Properties); } if (properties?.Count > 0 && newKey != null) { foreach (var property in newKey.Properties) { - _properties.Remove(property.Name); property.PrimaryKey = newKey; } _primaryKey = newKey; - foreach (var property in newKey.Properties) - { - _properties.Add(property.Name, property); - } + ReorderProperties(newKey.Properties); UpdatePrimaryKeyConfigurationSource(configurationSource); } @@ -680,7 +629,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 +642,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 +704,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 +783,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 +816,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 +896,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 +1047,10 @@ public virtual IEnumerable FindForeignKeys(IReadOnlyList FindForeignKeys(IReadOnlyList @@ -1192,9 +1141,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 +1152,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 +1213,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 +1227,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 +1241,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 +1255,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 +1357,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 +1534,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 +1548,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 +1561,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 +1594,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 +1606,7 @@ public virtual IEnumerable GetNavigations() /// public virtual SkipNavigation? AddSkipNavigation( string name, + Type? navigationType, MemberInfo? memberInfo, EntityType targetEntityType, bool collection, @@ -1671,7 +1622,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 +1651,7 @@ public virtual IEnumerable GetNavigations() var skipNavigation = new SkipNavigation( name, + navigationType, memberInfo as PropertyInfo, memberInfo as FieldInfo, this, @@ -1724,39 +1676,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 +1686,7 @@ public virtual IEnumerable GetNavigations() { Check.NotEmpty(name, nameof(name)); - return FindDeclaredSkipNavigation(name) ?? _baseType?.FindSkipNavigation(name); + return FindDeclaredSkipNavigation(name) ?? BaseType?.FindSkipNavigation(name); } /// @@ -1806,9 +1725,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 +1739,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 +1752,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 +1763,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 +1833,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 +1846,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 +1868,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 +2025,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 +2038,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 +2059,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 +2092,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 +2104,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 +2117,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 +2128,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)); @@ -2295,356 +2216,16 @@ public virtual IEnumerable FindIndexesInHierarchy(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 IEnumerable GetIndexes() - => _baseType != null - ? _namedIndexes.Count == 0 && _unnamedIndexes.Count == 0 - ? _baseType.GetIndexes() - : _baseType.GetIndexes().Concat(GetDeclaredIndexes()) - : GetDeclaredIndexes(); - - #endregion - - #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 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 - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in 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); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in 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) - { - 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 - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your 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()) - { - 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 - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your 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); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your 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; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your 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 - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your 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; + public virtual IEnumerable GetIndexes() + => BaseType != null + ? _namedIndexes.Count == 0 && _unnamedIndexes.Count == 0 + ? BaseType.GetIndexes() + : BaseType.GetIndexes().Concat(GetDeclaredIndexes()) + : GetDeclaredIndexes(); + + #endregion + + #region Lazy runtime logic /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -2833,7 +2414,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 +2446,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 +2460,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 +2471,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 +2527,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 +2536,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 +2558,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 +2646,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 @@ -3251,97 +2819,6 @@ public virtual void AddData(IEnumerable data) #region Other - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in 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( - 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; - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -3432,7 +2909,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 +3114,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 +3135,7 @@ IModel ITypeBase.Model IReadOnlyEntityType? IReadOnlyEntityType.BaseType { [DebuggerStepThrough] - get => _baseType; + get => BaseType; } /// @@ -3693,7 +3146,7 @@ IModel ITypeBase.Model /// IMutableEntityType? IMutableEntityType.BaseType { - get => _baseType; + get => BaseType; set => SetBaseType((EntityType?)value, ConfigurationSource.Explicit); } @@ -3745,29 +3198,6 @@ void IMutableEntityType.SetDiscriminatorProperty(IReadOnlyProperty? property) (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); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -3796,7 +3226,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 +3869,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 +3887,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); /// @@ -4860,36 +4292,8 @@ IEnumerable IEntityType.GetIndexes() /// 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)!; + IMutableServiceProperty IMutableEntityType.AddServiceProperty(MemberInfo memberInfo, Type? serviceType) + => AddServiceProperty(memberInfo, serviceType ?? memberInfo.GetMemberType(), ConfigurationSource.Explicit); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4898,17 +4302,10 @@ IMutableProperty IMutableEntityType.AddProperty( /// 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, + IConventionServiceProperty IConventionEntityType.AddServiceProperty(MemberInfo memberInfo, Type? serviceType, bool fromDataAnnotation) + => AddServiceProperty( + memberInfo, + serviceType ?? memberInfo.GetMemberType(), fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// @@ -4918,13 +4315,8 @@ IMutableProperty IMutableEntityType.AddProperty( /// 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)!; + IReadOnlyServiceProperty? IReadOnlyEntityType.FindServiceProperty(string name) + => FindServiceProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4933,20 +4325,8 @@ IMutableProperty IMutableEntityType.AddProperty( /// 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); + IMutableServiceProperty? IMutableEntityType.FindServiceProperty(string name) + => FindServiceProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4955,8 +4335,8 @@ IMutableProperty IMutableEntityType.AddProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IReadOnlyProperty? IReadOnlyEntityType.FindDeclaredProperty(string name) - => FindDeclaredProperty(name); + IConventionServiceProperty? IConventionEntityType.FindServiceProperty(string name) + => FindServiceProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4965,8 +4345,8 @@ IMutableProperty IMutableEntityType.AddProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IProperty? IEntityType.FindDeclaredProperty(string name) - => FindDeclaredProperty(name); + IServiceProperty? IEntityType.FindServiceProperty(string name) + => FindServiceProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4975,8 +4355,8 @@ IMutableProperty IMutableEntityType.AddProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IReadOnlyList? IReadOnlyEntityType.FindProperties(IReadOnlyList propertyNames) - => FindProperties(propertyNames); + IEnumerable IReadOnlyEntityType.GetDeclaredServiceProperties() + => GetDeclaredServiceProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4985,8 +4365,8 @@ IMutableProperty IMutableEntityType.AddProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IReadOnlyProperty? IReadOnlyEntityType.FindProperty(string name) - => FindProperty(name); + IEnumerable IEntityType.GetDeclaredServiceProperties() + => GetDeclaredServiceProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -4995,8 +4375,8 @@ IMutableProperty IMutableEntityType.AddProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IMutableProperty? IMutableEntityType.FindProperty(string name) - => FindProperty(name); + IEnumerable IReadOnlyEntityType.GetDerivedServiceProperties() + => GetDerivedServiceProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5005,8 +4385,8 @@ IMutableProperty IMutableEntityType.AddProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionProperty? IConventionEntityType.FindProperty(string name) - => FindProperty(name); + bool IReadOnlyEntityType.HasServiceProperties() + => HasServiceProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5015,8 +4395,8 @@ IMutableProperty IMutableEntityType.AddProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IProperty? IEntityType.FindProperty(string name) - => FindProperty(name); + IEnumerable IReadOnlyEntityType.GetServiceProperties() + => GetServiceProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5025,8 +4405,8 @@ IMutableProperty IMutableEntityType.AddProperty( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetDeclaredProperties() - => GetDeclaredProperties(); + IEnumerable IMutableEntityType.GetServiceProperties() + => GetServiceProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5035,8 +4415,8 @@ IEnumerable IReadOnlyEntityType.GetDeclaredProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IEntityType.GetDeclaredProperties() - => GetDeclaredProperties(); + IEnumerable IConventionEntityType.GetServiceProperties() + => GetServiceProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5045,8 +4425,8 @@ IEnumerable IEntityType.GetDeclaredProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetDerivedProperties() - => GetDerivedProperties(); + IEnumerable IEntityType.GetServiceProperties() + => GetServiceProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5055,8 +4435,8 @@ IEnumerable IReadOnlyEntityType.GetDerivedProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetProperties() - => GetProperties(); + IMutableServiceProperty? IMutableEntityType.RemoveServiceProperty(IReadOnlyServiceProperty property) + => RemoveServiceProperty((ServiceProperty)property); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5065,8 +4445,8 @@ IEnumerable IReadOnlyEntityType.GetProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IMutableEntityType.GetProperties() - => GetProperties(); + IConventionServiceProperty? IConventionEntityType.RemoveServiceProperty(IReadOnlyServiceProperty property) + => RemoveServiceProperty((ServiceProperty)property); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5075,8 +4455,8 @@ IEnumerable IMutableEntityType.GetProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IConventionEntityType.GetProperties() - => GetProperties(); + IMutableServiceProperty? IMutableEntityType.RemoveServiceProperty(string name) + => RemoveServiceProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5085,8 +4465,8 @@ IEnumerable IConventionEntityType.GetProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IEntityType.GetProperties() - => GetProperties(); + IConventionServiceProperty? IConventionEntityType.RemoveServiceProperty(string name) + => RemoveServiceProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5095,8 +4475,8 @@ IEnumerable IEntityType.GetProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IEntityType.GetForeignKeyProperties() - => ForeignKeyProperties; + IMutableComplexProperty IMutableEntityType.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 @@ -5105,8 +4485,11 @@ IEnumerable IEntityType.GetForeignKeyProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IEntityType.GetValueGeneratingProperties() - => ValueGeneratingProperties; + IConventionComplexProperty? IConventionEntityType.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 @@ -5115,8 +4498,9 @@ IEnumerable IEntityType.GetValueGeneratingProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IMutableProperty? IMutableEntityType.RemoveProperty(string name) - => RemoveProperty(name); + IMutableComplexProperty IMutableEntityType.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 @@ -5125,8 +4509,10 @@ IEnumerable IEntityType.GetValueGeneratingProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionProperty? IConventionEntityType.RemoveProperty(string name) - => RemoveProperty(name); + IConventionComplexProperty? IConventionEntityType.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 @@ -5135,8 +4521,9 @@ IEnumerable IEntityType.GetValueGeneratingProperties() /// 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); + IMutableComplexProperty IMutableEntityType.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 @@ -5145,8 +4532,10 @@ IEnumerable IEntityType.GetValueGeneratingProperties() /// 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); + IConventionComplexProperty? IConventionEntityType.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 @@ -5155,8 +4544,8 @@ IEnumerable IEntityType.GetValueGeneratingProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IMutableServiceProperty IMutableEntityType.AddServiceProperty(MemberInfo memberInfo, Type? serviceType) - => AddServiceProperty(memberInfo, serviceType ?? memberInfo.GetMemberType(), ConfigurationSource.Explicit); + IReadOnlyComplexProperty? IReadOnlyEntityType.FindComplexProperty(string name) + => FindComplexProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5165,11 +4554,8 @@ IMutableServiceProperty IMutableEntityType.AddServiceProperty(MemberInfo memberI /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionServiceProperty IConventionEntityType.AddServiceProperty(MemberInfo memberInfo, Type? serviceType, bool fromDataAnnotation) - => AddServiceProperty( - memberInfo, - serviceType ?? memberInfo.GetMemberType(), - fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + IMutableComplexProperty? IMutableEntityType.FindComplexProperty(string name) + => FindComplexProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5178,8 +4564,8 @@ IConventionServiceProperty IConventionEntityType.AddServiceProperty(MemberInfo m /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IReadOnlyServiceProperty? IReadOnlyEntityType.FindServiceProperty(string name) - => FindServiceProperty(name); + IConventionComplexProperty? IConventionEntityType.FindComplexProperty(string name) + => FindComplexProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5188,8 +4574,8 @@ IConventionServiceProperty IConventionEntityType.AddServiceProperty(MemberInfo m /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IMutableServiceProperty? IMutableEntityType.FindServiceProperty(string name) - => FindServiceProperty(name); + IComplexProperty? IEntityType.FindComplexProperty(string name) + => FindComplexProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5198,8 +4584,8 @@ IConventionServiceProperty IConventionEntityType.AddServiceProperty(MemberInfo m /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionServiceProperty? IConventionEntityType.FindServiceProperty(string name) - => FindServiceProperty(name); + IReadOnlyComplexProperty? IReadOnlyEntityType.FindDeclaredComplexProperty(string name) + => FindDeclaredComplexProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5208,8 +4594,8 @@ IConventionServiceProperty IConventionEntityType.AddServiceProperty(MemberInfo m /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IServiceProperty? IEntityType.FindServiceProperty(string name) - => FindServiceProperty(name); + IEnumerable IReadOnlyEntityType.GetComplexProperties() + => GetComplexProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5218,8 +4604,8 @@ IConventionServiceProperty IConventionEntityType.AddServiceProperty(MemberInfo m /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetDeclaredServiceProperties() - => GetDeclaredServiceProperties(); + IEnumerable IMutableEntityType.GetComplexProperties() + => GetComplexProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5228,8 +4614,8 @@ IEnumerable IReadOnlyEntityType.GetDeclaredServiceProp /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IEntityType.GetDeclaredServiceProperties() - => GetDeclaredServiceProperties(); + IEnumerable IConventionEntityType.GetComplexProperties() + => GetComplexProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5238,8 +4624,8 @@ IEnumerable IEntityType.GetDeclaredServiceProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetDerivedServiceProperties() - => GetDerivedServiceProperties(); + IEnumerable IEntityType.GetComplexProperties() + => GetComplexProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5248,8 +4634,8 @@ IEnumerable IReadOnlyEntityType.GetDerivedServicePrope /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - bool IReadOnlyEntityType.HasServiceProperties() - => HasServiceProperties(); + IEnumerable IReadOnlyEntityType.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5258,8 +4644,8 @@ bool IReadOnlyEntityType.HasServiceProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IReadOnlyEntityType.GetServiceProperties() - => GetServiceProperties(); + IEnumerable IMutableEntityType.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5268,8 +4654,8 @@ IEnumerable IReadOnlyEntityType.GetServiceProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IMutableEntityType.GetServiceProperties() - => GetServiceProperties(); + IEnumerable IConventionEntityType.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5278,8 +4664,8 @@ IEnumerable IMutableEntityType.GetServiceProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IConventionEntityType.GetServiceProperties() - => GetServiceProperties(); + IEnumerable IEntityType.GetDeclaredComplexProperties() + => GetDeclaredComplexProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5288,8 +4674,8 @@ IEnumerable IConventionEntityType.GetServiceProperti /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IEnumerable IEntityType.GetServiceProperties() - => GetServiceProperties(); + IEnumerable IReadOnlyEntityType.GetDerivedComplexProperties() + => GetDerivedComplexProperties(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5298,8 +4684,8 @@ IEnumerable IEntityType.GetServiceProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IMutableServiceProperty? IMutableEntityType.RemoveServiceProperty(IReadOnlyServiceProperty property) - => RemoveServiceProperty((ServiceProperty)property); + IMutableComplexProperty? IMutableEntityType.RemoveComplexProperty(string name) + => RemoveComplexProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5308,8 +4694,8 @@ IEnumerable IEntityType.GetServiceProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionServiceProperty? IConventionEntityType.RemoveServiceProperty(IReadOnlyServiceProperty property) - => RemoveServiceProperty((ServiceProperty)property); + IConventionComplexProperty? IConventionEntityType.RemoveComplexProperty(string name) + => RemoveComplexProperty(name); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5318,8 +4704,8 @@ IEnumerable IEntityType.GetServiceProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IMutableServiceProperty? IMutableEntityType.RemoveServiceProperty(string name) - => RemoveServiceProperty(name); + IMutableComplexProperty? IMutableEntityType.RemoveComplexProperty(IReadOnlyProperty property) + => RemoveComplexProperty((ComplexProperty)property); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5328,8 +4714,8 @@ IEnumerable IEntityType.GetServiceProperties() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [DebuggerStepThrough] - IConventionServiceProperty? IConventionEntityType.RemoveServiceProperty(string name) - => RemoveServiceProperty(name); + IConventionComplexProperty? IConventionEntityType.RemoveComplexProperty(IConventionComplexProperty property) + => RemoveComplexProperty((ComplexProperty)property); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -5451,13 +4837,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 +4925,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 +4939,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 +4999,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..f0f3bdacb73 --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalComplexPropertyBuilder.cs @@ -0,0 +1,318 @@ +// 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 = ((EntityType)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 = InternalComplexTypeBuilder.DetachProperties(complexType.GetDeclaredProperties().ToList()); + + return new ComplexPropertySnapshot( + complexProperty.Builder, + 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 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..3b336475c97 --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalComplexTypeBuilder.cs @@ -0,0 +1,1162 @@ +// 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 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 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, targetComplexType: 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, targetComplexType: 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? targetComplexType, + bool? collection, + ConfigurationSource? configurationSource) + { + var complexType = Metadata; + List? propertiesToDetach = null; + var existingComplexProperty = Metadata.FindComplexProperty(propertyName); + if (existingComplexProperty != null) + { + if (existingComplexProperty.DeclaringType != Metadata) + { + if (!IsIgnored(propertyName, configurationSource)) + { + Metadata.RemoveIgnored(propertyName); + } + + complexType = (ComplexType)existingComplexProperty.DeclaringType; + } + + var existingComplexType = existingComplexProperty.ComplexType; + if (InternalEntityTypeBuilder.IsCompatible(memberInfo, existingComplexProperty) + && (propertyType == null + || existingComplexProperty.ClrType == propertyType) + && (targetComplexType == null + || existingComplexType.ClrType == targetComplexType) + && (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; + targetComplexType ??= existingComplexType.ClrType; + + propertiesToDetach = new List { existingComplexProperty }; + } + else + { + if (configurationSource != ConfigurationSource.Explicit + && (!configurationSource.HasValue + || !CanAddComplexProperty( + propertyName, propertyType ?? memberInfo?.GetMemberType(), targetComplexType, 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) + { + targetComplexType = propertyType; + } + + if (collection == null + || targetComplexType == null) + { + var elementType = propertyType.TryGetSequenceType(); + collection ??= elementType != null; + targetComplexType ??= 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()) + { + var detachedProperties = propertiesToDetach == null ? null : DetachProperties(propertiesToDetach); + if (existingComplexProperty == null) + { + Metadata.RemoveIgnored(propertyName); + + foreach (var conflictingProperty in Metadata.FindPropertiesInHierarchy(propertyName)) + { + if (conflictingProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + ((ComplexType)conflictingProperty.DeclaringType).RemoveProperty(conflictingProperty); + } + } + } + + builder = complexType.AddComplexProperty( + propertyName, propertyType, memberInfo, targetComplexType!, 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 + ? (InternalEntityTypeBuilder.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 virtual 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 virtual InternalComplexTypeBuilder? 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); + + var removedProperty = ((EntityType)propertyToDetach.DeclaringType).RemoveComplexProperty(propertyToDetach); + Check.DebugAssert(removedProperty != null, "removedProperty is null"); + } + + 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 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 => ((ComplexType)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 => ((ComplexType)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( + ((ComplexType)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 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? 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 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] + IConventionPropertyBuilder? IConventionComplexTypeBuilder.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] + IConventionPropertyBuilder? IConventionComplexTypeBuilder.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] + bool IConventionComplexTypeBuilder.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 IConventionComplexTypeBuilder.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? IConventionComplexTypeBuilder.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 IConventionComplexTypeBuilder.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] + 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] + bool IConventionComplexTypeBuilder.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? IConventionComplexTypeBuilder.ComplexProperty( + Type propertyType, string propertyName, Type? complexType, bool fromDataAnnotation) + => ComplexProperty( + propertyType, + propertyName, + memberInfo: null, + targetComplexType: 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? IConventionComplexTypeBuilder.ComplexProperty( + MemberInfo memberInfo, Type? complexType, bool fromDataAnnotation) + => ComplexProperty( + propertyType: memberInfo.GetMemberType(), + propertyName: memberInfo.Name, + memberInfo: memberInfo, + targetComplexType: 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 IConventionComplexTypeBuilder.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 IConventionComplexTypeBuilder.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? IConventionComplexTypeBuilder.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 IConventionComplexTypeBuilder.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] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.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 IConventionComplexTypeBuilder.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. + /// + [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) + => 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 IConventionComplexTypeBuilder.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] + IConventionComplexTypeBuilder? IConventionComplexTypeBuilder.UsePropertyAccessMode( + PropertyAccessMode? propertyAccessMode, + bool fromDataAnnotation) + => (IConventionComplexTypeBuilder?)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 IConventionComplexTypeBuilder.CanSetPropertyAccessMode(PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) + => CanSetPropertyAccessMode( + propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); +} diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 8a406133a34..93aa3646313 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -13,7 +13,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 +32,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 EntityType Metadata => (EntityType)base.Metadata; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -396,7 +390,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,11 +473,60 @@ 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); + public override void RemoveMembersInHierarchy(string propertyName, ConfigurationSource configurationSource) + { + base.RemoveMembersInHierarchy(propertyName, configurationSource); + + 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 + { + var removed = foreignKey.Builder.HasNavigation( + (string?)null, + conflictingNavigation.IsOnDependent, + configurationSource); + + Check.DebugAssert(removed != null, $"Navigation {propertyName} not removed"); + } + } + + 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); + } + + conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation( + conflictingSkipNavigation, configurationSource); + } + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -485,15 +534,23 @@ 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( + protected override bool CanAddProperty( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, string propertyName, - ConfigurationSource? typeConfigurationSource, - ConfigurationSource? configurationSource) - => Property( - propertyType, propertyName, memberInfo: null, - typeConfigurationSource, - configurationSource); + 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.FindServicePropertiesInHierarchy(propertyName).Cast() + .Concat(Metadata.FindComplexPropertiesInHierarchy(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 @@ -501,8 +558,8 @@ 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(string propertyName, ConfigurationSource? configurationSource) - => Property(propertyType: null, propertyName, memberInfo: null, typeConfigurationSource: null, configurationSource); + public virtual IMutableNavigationBase Navigation(MemberInfo memberInfo) + => Navigation(memberInfo.GetSimpleMemberName()); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -510,8 +567,11 @@ 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(MemberInfo memberInfo, ConfigurationSource? configurationSource) - => Property(memberInfo.GetMemberType(), memberInfo.GetSimpleMemberName(), memberInfo, configurationSource, configurationSource); + public virtual IMutableNavigationBase Navigation(string navigationName) + => (IMutableNavigationBase?)Metadata.FindNavigation(navigationName) + ?? Metadata.FindSkipNavigation(navigationName) + ?? throw new InvalidOperationException( + CoreStrings.CanOnlyConfigureExistingNavigations(navigationName, Metadata.DisplayName())); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -519,31 +579,26 @@ 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? IndexerProperty( - [DynamicallyAccessedMembers(IProperty.DynamicallyAccessedMemberTypes)] Type? propertyType, - string propertyName, + public virtual InternalServicePropertyBuilder? ServiceProperty( + MemberInfo memberInfo, 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); - } + => ServiceProperty(memberInfo.GetMemberType(), memberInfo, configurationSource); - private InternalPropertyBuilder? Property( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, - string propertyName, - MemberInfo? memberInfo, - ConfigurationSource? typeConfigurationSource, + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your 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 InternalServicePropertyBuilder? ServiceProperty( + Type serviceType, + MemberInfo memberInfo, ConfigurationSource? configurationSource) { - var entityType = Metadata; - List? propertiesToDetach = null; - var existingProperty = entityType.FindProperty(propertyName); + var propertyName = memberInfo.GetSimpleMemberName(); + List? propertiesToDetach = null; + InternalServicePropertyBuilder? builder; + var existingProperty = Metadata.FindServiceProperty(propertyName); if (existingProperty != null) { if (existingProperty.DeclaringEntityType != Metadata) @@ -552,153 +607,80 @@ public virtual bool CanRemoveKey(ConfigurationSource configurationSource) { Metadata.RemoveIgnored(propertyName); } - - entityType = existingProperty.DeclaringEntityType; } - if (IsCompatible(memberInfo, existingProperty) - && (propertyType == null || propertyType == existingProperty.ClrType)) + if (existingProperty.GetIdentifyingMemberInfo()?.IsOverriddenBy(memberInfo) == true) { 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())) + if (!configurationSource.Overrides(existingProperty.GetConfigurationSource())) { return null; } - propertyType ??= existingProperty.ClrType; - - propertiesToDetach = new List { existingProperty }; + propertiesToDetach = new List { existingProperty }; + } + else if (configurationSource != ConfigurationSource.Explicit + && (!configurationSource.HasValue + || !CanAddServiceProperty(memberInfo, configurationSource.Value))) + { + return null; } 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) + foreach (EntityType derivedType in Metadata.GetDerivedTypes()) { - 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); + var derivedProperty = derivedType.FindDeclaredServiceProperty(propertyName); if (derivedProperty != null) { - propertiesToDetach ??= new List(); + propertiesToDetach ??= new List(); propertiesToDetach.Add(derivedProperty); } } } - Check.DebugAssert(configurationSource is not null, "configurationSource is null"); + Check.DebugAssert(configurationSource is not null, "configurationSource is not null"); - InternalPropertyBuilder builder; - using (Metadata.Model.DelayConventions()) + using (ModelBuilder.Metadata.DelayConventions()) { - var detachedProperties = propertiesToDetach == null ? null : DetachProperties(propertiesToDetach); + List? detachedProperties = null; + if (propertiesToDetach != null) + { + detachedProperties = new List(); + foreach (var propertyToDetach in propertiesToDetach) + { + detachedProperties.Add(DetachServiceProperty(propertyToDetach)!); + } + } if (existingProperty == null) { 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())); - } + RemoveMembersInHierarchy(propertyName, configurationSource.Value); + } - 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; - } - } + builder = Metadata.AddServiceProperty(memberInfo, serviceType, configurationSource.Value).Builder; - foreach (var conflictingSkipNavigation in Metadata.FindSkipNavigationsInHierarchy(propertyName)) + if (detachedProperties != null) + { + foreach (var detachedProperty in detachedProperties) { - 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); + detachedProperty.Attach(this); } } - - builder = entityType.AddProperty( - propertyName, propertyType, memberInfo, typeConfigurationSource, configurationSource.Value)!.Builder; - - detachedProperties?.Attach(this); } return builder.Metadata.IsInModel ? builder - : Metadata.FindProperty(propertyName)?.Builder; + : Metadata.FindServiceProperty(propertyName)?.Builder; } /// @@ -707,147 +689,65 @@ 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) + public virtual bool CanHaveServiceProperty(MemberInfo memberInfo, ConfigurationSource? configurationSource) { - var existingProperty = Metadata.FindProperty(propertyName); + var existingProperty = Metadata.FindServiceProperty(memberInfo); 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))) + ? existingProperty.DeclaringType == Metadata || configurationSource.Overrides(existingProperty.GetConfigurationSource()) : configurationSource.HasValue - && CanAddProperty(propertyType ?? memberInfo?.GetMemberType(), propertyName, configurationSource.Value, checkClrProperty); + && CanAddServiceProperty(memberInfo, configurationSource.Value); } - private 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.FindServicePropertiesInHierarchy(propertyName).Cast() + private bool CanAddServiceProperty(MemberInfo memberInfo, ConfigurationSource configurationSource) + { + var propertyName = memberInfo.GetSimpleMemberName(); + return !IsIgnored(propertyName, configurationSource) + && 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( m => configurationSource.Overrides(m.GetConfigurationSource()) - && m.GetConfigurationSource() != ConfigurationSource.Explicit); + && m.GetConfigurationSource() != ConfigurationSource.Explicit) + && Metadata.FindServicePropertiesInHierarchy(propertyName).All( + m => (configurationSource.Overrides(m.GetConfigurationSource()) + && m.GetConfigurationSource() != ConfigurationSource.Explicit) + || memberInfo.IsOverriddenBy(m.GetIdentifyingMemberInfo())); + } - private static bool IsCompatible(MemberInfo? newMemberInfo, Property existingProperty) + private static InternalServicePropertyBuilder? DetachServiceProperty(ServiceProperty? serviceProperty) { - 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 (serviceProperty is null || !serviceProperty.IsInModel) { - if (newMemberInfo.DeclaringType == baseType.ClrType) - { - return existingMemberDeclaringEntityType != null - && existingMemberInfo.IsOverriddenBy(newMemberInfo); - } - - if (existingMemberDeclaringEntityType == null - && existingMemberInfo.DeclaringType == baseType.ClrType) - { - existingMemberDeclaringEntityType = baseType; - } + return null; } - // 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)); + var builder = serviceProperty.Builder; + serviceProperty.DeclaringEntityType.RemoveServiceProperty(serviceProperty); + return builder; } - private ConfigurationSource? RemoveProperty( - Property property, - ConfigurationSource configurationSource, - bool canOverrideSameSource = 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? HasNoServiceProperty( + ServiceProperty serviceProperty, + ConfigurationSource configurationSource) { - var currentConfigurationSource = property.GetConfigurationSource(); - if (!configurationSource.Overrides(currentConfigurationSource) - || !(canOverrideSameSource || (configurationSource != currentConfigurationSource))) + if (!CanRemoveServiceProperty(serviceProperty, configurationSource)) { return null; } - using (Metadata.Model.DelayConventions()) - { - var detachedRelationships = property.GetContainingForeignKeys().ToList() - .Select(DetachRelationship).ToList(); + Metadata.RemoveServiceProperty(serviceProperty); - 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; + return this; } /// @@ -856,8 +756,8 @@ private bool CanRemoveProperty( /// any release. You should only use it directly in your 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 IMutableNavigationBase Navigation(MemberInfo memberInfo) - => Navigation(memberInfo.GetSimpleMemberName()); + 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 @@ -865,11 +765,22 @@ public virtual IMutableNavigationBase Navigation(MemberInfo memberInfo) /// any release. You should only use it directly in your 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 IMutableNavigationBase Navigation(string navigationName) - => (IMutableNavigationBase?)Metadata.FindNavigation(navigationName) - ?? Metadata.FindSkipNavigation(navigationName) - ?? throw new InvalidOperationException( - CoreStrings.CanOnlyConfigureExistingNavigations(navigationName, Metadata.DisplayName())); + 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 @@ -877,10 +788,12 @@ public virtual IMutableNavigationBase Navigation(string navigationName) /// any release. You should only use it directly in your 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 InternalServicePropertyBuilder? ServiceProperty( + public virtual InternalComplexPropertyBuilder? ComplexProperty( MemberInfo memberInfo, + bool? collection, ConfigurationSource? configurationSource) - => ServiceProperty(memberInfo.GetMemberType(), memberInfo, 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 @@ -888,130 +801,143 @@ public virtual IMutableNavigationBase Navigation(string navigationName) /// any release. You should only use it directly in your 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 InternalServicePropertyBuilder? ServiceProperty( - Type serviceType, - MemberInfo memberInfo, + 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 propertyName = memberInfo.GetSimpleMemberName(); - List? propertiesToDetach = null; - InternalServicePropertyBuilder? builder; - var existingProperty = Metadata.FindServiceProperty(propertyName); - if (existingProperty != null) + var entityType = Metadata; + List? propertiesToDetach = null; + var existingComplexProperty = Metadata.FindComplexProperty(propertyName); + if (existingComplexProperty != null) { - if (existingProperty.DeclaringEntityType != Metadata) + if (existingComplexProperty.DeclaringType != Metadata) { if (!IsIgnored(propertyName, configurationSource)) { Metadata.RemoveIgnored(propertyName); } + + entityType = (EntityType)existingComplexProperty.DeclaringType; } - if (existingProperty.GetIdentifyingMemberInfo()?.IsOverriddenBy(memberInfo) == true) + 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) { - existingProperty.UpdateConfigurationSource(configurationSource.Value); + existingComplexProperty.UpdateConfigurationSource(configurationSource.Value); } - return existingProperty.Builder; + return existingComplexProperty.Builder; } - if (!configurationSource.Overrides(existingProperty.GetConfigurationSource())) + if (!configurationSource.Overrides(existingComplexProperty.GetConfigurationSource())) { return null; } - propertiesToDetach = new List { existingProperty }; - } - else if (configurationSource != ConfigurationSource.Explicit - && (!configurationSource.HasValue - || !CanAddServiceProperty(memberInfo, configurationSource.Value))) - { - 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 { - foreach (var derivedType in Metadata.GetDerivedTypes()) + if (configurationSource != ConfigurationSource.Explicit + && (!configurationSource.HasValue + || !CanAddComplexProperty( + propertyName, propertyType ?? memberInfo?.GetMemberType(), complexType, collection, configurationSource.Value))) { - var derivedProperty = derivedType.FindDeclaredServiceProperty(propertyName); - if (derivedProperty != null) - { - propertiesToDetach ??= new List(); - - propertiesToDetach.Add(derivedProperty); - } + return null; } - } - Check.DebugAssert(configurationSource is not null, "configurationSource is not null"); + memberInfo ??= Metadata.IsPropertyBag + ? null + : Metadata.ClrType.GetMembersInHierarchy(propertyName).FirstOrDefault(); - using (ModelBuilder.Metadata.DelayConventions()) - { - List? detachedProperties = null; - if (propertiesToDetach != null) + if (propertyType == null) { - detachedProperties = new List(); - foreach (var propertyToDetach in propertiesToDetach) + if (memberInfo == null) { - detachedProperties.Add(DetachServiceProperty(propertyToDetach)!); + throw new InvalidOperationException(CoreStrings.NoPropertyType(propertyName, Metadata.DisplayName())); } + + propertyType = memberInfo.GetMemberType(); } - if (existingProperty == null) + if (collection == false) { - Metadata.RemoveIgnored(propertyName); + complexType = propertyType; + } - foreach (var conflictingProperty in Metadata.FindPropertiesInHierarchy(propertyName).ToList()) - { - if (conflictingProperty.GetConfigurationSource() != ConfigurationSource.Explicit) - { - conflictingProperty.DeclaringEntityType.Builder.RemoveProperty(conflictingProperty, configurationSource.Value); - } - } + if (collection == null + || complexType == null) + { + var elementType = propertyType.TryGetSequenceType(); + collection ??= elementType != null; + complexType ??= collection.Value ? elementType : propertyType; + } - foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(propertyName).ToList()) + foreach (var derivedType in Metadata.GetDerivedTypes()) + { + var derivedProperty = derivedType.FindDeclaredComplexProperty(propertyName); + if (derivedProperty != null) { - if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) - { - continue; - } + propertiesToDetach ??= new List(); - 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; - } + propertiesToDetach.Add(derivedProperty); } + } + } - foreach (var conflictingSkipNavigation in Metadata.FindSkipNavigationsInHierarchy(propertyName).ToList()) - { - if (conflictingSkipNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) - { - continue; - } + InternalComplexPropertyBuilder builder; + using (Metadata.Model.DelayConventions()) + { + Metadata.Model.AddComplex(complexType!, configurationSource.Value); - var inverse = conflictingSkipNavigation.Inverse; - if (inverse?.IsInModel == true - && inverse.GetConfigurationSource() != ConfigurationSource.Explicit) - { - inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource.Value); - } + foreach (var existingEntityType in Metadata.Model.FindEntityTypes(complexType!).ToList()) + { + Metadata.Model.Builder.HasNoEntityType(existingEntityType, ConfigurationSource.Convention); + } - conflictingSkipNavigation.DeclaringEntityType.Builder.HasNoSkipNavigation( - conflictingSkipNavigation, configurationSource.Value); - } + var detachedProperties = propertiesToDetach == null ? null : DetachProperties(propertiesToDetach); + if (existingComplexProperty == null) + { + Metadata.RemoveIgnored(propertyName); + + RemoveMembersInHierarchy(propertyName, configurationSource.Value); } - builder = Metadata.AddServiceProperty(memberInfo, serviceType, configurationSource.Value).Builder; + builder = entityType.AddComplexProperty( + propertyName, propertyType, memberInfo, complexType!, collection.Value, configurationSource.Value)!.Builder; if (detachedProperties != null) { @@ -1024,7 +950,7 @@ public virtual IMutableNavigationBase Navigation(string navigationName) return builder.Metadata.IsInModel ? builder - : Metadata.FindServiceProperty(propertyName)?.Builder; + : Metadata.FindComplexProperty(propertyName)?.Builder; } /// @@ -1033,44 +959,113 @@ public virtual IMutableNavigationBase Navigation(string navigationName) /// any release. You should only use it directly in your 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 CanHaveServiceProperty(MemberInfo memberInfo, ConfigurationSource? configurationSource) + public virtual bool CanHaveComplexProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? propertyType, + string propertyName, + MemberInfo? memberInfo, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type? complexType, + bool? collection, + ConfigurationSource? configurationSource) { - var existingProperty = Metadata.FindServiceProperty(memberInfo); - return existingProperty != null - ? existingProperty.DeclaringEntityType == Metadata - || configurationSource.Overrides(existingProperty.GetConfigurationSource()) + 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 - && CanAddServiceProperty(memberInfo, configurationSource.Value); + && CanAddComplexProperty(propertyName, propertyType, complexType, collection, configurationSource.Value); } - private bool CanAddServiceProperty(MemberInfo memberInfo, ConfigurationSource configurationSource) - { - var propertyName = memberInfo.GetSimpleMemberName(); - return !IsIgnored(propertyName, configurationSource) - && Metadata.Model.Builder.CanBeConfigured( - memberInfo.GetMemberType(), TypeConfigurationType.ServiceProperty, 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 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) - && Metadata.FindServicePropertiesInHierarchy(propertyName).All( - m => (configurationSource.Overrides(m.GetConfigurationSource()) - && m.GetConfigurationSource() != ConfigurationSource.Explicit) - || memberInfo.IsOverriddenBy(m.GetIdentifyingMemberInfo())); + .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 virtual InternalEntityTypeBuilder? HasNoComplexProperty( + ComplexProperty complexProperty, + ConfigurationSource configurationSource) + { + if (!CanRemoveComplexProperty(complexProperty, configurationSource)) + { + return null; + } + + Metadata.RemoveComplexProperty(complexProperty); + + return this; } - private static InternalServicePropertyBuilder? DetachServiceProperty(ServiceProperty? serviceProperty) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your 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 (serviceProperty is null || !serviceProperty.IsInModel) + if (propertiesToDetach.Count == 0) { return null; } - var builder = serviceProperty.Builder; - serviceProperty.DeclaringEntityType.RemoveServiceProperty(serviceProperty); - return builder; + var detachedProperties = new List(); + foreach (var propertyToDetach in propertiesToDetach) + { + var snapshot = InternalComplexPropertyBuilder.Detach(propertyToDetach); + if (snapshot == null) + { + continue; + } + + detachedProperties.Add(snapshot); + + var removedProperty = ((EntityType)propertyToDetach.DeclaringType).RemoveComplexProperty(propertyToDetach); + Check.DebugAssert(removedProperty != null, "removedProperty is null"); + } + + return detachedProperties; } /// @@ -1107,6 +1102,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 +1138,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 +1151,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 +1209,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 +1222,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( - skipNavigation.DeclaringEntityType == Metadata, "skipNavigation.DeclaringEntityType != Metadata"); + Check.DebugAssert(complexProperty.DeclaringType == Metadata, "property.DeclaringType != 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 +1322,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) - { - inverse.DeclaringEntityType.Builder.HasNoSkipNavigation(inverse, configurationSource); - } - - if (skipNavigation.GetConfigurationSource() != ConfigurationSource.Explicit) + if (configurationSource.Overrides(declaredComplexProperty.GetConfigurationSource()) + && declaredComplexProperty.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 +1373,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 +1406,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 +1426,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 +1647,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 +1661,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 +1713,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 +1724,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 +1752,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 => ((EntityType)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 +1814,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 +1867,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 +1921,22 @@ public virtual bool CanSetDefiningQuery(LambdaExpression? query, ConfigurationSo } } + if (detachedComplexProperties != null) + { + foreach (var detachedComplexProperty in detachedComplexProperties) + { + detachedComplexProperty.Attach( + ((EntityType)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) { @@ -1989,127 +2047,57 @@ public virtual bool CanSetBaseType(EntityType? baseEntityType, ConfigurationSour if (!configurationSource.Overrides(Metadata.GetBaseTypeConfigurationSource())) { - return false; - } - - if (baseEntityType == null) - { - return true; - } - - var configurationSourceForRemoval = ConfigurationSource.DataAnnotation.Max(configurationSource); - if (Metadata.GetDeclaredKeys().Any( - k => !configurationSourceForRemoval.Overrides(k.GetConfigurationSource()) - && k.Properties.Any(p => baseEntityType.FindProperty(p.Name) == null)) - || (Metadata.IsKeyless && !configurationSource.Overrides(Metadata.GetIsKeylessConfigurationSource()))) - { - return false; - } - - if (Metadata.GetDerivedTypesInclusive() - .SelectMany(t => t.GetDeclaredForeignKeys()) - .Where(fk => fk.Properties.Any(p => baseEntityType.FindProperty(p.Name)?.IsKey() == true)) - .Any(fk => !configurationSourceForRemoval.Overrides(fk.GetPropertiesConfigurationSource()))) - { - return false; - } - - var baseMembers = baseEntityType.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 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 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)); - } + return false; } - var detachedIndexes = DetachIndexes(propertiesToDetach.SelectMany(p => p.GetContainingIndexes()).Distinct()); + if (baseEntityType == null) + { + return true; + } - var keysToDetach = propertiesToDetach.SelectMany(p => p.GetContainingKeys()).Distinct().ToList(); - foreach (var key in keysToDetach) + var configurationSourceForRemoval = ConfigurationSource.DataAnnotation.Max(configurationSource); + if (Metadata.GetDeclaredKeys().Any( + k => !configurationSourceForRemoval.Overrides(k.GetConfigurationSource()) + && k.Properties.Any(p => baseEntityType.FindProperty(p.Name) == null)) + || (Metadata.IsKeyless && !configurationSource.Overrides(Metadata.GetIsKeylessConfigurationSource()))) { - foreach (var referencingForeignKey in key.GetReferencingForeignKeys().ToList()) - { - detachedRelationships ??= new List(); + return false; + } - detachedRelationships.Add(DetachRelationship(referencingForeignKey)); - } + 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()))) + { + return false; } - var detachedKeys = DetachKeys(keysToDetach); + var baseMembers = baseEntityType.GetMembers() + .Where(m => m.GetConfigurationSource() == ConfigurationSource.Explicit) + .ToDictionary(m => m.Name); - var detachedProperties = new List(); - foreach (var propertyToDetach in propertiesToDetach) + foreach (var derivedMember in Metadata.GetDerivedTypesInclusive().SelectMany(et => et.GetDeclaredMembers())) { - var property = propertyToDetach.DeclaringEntityType.FindDeclaredProperty(propertyToDetach.Name); - if (property != null) + if (derivedMember.GetConfigurationSource() == ConfigurationSource.Explicit + && baseMembers.TryGetValue(derivedMember.Name, out var baseMember)) { - 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 + switch (derivedMember) { - removedConfigurationSource = property.GetConfigurationSource(); - property.DeclaringEntityType.RemoveProperty(property.Name); + case IReadOnlyProperty: + return baseMember is IReadOnlyProperty; + case IReadOnlyNavigation derivedNavigation: + return baseMember is IReadOnlyNavigation baseNavigation + && derivedNavigation.TargetEntityType == baseNavigation.TargetEntityType; + case IReadOnlyServiceProperty: + return baseMember is IReadOnlyServiceProperty; + case IReadOnlySkipNavigation derivedSkipNavigation: + return baseMember is IReadOnlySkipNavigation baseSkipNavigation + && derivedSkipNavigation.TargetEntityType == baseSkipNavigation.TargetEntityType; } - - Check.DebugAssert(removedConfigurationSource.HasValue, "removedConfigurationSource.HasValue is false"); - detachedProperties.Add(propertyBuilder); } } - return new PropertiesSnapshot(detachedProperties, detachedIndexes, detachedKeys, detachedRelationships); + return true; } /// @@ -2295,11 +2283,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 +2325,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 +2564,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 +3535,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 +3614,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 +3657,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 +3686,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 +3708,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 +4052,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 +4199,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 +4231,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 +4282,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 +4327,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) @@ -4307,9 +4339,8 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK } if (collection == null - && memberInfo != null) + && navigationType != null) { - var navigationType = memberInfo.GetMemberType(); var navigationTargetClrType = navigationType.TryGetSequenceType(); collection = navigationTargetClrType != null && navigationType != targetEntityType.ClrType @@ -4320,44 +4351,7 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK { 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; - } - } + RemoveMembersInHierarchy(navigationName, configurationSource.Value); if (navigationsToDetach != null) { @@ -4370,14 +4364,14 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK } builder = Metadata.AddSkipNavigation( - navigationName, memberInfo, - targetEntityType, collection ?? true, onDependent ?? false, configurationSource.Value)!.Builder; + navigationName, navigationType, memberInfo, targetEntityType, + collection ?? true, onDependent ?? false, configurationSource.Value)!.Builder; if (detachedNavigations != null) { - foreach (var (navigation, inverse) in detachedNavigations) + foreach (var (detachedNavigation, inverse) in detachedNavigations) { - navigation.Attach(this, inverseBuilder: inverse); + detachedNavigation.Attach(this, inverseBuilder: inverse); } } } @@ -4393,8 +4387,8 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK } builder = Metadata.AddSkipNavigation( - navigationName, null, - targetEntityType, collection ?? true, onDependent ?? false, ConfigurationSource.Explicit)!.Builder; + navigationName, navigationType, null, targetEntityType, + collection ?? true, onDependent ?? false, ConfigurationSource.Explicit)!.Builder; } return builder.Metadata.IsInModel @@ -4518,7 +4512,13 @@ public virtual bool ShouldReuniquifyTemporaryProperties(ForeignKey foreignKey) isRequired, baseName).Item2; - private (bool, IReadOnlyList?) TryCreateUniqueProperties( + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your 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, @@ -4556,207 +4556,56 @@ public virtual bool ShouldReuniquifyTemporaryProperties(ForeignKey foreignKey) && !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.DeclaringEntityType.Builder.Property( - property.ClrType.MakeNullable(false), - property.Name, - configurationSource.Value)! - .Metadata; - } - else - { - property = property.DeclaringEntityType.Builder.Property(property.Name, configurationSource.Value)!.Metadata; - } - } - - propertyList.Add(property); - } + { + var propertyBuilder = Property( + clrType, propertyName, typeConfigurationSource: null, + configurationSource: ConfigurationSource.Convention); - return propertyList; - } + if (propertyBuilder == null) + { + return (false, 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 IReadOnlyList? GetOrCreateProperties( - IEnumerable? clrMembers, - ConfigurationSource? configurationSource) - { - if (clrMembers == null) - { - return null; - } + if (index > 0) + { + propertyBuilder.HasAnnotation( + CoreAnnotationNames.PreUniquificationName, + keyModifiedBaseName, + ConfigurationSource.Convention); + } - var list = new List(); - foreach (var propertyInfo in clrMembers) - { - var propertyBuilder = Property(propertyInfo, configurationSource); - if (propertyBuilder == null) - { - return null; - } + if (clrType.IsNullableType()) + { + propertyBuilder.IsRequired(isRequired, ConfigurationSource.Convention); + } - list.Add(propertyBuilder.Metadata); - } + newProperties![i] = propertyBuilder.Metadata; + } + else if (Metadata.Model.Builder.CanBeConfigured( + clrType, TypeConfigurationType.Property, ConfigurationSource.Convention)) + { + canReuniquify = true; + } - return list; - } + break; + } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your 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 currentProperty = currentProperties?.SingleOrDefault(p => p.Name == propertyName); + if (currentProperty != null) + { + if (((IConventionProperty)currentProperty).IsImplicitlyCreated() + && currentProperty.ClrType != clrType + && isRequired) + { + canReuniquify = true; + } - 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; + break; + } } - - actualProperties[i] = builder.Metadata; } - return actualProperties; + return (canReuniquify, newProperties); } /// @@ -4791,36 +4640,6 @@ public virtual bool CanSetChangeTrackingStrategy( => 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 InternalEntityTypeBuilder? 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(((IConventionEntityType)Metadata).GetPropertyAccessModeConfigurationSource()) - || ((IConventionEntityType)Metadata).GetPropertyAccessMode() == propertyAccessMode; - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -4937,7 +4756,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 +4774,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 +4791,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 +4882,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 +4914,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 @@ -5141,6 +4960,39 @@ IConventionEntityType IConventionEntityTypeBuilder.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] + 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 + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in 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.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 + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in 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.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 /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -5298,7 +5150,158 @@ bool IConventionEntityTypeBuilder.CanHaveIndexerProperty( [DebuggerStepThrough] IConventionEntityTypeBuilder IConventionEntityTypeBuilder.RemoveUnusedImplicitProperties( IReadOnlyList properties) - => RemoveUnusedImplicitProperties(properties); + => (IConventionEntityTypeBuilder)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] + 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 + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in 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.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? IConventionEntityTypeBuilder.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? IConventionEntityTypeBuilder.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 IConventionEntityTypeBuilder.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 IConventionEntityTypeBuilder.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? IConventionEntityTypeBuilder.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 IConventionEntityTypeBuilder.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] + IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.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 IConventionEntityTypeBuilder.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 @@ -5337,8 +5340,24 @@ bool IConventionEntityTypeBuilder.CanHaveServiceProperty(MemberInfo memberInfo, /// any release. You should only use it directly in 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); + [DebuggerStepThrough] + 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 + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in 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.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 +5376,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 +5751,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 +5973,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 +6021,7 @@ bool IConventionEntityTypeBuilder.CanHaveSkipNavigation(string skipNavigationNam => HasSkipNavigation( MemberIdentity.Create(navigation), (EntityType)targetEntityType, + navigation.GetMemberType(), fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention, collection, onDependent); @@ -5981,12 +6031,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); @@ -6097,7 +6149,7 @@ bool IConventionEntityTypeBuilder.CanSetChangeTrackingStrategy( IConventionEntityTypeBuilder? IConventionEntityTypeBuilder.UsePropertyAccessMode( PropertyAccessMode? propertyAccessMode, bool fromDataAnnotation) - => UsePropertyAccessMode( + => (IConventionEntityTypeBuilder?)UsePropertyAccessMode( propertyAccessMode, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); /// diff --git a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs index b24fac41ada..0f37ae6b29a 100644 --- a/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalForeignKeyBuilder.cs @@ -264,78 +264,20 @@ public InternalForeignKeyBuilder( } } - if (navigationToPrincipalName != null) + if (navigationToPrincipalName != null + && (!Metadata.IsInModel + || (!(shouldInvert ?? false) && Metadata.DependentToPrincipal?.Name != navigationToPrincipalName) + || ((shouldInvert ?? false) && Metadata.PrincipalToDependent?.Name != navigationToPrincipalName))) { - 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 + && (!Metadata.IsInModel + || (!(shouldInvert ?? false) && Metadata.PrincipalToDependent?.Name != navigationToDependentName) + || ((shouldInvert ?? false) && Metadata.DependentToPrincipal?.Name != navigationToDependentName))) { - 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 +1501,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 +1539,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 +2387,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 +2868,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 +3941,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..ed50b91ee54 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. @@ -402,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. @@ -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 - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in 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); - - /// - /// 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( + 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. /// - 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,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. @@ -1173,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. @@ -1182,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. @@ -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..e7ac6d07695 --- /dev/null +++ b/src/EFCore/Metadata/Internal/InternalTypeBaseBuilder.cs @@ -0,0 +1,807 @@ +// 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 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 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) + { + ((EntityType)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 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? 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. + /// + 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); +} 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/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..a32c9f5510b 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 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..9a6b74a54e3 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 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,1116 @@ 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 void ReorderProperties(IReadOnlyList properties) + { + foreach (var property in properties) + { + _properties.Remove(property.Name); + _properties.Add(property.Name, 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 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. + /// + 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 + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your 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; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in 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] + 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..ece33c8e940 --- /dev/null +++ b/src/EFCore/Metadata/RuntimeComplexType.cs @@ -0,0 +1,320 @@ +// 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 readonly SortedDictionary _complexProperties = + new SortedDictionary(StringComparer.Ordinal); + + private readonly RuntimeComplexType? _baseType; + private readonly SortedSet _directlyDerivedTypes = new(TypeBaseNameComparer.Instance); + 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, changeTrackingStrategy, indexerPropertyInfo, propertyBag) + { + ComplexProperty = complexProperty; + _baseType = null; + FundametalEntityType = complexProperty.DeclaringType switch + { + RuntimeEntityType entityType => entityType, + RuntimeComplexType declaringComplexType => declaringComplexType.FundametalEntityType, + _ => 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; } + + /// + /// Gets the model that this type belongs to. + /// + public override RuntimeModel Model + => ComplexProperty.DeclaringType.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. + /// + private RuntimeEntityType FundametalEntityType { get; } + + 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; + } + + /// + /// Adds a complex property to this complex 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 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().SelectMany(et => et.GetDeclaredComplexProperties()); + + private IEnumerable GetComplexProperties() + => _baseType != null + ? _baseType.GetComplexProperties().Concat(_complexProperties.Values) + : _complexProperties.Values; + + /// + /// 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 IReadOnlyComplexType.FundametalEntityType + { + [DebuggerStepThrough] + get => FundametalEntityType; + } + + /// + [DebuggerStepThrough] + IEnumerable IComplexType.GetComplexProperties() + => GetComplexProperties(); + + /// + [DebuggerStepThrough] + IEnumerable IReadOnlyComplexType.GetComplexProperties() + => GetComplexProperties(); + + /// + [DebuggerStepThrough] + IComplexProperty? IComplexType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + [DebuggerStepThrough] + IReadOnlyComplexProperty? IReadOnlyComplexType.FindComplexProperty(string name) + => FindComplexProperty(name); + + /// + PropertyAccessMode IReadOnlyTypeBase.GetPropertyAccessMode() + => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + + /// + PropertyAccessMode IReadOnlyTypeBase.GetNavigationAccessMode() + => 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/RuntimeEntityType.cs b/src/EFCore/Metadata/RuntimeEntityType.cs index 4aedc345306..613b712a9f8 100644 --- a/src/EFCore/Metadata/RuntimeEntityType.cs +++ b/src/EFCore/Metadata/RuntimeEntityType.cs @@ -15,7 +15,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(); @@ -30,6 +30,9 @@ private readonly SortedDictionary _serviceProper private readonly SortedDictionary _properties; + private readonly SortedDictionary _complexProperties = + new SortedDictionary(StringComparer.Ordinal); + private readonly SortedDictionary, RuntimeIndex> _unnamedIndexes = new(PropertyListComparer.Instance); @@ -45,16 +48,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 +57,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,9 +78,8 @@ public RuntimeEntityType( PropertyInfo? indexerPropertyInfo, bool propertyBag, object? discriminatorValue) + : base(name, type, changeTrackingStrategy, indexerPropertyInfo, propertyBag) { - Name = name; - _clrType = type; _hasSharedClrType = sharedClrType; Model = model; if (baseType != null) @@ -99,24 +88,16 @@ public RuntimeEntityType( 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; } + public override RuntimeModel Model => Model; /// /// Re-parents this entity type to the given model. @@ -127,7 +108,7 @@ public virtual void Reparent(RuntimeModel model) private IEnumerable GetDerivedTypes() { - if (_directlyDerivedTypes.Count == 0) + if (DirectlyDerivedTypes.Count == 0) { return Enumerable.Empty(); } @@ -137,7 +118,7 @@ private IEnumerable GetDerivedTypes() var currentTypeIndex = 0; while (type != null) { - derivedTypes.AddRange(type._directlyDerivedTypes); + derivedTypes.AddRange(type.DirectlyDerivedTypes); type = derivedTypes.Count > currentTypeIndex ? derivedTypes[currentTypeIndex] : null; @@ -148,7 +129,7 @@ private IEnumerable GetDerivedTypes() } private RuntimeKey? FindPrimaryKey() - => _baseType?.FindPrimaryKey() ?? _primaryKey; + => BaseType?.FindPrimaryKey() ?? _primaryKey; /// /// Sets the primary key for this entity type. @@ -204,13 +185,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 +262,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 +285,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); 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 +332,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 +396,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 +455,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 +469,15 @@ private IEnumerable GetDeclaredSkipNavigations() => _skipNavigations.Values; private IEnumerable GetDerivedSkipNavigations() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() : GetDerivedTypes().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 +528,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 +538,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,165 +546,95 @@ private IEnumerable GetDeclaredIndexes() : _unnamedIndexes.Values.Concat(_namedIndexes.Values); private IEnumerable GetDerivedIndexes() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() : GetDerivedTypes().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. + /// Adds a complex 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 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 . - /// 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. + /// 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. /// - /// - /// 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( + public virtual RuntimeComplexProperty AddComplexProperty( string name, Type clrType, - object? sentinel = null, + string targetTypeName, + [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type targetType, 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) + bool collection = false, + ChangeTrackingStrategy changeTrackingStrategy = ChangeTrackingStrategy.Snapshot, + PropertyInfo? indexerPropertyInfo = null, + bool propertyBag = false) { - var property = new RuntimeProperty( + var property = new RuntimeComplexProperty( name, clrType, - sentinel, + targetTypeName, + targetType, 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); + collection, + changeTrackingStrategy, + indexerPropertyInfo, + propertyBag); + + _complexProperties.Add(property.Name, property); return property; } /// - /// Gets the property with a given name. Returns if no property with the given name is defined. + /// Gets the complex 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); + public virtual RuntimeComplexProperty? FindComplexProperty(string name) + => FindDeclaredComplexProperty(name) ?? BaseType?.FindComplexProperty(name); - private RuntimeProperty? FindDeclaredProperty(string name) - => _properties.TryGetValue(name, out var property) + private RuntimeComplexProperty? FindDeclaredComplexProperty(string name) + => _complexProperties.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 GetDeclaredComplexProperties() + => _complexProperties.Values; - private IEnumerable GetProperties() - => _baseType != null - ? _baseType.GetProperties().Concat(_properties.Values) - : _properties.Values; + private IEnumerable GetDerivedComplexProperties() + => DirectlyDerivedTypes.Count == 0 + ? Enumerable.Empty() + : GetDerivedTypes().SelectMany(et => et.GetDeclaredComplexProperties()); - /// - [DebuggerStepThrough] - public virtual PropertyInfo? FindIndexerPropertyInfo() - => _indexerPropertyInfo; + private IEnumerable GetComplexProperties() + => BaseType != null + ? BaseType.GetComplexProperties().Concat(_complexProperties.Values) + : _complexProperties.Values; /// /// Adds a service property to this entity type. @@ -765,7 +676,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,20 +684,20 @@ 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()); @@ -823,8 +734,8 @@ private IEnumerable GetDeclaredTriggers() => _triggers.Values; private IEnumerable GetTriggers() - => _baseType != null - ? _baseType.GetTriggers().Concat(GetDeclaredTriggers()) + => BaseType != null + ? BaseType.GetTriggers().Concat(GetDeclaredTriggers()) : GetDeclaredTriggers(); /// @@ -832,15 +743,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 +775,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 +802,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 +811,7 @@ ChangeTrackingStrategy IReadOnlyEntityType.GetChangeTrackingStrategy() [DebuggerStepThrough] string? IReadOnlyEntityType.GetDiscriminatorPropertyName() { - if (_baseType != null) + if (BaseType != null) { return ((IReadOnlyEntityType)this).GetRootType().GetDiscriminatorPropertyName(); } @@ -934,13 +831,6 @@ bool IReadOnlyTypeBase.HasSharedClrType get => _hasSharedClrType; } - /// - bool IReadOnlyTypeBase.IsPropertyBag - { - [DebuggerStepThrough] - get => _isPropertyBag; - } - /// IReadOnlyModel IReadOnlyTypeBase.Model { @@ -959,14 +849,14 @@ IModel ITypeBase.Model IReadOnlyEntityType? IReadOnlyEntityType.BaseType { [DebuggerStepThrough] - get => _baseType; + get => BaseType; } /// IEntityType? IEntityType.BaseType { [DebuggerStepThrough] - get => _baseType; + get => BaseType; } /// @@ -976,19 +866,19 @@ IEnumerable IReadOnlyEntityType.GetDerivedTypes() /// IEnumerable IReadOnlyEntityType.GetDerivedTypesInclusive() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? new[] { this } : new[] { this }.Concat(GetDerivedTypes()); /// [DebuggerStepThrough] IEnumerable IReadOnlyEntityType.GetDirectlyDerivedTypes() - => _directlyDerivedTypes; + => DirectlyDerivedTypes; /// [DebuggerStepThrough] IEnumerable IEntityType.GetDirectlyDerivedTypes() - => _directlyDerivedTypes; + => DirectlyDerivedTypes; /// [DebuggerStepThrough] @@ -1139,7 +1029,7 @@ IEnumerable IEntityType.GetDeclaredNavigations() /// [DebuggerStepThrough] IEnumerable IReadOnlyEntityType.GetDerivedNavigations() - => _directlyDerivedTypes.Count == 0 + => DirectlyDerivedTypes.Count == 0 ? Enumerable.Empty() : GetDerivedTypes().SelectMany(et => et.GetDeclaredNavigations()); @@ -1258,56 +1148,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 +1168,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() @@ -1382,6 +1188,86 @@ IEnumerable IEntityType.GetValueGeneratingProperties() ref _valueGeneratingProperties, this, static entityType => { return entityType.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. + /// + [DebuggerStepThrough] + IEnumerable IEntityType.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 IReadOnlyEntityType.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 IEntityType.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 IReadOnlyEntityType.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 IReadOnlyEntityType.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] + IComplexProperty? IEntityType.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? IReadOnlyEntityType.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? IReadOnlyEntityType.FindDeclaredComplexProperty(string name) + => FindDeclaredComplexProperty(name); + /// [DebuggerStepThrough] IReadOnlyServiceProperty? IReadOnlyEntityType.FindServiceProperty(string name) @@ -1431,9 +1317,9 @@ PropertyAccessMode IReadOnlyTypeBase.GetPropertyAccessMode() PropertyAccessMode IReadOnlyTypeBase.GetNavigationAccessMode() => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); - ConfigurationSource? IRuntimeEntityType.GetConstructorBindingConfigurationSource() + ConfigurationSource? IRuntimeTypeBase.GetConstructorBindingConfigurationSource() => throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); - ConfigurationSource? IRuntimeEntityType.GetServiceOnlyConstructorBindingConfigurationSource() + ConfigurationSource? IRuntimeTypeBase.GetServiceOnlyConstructorBindingConfigurationSource() => 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..f77545bd24c --- /dev/null +++ b/src/EFCore/Metadata/RuntimeTypeBase.cs @@ -0,0 +1,383 @@ +// 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 RuntimeEntityType? _baseType; + private readonly SortedSet _directlyDerivedTypes = new(TypeBaseNameComparer.Instance); + private readonly SortedDictionary _properties; + 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, + ChangeTrackingStrategy changeTrackingStrategy, + PropertyInfo? indexerPropertyInfo, + bool propertyBag) + { + Name = name; + ClrType = type; + _changeTrackingStrategy = changeTrackingStrategy; + _indexerPropertyInfo = indexerPropertyInfo; + _isPropertyBag = propertyBag; + } + + /// + /// 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 RuntimeModel Model { get => _model; set => _model = value; } + + public RuntimeEntityType? BaseType => _baseType; + + public 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 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; + + /// + [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(); + + /// + 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? 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(); + + /// + 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); + + /// + PropertyAccessMode IReadOnlyTypeBase.GetNavigationAccessMode() + => 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..b20fe0d0285 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( @@ -1624,10 +1660,10 @@ public static string MultipleProvidersConfigured(object? 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. /// - public static string MustRewriteToSameNode(object? caller, object? type) + public static string MustRewriteToSameNode(object? 0, object? 1) => string.Format( - GetString("MustRewriteToSameNode", nameof(caller), nameof(type)), - caller, type); + GetString("MustRewriteToSameNode", nameof(0), nameof(1)), + 0, 1); /// /// 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. @@ -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( @@ -2120,12 +2156,12 @@ public static string PropertyCalledOnNavigation(object? property, object? entity 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. + /// 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 PropertyClashingNonIndexer(object? property, object? entityType) + public static string PropertyClashingNonIndexer(object? property, object? type) => 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 +2180,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 +2204,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 +2266,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 +2976,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 +3725,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 +3791,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 +3816,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..c8e0f35093a 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 an entity 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. @@ -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. @@ -1227,7 +1243,7 @@ '{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 +1252,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 +1285,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 +1551,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..bbba4b2536d 100644 --- a/src/EFCore/Storage/CoreTypeMapping.cs +++ b/src/EFCore/Storage/CoreTypeMapping.cs @@ -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/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..5c48c2f5fbc 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -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 IReadOnlyEntityType.FindComplexProperty(string name) + => throw new NotImplementedException(); + + public IReadOnlyComplexProperty FindDeclaredComplexProperty(string name) + => throw new NotImplementedException(); + + IEnumerable IReadOnlyEntityType.GetComplexProperties() + => throw new NotImplementedException(); + + IEnumerable IReadOnlyEntityType.GetDeclaredComplexProperties() + => throw new NotImplementedException(); + + public IEnumerable GetDerivedComplexProperties() + => throw new NotImplementedException(); } } 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..df0970b9012 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] @@ -4047,7 +4047,7 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot() var ownedProperties1 = ownedType1.GetProperties().ToList(); Assert.Equal("EntityWithOnePropertyId", ownedProperties1[0].Name); Assert.Equal("AlternateId", ownedProperties1[1].Name); - Assert.Equal("NotKey", RelationalPropertyExtensions.GetJsonPropertyName(ownedProperties1[1])); + Assert.Equal("NotKey", RelationalPrimitivePropertyBaseExtensions.GetJsonPropertyName(ownedProperties1[1])); Assert.Equal(nameof(EntityWithOneProperty), ownedType1.GetTableName()); Assert.Equal("EntityWithTwoProperties", ownedType1.GetContainerColumnName()); @@ -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..573bc45fa91 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,687 @@ 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), + 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), + 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), + 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), + 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), + 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), + 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( + CoreStrings.RuntimeModelMissingData, + Assert.Throws(() => complexType.GetNavigationAccessMode()).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 +5492,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 +7004,10 @@ public class OwnedType : INotifyPropertyChanged, INotifyPropertyChanging { private DbContext _context; + public OwnedType() + { + } + public OwnedType(DbContext context) { Context = context; @@ -6286,7 +7025,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.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/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..fc65df8138e 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,143 @@ 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 - ), - ( + ( + null, + null, + null, + typeof(RelationalPropertyBuilderExtensions), + null + ) + }, + { + typeof(IReadOnlyProperty), + ( + null, + null, + null, + null, + null + ) + }, + { + typeof(IReadOnlyProperty), + ( + typeof(RelationalPrimitivePropertyBaseExtensions), + typeof(RelationalPrimitivePropertyBaseExtensions), + typeof(RelationalPrimitivePropertyBaseExtensions), + 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; } @@ -260,11 +341,11 @@ public override typeof(RelationalIndexBuilderExtensions).GetMethod( nameof(RelationalIndexBuilderExtensions.HasName), new[] { typeof(IndexBuilder), typeof(string) }), - typeof(RelationalPropertyExtensions).GetMethod( - nameof(RelationalPropertyExtensions.FindOverrides), + typeof(RelationalPrimitivePropertyBaseExtensions).GetMethod( + nameof(RelationalPrimitivePropertyBaseExtensions.FindOverrides), new[] { typeof(IReadOnlyProperty), typeof(StoreObjectIdentifier).MakeByRefType() }), - typeof(RelationalPropertyExtensions).GetMethod( - nameof(RelationalPropertyExtensions.GetOverrides), + typeof(RelationalPrimitivePropertyBaseExtensions).GetMethod( + nameof(RelationalPrimitivePropertyBaseExtensions.GetOverrides), new[] { typeof(IReadOnlyProperty) }), GetMethod( typeof(StoredProcedureBuilder<>), 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.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..c9a2414c397 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,7 @@ 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(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/Infrastructure/CoreEventIdTest.cs b/test/EFCore.Tests/Infrastructure/CoreEventIdTest.cs index dde6de1d94f..12357e58ca9 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>), diff --git a/test/EFCore.Tests/Metadata/Conventions/BackingFieldConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/BackingFieldConventionTest.cs index e0cecbb846d..cc6c7e61486 100644 --- a/test/EFCore.Tests/Metadata/Conventions/BackingFieldConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/BackingFieldConventionTest.cs @@ -269,7 +269,7 @@ private void RunConvention(IMutableProperty property) private void Validate(IMutableProperty property) => new BackingFieldConvention(CreateDependencies()) .ProcessModelFinalizing( - ((Property)property).DeclaringEntityType.Model.Builder, + ((Property)property).DeclaringType.Model.Builder, new ConventionContext(((Model)property.DeclaringEntityType.Model).ConventionDispatcher)); private ProviderConventionSetBuilderDependencies CreateDependencies() diff --git a/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs b/test/EFCore.Tests/Metadata/Conventions/ConventionDispatcherTest.cs index 6e7ec72499a..9f0e9f7cb68 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); + ((EntityType)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/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/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..29ed325a349 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] @@ -1808,7 +1808,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()); } @@ -2707,7 +2707,7 @@ private EntityType GetDeclaringType( switch (memberType) { case MemberType.Property: - return entityTypeBuilder.Metadata.FindProperty(nameof(Order.Products))?.DeclaringEntityType; + return entityTypeBuilder.Metadata.FindProperty(nameof(Order.Products))?.DeclaringType; case MemberType.ServiceProperty: return entityTypeBuilder.Metadata.FindServiceProperty(nameof(Order.Products))?.DeclaringEntityType; case MemberType.Navigation: 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/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs index 136f94c24d5..9b912798af2 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) @@ -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..1432fa7b582 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -15,6 +15,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 +260,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 +521,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) @@ -677,6 +800,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..4e0fdf5cde4 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -4,6 +4,7 @@ #nullable enable using Microsoft.EntityFrameworkCore.Diagnostics.Internal; +using static Microsoft.EntityFrameworkCore.ModelBuilding.ModelBuilderTest; // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ModelBuilding; @@ -201,6 +202,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 +346,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); @@ -451,6 +496,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;