From 3f62586c3fb4ab445cb7ffa3fa8364a339ca8ed0 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Thu, 10 Dec 2020 14:52:26 +0200 Subject: [PATCH] Big overhaul for 5.0 (#47) * Use EF Core 5.0 * Fixes around TPH, owned entity management * Redid tests Fixes #45 Closes #46 Fixes #41 --- .github/workflows/build.yml | 2 +- Directory.Build.props | 1 + Directory.Build.targets | 11 +- .../EFCore.NamingConventions.Test.csproj | 3 +- .../NameRewritingConventionTest.cs | 321 ++++++++++++------ .../TestUtilities/InMemoryTestHelpers.cs | 29 ++ .../EFCore.NamingConventions.csproj | 2 +- .../Internal/NameRewritingConvention.cs | 105 +++++- .../Internal/NamingConventionSetPlugin.cs | 1 + .../NamingConventionsOptionsExtension.cs | 2 +- .../NamingConventionsExtensions.cs | 36 +- global.json | 7 + 12 files changed, 388 insertions(+), 132 deletions(-) create mode 100644 EFCore.NamingConventions.Test/TestUtilities/InMemoryTestHelpers.cs create mode 100644 global.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b408073..4af7c9a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: - name: Setup .NET Core SDK uses: actions/setup-dotnet@v1 with: - dotnet-version: '3.1.x' + dotnet-version: '5.0.x' - name: Test run: dotnet test diff --git a/Directory.Build.props b/Directory.Build.props index 024d698..77bb455 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,6 +2,7 @@ true snupkg + 9.0 diff --git a/Directory.Build.targets b/Directory.Build.targets index a8cdcee..c318b6f 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,13 +1,14 @@  - - - + + + - - + + + diff --git a/EFCore.NamingConventions.Test/EFCore.NamingConventions.Test.csproj b/EFCore.NamingConventions.Test/EFCore.NamingConventions.Test.csproj index 62c5294..77bf493 100644 --- a/EFCore.NamingConventions.Test/EFCore.NamingConventions.Test.csproj +++ b/EFCore.NamingConventions.Test/EFCore.NamingConventions.Test.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net5.0 @@ -9,6 +9,7 @@ + diff --git a/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs b/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs index 666bb80..ee1052e 100644 --- a/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs +++ b/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs @@ -1,9 +1,12 @@ using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; +using EFCore.NamingConventions.Internal; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; // ReSharper disable UnusedMember.Global @@ -12,146 +15,272 @@ namespace EFCore.NamingConventions.Test public class NameRewritingConventionTest { [Fact] - public void Table_name_is_rewritten() + public void Table_name() { - using var context = CreateContext(); - var entityType = context.Model.FindEntityType(typeof(SimpleBlog)); + var entityType = BuildEntityType("SimpleBlog", _ => {}); Assert.Equal("simple_blog", entityType.GetTableName()); } [Fact] - public void Column_name_is_rewritten() + public void Column_name() { - using var context = CreateContext(); - var entityType = context.Model.FindEntityType(typeof(SimpleBlog)); - Assert.Equal("id_with_special_name", entityType.FindProperty("IdWithSpecialName").GetColumnName()); - Assert.Equal("full_name", entityType.FindProperty("FullName").GetColumnName()); + var entityType = BuildEntityType("SimpleBlog", e => e.Property("SimpleBlogId")); + + Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId") + .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value)); } [Fact] - public void Column_name_is_rewritten_in_turkish() + public void Column_name_on_view() { - using var context = CreateContext(CultureInfo.CreateSpecificCulture("tr-TR")); - var entityType = context.Model.FindEntityType(typeof(SimpleBlog)); - Assert.Equal("ıd_with_special_name", entityType.FindProperty("IdWithSpecialName").GetColumnName()); - Assert.Equal("full_name", entityType.FindProperty("FullName").GetColumnName()); + var entityType = BuildEntityType("SimpleBlog", e => + { + e.ToTable("SimpleBlogTable"); + e.ToView("SimpleBlogView"); + e.ToFunction("SimpleBlogFunction"); + e.Property("SimpleBlogId"); + }); + + foreach (var type in new[] { StoreObjectType.Table, StoreObjectType.View, StoreObjectType.Function }) + { + Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId") + .GetColumnName(StoreObjectIdentifier.Create(entityType, type)!.Value)); + } } [Fact] - public void Column_name_is_rewritten_in_invariant() + public void Column_name_turkish_culture() { - using var context = CreateContext(CultureInfo.InvariantCulture); - var entityType = context.Model.FindEntityType(typeof(SimpleBlog)); - Assert.Equal("id_with_special_name", entityType.FindProperty("IdWithSpecialName").GetColumnName()); - Assert.Equal("full_name", entityType.FindProperty("FullName").GetColumnName()); + var entityType = BuildEntityType( + "SimpleBlog", + e => e.Property("SimpleBlogId"), + CultureInfo.CreateSpecificCulture("tr-TR")); + + Assert.Equal("simple_blog_ıd", entityType.FindProperty("SimpleBlogId") + .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value)); } [Fact] - public void Owned_entity_is_rewritten() + public void Column_name_invariant_culture() { - using var context = CreateContext(); - var entityType = context.Model.FindEntityType(typeof(OwnedStatistics1)); - Assert.Equal("simple_blog", entityType.GetTableName()); - var property = entityType.GetProperty(nameof(OwnedStatistics1.SomeStatistic)); - Assert.Equal("owned_statistics1_some_statistic", property.GetColumnName()); + var entityType = BuildEntityType( + "SimpleBlog", + e => e.Property("SimpleBlogId"), + CultureInfo.InvariantCulture); + + Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId") + .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value)); } [Fact] - public void Owned_entity_split_is_rewritten() + public void Primary_key_name() { - using var context = CreateContext(); - var entityType = context.Model.FindEntityType(typeof(OwnedStatistics2)); - Assert.Equal("OwnedStatisticsSplit", entityType.GetTableName()); - var property = entityType.GetProperty(nameof(OwnedStatistics2.SomeStatistic)); - Assert.Equal("some_statistic", property.GetColumnName()); - } + var entityType = BuildEntityType("SimpleBlog", e => + { + e.Property("SimpleBlogId"); + e.HasKey("SimpleBlogId"); + }); - [Fact] - public void Primary_key_name_is_rewritten() - { - using var context = CreateContext(); - var entityType = context.Model.FindEntityType(typeof(SimpleBlog)); Assert.Equal("pk_simple_blog", entityType.GetKeys().Single(k => k.IsPrimaryKey()).GetName()); } [Fact] - public void Alternative_key_name_is_rewritten() + public void Alternative_key_name() { - using var context = CreateContext(); - var entityType = context.Model.FindEntityType(typeof(SimpleBlog)); + var entityType = BuildEntityType("SimpleBlog", e => + { + e.Property("SimpleBlogId"); + e.Property("SomeAlternativeKey"); + e.HasKey("SimpleBlogId"); + e.HasAlternateKey("SomeAlternativeKey"); + }); + Assert.Equal("ak_simple_blog_some_alternative_key", entityType.GetKeys().Single(k => !k.IsPrimaryKey()).GetName()); } [Fact] - public void Foreign_key_name_is_rewritten() + public void Foreign_key_name() { - using var context = CreateContext(); - var entityType = context.Model.FindEntityType(typeof(Post)); - Assert.Equal("fk_post_simple_blog_blog_id", entityType.GetForeignKeys().Single().GetConstraintName()); + var model = BuildModel(b => + { + b.Entity("Blog", e => + { + e.Property("BlogId"); + e.HasKey("BlogId"); + e.HasMany("Post").WithOne("Blog"); + }); + b.Entity("Post", e => + { + e.Property("PostId"); + e.Property("BlogId"); + e.HasKey("PostId"); + }); + }); + var entityType = model.FindEntityType("Post"); + + Assert.Equal("fk_post_blog_blog_id", entityType.GetForeignKeys().Single().GetConstraintName()); } [Fact] - public void Index_name_is_rewritten() + public void Index_name() { - using var context = CreateContext(); - var entityType = context.Model.FindEntityType(typeof(SimpleBlog)); - Assert.Equal("ix_simple_blog_full_name", entityType.GetIndexes().Single().GetDatabaseName()); + var entityType = BuildEntityType("SimpleBlog", e => + { + e.Property("IndexedProperty"); + e.HasIndex("IndexedProperty"); + }); + + Assert.Equal("ix_simple_blog_indexed_property", entityType.GetIndexes().Single().GetDatabaseName()); } + #region Owned entities + + [Fact] + public void Owned_entity_with_table_splitting() + { + var model = BuildModel(b => + { + b.Entity("SimpleBlog", e => + { + e.OwnsOne("OwnedEntity", "Nav", o => o.Property("OwnedProperty")); + }); + }); + + var entityType = model.FindEntityType("OwnedEntity"); + Assert.Equal("pk_simple_blog", entityType.FindPrimaryKey().GetName()); + Assert.Equal("simple_blog", entityType.GetTableName()); + Assert.Equal("owned_property", entityType.FindProperty("OwnedProperty") + .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value)); + } + + [Fact] + public void Owned_entity_without_table_splitting() + { + var model = BuildModel(b => + { + b.Entity("SimpleBlog", e => + { + e.Property("SimpleBlogId"); + e.HasKey("SimpleBlogId"); + e.OwnsOne("OwnedEntity", "Nav", o => + { + o.ToTable("another_table"); + o.Property("OwnedProperty"); + }); + }); + }); + var entityType = model.FindEntityType("OwnedEntity"); + + Assert.Equal("pk_another_table", entityType.FindPrimaryKey().GetName()); + Assert.Equal("another_table", entityType.GetTableName()); + Assert.Equal("owned_property", entityType.FindProperty("OwnedProperty") + .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value)); + } + + [Fact] + public void Owned_entity_with_view_without_table_splitting() + { + var model = BuildModel(b => + { + b.Entity("OwnedEntity", e => + { + e.ToTable("OwnedEntityTable"); + e.ToView("OwnedEntityView"); + e.Property("OwnedProperty"); + }); + b.Entity("SimpleBlog", e => e.OwnsOne("OwnedEntity", "Nav")); + }); + var entityType = model.FindEntityType("OwnedEntity"); + + Assert.Equal("OwnedEntityTable", entityType.GetTableName()); + Assert.Equal("OwnedEntityView", entityType.GetViewName()); + Assert.Equal("owned_property", entityType.FindProperty("OwnedProperty") + .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value)); + Assert.Equal("owned_property", entityType.FindProperty("OwnedProperty") + .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.View)!.Value)); + } + + #endregion Owned entities + + #region Inheritance + + [Fact] + public void TPH() + { + var model = BuildModel(b => + { + b.Entity("SimpleBlog", e => + { + e.Property("SimpleBlogId"); + e.HasKey("SimpleBlogId"); + }); + b.Entity("FancyBlog", e => + { + e.HasBaseType("SimpleBlog"); + e.Property("FancyProperty"); + }); + }); + + var simpleBlogEntityType = model.FindEntityType("SimpleBlog"); + Assert.Equal("simple_blog", simpleBlogEntityType.GetTableName()); + Assert.Equal("simple_blog_id", simpleBlogEntityType.FindProperty("SimpleBlogId") + .GetColumnName(StoreObjectIdentifier.Create(simpleBlogEntityType, StoreObjectType.Table)!.Value)); + + var fancyBlogEntityType = model.FindEntityType("FancyBlog"); + Assert.Equal("simple_blog", fancyBlogEntityType.GetTableName()); + Assert.Equal("fancy_property", fancyBlogEntityType.FindProperty("FancyProperty") + .GetColumnName(StoreObjectIdentifier.Create(fancyBlogEntityType, StoreObjectType.Table)!.Value)); + } + + [Fact] + public void TPT() + { + var model = BuildModel(b => + { + b.Entity("SimpleBlog", e => + { + e.Property("SimpleBlogId"); + e.HasKey("SimpleBlogId"); + }); + b.Entity("FancyBlog", e => + { + e.HasBaseType("SimpleBlog"); + e.ToTable("fancy_blog"); + e.Property("FancyProperty"); + }); + }); + + var simpleBlogEntityType = model.FindEntityType("SimpleBlog"); + Assert.Equal("simple_blog", simpleBlogEntityType.GetTableName()); + Assert.Equal("simple_blog_id", simpleBlogEntityType.FindProperty("SimpleBlogId") + .GetColumnName(StoreObjectIdentifier.Create(simpleBlogEntityType, StoreObjectType.Table)!.Value)); + + var fancyBlogEntityType = model.FindEntityType("FancyBlog"); + Assert.Equal("fancy_blog", fancyBlogEntityType.GetTableName()); + Assert.Equal("fancy_property", fancyBlogEntityType.FindProperty("FancyProperty") + .GetColumnName(StoreObjectIdentifier.Create(fancyBlogEntityType, StoreObjectType.Table)!.Value)); + } + + #endregion Inheritance + #region Support - TestContext CreateContext(CultureInfo culture = null) => new TestContext(builder => builder.UseSnakeCaseNamingConvention(culture)); - - public class TestContext : DbContext + private IModel BuildModel(Action buildAction, CultureInfo cultureInfo = null) { - private readonly Func _useNamingConvention; - public TestContext(Func useNamingConvention) - => _useNamingConvention = useNamingConvention; + var conventionSet = InMemoryTestHelpers.Instance.CreateConventionSetBuilder().CreateConventionSet(); + ConventionSet.Remove(conventionSet.ModelFinalizedConventions, typeof(ValidatingConvention)); - protected override void OnModelCreating(ModelBuilder modelBuilder) - => modelBuilder.Entity(e => - { - e.HasIndex(b => b.FullName); - e.OwnsOne(b => b.OwnedStatistics1); - e.OwnsOne(b => b.OwnedStatistics2, s => s.ToTable("OwnedStatisticsSplit")); - e.HasAlternateKey(b => b.SomeAlternativeKey); - }); + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSnakeCaseNamingConvention(cultureInfo); + new NamingConventionSetPlugin(optionsBuilder.Options).ModifyConventions(conventionSet); - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - => _useNamingConvention(optionsBuilder.UseInMemoryDatabase("test")); + var builder = new ModelBuilder(conventionSet); + buildAction(builder); + return builder.FinalizeModel(); } - public class SimpleBlog - { - [Key] - public int IdWithSpecialName { get; set; } - public string FullName { get; set; } - public int SomeAlternativeKey { get; set; } - - public List Posts { get; set; } - - public OwnedStatistics1 OwnedStatistics1 { get; set; } - public OwnedStatistics2 OwnedStatistics2 { get; set; } - } - - public class Post - { - public int Id { get; set; } - public string FullName { get; set; } - - public int BlogId { get; set; } - public SimpleBlog Blog { get; set; } - } - - public class OwnedStatistics1 - { - public int SomeStatistic { get; set; } - } - - public class OwnedStatistics2 - { - public int SomeStatistic { get; set; } - } + private IEntityType BuildEntityType(string entityTypeName, Action buildAction, CultureInfo cultureInfo = null) + => BuildModel(b => buildAction(b.Entity(entityTypeName)), cultureInfo).GetEntityTypes().Single(); #endregion } diff --git a/EFCore.NamingConventions.Test/TestUtilities/InMemoryTestHelpers.cs b/EFCore.NamingConventions.Test/TestUtilities/InMemoryTestHelpers.cs new file mode 100644 index 0000000..0196865 --- /dev/null +++ b/EFCore.NamingConventions.Test/TestUtilities/InMemoryTestHelpers.cs @@ -0,0 +1,29 @@ +// 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.Diagnostics; +using Microsoft.EntityFrameworkCore.InMemory.Diagnostics.Internal; +using Microsoft.Extensions.DependencyInjection; + +#pragma warning disable EF1001 + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore.TestUtilities +{ + public class InMemoryTestHelpers : TestHelpers + { + protected InMemoryTestHelpers() + { + } + + public static InMemoryTestHelpers Instance { get; } = new(); + + public override IServiceCollection AddProviderServices(IServiceCollection services) + => services.AddEntityFrameworkInMemoryDatabase(); + + public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseInMemoryDatabase(nameof(InMemoryTestHelpers)); + + public override LoggingDefinitions LoggingDefinitions { get; } = new InMemoryLoggingDefinitions(); + } +} diff --git a/EFCore.NamingConventions/EFCore.NamingConventions.csproj b/EFCore.NamingConventions/EFCore.NamingConventions.csproj index 2414276..b5e4d7c 100644 --- a/EFCore.NamingConventions/EFCore.NamingConventions.csproj +++ b/EFCore.NamingConventions/EFCore.NamingConventions.csproj @@ -2,7 +2,7 @@ netstandard2.1 - 5.0.0-rc1 + 5.0.0 true true ../EFCore.NamingConventions.snk diff --git a/EFCore.NamingConventions/Internal/NameRewritingConvention.cs b/EFCore.NamingConventions/Internal/NameRewritingConvention.cs index fc3a753..2ec3169 100644 --- a/EFCore.NamingConventions/Internal/NameRewritingConvention.cs +++ b/EFCore.NamingConventions/Internal/NameRewritingConvention.cs @@ -1,17 +1,22 @@ using System; +using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace EFCore.NamingConventions.Internal { public class NameRewritingConvention : IEntityTypeAddedConvention, IEntityTypeAnnotationChangedConvention, IPropertyAddedConvention, IForeignKeyOwnershipChangedConvention, IKeyAddedConvention, IForeignKeyAddedConvention, - IIndexAddedConvention + IIndexAddedConvention, IEntityTypeBaseTypeChangedConvention { + private static readonly StoreObjectType[] _storeObjectTypes + = { StoreObjectType.Table, StoreObjectType.View, StoreObjectType.Function, StoreObjectType.SqlQuery}; + private readonly INameRewriter _namingNameRewriter; public NameRewritingConvention(INameRewriter nameRewriter) => _namingNameRewriter = nameRewriter; @@ -21,17 +26,60 @@ namespace EFCore.NamingConventions.Internal { var entityType = entityTypeBuilder.Metadata; - // Only touch root entities for now (TPH). Revisit for TPT/TPC. - if (entityType.BaseType == null) + // Note that the base type is null when the entity type is first added - a base type only gets added later + // (see ProcessEntityTypeBaseTypeChanged). But we still have this check for safety. + if (entityType.BaseType is null) { entityTypeBuilder.ToTable(_namingNameRewriter.RewriteName(entityType.GetTableName()), entityType.GetSchema()); } } - public virtual void ProcessPropertyAdded( - IConventionPropertyBuilder propertyBuilder, IConventionContext context) - => propertyBuilder.HasColumnName(_namingNameRewriter.RewriteName(propertyBuilder.Metadata.GetColumnName())); + public void ProcessEntityTypeBaseTypeChanged( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionEntityType newBaseType, + IConventionEntityType oldBaseType, + IConventionContext context) + { + var entityType = entityTypeBuilder.Metadata; + if (newBaseType is null) + { + // The entity is getting removed from a hierarchy. Set the (rewritten) TableName. + entityTypeBuilder.ToTable(_namingNameRewriter.RewriteName(entityType.GetTableName()), entityType.GetSchema()); + } + else + { + // The entity is getting a new base type (e.g. joining a hierarchy). + // If this is TPH, we remove the previously rewritten TableName (and non-rewritten Schema) which we set when the + // entity type was first added to the model (see ProcessEntityTypeAdded). + // If this is TPT, TableName and Schema are set explicitly, so the following will be ignored. + entityTypeBuilder.HasNoAnnotation(RelationalAnnotationNames.TableName); + entityTypeBuilder.HasNoAnnotation(RelationalAnnotationNames.Schema); + } + } + + public virtual void ProcessPropertyAdded( + IConventionPropertyBuilder propertyBuilder, + IConventionContext context) + { + var entityType = propertyBuilder.Metadata.DeclaringEntityType; + var property = propertyBuilder.Metadata; + + property.SetColumnName(_namingNameRewriter.RewriteName(property.GetColumnBaseName())); + + foreach (var storeObjectType in _storeObjectTypes) + { + var identifier = StoreObjectIdentifier.Create(entityType, storeObjectType); + if (identifier is null) + continue; + + if (property.GetColumnNameConfigurationSource(identifier.Value) == ConfigurationSource.Convention) + { + property.SetColumnName( + _namingNameRewriter.RewriteName(property.GetColumnName(identifier.Value)), identifier.Value); + } + } + } public void ProcessForeignKeyOwnershipChanged(IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) { @@ -46,12 +94,30 @@ namespace EFCore.NamingConventions.Internal // Reset the table name which we've set when the entity type was added // If table splitting was configured by explicitly setting the table name, the following // does nothing. - ownedEntityType.SetTableName(ownedEntityType.GetDefaultTableName()); + ownedEntityType.Builder.HasNoAnnotation(RelationalAnnotationNames.TableName); + ownedEntityType.Builder.HasNoAnnotation(RelationalAnnotationNames.Schema); // Also need to reset all primary key properties foreach (var keyProperty in ownedEntityType.FindPrimaryKey().Properties) { - keyProperty.SetColumnName(keyProperty.GetDefaultColumnName()); + keyProperty.Builder.HasNoAnnotation(RelationalAnnotationNames.ColumnName); + + foreach (var storeObjectType in _storeObjectTypes) + { + var identifier = StoreObjectIdentifier.Create(ownedEntityType, storeObjectType); + if (identifier is null) + continue; + + if (keyProperty.GetColumnNameConfigurationSource(identifier.Value) == ConfigurationSource.Convention) + { +#pragma warning disable EF1001 + // TODO: Using internal APIs to remove the override + var tableOverrides = (IDictionary) + keyProperty[RelationalAnnotationNames.RelationalOverrides]; + tableOverrides.Remove(identifier.Value); +#pragma warning restore EF1001 + } + } } // Finally, we need to apply the entity type prefix to all the owned properties, since @@ -61,7 +127,7 @@ namespace EFCore.NamingConventions.Internal .Except(ownedEntityType.FindPrimaryKey().Properties) .Where(p => p.Builder.CanSetColumnName(null))) { - var columnName = property.GetColumnName(); + var columnName = property.GetColumnBaseName(); var prefix = _namingNameRewriter.RewriteName(ownedEntityType.ShortName()); if (!columnName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { @@ -81,17 +147,26 @@ namespace EFCore.NamingConventions.Internal IConventionAnnotation annotation, IConventionAnnotation oldAnnotation, IConventionContext context) { if (name == RelationalAnnotationNames.TableName && - annotation?.GetConfigurationSource() == ConfigurationSource.Explicit && - entityTypeBuilder.Metadata.FindOwnership() != null) + oldAnnotation?.Value is null && + annotation?.Value is not null && + entityTypeBuilder.Metadata.FindOwnership() is IConventionForeignKey ownership && + (string)annotation.Value != ownership.PrincipalEntityType.GetTableName()) { - // An owned entity's table is being set explicitly - this is the trigger to do table - // splitting. When the entity became owned, we prefixed all of its properties - we - // must now undo that. + // An owned entity's table is being set explicitly - this is the trigger to undo table splitting (which is the default). + + // When the entity became owned, we prefixed all of its properties - we must now undo that. foreach (var property in entityTypeBuilder.Metadata.GetProperties() .Except(entityTypeBuilder.Metadata.FindPrimaryKey().Properties) .Where(p => p.Builder.CanSetColumnName(null))) { - property.Builder.HasColumnName(_namingNameRewriter.RewriteName(property.GetDefaultColumnName())); + property.Builder.HasColumnName(_namingNameRewriter.RewriteName(property.GetDefaultColumnBaseName())); + } + + // We previously rewrote the owned entity's primary key name, when the owned entity was still in table splitting. + // Now that its getting its own table, rewrite the primary key constraint name again. + if (entityTypeBuilder.Metadata.FindPrimaryKey() is IConventionKey key) + { + key.Builder.HasName(_namingNameRewriter.RewriteName(key.GetDefaultName())); } } } diff --git a/EFCore.NamingConventions/Internal/NamingConventionSetPlugin.cs b/EFCore.NamingConventions/Internal/NamingConventionSetPlugin.cs index 01f9abe..401fcb2 100644 --- a/EFCore.NamingConventions/Internal/NamingConventionSetPlugin.cs +++ b/EFCore.NamingConventions/Internal/NamingConventionSetPlugin.cs @@ -38,6 +38,7 @@ namespace EFCore.NamingConventions.Internal conventionSet.KeyAddedConventions.Add(convention); conventionSet.ForeignKeyAddedConventions.Add(convention); conventionSet.IndexAddedConventions.Add(convention); + conventionSet.EntityTypeBaseTypeChangedConventions.Add(convention); return conventionSet; } diff --git a/EFCore.NamingConventions/Internal/NamingConventionsOptionsExtension.cs b/EFCore.NamingConventions/Internal/NamingConventionsOptionsExtension.cs index 234427c..b514145 100644 --- a/EFCore.NamingConventions/Internal/NamingConventionsOptionsExtension.cs +++ b/EFCore.NamingConventions/Internal/NamingConventionsOptionsExtension.cs @@ -23,7 +23,7 @@ namespace EFCore.NamingConventions.Internal public virtual DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this); - protected virtual NamingConventionsOptionsExtension Clone() => new NamingConventionsOptionsExtension(this); + protected virtual NamingConventionsOptionsExtension Clone() => new(this); internal virtual NamingConvention NamingConvention => _namingConvention; internal virtual CultureInfo Culture => _culture; diff --git a/EFCore.NamingConventions/NamingConventionsExtensions.cs b/EFCore.NamingConventions/NamingConventionsExtensions.cs index b040478..027f2f3 100644 --- a/EFCore.NamingConventions/NamingConventionsExtensions.cs +++ b/EFCore.NamingConventions/NamingConventionsExtensions.cs @@ -8,11 +8,13 @@ namespace Microsoft.EntityFrameworkCore { public static class NamingConventionsExtensions { - public static DbContextOptionsBuilder UseSnakeCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder , CultureInfo culture = null) + public static DbContextOptionsBuilder UseSnakeCaseNamingConvention( + [NotNull] this DbContextOptionsBuilder optionsBuilder , CultureInfo culture = null) { Check.NotNull(optionsBuilder, nameof(optionsBuilder)); - var extension = (optionsBuilder.Options.FindExtension() ?? new NamingConventionsOptionsExtension()) + var extension = (optionsBuilder.Options.FindExtension() + ?? new NamingConventionsOptionsExtension()) .WithSnakeCaseNamingConvention(culture); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); @@ -20,15 +22,18 @@ namespace Microsoft.EntityFrameworkCore return optionsBuilder; } - public static DbContextOptionsBuilder UseSnakeCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder , CultureInfo culture = null) + public static DbContextOptionsBuilder UseSnakeCaseNamingConvention( + [NotNull] this DbContextOptionsBuilder optionsBuilder , CultureInfo culture = null) where TContext : DbContext => (DbContextOptionsBuilder)UseSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture); - public static DbContextOptionsBuilder UseLowerCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) + public static DbContextOptionsBuilder UseLowerCaseNamingConvention( + [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) { Check.NotNull(optionsBuilder, nameof(optionsBuilder)); - var extension = (optionsBuilder.Options.FindExtension() ?? new NamingConventionsOptionsExtension()) + var extension = (optionsBuilder.Options.FindExtension() + ?? new NamingConventionsOptionsExtension()) .WithLowerCaseNamingConvention(culture); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); @@ -36,15 +41,18 @@ namespace Microsoft.EntityFrameworkCore return optionsBuilder; } - public static DbContextOptionsBuilder UseLowerCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) + public static DbContextOptionsBuilder UseLowerCaseNamingConvention( + [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) where TContext : DbContext => (DbContextOptionsBuilder)UseLowerCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder ,culture); - public static DbContextOptionsBuilder UseUpperCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) + public static DbContextOptionsBuilder UseUpperCaseNamingConvention( + [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) { Check.NotNull(optionsBuilder, nameof(optionsBuilder)); - var extension = (optionsBuilder.Options.FindExtension() ?? new NamingConventionsOptionsExtension()) + var extension = (optionsBuilder.Options.FindExtension() + ?? new NamingConventionsOptionsExtension()) .WithUpperCaseNamingConvention(culture); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); @@ -52,15 +60,18 @@ namespace Microsoft.EntityFrameworkCore return optionsBuilder; } - public static DbContextOptionsBuilder UseUpperCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) + public static DbContextOptionsBuilder UseUpperCaseNamingConvention( + [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) where TContext : DbContext => (DbContextOptionsBuilder)UseUpperCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture); - public static DbContextOptionsBuilder UseUpperSnakeCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) + public static DbContextOptionsBuilder UseUpperSnakeCaseNamingConvention( + [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) { Check.NotNull(optionsBuilder, nameof(optionsBuilder)); - var extension = (optionsBuilder.Options.FindExtension() ?? new NamingConventionsOptionsExtension()) + var extension = (optionsBuilder.Options.FindExtension() + ?? new NamingConventionsOptionsExtension()) .WithUpperSnakeCaseNamingConvention(culture); ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); @@ -68,7 +79,8 @@ namespace Microsoft.EntityFrameworkCore return optionsBuilder; } - public static DbContextOptionsBuilder UseUpperSnakeCaseNamingConvention([NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) + public static DbContextOptionsBuilder UseUpperSnakeCaseNamingConvention( + [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) where TContext : DbContext => (DbContextOptionsBuilder)UseUpperSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture); } diff --git a/global.json b/global.json new file mode 100644 index 0000000..a496d16 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "5.0.100", + "rollForward": "latestMajor", + "allowPrerelease": "false" + } +}