diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs index 655013a1408..6c5d41210fb 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs @@ -86,6 +86,7 @@ private enum Id AllIndexPropertiesNotToMappedToAnyTable, IndexPropertiesBothMappedAndNotMappedToTable, IndexPropertiesMappedToNonOverlappingTables, + ForeignKeyPropertiesMappedToUnrelatedTables, // Update events BatchReadyForExecution = CoreEventId.RelationalBaseId + 700, @@ -709,6 +710,19 @@ private static EventId MakeValidationId(Id id) public static readonly EventId IndexPropertiesMappedToNonOverlappingTables = MakeValidationId(Id.IndexPropertiesMappedToNonOverlappingTables); + /// + /// + /// A foreign key specifies properties which don't map to the related tables. + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId ForeignKeyPropertiesMappedToUnrelatedTables = MakeValidationId(Id.ForeignKeyPropertiesMappedToUnrelatedTables); + private static readonly string _updatePrefix = DbLoggerCategory.Update.Name + "."; private static EventId MakeUpdateId(Id id) diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs index 1648b1828f4..a9b724e6cb3 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs @@ -4650,6 +4650,62 @@ private static string NamedIndexPropertiesMappedToNonOverlappingTables(EventDefi p.TablesMappedToProperty2.FormatTables())); } + /// + /// Logs the event. + /// + /// The diagnostics logger to use. + /// The foreign key. + public static void ForeignKeyPropertiesMappedToUnrelatedTables( + [NotNull] this IDiagnosticsLogger diagnostics, + [NotNull] IForeignKey foreignKey) + { + var definition = RelationalResources.LogForeignKeyPropertiesMappedToUnrelatedTables(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + definition.Log(diagnostics, + l => l.Log( + definition.Level, + definition.EventId, + definition.MessageFormat, + foreignKey.Properties.Format(), + foreignKey.DeclaringEntityType.DisplayName(), + foreignKey.PrincipalEntityType.DisplayName(), + foreignKey.Properties.Format(), + foreignKey.DeclaringEntityType.GetSchemaQualifiedTableName(), + foreignKey.PrincipalKey.Properties.Format(), + foreignKey.PrincipalEntityType.GetSchemaQualifiedTableName())); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new ForeignKeyEventData( + definition, + ForeignKeyPropertiesMappedToUnrelatedTables, + foreignKey); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string ForeignKeyPropertiesMappedToUnrelatedTables(EventDefinitionBase definition, EventData payload) + { + var d = (FallbackEventDefinition)definition; + var p = (ForeignKeyEventData)payload; + return d.GenerateMessage( + l => l.Log( + d.Level, + d.EventId, + d.MessageFormat, + p.ForeignKey.Properties.Format(), + p.ForeignKey.DeclaringEntityType.DisplayName(), + p.ForeignKey.PrincipalEntityType.DisplayName(), + p.ForeignKey.Properties.Format(), + p.ForeignKey.DeclaringEntityType.GetSchemaQualifiedTableName(), + p.ForeignKey.PrincipalKey.Properties.Format(), + p.ForeignKey.PrincipalEntityType.GetSchemaQualifiedTableName())); + } + /// /// Logs for the event. /// diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs index 6a2cc467c8e..d050a25d329 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs @@ -468,6 +468,15 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions [EntityFrameworkInternal] public EventDefinitionBase LogUnnamedIndexPropertiesMappedToNonOverlappingTables; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in 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 LogForeignKeyPropertiesMappedToUnrelatedTables; + /// /// This is an internal API that supports the 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/Extensions/RelationalForeignKeyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs index ae648e4d96a..461bbac569e 100644 --- a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; @@ -23,13 +23,12 @@ public static class RelationalForeignKeyExtensions /// The foreign key. /// The foreign key constraint name. public static string GetConstraintName([NotNull] this IForeignKey foreignKey) - => foreignKey.GetConstraintName( - StoreObjectIdentifier.Create(foreignKey.DeclaringEntityType, StoreObjectType.Table).Value, - StoreObjectIdentifier.Create( - foreignKey.PrincipalKey.IsPrimaryKey() - ? foreignKey.PrincipalEntityType - : foreignKey.PrincipalKey.DeclaringEntityType, - StoreObjectType.Table).Value); + { + var annotation = foreignKey.FindAnnotation(RelationalAnnotationNames.Name); + return annotation != null + ? (string)annotation.Value + : foreignKey.GetDefaultName(); + } /// /// Returns the foreign key constraint name. diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index 68806ec67e7..f04d4ec91cc 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -50,12 +50,30 @@ public static string GetColumnName([NotNull] this IProperty property, in StoreOb return overrides.ColumnName; } - if (!property.IsPrimaryKey() - && storeObject.StoreObjectType != StoreObjectType.Function - && storeObject.StoreObjectType != StoreObjectType.SqlQuery - && StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObject.StoreObjectType) != storeObject) + if (storeObject.StoreObjectType != StoreObjectType.Function + && storeObject.StoreObjectType != StoreObjectType.SqlQuery) { - return null; + if (property.IsPrimaryKey()) + { + var tableFound = false; + foreach (var containingType in property.DeclaringEntityType.GetDerivedTypesInclusive()) + { + if (StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType) == storeObject) + { + tableFound = true; + break; + } + } + + if (!tableFound) + { + return null; + } + } + else if (StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObject.StoreObjectType) != storeObject) + { + return null; + } } var columnAnnotation = property.FindAnnotation(RelationalAnnotationNames.ColumnName); diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 7f2039cf8e0..b8c6c3b4dc5 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -924,6 +924,17 @@ protected virtual void ValidateSharedForeignKeysCompatibility( var foreignKeyName = foreignKey.GetConstraintName(storeObject, principalTable.Value); if (foreignKeyName == null) { + var derivedTables = foreignKey.DeclaringEntityType.GetDerivedTypes() + .Select(t => StoreObjectIdentifier.Create(t, StoreObjectType.Table)) + .Where(t => t != null); + if (foreignKey.GetConstraintName() != null + && derivedTables.All(t => foreignKey.GetConstraintName( + t.Value, + principalTable.Value) == null)) + { + logger.ForeignKeyPropertiesMappedToUnrelatedTables(foreignKey); + } + continue; } @@ -1254,8 +1265,7 @@ protected virtual void ValidateIndexProperties( if (firstPropertyTables != null) { - // Property is not mapped but we already found - // a property that is mapped. + // Property is not mapped but we already found a property that is mapped. break; } @@ -1264,21 +1274,18 @@ protected virtual void ValidateIndexProperties( if (firstPropertyTables == null) { - // store off which tables the first member maps to firstPropertyTables = new Tuple>(property.Name, tablesMappedToProperty); } else { - // store off which tables the last member we encountered maps to lastPropertyTables = new Tuple>(property.Name, tablesMappedToProperty); } if (propertyNotMappedToAnyTable != null) { - // Property is mapped but we already found - // a property that is not mapped. + // Property is mapped but we already found a property that is not mapped. overlappingTables = null; break; } diff --git a/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs b/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs index 4b62a75f515..d9337c9080f 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs @@ -388,6 +388,18 @@ public virtual void Validate(IDbContextOptions options) { } + /// + /// Adds default for relational events. + /// + /// The core options extension. + /// The new core options extension. + public static CoreOptionsExtension WithDefaultWarningConfiguration([NotNull] CoreOptionsExtension coreOptionsExtension) + => coreOptionsExtension.WithWarningsConfiguration(coreOptionsExtension.WarningsConfiguration + .TryWithExplicit(RelationalEventId.AmbientTransactionWarning, WarningBehavior.Throw) + .TryWithExplicit(RelationalEventId.IndexPropertiesBothMappedAndNotMappedToTable, WarningBehavior.Throw) + .TryWithExplicit(RelationalEventId.IndexPropertiesMappedToNonOverlappingTables, WarningBehavior.Throw) + .TryWithExplicit(RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables, WarningBehavior.Throw)); + /// /// Information/metadata for a . /// @@ -396,7 +408,7 @@ protected abstract class RelationalExtensionInfo : DbContextOptionsExtensionInfo private string _logFragment; /// - /// Creates a new instance containing + /// Creates a new instance containing /// info/metadata for the given extension. /// /// The extension. diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index f10f6119e58..025f8ecb9f9 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -830,9 +830,17 @@ private static void PopulateConstraints(Table table) break; } + if (entityTypeMapping.IncludesDerivedTypes + && foreignKey.DeclaringEntityType != entityType + && foreignKey.Properties.SequenceEqual(entityType.FindPrimaryKey().Properties)) + { + // The identifying FK constraint is needed to be created only on the table that corresponds + // to the declaring entity type + break; + } + constraint = new ForeignKeyConstraint( - name, table, principalTable, columns, principalColumns, - ToReferentialAction(foreignKey.DeleteBehavior)); + name, table, principalTable, columns, principalColumns, ToReferentialAction(foreignKey.DeleteBehavior)); constraint.MappedForeignKeys.Add(foreignKey); if (foreignKeyConstraints == null) diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index f1a9ffe8fd4..4587186a00a 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1771,6 +1771,27 @@ public static EventDefinition LogExplicitTransactionEnlisted([NotNull] I return (EventDefinition)definition; } + /// + /// The foreign key {foreignKeyProperties} on the entity type '{entityType}' targeting '{principalEntityType}' cannot be represented in the database. Either the properties {foreignKeyProperties} aren't mapped to table '{table}' or the principal properties {principalProperties} aren't mapped to table '{principalTable}'. All foreign key properties must map to the table that the dependent type is mapped to and all principal properties must map to a single table that the principal type is mapped to. + /// + public static FallbackEventDefinition LogForeignKeyPropertiesMappedToUnrelatedTables([NotNull] IDiagnosticsLogger logger) + { + var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyPropertiesMappedToUnrelatedTables; + if (definition == null) + { + definition = LazyInitializer.EnsureInitialized( + ref ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyPropertiesMappedToUnrelatedTables, + () => new FallbackEventDefinition( + logger.Options, + RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables, + LogLevel.Error, + "RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables", + _resourceManager.GetString("LogForeignKeyPropertiesMappedToUnrelatedTables"))); + } + + return (FallbackEventDefinition)definition; + } + /// /// Generating down script for migration '{migration}'. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 6cbc84a97b9..1704e062e8f 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -468,6 +468,10 @@ Enlisted in an explicit transaction with isolation level '{isolationLevel}'. Debug RelationalEventId.ExplicitTransactionEnlisted string + + The foreign key {foreignKeyProperties} on the entity type '{entityType}' targeting '{principalEntityType}' cannot be represented in the database. Either the properties {foreignKeyProperties} aren't mapped to table '{table}' or the principal properties {principalProperties} aren't mapped to table '{principalTable}'. All foreign key properties must map to the table that the dependent type is mapped to and all principal properties must map to a single table that the principal type is mapped to. + Error RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables string string string string string string string + Generating down script for migration '{migration}'. Debug RelationalEventId.MigrationGeneratingDownScript string diff --git a/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs index 285b5bd9f18..4e2e24b99f9 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerDbContextOptionsBuilderExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -169,10 +169,8 @@ var coreOptionsExtension = optionsBuilder.Options.FindExtension() ?? new CoreOptionsExtension(); - coreOptionsExtension = coreOptionsExtension.WithWarningsConfiguration( - coreOptionsExtension.WarningsConfiguration.TryWithExplicit( - RelationalEventId.AmbientTransactionWarning, WarningBehavior.Throw)).WithWarningsConfiguration( - coreOptionsExtension.WarningsConfiguration.TryWithExplicit( + coreOptionsExtension = RelationalOptionsExtension.WithDefaultWarningConfiguration(coreOptionsExtension) + .WithWarningsConfiguration(coreOptionsExtension.WarningsConfiguration.TryWithExplicit( SqlServerEventId.ConflictingValueGenerationStrategiesWarning, WarningBehavior.Throw)); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(coreOptionsExtension); diff --git a/src/EFCore.Sqlite.Core/Extensions/SqliteDbContextOptionsBuilderExtensions.cs b/src/EFCore.Sqlite.Core/Extensions/SqliteDbContextOptionsBuilderExtensions.cs index 16785d124e8..c92935c026e 100644 --- a/src/EFCore.Sqlite.Core/Extensions/SqliteDbContextOptionsBuilderExtensions.cs +++ b/src/EFCore.Sqlite.Core/Extensions/SqliteDbContextOptionsBuilderExtensions.cs @@ -167,9 +167,7 @@ var coreOptionsExtension = optionsBuilder.Options.FindExtension() ?? new CoreOptionsExtension(); - coreOptionsExtension = coreOptionsExtension.WithWarningsConfiguration( - coreOptionsExtension.WarningsConfiguration.TryWithExplicit( - RelationalEventId.AmbientTransactionWarning, WarningBehavior.Throw)); + coreOptionsExtension = RelationalOptionsExtension.WithDefaultWarningConfiguration(coreOptionsExtension); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(coreOptionsExtension); } diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index b4cc522ddd0..bc0d2879f53 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -1353,6 +1353,27 @@ public virtual void Detects_linking_relationship_on_derived_type_in_TPT_views() modelBuilder.Model); } + [ConditionalFact] + public virtual void Detects_unmapped_foreign_keys_in_TPT() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity() + .Ignore(a => a.FavoritePerson) + .Property("FavoritePersonId"); + modelBuilder.Entity().ToTable("Cat") + .HasOne().WithMany() + .HasForeignKey("FavoritePersonId"); + + var definition = RelationalResources.LogForeignKeyPropertiesMappedToUnrelatedTables(new TestLogger()); + VerifyWarning(definition.GenerateMessage(l => l.Log( + definition.Level, + definition.EventId, + definition.MessageFormat, + "{'FavoritePersonId'}", nameof(Cat), nameof(Person), "{'FavoritePersonId'}", nameof(Cat), "{'Id'}", nameof(Person))), + modelBuilder.Model, + LogLevel.Error); + } + [ConditionalFact] public virtual void Passes_for_valid_table_overrides() { diff --git a/test/EFCore.Relational.Tests/RelationalEventIdTest.cs b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs index c86d7730355..ab80a7fc823 100644 --- a/test/EFCore.Relational.Tests/RelationalEventIdTest.cs +++ b/test/EFCore.Relational.Tests/RelationalEventIdTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -40,10 +40,11 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() var constantExpression = Expression.Constant("A"); var model = new Model(); var entityType = new EntityType(typeof(object), model, ConfigurationSource.Convention); - var property = new Property( - "A", typeof(int), null, null, entityType, ConfigurationSource.Convention, ConfigurationSource.Convention); + var property = entityType.AddProperty("A", typeof(int), ConfigurationSource.Convention, ConfigurationSource.Convention); + var key = entityType.AddKey(property, ConfigurationSource.Convention); + var foreignKey = new ForeignKey(new List { property }, key, entityType, entityType, ConfigurationSource.Convention); + var index = new Metadata.Internal.Index(new List { property }, "IndexName", entityType, ConfigurationSource.Convention); var contextServices = RelationalTestHelpers.Instance.CreateContextServices(model.FinalizeModel()); - var index = new Index(new List { property }, "IndexName", entityType, ConfigurationSource.Convention); var fakeFactories = new Dictionary> { @@ -71,6 +72,8 @@ public void Every_eventId_has_a_logger_method_and_logs_when_level_enabled() { typeof(Expression), () => constantExpression }, { typeof(IEntityType), () => entityType }, { typeof(IProperty), () => property }, + { typeof(IKey), () => key }, + { typeof(IForeignKey), () => foreignKey }, { typeof(IIndex), () => index }, { typeof(TypeInfo), () => typeof(object).GetTypeInfo() }, { typeof(Type), () => typeof(object) }, diff --git a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs index 8f7f95f598f..658981e6208 100644 --- a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs +++ b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs @@ -2230,6 +2230,48 @@ protected class ConflictingFKAttributes public A As { get; set; } } + [ConditionalFact] + public virtual void Attribute_set_shadow_FK_name_is_preserved_with_HasPrincipalKey() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + m => + { + m.Property("_email"); + + m.HasMany("_profiles") + .WithOne("User") + .HasPrincipalKey("_email"); + }); + + modelBuilder.Entity().Property("Email"); + + var model = modelBuilder.FinalizeModel(); + + var fk = model.FindEntityType(typeof(Profile13694)).GetForeignKeys().Single(); + Assert.Equal("_profiles", fk.PrincipalToDependent.Name); + Assert.Equal("User", fk.DependentToPrincipal.Name); + Assert.Equal("Email", fk.Properties[0].Name); + Assert.Equal(typeof(string), fk.Properties[0].ClrType); + Assert.Equal("_email", fk.PrincipalKey.Properties[0].Name); + } + + protected class User13694 + { + public Guid Id { get; set; } + private readonly string _email = string.Empty; + private readonly List _profiles = new List(); + } + + protected class Profile13694 + { + public Guid Id { get; set; } + + [ForeignKey("Email")] + public User13694 User { get; set; } + } + [ConditionalFact] public virtual void RequiredAttribute_for_navigation_throws_while_inserting_null_value() { diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index c4e072023b7..da1b52beb30 100644 --- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.Extensions.Logging; using Xunit; // ReSharper disable InconsistentNaming diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/ModelBuilderSqlServerTest.Other.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/ModelBuilderSqlServerTest.Other.cs deleted file mode 100644 index 9606a5f70cc..00000000000 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/ModelBuilderSqlServerTest.Other.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.EntityFrameworkCore.ModelBuilding -{ - public class ModelBuilderSqlServerTest : ModelBuilderOtherTest - { - protected override DbContextOptions Configure() - => new DbContextOptionsBuilder() - .UseInternalServiceProvider( - new ServiceCollection() - .AddEntityFrameworkSqlServer() - .AddSingleton() - .BuildServiceProvider()) - .UseSqlServer("Database = None") - .Options; - - protected override void RunThroughDifferPipeline(DbContext context) - => context.GetService().GetDifferences(null, context.Model.GetRelationalModel()); - } -} diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs index 574c3478157..ca414e118ee 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs @@ -149,6 +149,63 @@ public void Index_convention_sets_filter_for_unique_index_when_base_type_changed Assert.Null(index.GetFilter()); } + [ConditionalFact] + public virtual void TPT_identifying_FK_are_created_only_on_declaring_type() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .Ignore(b => b.Bun) + .Ignore(b => b.Pickles); + modelBuilder.Entity(b => + { + b.ToTable("Ingredients"); + b.Ignore(i => i.BigMak); + }); + modelBuilder.Entity(b => + { + b.ToTable("Buns"); + b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); + }); + modelBuilder.Entity(b => + { + b.ToTable("SesameBuns"); + }); + + var model = modelBuilder.FinalizeModel(); + + var principalType = model.FindEntityType(typeof(BigMak)); + Assert.Empty(principalType.GetForeignKeys()); + Assert.Empty(principalType.GetIndexes()); + + var ingredientType = model.FindEntityType(typeof(Ingredient)); + + var bunType = model.FindEntityType(typeof(Bun)); + Assert.Empty(bunType.GetIndexes()); + var bunFk = bunType.GetDeclaredForeignKeys().Single(fk => !fk.IsBaseLinking()); + Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); + Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName( + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table).Value, + StoreObjectIdentifier.Create(principalType, StoreObjectType.Table).Value)); + Assert.Single(bunFk.GetMappedConstraints()); + + var bunLinkingFk = bunType.GetDeclaredForeignKeys().Single(fk => fk.IsBaseLinking()); + Assert.Equal("FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName()); + Assert.Equal("FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName( + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table).Value, + StoreObjectIdentifier.Create(ingredientType, StoreObjectType.Table).Value)); + Assert.Single(bunLinkingFk.GetMappedConstraints()); + + var sesameBunType = model.FindEntityType(typeof(SesameBun)); + Assert.Empty(sesameBunType.GetIndexes()); + var sesameBunFk = sesameBunType.GetDeclaredForeignKeys().Single(); + Assert.True(sesameBunFk.IsBaseLinking()); + Assert.Equal("FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName()); + Assert.Equal("FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName( + StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table).Value, + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table).Value)); + Assert.Single(sesameBunFk.GetMappedConstraints()); + } + public class Parent { public int Id { get; set; } diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs index 9e047662cf6..787eeefac86 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; diff --git a/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs b/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs index 4fac3d28b05..92466686788 100644 --- a/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ManyToOneTestBase.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; @@ -1164,6 +1165,47 @@ public virtual void Can_have_principal_key_by_convention_specified_with_explicit Assert.Empty(principalType.GetIndexes()); } + [ConditionalFact] //Issue#13300 + public virtual void Explicitly_set_shadow_FK_name_is_preserved_with_HasPrincipalKey() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + m => + { + m.Property("_email"); + + m.HasMany("_profiles") + .WithOne("User") + .HasForeignKey("Email") + .HasPrincipalKey("_email"); + }); + + modelBuilder.Entity().Property("Email"); + + var model = modelBuilder.FinalizeModel(); + + var fk = model.FindEntityType(typeof(Profile13300)).GetForeignKeys().Single(); + Assert.Equal("_profiles", fk.PrincipalToDependent.Name); + Assert.Equal("User", fk.DependentToPrincipal.Name); + Assert.Equal("Email", fk.Properties[0].Name); + Assert.Equal(typeof(string), fk.Properties[0].ClrType); + Assert.Equal("_email", fk.PrincipalKey.Properties[0].Name); + } + + protected class User13300 + { + public Guid Id { get; set; } + private readonly string _email = string.Empty; + private readonly List _profiles = new List(); + } + + protected class Profile13300 + { + public Guid Id { get; set; } + public User13300 User { get; set; } + } + [ConditionalFact] public virtual void Creates_both_navigations_and_finds_existing_composite_FK() { diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilder.Other.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilder.Other.cs deleted file mode 100644 index 4cf1296b409..00000000000 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilder.Other.cs +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Microsoft.EntityFrameworkCore.ModelBuilding -{ - public class ModelBuilderOtherTest - { - [ConditionalFact] - public virtual void HasOne_with_just_string_navigation_for_non_CLR_property_throws() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => - { - b.Entity().HasOne("Snoop"); - }); - Assert.Equal( - CoreStrings.NoClrNavigation("Snoop", nameof(Dr)), - Assert.Throws(() => context.Model).Message); - } - - [ConditionalFact] - public virtual void HasMany_with_just_string_navigation_for_non_CLR_property_throws() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => - { - b.Entity().HasMany("Snoop"); - }); - Assert.Equal( - CoreStrings.NoClrNavigation("Snoop", nameof(Dr)), - Assert.Throws(() => context.Model).Message); - } - - [ConditionalFact] - public virtual void HasMany_with_a_non_collection_just_string_navigation_CLR_property_throws() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => - { - b.Entity().HasMany("Dre"); - }); - Assert.Equal( - CoreStrings.NavigationCollectionWrongClrType("Dre", nameof(Dr), nameof(Dre), "T"), - Assert.Throws(() => context.Model).Message); - } - - [ConditionalFact] - public virtual void HasMany_with_a_collection_navigation_CLR_property_to_derived_type_throws() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => - { - b.Entity().HasMany(d => d.Jrs); - }); - Assert.Equal( - CoreStrings.NavigationCollectionWrongClrType(nameof(Dr.Jrs), nameof(Dr), "ICollection", nameof(Dre)), - Assert.Throws(() => context.Model).Message); - } - - [ConditionalFact] - public virtual void OwnsOne_HasOne_with_just_string_navigation_for_non_CLR_property_throws() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => - { - b.Entity().OwnsOne(e => e.Dre).HasOne("Snoop"); - }); - Assert.Equal( - CoreStrings.NoClrNavigation("Snoop", nameof(Dre)), - Assert.Throws(() => context.Model).Message); - } - - protected class Dr - { - public int Id { get; set; } - - public Dre Dre { get; set; } - - public ICollection Jrs { get; set; } - } - - protected class Dre - { - } - - protected class DreJr : Dre - { - } - - [ConditionalFact] //Issue#13108 - public virtual void HasForeignKey_infers_type_for_shadow_property_when_not_specified() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => - { - b.Entity( - e => - { - e.HasKey(c => c.Key); - e.Property("ParentKey"); - e.HasOne(c => c.Parent).WithMany(c => c.Children).HasForeignKey("ParentKey"); - }); - - b.Entity().HasKey(c => c.Key); - }); - var model = (IConventionModel)context.Model; - var property = model - .FindEntityType(typeof(ComplexCaseChild13108)).GetProperties().Single(p => p.Name == "ParentKey"); - Assert.Equal(typeof(int), property.ClrType); - Assert.Equal(ConfigurationSource.Explicit, property.GetTypeConfigurationSource()); - } - - protected class ComplexCaseChild13108 - { - public int Key { get; set; } - public string Id { get; set; } - private int ParentKey { get; set; } - public ComplexCaseParent13108 Parent { get; set; } - } - - protected class ComplexCaseParent13108 - { - public int Key { get; set; } - public string Id { get; set; } - public ICollection Children { get; set; } - } - - [ConditionalFact] //Issue#12617 - [UseCulture("de-DE")] - public virtual void EntityType_name_is_stored_culture_invariantly() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => - { - b.Entity(); - b.Entity(); - }); - Assert.Equal(2, context.Model.GetEntityTypes().Count()); - Assert.Equal(2, context.Model.FindEntityType(typeof(Entityss)).GetNavigations().Count()); - } - - protected class Entityß - { - public int Id { get; set; } - } - - protected class Entityss - { - public int Id { get; set; } - public Entityß Navigationß { get; set; } - public Entityß Navigationss { get; set; } - } - - [ConditionalFact] //Issue#13300 - public virtual void Explicitly_set_shadow_FK_name_is_preserved_with_HasPrincipalKey() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => - { - b.Entity( - m => - { - m.Property("_email"); - - m.HasMany("_profiles") - .WithOne("User") - .HasForeignKey("Email") - .HasPrincipalKey("_email"); - }); - - b.Entity().Property("Email"); - }); - var model = context.Model; - - var fk = model.FindEntityType(typeof(Profile13300)).GetForeignKeys().Single(); - Assert.Equal("_profiles", fk.PrincipalToDependent.Name); - Assert.Equal("User", fk.DependentToPrincipal.Name); - Assert.Equal("Email", fk.Properties[0].Name); - Assert.Equal(typeof(string), fk.Properties[0].ClrType); - Assert.Equal("_email", fk.PrincipalKey.Properties[0].Name); - } - - protected class User13300 - { - public Guid Id { get; set; } - private readonly string _email = string.Empty; - private readonly List _profiles = new List(); - } - - protected class Profile13300 - { - public Guid Id { get; set; } - public User13300 User { get; set; } - } - - [ConditionalFact] - public virtual void Attribute_set_shadow_FK_name_is_preserved_with_HasPrincipalKey() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => - { - b.Entity( - m => - { - m.Property("_email"); - - m.HasMany("_profiles") - .WithOne("User") - .HasPrincipalKey("_email"); - }); - - b.Entity().Property("Email"); - }); - var model = context.Model; - - var fk = model.FindEntityType(typeof(Profile13694)).GetForeignKeys().Single(); - Assert.Equal("_profiles", fk.PrincipalToDependent.Name); - Assert.Equal("User", fk.DependentToPrincipal.Name); - Assert.Equal("Email", fk.Properties[0].Name); - Assert.Equal(typeof(string), fk.Properties[0].ClrType); - Assert.Equal("_email", fk.PrincipalKey.Properties[0].Name); - } - - protected class User13694 - { - public Guid Id { get; set; } - private readonly string _email = string.Empty; - private readonly List _profiles = new List(); - } - - protected class Profile13694 - { - public Guid Id { get; set; } - - [ForeignKey("Email")] - public User13694 User { get; set; } - } - - [ConditionalFact] - protected virtual void Mapping_throws_for_non_ignored_array() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => b.Entity()); - Assert.Equal( - CoreStrings.PropertyNotAdded( - typeof(OneDee).ShortDisplayName(), "One", typeof(int[]).ShortDisplayName()), - Assert.Throws(() => context.Model).Message); - } - - [ConditionalFact] - protected virtual void Mapping_ignores_ignored_array() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => b.Entity().Ignore(e => e.One)); - Assert.Null(context.Model.FindEntityType(typeof(OneDee)).FindProperty("One")); - - RunThroughDifferPipeline(context); - } - - [ConditionalFact] - protected virtual void Mapping_throws_for_non_ignored_two_dimensional_array() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => b.Entity()); - Assert.Equal( - CoreStrings.PropertyNotAdded( - typeof(TwoDee).ShortDisplayName(), "Two", typeof(int[,]).ShortDisplayName()), - Assert.Throws(() => context.Model).Message); - } - - [ConditionalFact] - protected virtual void Mapping_ignores_ignored_two_dimensional_array() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => b.Entity().Ignore(e => e.Two)); - Assert.Null(context.Model.FindEntityType(typeof(TwoDee)).FindProperty("Two")); - - RunThroughDifferPipeline(context); - } - - [ConditionalFact] - protected virtual void Mapping_throws_for_non_ignored_three_dimensional_array() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => b.Entity()); - Assert.Equal( - CoreStrings.PropertyNotAdded( - typeof(ThreeDee).ShortDisplayName(), "Three", typeof(int[,,]).ShortDisplayName()), - Assert.Throws(() => context.Model).Message); - } - - [ConditionalFact] - protected virtual void Mapping_ignores_ignored_three_dimensional_array() - { - using var context = new CustomModelBuildingContext( - Configure(), - b => b.Entity().Ignore(e => e.Three)); - Assert.Null(context.Model.FindEntityType(typeof(ThreeDee)).FindProperty("Three")); - - RunThroughDifferPipeline(context); - } - - protected class CustomModelBuildingContext : DbContext - { - private readonly Action _builder; - - public CustomModelBuildingContext(DbContextOptions options, Action builder) - : base(options) - { - _builder = builder; - } - - protected internal override void OnModelCreating(ModelBuilder modelBuilder) - => _builder(modelBuilder); - } - - protected virtual void RunThroughDifferPipeline(DbContext context) - { - } - - protected class TestModelCacheKeyFactory : IModelCacheKeyFactory - { - public object Create(DbContext context) - => new object(); - } - - protected class OneDee - { - public int Id { get; set; } - - public int[] One { get; set; } - } - - protected class TwoDee - { - public int Id { get; set; } - - public int[,] Two { get; set; } - } - - protected class ThreeDee - { - public int Id { get; set; } - - public int[,,] Three { get; set; } - } - - protected virtual DbContextOptions Configure() - => new DbContextOptionsBuilder() - .UseInternalServiceProvider( - InMemoryFixture.BuildServiceProvider( - new ServiceCollection() - .AddSingleton())) - .UseInMemoryDatabase(nameof(CustomModelBuildingContext)) - .Options; - } -} diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index e6659c09d77..5190b341dde 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -6,12 +6,14 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.EntityFrameworkCore.ValueGeneration; +using Xunit; namespace Microsoft.EntityFrameworkCore.ModelBuilding { @@ -31,6 +33,18 @@ protected override TestModelBuilder CreateTestModelBuilder(TestHelpers testHelpe public class NonGenericOwnedTypes : OwnedTypesTestBase { + [ConditionalFact] + public virtual void OwnsOne_HasOne_with_just_string_navigation_for_non_CLR_property_throws() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + CoreStrings.NoClrNavigation("Snoop", nameof(Dre)), + Assert.Throws(() => + ((NonGenericTestOwnedNavigationBuilder)modelBuilder.Entity().OwnsOne(e => e.Dre)).GetInfrastructure() + .HasOne("Snoop")).Message); + } + protected override TestModelBuilder CreateTestModelBuilder(TestHelpers testHelpers) => new NonGenericTestModelBuilder(testHelpers); } @@ -43,6 +57,80 @@ protected override TestModelBuilder CreateTestModelBuilder(TestHelpers testHelpe public class NonGenericOneToMany : OneToManyTestBase { + + [ConditionalFact] + public virtual void HasOne_with_just_string_navigation_for_non_CLR_property_throws() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + CoreStrings.NoClrNavigation("Snoop", nameof(Dr)), + Assert.Throws(() => + ((NonGenericTestEntityTypeBuilder)modelBuilder.Entity()).GetInfrastructure() + .HasOne("Snoop")).Message); + } + + [ConditionalFact] + public virtual void HasMany_with_just_string_navigation_for_non_CLR_property_throws() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + CoreStrings.NoClrNavigation("Snoop", nameof(Dr)), + Assert.Throws(() => + ((NonGenericTestEntityTypeBuilder)modelBuilder.Entity()).GetInfrastructure() + .HasMany("Snoop")).Message); + } + + [ConditionalFact] + public virtual void HasMany_with_a_non_collection_just_string_navigation_CLR_property_throws() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + CoreStrings.NavigationCollectionWrongClrType("Dre", nameof(Dr), nameof(Dre), "T"), + Assert.Throws(() => + ((NonGenericTestEntityTypeBuilder)modelBuilder.Entity()).GetInfrastructure() + .HasMany("Dre")).Message); + } + + [ConditionalFact] //Issue#13108 + public virtual void HasForeignKey_infers_type_for_shadow_property_when_not_specified() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity( + e => + { + e.HasKey(c => c.Key); + ((NonGenericTestEntityTypeBuilder)e).GetInfrastructure().Property("ParentKey"); + e.HasOne(c => c.Parent).WithMany(c => c.Children).HasForeignKey("ParentKey"); + }); + + modelBuilder.Entity().HasKey(c => c.Key); + + var model = (IConventionModel)modelBuilder.FinalizeModel(); + var property = model + .FindEntityType(typeof(ComplexCaseChild13108)).GetProperties().Single(p => p.Name == "ParentKey"); + Assert.Equal(typeof(int), property.ClrType); + Assert.Equal(ConfigurationSource.Explicit, property.GetTypeConfigurationSource()); + } + + protected class ComplexCaseChild13108 + { + public int Key { get; set; } + public string Id { get; set; } + private int ParentKey { get; set; } + public ComplexCaseParent13108 Parent { get; set; } + } + + protected class ComplexCaseParent13108 + { + public int Key { get; set; } + public string Id { get; set; } + public ICollection Children { get; set; } + } + protected override TestModelBuilder CreateTestModelBuilder(TestHelpers testHelpers) => new NonGenericTestModelBuilder(testHelpers); } diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index 97899ca39fd..485065f1522 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -8,10 +8,12 @@ using System.Text; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; using Microsoft.EntityFrameworkCore.ValueGeneration; using Xunit; @@ -1081,6 +1083,103 @@ protected class StringCollectionEntity public ICollection Property { get; set; } } + [ConditionalFact] + protected virtual void Mapping_throws_for_non_ignored_array() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity(); + + Assert.Equal( + CoreStrings.PropertyNotAdded( + typeof(OneDee).ShortDisplayName(), "One", typeof(int[]).ShortDisplayName()), + Assert.Throws(() => modelBuilder.FinalizeModel()).Message); + } + + [ConditionalFact] + protected virtual void Mapping_ignores_ignored_array() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().Ignore(e => e.One); + + var model = modelBuilder.FinalizeModel(); + + Assert.Null(model.FindEntityType(typeof(OneDee)).FindProperty("One")); + } + + [ConditionalFact] + protected virtual void Mapping_throws_for_non_ignored_two_dimensional_array() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity(); + + Assert.Equal( + CoreStrings.PropertyNotAdded( + typeof(TwoDee).ShortDisplayName(), "Two", typeof(int[,]).ShortDisplayName()), + Assert.Throws(() => modelBuilder.FinalizeModel()).Message); + } + + [ConditionalFact] + protected virtual void Mapping_ignores_ignored_two_dimensional_array() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().Ignore(e => e.Two); + + var model = modelBuilder.FinalizeModel(); + + Assert.Null(model.FindEntityType(typeof(TwoDee)).FindProperty("Two")); + } + + [ConditionalFact] + protected virtual void Mapping_throws_for_non_ignored_three_dimensional_array() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity(); + + Assert.Equal( + CoreStrings.PropertyNotAdded( + typeof(ThreeDee).ShortDisplayName(), "Three", typeof(int[,,]).ShortDisplayName()), + Assert.Throws(() => modelBuilder.FinalizeModel()).Message); + } + + [ConditionalFact] + protected virtual void Mapping_ignores_ignored_three_dimensional_array() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().Ignore(e => e.Three); + + var model = modelBuilder.FinalizeModel(); + + Assert.Null(model.FindEntityType(typeof(ThreeDee)).FindProperty("Three")); + } + + protected class OneDee + { + public int Id { get; set; } + + public int[] One { get; set; } + } + + protected class TwoDee + { + public int Id { get; set; } + + public int[,] Two { get; set; } + } + + protected class ThreeDee + { + public int Id { get; set; } + + public int[,,] Three { get; set; } + } + + [ConditionalFact] public virtual void Can_set_unicode_for_properties() { @@ -1527,7 +1626,6 @@ public virtual void Can_add_seed_data_objects_indexed_property_dictionary() public virtual void Can_add_seed_data_anonymous_objects_indexed_property_dictionary() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; modelBuilder.Entity( b => { @@ -1536,7 +1634,7 @@ public virtual void Can_add_seed_data_anonymous_objects_indexed_property_diction b.HasData(new { Id = -1, Required = 2 }); }); - modelBuilder.FinalizeModel(); + var model = modelBuilder.FinalizeModel(); var entityType = model.FindEntityType(typeof(IndexedClassByDictionary)); var data = Assert.Single(entityType.GetSeedData()); @@ -1545,6 +1643,33 @@ public virtual void Can_add_seed_data_anonymous_objects_indexed_property_diction Assert.False(data.ContainsKey("Optional")); } + [ConditionalFact] //Issue#12617 + [UseCulture("de-DE")] + public virtual void EntityType_name_is_stored_culture_invariantly() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity(); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + Assert.Equal(2, model.GetEntityTypes().Count()); + Assert.Equal(2, model.FindEntityType(typeof(Entityss)).GetNavigations().Count()); + } + + protected class Entityß + { + public int Id { get; set; } + } + + protected class Entityss + { + public int Id { get; set; } + public Entityß Navigationß { get; set; } + public Entityß Navigationss { get; set; } + } + [ConditionalFact] public virtual void Can_add_shared_type_entity_type() { diff --git a/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs b/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs index 73b6b743041..03fbf4cbd13 100644 --- a/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OneToManyTestBase.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata; @@ -1882,6 +1883,16 @@ public virtual void Throws_on_existing_one_to_one_relationship() modelBuilder.Entity().HasMany(e => e.Hobs).WithOne(e => e.Nob)).Message); } + [ConditionalFact] + public virtual void HasMany_with_a_collection_navigation_CLR_property_to_derived_type_throws() + { + var modelBuilder = CreateModelBuilder(); + + Assert.Equal( + CoreStrings.NavigationCollectionWrongClrType(nameof(Dr.Jrs), nameof(Dr), "ICollection", nameof(Dre)), + Assert.Throws(() => modelBuilder.Entity().HasMany(d => d.Jrs)).Message); + } + [ConditionalFact] public virtual void Removes_existing_unidirectional_one_to_one_relationship() { diff --git a/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs b/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs index 89f8d8f2120..9d8d1ad26b6 100644 --- a/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/OneToOneTestBase.cs @@ -511,24 +511,25 @@ public virtual void Creates_both_navigations_and_uses_specified_FK_even_if_PK() public virtual void Creates_both_navigations_and_uses_existing_FK_not_found_by_convention() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; modelBuilder.Entity(); modelBuilder.Entity().HasOne().WithOne() .HasForeignKey(e => e.BurgerId); modelBuilder.Ignore(); + modelBuilder + .Entity().HasOne(e => e.Bun).WithOne(e => e.BigMak) + .HasForeignKey(e => e.BurgerId); + + var model = modelBuilder.FinalizeModel(); + var dependentType = model.FindEntityType(typeof(Bun)); var principalType = model.FindEntityType(typeof(BigMak)); var fk = dependentType.GetForeignKeys().Single(foreignKey => foreignKey.Properties.All(p => p.Name == "BurgerId")); - fk.IsUnique = true; + Assert.True(fk.IsUnique); var principalKey = principalType.FindPrimaryKey(); var dependentKey = dependentType.FindPrimaryKey(); - modelBuilder - .Entity().HasOne(e => e.Bun).WithOne(e => e.BigMak) - .HasForeignKey(e => e.BurgerId); - Assert.Same(fk, dependentType.GetForeignKeys().Single()); Assert.Equal("BigMak", dependentType.GetNavigations().Single().Name); Assert.Equal("Bun", principalType.GetNavigations().Single().Name); diff --git a/test/EFCore.Tests/ModelBuilding/TestModel.cs b/test/EFCore.Tests/ModelBuilding/TestModel.cs index 0941082bac8..624fa856e0d 100644 --- a/test/EFCore.Tests/ModelBuilding/TestModel.cs +++ b/test/EFCore.Tests/ModelBuilding/TestModel.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.ModelBuilding { public abstract partial class ModelBuilderTest { - private class BigMak + protected class BigMak { public int Id { get; set; } public int AlternateKey { get; set; } @@ -25,7 +25,7 @@ private class BigMak public Bun Bun { get; set; } } - private class Ingredient + protected class Ingredient { public static readonly PropertyInfo BurgerIdProperty = typeof(Ingredient).GetProperty("BurgerId"); @@ -34,15 +34,19 @@ private class Ingredient public BigMak BigMak { get; set; } } - private class Pickle : Ingredient + protected class Pickle : Ingredient { } - private class Bun : Ingredient + protected class Bun : Ingredient { } - private class Whoopper + protected class SesameBun : Bun + { + } + + protected class Whoopper { public int Id1 { get; set; } public int Id2 { get; set; } @@ -56,7 +60,7 @@ private class Whoopper public Mustard Mustard { get; set; } } - private class Tomato + protected class Tomato { public int Id { get; set; } @@ -65,7 +69,7 @@ private class Tomato public Whoopper Whoopper { get; set; } } - private class ToastedBun + protected class ToastedBun { public int Id { get; set; } @@ -74,7 +78,7 @@ private class ToastedBun public Whoopper Whoopper { get; set; } } - private class Mustard + protected class Mustard { public int Id1 { get; set; } public int Id2 { get; set; } @@ -1126,5 +1130,22 @@ protected class NestedOwnerOfSharedType public Dictionary Reference { get; set; } public List> Collection { get; set; } } + + protected class Dr + { + public int Id { get; set; } + + public Dre Dre { get; set; } + + public ICollection Jrs { get; set; } + } + + protected class Dre + { + } + + protected class DreJr : Dre + { + } } }