diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index 6b2da830fbc..690e39d60a9 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -694,7 +694,7 @@ private void Create( property.DeclaringEntityType.ShortName(), property.Name, nameof(PropertyBuilder.HasConversion))); } - var valueConverterType = (Type?)property[CoreAnnotationNames.ValueConverterType]; + var valueConverterType = GetValueConverterType(property); if (valueConverterType == null && property.GetValueConverter() != null) { @@ -847,6 +847,45 @@ private void Create( mainBuilder.AppendLine(); } + private static Type? GetValueConverterType(IProperty property) + { + var type = (Type?)property[CoreAnnotationNames.ValueConverterType]; + if (type != null) + { + return type; + } + + var principalProperty = property; + for (var i = 0; i < 10000; i++) + { + foreach (var foreignKey in principalProperty.GetContainingForeignKeys()) + { + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + { + if (principalProperty == foreignKey.Properties[propertyIndex]) + { + var newPrincipalProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; + if (property == principalProperty + || newPrincipalProperty == principalProperty) + { + break; + } + + principalProperty = newPrincipalProperty; + + type = (Type?)principalProperty[CoreAnnotationNames.ValueConverterType]; + if (type != null) + { + return type; + } + } + } + } + } + + return null; + } + private void PropertyBaseParameters( IPropertyBase property, CSharpRuntimeAnnotationCodeGeneratorParameters parameters, diff --git a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs index 91cdf62e3f9..7965038920d 100644 --- a/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs +++ b/src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs @@ -241,7 +241,8 @@ public virtual InternalCheckConstraintBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && ((IConventionAnnotatable)EntityType).IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs b/src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs index e0b475797fe..437ebffadbf 100644 --- a/src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs +++ b/src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs @@ -57,7 +57,8 @@ public virtual InternalEntityTypeMappingFragmentBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && ((IConventionAnnotatable)EntityType).IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs index 699011c6517..45b56c7cf8f 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs @@ -83,7 +83,8 @@ public virtual InternalRelationalPropertyOverridesBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && ((IConventionAnnotatable)Property).IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameter.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameter.cs index 92d5ad08f1d..00f38a9abab 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameter.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedureParameter.cs @@ -68,7 +68,8 @@ public virtual InternalStoredProcedureParameterBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && StoredProcedure.IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumn.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumn.cs index 044c093c834..bd633a0fb41 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumn.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedureResultColumn.cs @@ -56,7 +56,8 @@ public virtual InternalStoredProcedureResultColumnBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && StoredProcedure.IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 7abcb5f82e9..52df30d463c 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -864,6 +864,7 @@ protected virtual void ValidateTypeMappings( var type = converter.ModelClrType; if (type != typeof(string) && !(type == typeof(byte[]) && property.IsKey()) // Already special-cased elsewhere + && !property.IsForeignKey() && type.TryGetSequenceType() != null) { logger.CollectionWithoutComparer(property); diff --git a/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs index 068cf6e37e6..cbb088a4e62 100644 --- a/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/KeyDiscoveryConvention.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.EntityFrameworkCore.Metadata.Builders; - namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// diff --git a/src/EFCore/Metadata/Conventions/ModelCleanupConvention.cs b/src/EFCore/Metadata/Conventions/ModelCleanupConvention.cs index eeff662fc76..141fbe653f4 100644 --- a/src/EFCore/Metadata/Conventions/ModelCleanupConvention.cs +++ b/src/EFCore/Metadata/Conventions/ModelCleanupConvention.cs @@ -9,7 +9,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions; /// /// See Model building conventions for more information and examples. /// -public class ModelCleanupConvention : IModelFinalizingConvention +public class ModelCleanupConvention : + IForeignKeyRemovedConvention, + IModelFinalizingConvention { /// /// Creates a new instance of . @@ -25,6 +27,21 @@ public ModelCleanupConvention(ProviderConventionSetBuilderDependencies dependenc /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } + /// + public virtual void ProcessForeignKeyRemoved( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionForeignKey foreignKey, + IConventionContext context) + { + var principalKey = foreignKey.PrincipalKey; + if (principalKey.IsInModel + && !principalKey.IsPrimaryKey() + && !principalKey.GetReferencingForeignKeys().Any()) + { + principalKey.DeclaringEntityType.Builder.HasNoKey(principalKey); + } + } + /// public virtual void ProcessModelFinalizing( IConventionModelBuilder modelBuilder, diff --git a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs index c2d8ef1b5a1..0fc7dd606d9 100644 --- a/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs +++ b/src/EFCore/Metadata/Conventions/RelationshipDiscoveryConvention.cs @@ -809,7 +809,10 @@ private void DiscoverUnidirectionalInverses( { foreach (var inverseCandidateEntityType in model.FindEntityTypes(inverseCandidateType).ToList()) { - DiscoverRelationships(inverseCandidateEntityType.Builder, context); + if (inverseCandidateEntityType.IsInModel) + { + DiscoverRelationships(inverseCandidateEntityType.Builder, context); + } } } } diff --git a/src/EFCore/Metadata/Internal/ForeignKey.cs b/src/EFCore/Metadata/Internal/ForeignKey.cs index 71e83cc0cf0..166bec83544 100644 --- a/src/EFCore/Metadata/Internal/ForeignKey.cs +++ b/src/EFCore/Metadata/Internal/ForeignKey.cs @@ -118,7 +118,8 @@ public virtual InternalForeignKeyBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && DeclaringEntityType.IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/Index.cs b/src/EFCore/Metadata/Internal/Index.cs index 784c249506a..966ba49aead 100644 --- a/src/EFCore/Metadata/Internal/Index.cs +++ b/src/EFCore/Metadata/Internal/Index.cs @@ -103,7 +103,8 @@ public virtual InternalIndexBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && DeclaringEntityType.IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 3f74eb23be1..0a15148525d 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -102,7 +102,8 @@ public InternalEntityTypeBuilder(EntityType metadata, InternalModelBuilder model var newKey = Metadata.SetPrimaryKey(keyBuilder.Metadata.Properties, configurationSource); foreach (var key in Metadata.GetDeclaredKeys().ToList()) { - if (key == keyBuilder.Metadata) + if (key == keyBuilder.Metadata + || !key.IsInModel) { continue; } @@ -3956,6 +3957,12 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK } else { + if (targetEntityTypeBuilder != null + && targetEntityTypeBuilder.Metadata.GetConfigurationSource().OverridesStrictly(configurationSource)) + { + return targetEntityTypeBuilder; + } + targetEntityTypeBuilder = targetEntityType.IsNamed ? ModelBuilder.SharedTypeEntity(targetTypeName, targetType, configurationSource.Value, targetShouldBeOwned) : ModelBuilder.Entity(targetType, configurationSource.Value, targetShouldBeOwned); @@ -4490,81 +4497,77 @@ public virtual bool ShouldReuniquifyTemporaryProperties(ForeignKey foreignKey) var clrProperties = Metadata.GetRuntimeProperties(); var clrFields = Metadata.GetRuntimeFields(); var canReuniquify = false; - using (var principalPropertyNamesEnumerator = principalPropertyNames.GetEnumerator()) - { - using var principalPropertyTypesEnumerator = principalPropertyTypes.GetEnumerator(); - for (var i = 0; - i < propertyCount - && principalPropertyNamesEnumerator.MoveNext() - && principalPropertyTypesEnumerator.MoveNext(); - i++) - { - var keyPropertyName = principalPropertyNamesEnumerator.Current; - var keyPropertyType = principalPropertyTypesEnumerator.Current; - var keyModifiedBaseName = keyPropertyName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase) - ? keyPropertyName - : baseName + keyPropertyName; - string propertyName; - var clrType = keyPropertyType.MakeNullable(!isRequired); - var index = -1; - while (true) + using var principalPropertyNamesEnumerator = principalPropertyNames.GetEnumerator(); + using var principalPropertyTypesEnumerator = principalPropertyTypes.GetEnumerator(); + for (var i = 0; + i < propertyCount + && principalPropertyNamesEnumerator.MoveNext() + && principalPropertyTypesEnumerator.MoveNext(); + i++) + { + var keyPropertyName = principalPropertyNamesEnumerator.Current; + var keyPropertyType = principalPropertyTypesEnumerator.Current; + + var keyModifiedBaseName = keyPropertyName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase) + ? keyPropertyName + : baseName + keyPropertyName; + string propertyName; + var clrType = keyPropertyType.MakeNullable(!isRequired); + var index = -1; + while (true) + { + propertyName = keyModifiedBaseName + (++index > 0 ? index.ToString(CultureInfo.InvariantCulture) : ""); + if (!Metadata.FindPropertiesInHierarchy(propertyName).Any() + && !clrProperties.ContainsKey(propertyName) + && !clrFields.ContainsKey(propertyName) + && !IsIgnored(propertyName, ConfigurationSource.Convention)) { - propertyName = keyModifiedBaseName + (++index > 0 ? index.ToString(CultureInfo.InvariantCulture) : ""); - if (!Metadata.FindPropertiesInHierarchy(propertyName).Any() - && !clrProperties.ContainsKey(propertyName) - && !clrFields.ContainsKey(propertyName) - && !IsIgnored(propertyName, ConfigurationSource.Convention)) + if (currentProperties == null) { - 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); - } + var propertyBuilder = Property( + clrType, propertyName, typeConfigurationSource: null, + configurationSource: ConfigurationSource.Convention); - newProperties![i] = propertyBuilder.Metadata; + if (propertyBuilder == null) + { + return (false, null); } - else if (!Metadata.Model.Builder.CanBeConfigured( - clrType, TypeConfigurationType.Property, ConfigurationSource.Convention)) + + if (index > 0) { + propertyBuilder.HasAnnotation( + CoreAnnotationNames.PreUniquificationName, + keyModifiedBaseName, + ConfigurationSource.Convention); } - else + + if (clrType.IsNullableType()) { - canReuniquify = true; + propertyBuilder.IsRequired(isRequired, ConfigurationSource.Convention); } - break; + newProperties![i] = propertyBuilder.Metadata; } - - var currentProperty = currentProperties?.SingleOrDefault(p => p.Name == propertyName); - if (currentProperty != null) + else if (Metadata.Model.Builder.CanBeConfigured( + clrType, TypeConfigurationType.Property, ConfigurationSource.Convention)) { - if (((IConventionProperty)currentProperty).IsImplicitlyCreated() - && currentProperty.ClrType != clrType - && isRequired) - { - canReuniquify = true; - } + canReuniquify = true; + } + + break; + } - break; + var currentProperty = currentProperties?.SingleOrDefault(p => p.Name == propertyName); + if (currentProperty != null) + { + if (((IConventionProperty)currentProperty).IsImplicitlyCreated() + && currentProperty.ClrType != clrType + && isRequired) + { + canReuniquify = true; } + + break; } } } diff --git a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs index f2fa398780a..7e6ccce0557 100644 --- a/src/EFCore/Metadata/Internal/InternalModelBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalModelBuilder.cs @@ -315,6 +315,14 @@ public override InternalModelBuilder ModelBuilder return null; } + foreach (var existingEntityType in Metadata.FindEntityTypes(type)) + { + if (!existingEntityType.Builder.CanSetIsOwned(true, configurationSource)) + { + return null; + } + } + Metadata.RemoveIgnored(type); Metadata.AddOwned(type, ConfigurationSource.Explicit); @@ -337,15 +345,8 @@ public override InternalModelBuilder ModelBuilder } else { - if (entityType.Builder.CanSetIsOwned(true, configurationSource)) - { - // Discover the ownership when the type is added back - HasNoEntityType(entityType, configurationSource); - } - else - { - return null; - } + // Discover the ownership when the type is added back + HasNoEntityType(entityType, configurationSource); } } @@ -405,9 +406,8 @@ public virtual bool CanBeConfigured(Type type, TypeConfigurationType configurati } if (!configurationType.IsEntityType() - && (!configurationSource.Overrides(Metadata.FindEntityType(type)?.GetConfigurationSource()) - || !configurationSource.Overrides(Metadata.FindIsOwnedConfigurationSource(type)) - || Metadata.IsShared(type))) + && (!configurationSource.Overrides(Metadata.FindIsOwnedConfigurationSource(type)) + || Metadata.FindEntityTypes(type).Any(e => !configurationSource.Overrides(e.GetConfigurationSource())))) { return false; } diff --git a/src/EFCore/Metadata/Internal/Key.cs b/src/EFCore/Metadata/Internal/Key.cs index 98b4ef6c540..e2c429977de 100644 --- a/src/EFCore/Metadata/Internal/Key.cs +++ b/src/EFCore/Metadata/Internal/Key.cs @@ -76,7 +76,8 @@ public virtual InternalKeyBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && DeclaringEntityType.IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/Navigation.cs b/src/EFCore/Metadata/Internal/Navigation.cs index f87c1b7e809..d908799692b 100644 --- a/src/EFCore/Metadata/Internal/Navigation.cs +++ b/src/EFCore/Metadata/Internal/Navigation.cs @@ -76,7 +76,8 @@ public virtual InternalNavigationBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && ForeignKey.IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index 4a9b6abcb3a..205bb52c847 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.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; using System.Diagnostics.CodeAnalysis; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; @@ -97,7 +98,8 @@ public virtual InternalPropertyBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && DeclaringEntityType.IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -657,7 +659,43 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual ValueConverter? GetValueConverter() - => (ValueConverter?)this[CoreAnnotationNames.ValueConverter]; + { + var converter = (ValueConverter?)this[CoreAnnotationNames.ValueConverter]; + if (converter != null) + { + return converter; + } + + var property = this; + for (var i = 0; i < 10000; i++) + { + foreach (var foreignKey in property.GetContainingForeignKeys()) + { + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + { + if (property == foreignKey.Properties[propertyIndex]) + { + var principalProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; + if (principalProperty == this + || principalProperty == property) + { + break; + } + + property = principalProperty; + + converter = (ValueConverter?)property[CoreAnnotationNames.ValueConverter]; + if (converter != null) + { + return converter; + } + } + } + } + } + + return null; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -700,7 +738,43 @@ public virtual PropertySaveBehavior GetAfterSaveBehavior() /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual Type? GetProviderClrType() - => (Type?)this[CoreAnnotationNames.ProviderClrType]; + { + var type = (Type?)this[CoreAnnotationNames.ProviderClrType]; + if (type != null) + { + return type; + } + + var property = this; + for (var i = 0; i < 10000; i++) + { + foreach (var foreignKey in property.GetContainingForeignKeys()) + { + for (var propertyIndex = 0; propertyIndex < foreignKey.Properties.Count; propertyIndex++) + { + if (property == foreignKey.Properties[propertyIndex]) + { + var principalProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; + if (principalProperty == this + || principalProperty == property) + { + break; + } + + property = principalProperty; + + type = (Type?)property[CoreAnnotationNames.ProviderClrType]; + if (type != null) + { + return type; + } + } + } + } + } + + return null; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/ServiceProperty.cs b/src/EFCore/Metadata/Internal/ServiceProperty.cs index fbf58a01767..deb8ea20363 100644 --- a/src/EFCore/Metadata/Internal/ServiceProperty.cs +++ b/src/EFCore/Metadata/Internal/ServiceProperty.cs @@ -85,7 +85,8 @@ public virtual InternalServicePropertyBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && DeclaringEntityType.IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/SkipNavigation.cs b/src/EFCore/Metadata/Internal/SkipNavigation.cs index f1849bdeab8..1c3fb7136dc 100644 --- a/src/EFCore/Metadata/Internal/SkipNavigation.cs +++ b/src/EFCore/Metadata/Internal/SkipNavigation.cs @@ -91,7 +91,8 @@ public virtual InternalSkipNavigationBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && DeclaringEntityType.IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/Trigger.cs b/src/EFCore/Metadata/Internal/Trigger.cs index 855c4868294..b70f749a1ca 100644 --- a/src/EFCore/Metadata/Internal/Trigger.cs +++ b/src/EFCore/Metadata/Internal/Trigger.cs @@ -52,7 +52,8 @@ public virtual InternalTriggerBuilder Builder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool IsInModel - => _builder is not null; + => _builder is not null + && EntityType.IsInModel; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs index c486739e8cc..3cabc98b4f3 100644 --- a/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs +++ b/test/EFCore.Cosmos.Tests/ModelBuilding/CosmosModelBuilderGenericTest.cs @@ -338,20 +338,6 @@ public override void Navigation_to_shared_type_is_not_discovered_by_convention() owned.DisplayName()); } - [ConditionalFact] - public virtual void Inverse_discovered_after_entity_becomes_non_owned() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity(); - modelBuilder.Entity(); - - var model = modelBuilder.FinalizeModel(); - - var queryResult = model.FindEntityType(typeof(QueryResult)); - Assert.NotNull(queryResult.FindNavigation(nameof(QueryResult.Value))); - } - protected override TestModelBuilder CreateModelBuilder(Action configure = null) => CreateTestModelBuilder(CosmosTestHelpers.Instance, configure); } diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs index 77a132a3415..88ee0c29aa6 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTest.cs @@ -995,6 +995,7 @@ public virtual void Detects_owned_entity_type_without_ownership() { var builder = CreateConventionlessModelBuilder(); var modelBuilder = (InternalModelBuilder)builder.GetInfrastructure(); + modelBuilder.Owned(typeof(A), ConfigurationSource.Convention); var aBuilder = modelBuilder.Entity(typeof(A), ConfigurationSource.Convention); aBuilder.Ignore(nameof(A.Id), ConfigurationSource.Explicit); aBuilder.Ignore(nameof(A.P0), ConfigurationSource.Explicit); @@ -1002,8 +1003,6 @@ public virtual void Detects_owned_entity_type_without_ownership() aBuilder.Ignore(nameof(A.P2), ConfigurationSource.Explicit); aBuilder.Ignore(nameof(A.P3), ConfigurationSource.Explicit); - modelBuilder.Owned(typeof(A), ConfigurationSource.Convention); - VerifyError(CoreStrings.OwnerlessOwnedType(nameof(A)), builder); } diff --git a/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs b/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs index efc8c270fc6..8ee3d93cd9d 100644 --- a/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs @@ -4112,14 +4112,18 @@ public virtual void Inverse_discovered_after_entity_unignored() { var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); modelBuilder.Ignore(); - modelBuilder.Entity(); + modelBuilder.Entity() + .Property(x => x.Id) + .HasConversion(x => x.Id, x => new CustomId { Id = x }); modelBuilder.Entity(); var model = modelBuilder.FinalizeModel(); var queryResult = model.FindEntityType(typeof(QueryResult)); Assert.NotNull(queryResult.FindNavigation(nameof(QueryResult.Value))); + Assert.Null(queryResult.FindProperty("TempId")); } } } diff --git a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs index fc1e2fea865..c4b5e2d14ac 100644 --- a/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OwnedTypesTestBase.cs @@ -887,13 +887,18 @@ public virtual void Can_configure_fk_on_multiple_ownerships() lb.WithOwner() .HasForeignKey("BookLabelId") .HasAnnotation("Foo", "Bar"); + lb.Ignore(l => l.Book); }); - modelBuilder.Entity() - .OwnsOne(b => b.AlternateLabel) - .WithOwner() - .HasForeignKey("BookLabelId"); + modelBuilder.Entity().OwnsOne( + b => b.AlternateLabel, lb => + { + lb.WithOwner() + .HasForeignKey("BookLabelId"); - IReadOnlyModel model = modelBuilder.Model; + lb.Ignore(l => l.Book); + }); + + var model = modelBuilder.FinalizeModel(); var bookOwnership1 = model.FindEntityType(typeof(Book)).FindNavigation(nameof(Book.Label)).ForeignKey; var bookOwnership2 = model.FindEntityType(typeof(Book)).FindNavigation(nameof(Book.AlternateLabel)).ForeignKey; @@ -904,11 +909,6 @@ public virtual void Can_configure_fk_on_multiple_ownerships() Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); Assert.Equal(3, model.GetEntityTypes().Count()); - - modelBuilder.Entity().OwnsOne(b => b.Label).Ignore(l => l.Book); - modelBuilder.Entity().OwnsOne(b => b.AlternateLabel).Ignore(l => l.Book); - - modelBuilder.FinalizeModel(); } [ConditionalFact] @@ -1039,6 +1039,45 @@ public virtual void Can_map_derived_of_owned_type_first() modelBuilder.FinalizeModel(); } + [ConditionalFact] + public virtual void Can_configure_relationship_with_PK_ValueConverter() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().Property(x => x.Id) + .HasConversion(x => x.Id, x => new CustomId { Id = x }); + + modelBuilder.Entity() + .Property(x => x.Id) + .HasConversion(x => x.Id, x => new CustomId { Id = x }); + + modelBuilder.Entity() + .OwnsOne(q => q.Value) + .Property(x => x.CategoryId) + .HasConversion(x => x.Id, x => new CustomId { Id = x }); + + var model = modelBuilder.FinalizeModel(); + + var result = model.FindEntityType(typeof(QueryResult)); + Assert.Null(result.FindProperty("TempId")); + + var owned = result.GetDeclaredNavigations().Single().TargetEntityType; + Assert.Null(owned.FindProperty("TempId")); + + var ownedPkProperty = owned.FindPrimaryKey().Properties.Single(); + Assert.NotNull(ownedPkProperty.GetValueConverter()); + + var category = model.FindEntityType(typeof(ValueCategory)); + Assert.Null(category.FindProperty("TempId")); + + var barNavigation = owned.GetDeclaredNavigations().Single(n => !n.ForeignKey.IsOwnership); + Assert.Same(category, barNavigation.TargetEntityType); + var fkProperty = barNavigation.ForeignKey.Properties.Single(); + Assert.Equal("CategoryId", fkProperty.Name); + + Assert.Equal(3, model.GetEntityTypes().Count()); + } + [ConditionalFact] public virtual void Throws_on_FK_matching_two_relationships() { diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index 6432e47f3c8..e73dbc5c7f1 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -750,15 +750,27 @@ public class KeylessEntityWithFields protected class QueryResult { - public int Id { get; set; } + public CustomId Id { get; set; } = null!; public int ValueFk { get; set; } public Value Value { get; set; } = null!; } + [Owned] protected class Value { public int Id { get; set; } - public int AlternateId { get; set; } + public CustomId? CategoryId { get; set; } + public ValueCategory? Category { get; set; } + } + + protected class CustomId + { + public int Id { get; set; } + } + + protected class ValueCategory + { + public CustomId Id { get; set; } = null!; } protected class KeylessEntity