From ea9e59ca805915425db1002558b3e04ee1586a14 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Sat, 9 Jan 2021 15:35:44 +0100 Subject: [PATCH] Fix owned entity support (#58) * Also avoid rewriting primary key name with TPT * Also redo tests again to be properly end-to-end Fixes #50 --- .../EFCore.NamingConventions.Test.csproj | 1 - EFCore.NamingConventions.Test/EndToEndTest.cs | 103 ---- .../NameRewritingConventionTest.cs | 503 +++++++++--------- ...oryTestHelpers.cs => SqliteTestHelpers.cs} | 19 +- .../Internal/NameRewritingConvention.cs | 106 ++-- 5 files changed, 305 insertions(+), 427 deletions(-) delete mode 100644 EFCore.NamingConventions.Test/EndToEndTest.cs rename EFCore.NamingConventions.Test/TestUtilities/{InMemoryTestHelpers.cs => SqliteTestHelpers.cs} (59%) diff --git a/EFCore.NamingConventions.Test/EFCore.NamingConventions.Test.csproj b/EFCore.NamingConventions.Test/EFCore.NamingConventions.Test.csproj index bbb0e1a..99c386a 100644 --- a/EFCore.NamingConventions.Test/EFCore.NamingConventions.Test.csproj +++ b/EFCore.NamingConventions.Test/EFCore.NamingConventions.Test.csproj @@ -11,7 +11,6 @@ - diff --git a/EFCore.NamingConventions.Test/EndToEndTest.cs b/EFCore.NamingConventions.Test/EndToEndTest.cs deleted file mode 100644 index 506106b..0000000 --- a/EFCore.NamingConventions.Test/EndToEndTest.cs +++ /dev/null @@ -1,103 +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.Linq; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; -using Xunit; - -namespace EFCore.NamingConventions.Test -{ - public class EndToEndTest : IClassFixture - { - public EndToEndTest(EndToEndTestFixture fixture) - => Fixture = fixture; - - [Fact] - public void Table_splitting() - { - using var context = CreateContext(); - - var split1EntityType = context.Model.FindEntityType(typeof(Split1)); - var split2EntityType = context.Model.FindEntityType(typeof(Split2)); - - var table = StoreObjectIdentifier.Create(split1EntityType, StoreObjectType.Table)!.Value; - Assert.Equal(table, StoreObjectIdentifier.Create(split2EntityType, StoreObjectType.Table)); - - Assert.Equal("common", split1EntityType.FindProperty("Common").GetColumnName(table)); - Assert.Equal("split2_common", split2EntityType.FindProperty("Common").GetColumnName(table)); - - var split1 = context.Set().Include(s1 => s1.Split2).Single(); - Assert.Equal(100, split1.Common); - Assert.Equal(101, split1.Split2.Common); - } - - TestContext CreateContext() => Fixture.CreateContext(); - - readonly EndToEndTestFixture Fixture; - - public class TestContext : DbContext - { - public TestContext(SqliteConnection connection) - : base(new DbContextOptionsBuilder() - .UseSqlite(connection) - .UseSnakeCaseNamingConvention() - .Options) - { - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(e => - { - e.ToTable("split"); - e.HasOne(s1 => s1.Split2).WithOne(s2 => s2.Split1).HasForeignKey(s2 => s2.Id); - e.HasData(new Split1 { Id = 1, OneProp = 1, Common = 100 }); - }); - - modelBuilder.Entity(e => - { - e.ToTable("split"); - e.HasData(new Split2 { Id = 1, TwoProp = 2, Common = 101 }); - }); - } - } - - public class Split1 - { - public int Id { get; set; } - public int OneProp { get; set; } - public int Common { get; set; } - - public Split2 Split2 { get; set; } - } - - public class Split2 - { - public int Id { get; set; } - public int TwoProp { get; set; } - public int Common { get; set; } - - public Split1 Split1 { get; set; } - } - - public class EndToEndTestFixture : IDisposable - { - private readonly SqliteConnection _connection; - - public TestContext CreateContext() => new(_connection); - - public EndToEndTestFixture() - { - _connection = new SqliteConnection("Filename=:memory:"); - _connection.Open(); - using var context = new TestContext(_connection); - context.Database.EnsureCreated(); - } - - public void Dispose() => _connection.Dispose(); - } - } -} diff --git a/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs b/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs index a579228..73d0ff7 100644 --- a/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs +++ b/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs @@ -1,283 +1,131 @@ -using System; +// 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.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 namespace EFCore.NamingConventions.Test { public class NameRewritingConventionTest { [Fact] - public void Table_name() + public void Table() { - var entityType = BuildEntityType("SimpleBlog", _ => {}); - Assert.Equal("simple_blog", entityType.GetTableName()); + var entityType = BuildEntityType(b => b.Entity()); + Assert.Equal("sample_entity", entityType.GetTableName()); } [Fact] - public void Column_name() + public void Column() { - var entityType = BuildEntityType("SimpleBlog", e => e.Property("SimpleBlogId")); - - Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId") + var entityType = BuildEntityType(b => b.Entity()); + Assert.Equal("sample_entity_id", entityType.FindProperty(nameof(SampleEntity.SampleEntityId)) .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value)); } [Fact] - public void Column_name_on_view() + public void Column_with_turkish_culture() { - var entityType = BuildEntityType("SimpleBlog", e => - { - e.ToTable("SimpleBlogTable"); - e.ToView("SimpleBlogView"); - e.ToFunction("SimpleBlogFunction"); - e.Property("SimpleBlogId"); - }); + var entityType = BuildEntityType( + b => b.Entity(), + CultureInfo.CreateSpecificCulture("tr-TR")); + Assert.Equal("sample_entity_ıd", entityType.FindProperty(nameof(SampleEntity.SampleEntityId)) + .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value)); + } + + [Fact] + public void Column_with_invariant_culture() + { + var entityType = BuildEntityType( + b => b.Entity(), + CultureInfo.InvariantCulture); + Assert.Equal("sample_entity_id", entityType.FindProperty(nameof(SampleEntity.SampleEntityId)) + .GetColumnName(StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)!.Value)); + } + + [Fact] + public void Column_on_view() + { + var entityType = BuildEntityType(b => b.Entity( + e => + { + e.ToTable("SimpleBlogTable"); + e.ToView("SimpleBlogView"); + e.ToFunction("SimpleBlogFunction"); + })); foreach (var type in new[] { StoreObjectType.Table, StoreObjectType.View, StoreObjectType.Function }) { - Assert.Equal("simple_blog_id", entityType.FindProperty("SimpleBlogId") + Assert.Equal("sample_entity_id", entityType.FindProperty(nameof(SampleEntity.SampleEntityId)) .GetColumnName(StoreObjectIdentifier.Create(entityType, type)!.Value)); } } [Fact] - public void Column_name_turkish_culture() + public void Primary_key() + { + var entityType = BuildEntityType(b => b.Entity()); + Assert.Equal("pk_sample_entity", entityType.GetKeys().Single(k => k.IsPrimaryKey()).GetName()); + } + + [Fact] + public void Alternative_key() { 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)); + b => b.Entity( + e => + { + e.Property("SomeAlternateKey"); + e.HasAlternateKey("SomeAlternateKey"); + })); + Assert.Equal("ak_sample_entity_some_alternate_key", entityType.GetKeys().Single(k => !k.IsPrimaryKey()).GetName()); } [Fact] - public void Column_name_invariant_culture() + public void Foreign_key() { - 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 Primary_key_name() - { - var entityType = BuildEntityType("SimpleBlog", e => - { - e.Property("SimpleBlogId"); - e.HasKey("SimpleBlogId"); - }); - - Assert.Equal("pk_simple_blog", entityType.GetKeys().Single(k => k.IsPrimaryKey()).GetName()); - } - - [Fact] - public void Alternative_key_name() - { - 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() - { - 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"); - + var model = BuildModel(b => b.Entity()); + var entityType = model.FindEntityType(typeof(Post)); Assert.Equal("fk_post_blog_blog_id", entityType.GetForeignKeys().Single().GetConstraintName()); } [Fact] - public void Index_name() + public void Index() { - var entityType = BuildEntityType("SimpleBlog", e => - { - e.Property("IndexedProperty"); - e.HasIndex("IndexedProperty"); - }); - - Assert.Equal("ix_simple_blog_indexed_property", entityType.GetIndexes().Single().GetDatabaseName()); + var entityType = BuildEntityType(b => b.Entity().HasIndex(s => s.SomeProperty)); + Assert.Equal("ix_sample_entity_some_property", entityType.GetIndexes().Single().GetDatabaseName()); } - [Fact] - public void Table_splitting() - { - var model = BuildModel(b => - { - b.Entity("One", e => - { - e.ToTable("table"); - e.Property("Id"); - e.Property("OneProp"); - e.Property("Common"); - - e.HasOne("Two").WithOne().HasForeignKey("Two", "Id"); - }); - - b.Entity("Two", e => - { - e.ToTable("table"); - e.Property("Id"); - e.Property("TwoProp"); - e.Property("Common"); - }); - }); - - var oneEntityType = model.FindEntityType("One"); - var twoEntityType = model.FindEntityType("Two"); - - var table = StoreObjectIdentifier.Create(oneEntityType, StoreObjectType.Table)!.Value; - Assert.Equal(table, StoreObjectIdentifier.Create(twoEntityType, StoreObjectType.Table)); - - Assert.Equal("table", oneEntityType.GetTableName()); - Assert.Equal("one_prop", oneEntityType.FindProperty("OneProp").GetColumnName(table)); - - Assert.Equal("table", twoEntityType.GetTableName()); - Assert.Equal("two_prop", twoEntityType.FindProperty("TwoProp").GetColumnName(table)); - - var foreignKey = twoEntityType.GetForeignKeys().Single(); - Assert.Same(oneEntityType.FindPrimaryKey(), foreignKey.PrincipalKey); - Assert.Same(twoEntityType.FindPrimaryKey().Properties.Single(), foreignKey.Properties.Single()); - Assert.Equal(oneEntityType.FindPrimaryKey().GetName(), twoEntityType.FindPrimaryKey().GetName()); - - Assert.Equal( - foreignKey.PrincipalKey.Properties.Single().GetColumnName(table), - foreignKey.Properties.Single().GetColumnName(table)); - - Assert.Empty(oneEntityType.GetForeignKeys()); - } - - #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"); - }); + b.Entity(); + b.Entity(); }); - 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 parentEntityType = model.FindEntityType(typeof(Parent)); + var childEntityType = model.FindEntityType(typeof(Child)); - 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)); + Assert.Equal("parent", parentEntityType.GetTableName()); + Assert.Equal("id", parentEntityType.FindProperty(nameof(Parent.Id)) + .GetColumnName(StoreObjectIdentifier.Create(parentEntityType, StoreObjectType.Table)!.Value)); + Assert.Equal("parent_property", parentEntityType.FindProperty(nameof(Parent.ParentProperty)) + .GetColumnName(StoreObjectIdentifier.Create(childEntityType, StoreObjectType.Table)!.Value)); + + Assert.Equal("parent", childEntityType.GetTableName()); + Assert.Equal("child_property", childEntityType.FindProperty(nameof(Child.ChildProperty)) + .GetColumnName(StoreObjectIdentifier.Create(childEntityType, StoreObjectType.Table)!.Value)); + + Assert.Same(parentEntityType.FindPrimaryKey(), childEntityType.FindPrimaryKey()); } [Fact] @@ -285,51 +133,182 @@ namespace EFCore.NamingConventions.Test { 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"); - }); + b.Entity().ToTable("parent"); + b.Entity().ToTable("child"); }); - 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 parentEntityType = model.FindEntityType(typeof(Parent)); + var childEntityType = model.FindEntityType(typeof(Child)); - 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)); + Assert.Equal("parent", parentEntityType.GetTableName()); + Assert.Equal("id", parentEntityType.FindProperty(nameof(Parent.Id)) + .GetColumnName(StoreObjectIdentifier.Create(parentEntityType, StoreObjectType.Table)!.Value)); + Assert.Equal("parent_property", parentEntityType.FindProperty(nameof(Parent.ParentProperty)) + .GetColumnName(StoreObjectIdentifier.Create(parentEntityType, StoreObjectType.Table)!.Value)); + + Assert.Equal("child", childEntityType.GetTableName()); + Assert.Equal("child_property", childEntityType.FindProperty(nameof(Child.ChildProperty)) + .GetColumnName(StoreObjectIdentifier.Create(childEntityType, StoreObjectType.Table)!.Value)); + + var parentKey = parentEntityType.FindPrimaryKey(); + var childKey = childEntityType.FindPrimaryKey(); + + Assert.Equal("PK_parent", parentKey.GetName()); + Assert.Equal("PK_parent", childKey.GetName()); } - #endregion Inheritance - - #region Support - - private IModel BuildModel(Action buildAction, CultureInfo cultureInfo = null) + [Fact] + public void Table_splitting() { - var conventionSet = InMemoryTestHelpers.Instance.CreateConventionSetBuilder().CreateConventionSet(); - ConventionSet.Remove(conventionSet.ModelFinalizedConventions, typeof(ValidatingConvention)); + var model = BuildModel(b => + { + b.Entity( + e => + { + e.ToTable("split_table"); + e.HasOne(s1 => s1.S2).WithOne(s2 => s2.S1).HasForeignKey(s2 => s2.Id); + }); + + b.Entity(e => e.ToTable("split_table")); + }); + + var split1EntityType = model.FindEntityType(typeof(Split1)); + var split2EntityType = model.FindEntityType(typeof(Split2)); + + var table = StoreObjectIdentifier.Create(split1EntityType, StoreObjectType.Table)!.Value; + Assert.Equal(table, StoreObjectIdentifier.Create(split2EntityType, StoreObjectType.Table)); + + Assert.Equal("split_table", split1EntityType.GetTableName()); + Assert.Equal("one_prop", split1EntityType.FindProperty(nameof(Split1.OneProp)).GetColumnName(table)); + + Assert.Equal("split_table", split2EntityType.GetTableName()); + Assert.Equal("two_prop", split2EntityType.FindProperty(nameof(Split2.TwoProp)).GetColumnName(table)); + + Assert.Equal("common", split1EntityType.FindProperty(nameof(Split1.Common)).GetColumnName(table)); + Assert.Equal("split2_common", split2EntityType.FindProperty(nameof(Split2.Common)).GetColumnName(table)); + + var foreignKey = split2EntityType.GetForeignKeys().Single(); + Assert.Same(split1EntityType.FindPrimaryKey(), foreignKey.PrincipalKey); + Assert.Same(split2EntityType.FindPrimaryKey().Properties.Single(), foreignKey.Properties.Single()); + Assert.Equal(split1EntityType.FindPrimaryKey().GetName(), split2EntityType.FindPrimaryKey().GetName()); + Assert.Equal( + foreignKey.PrincipalKey.Properties.Single().GetColumnName(table), + foreignKey.Properties.Single().GetColumnName(table)); + Assert.Empty(split1EntityType.GetForeignKeys()); + } + + [Fact] + public void Owned_entity_with_table_splitting() + { + var model = BuildModel(b => b.Entity().OwnsOne(o => o.Owned)); + + var ownerEntityType = model.FindEntityType(typeof(Owner)); + var ownedEntityType = model.FindEntityType(typeof(Owned)); + + Assert.Equal("owner", ownerEntityType.GetTableName()); + Assert.Equal("owner", ownedEntityType.GetTableName()); + var table = StoreObjectIdentifier.Create(ownerEntityType, StoreObjectType.Table)!.Value; + Assert.Equal(table, StoreObjectIdentifier.Create(ownedEntityType, StoreObjectType.Table)!.Value); + + Assert.Equal("owned_owned_property", ownedEntityType.FindProperty(nameof(Owned.OwnedProperty)).GetColumnName(table)); + + var (ownerKey, ownedKey) = (ownerEntityType.FindPrimaryKey(), ownedEntityType.FindPrimaryKey()); + Assert.Equal("pk_owner", ownerKey.GetName()); + Assert.Equal("pk_owner", ownedKey.GetName()); + Assert.Equal("id", ownerKey.Properties.Single().GetColumnName(table)); + Assert.Equal("id", ownedKey.Properties.Single().GetColumnName(table)); + } + + [Fact] + public void Owned_entity_without_table_splitting() + { + var model = BuildModel(b => + b.Entity().OwnsOne(o => o.Owned).ToTable("another_table")); + + var ownedEntityType = model.FindEntityType(typeof(Owned)); + + Assert.Equal("pk_another_table", ownedEntityType.FindPrimaryKey().GetName()); + Assert.Equal("another_table", ownedEntityType.GetTableName()); + Assert.Equal("owned_property", ownedEntityType.FindProperty("OwnedProperty") + .GetColumnName(StoreObjectIdentifier.Create(ownedEntityType, StoreObjectType.Table)!.Value)); + } + + private IEntityType BuildEntityType(Action builderAction, CultureInfo culture = null) + => BuildModel(builderAction, culture).GetEntityTypes().Single(); + + private IModel BuildModel(Action builderAction, CultureInfo culture = null) + { + var conventionSet = SqliteTestHelpers.Instance.CreateConventionSetBuilder().CreateConventionSet(); var optionsBuilder = new DbContextOptionsBuilder(); - optionsBuilder.UseSnakeCaseNamingConvention(cultureInfo); - new NamingConventionSetPlugin(optionsBuilder.Options).ModifyConventions(conventionSet); + SqliteTestHelpers.Instance.UseProviderOptions(optionsBuilder); + optionsBuilder.UseSnakeCaseNamingConvention(culture); + var plugin = new NamingConventionSetPlugin(optionsBuilder.Options); + plugin.ModifyConventions(conventionSet); - var builder = new ModelBuilder(conventionSet); - buildAction(builder); - return builder.FinalizeModel(); + var modelBuilder = new ModelBuilder(conventionSet); + builderAction(modelBuilder); + return modelBuilder.FinalizeModel(); } - private IEntityType BuildEntityType(string entityTypeName, Action buildAction, CultureInfo cultureInfo = null) - => BuildModel(b => buildAction(b.Entity(entityTypeName)), cultureInfo).GetEntityTypes().Single(); + public class SampleEntity + { + public int SampleEntityId { get; set; } + public int SomeProperty { get; set; } + } - #endregion + public class Blog + { + public int BlogId { get; set; } + public List Posts { get; set; } + } + + public class Post + { + public int PostId { get; set; } + public Blog Blog { get; set; } + public int BlogId { get; set; } + } + + public class Parent + { + public int Id { get; set; } + public int ParentProperty { get; set; } + } + + public class Child : Parent + { + public int ChildProperty { get; set; } + } + + public class Split1 + { + public int Id { get; set; } + public int OneProp { get; set; } + public int Common { get; set; } + + public Split2 S2 { get; set; } + } + + public class Split2 + { + public int Id { get; set; } + public int TwoProp { get; set; } + public int Common { get; set; } + + public Split1 S1 { get; set; } + } + + public class Owner + { + public int Id { get; set; } + public int OwnerProperty { get; set; } + public Owned Owned { get; set; } + } + + public class Owned + { + public int OwnedProperty { get; set; } + } } } diff --git a/EFCore.NamingConventions.Test/TestUtilities/InMemoryTestHelpers.cs b/EFCore.NamingConventions.Test/TestUtilities/SqliteTestHelpers.cs similarity index 59% rename from EFCore.NamingConventions.Test/TestUtilities/InMemoryTestHelpers.cs rename to EFCore.NamingConventions.Test/TestUtilities/SqliteTestHelpers.cs index 0196865..e33d857 100644 --- a/EFCore.NamingConventions.Test/TestUtilities/InMemoryTestHelpers.cs +++ b/EFCore.NamingConventions.Test/TestUtilities/SqliteTestHelpers.cs @@ -1,29 +1,30 @@ // 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.Data.Sqlite; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.InMemory.Diagnostics.Internal; +using Microsoft.EntityFrameworkCore.Sqlite.Diagnostics.Internal; using Microsoft.Extensions.DependencyInjection; -#pragma warning disable EF1001 - // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.TestUtilities { - public class InMemoryTestHelpers : TestHelpers + public class SqliteTestHelpers : TestHelpers { - protected InMemoryTestHelpers() + protected SqliteTestHelpers() { } - public static InMemoryTestHelpers Instance { get; } = new(); + public static SqliteTestHelpers Instance { get; } = new(); public override IServiceCollection AddProviderServices(IServiceCollection services) - => services.AddEntityFrameworkInMemoryDatabase(); + => services.AddEntityFrameworkSqlite(); public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseInMemoryDatabase(nameof(InMemoryTestHelpers)); + => optionsBuilder.UseSqlite(new SqliteConnection("Data Source=:memory:")); - public override LoggingDefinitions LoggingDefinitions { get; } = new InMemoryLoggingDefinitions(); +#pragma warning disable EF1001 + public override LoggingDefinitions LoggingDefinitions { get; } = new SqliteLoggingDefinitions(); +#pragma warning enable EF1001 } } diff --git a/EFCore.NamingConventions/Internal/NameRewritingConvention.cs b/EFCore.NamingConventions/Internal/NameRewritingConvention.cs index 6104e67..f2ccd0c 100644 --- a/EFCore.NamingConventions/Internal/NameRewritingConvention.cs +++ b/EFCore.NamingConventions/Internal/NameRewritingConvention.cs @@ -1,11 +1,9 @@ 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 { @@ -66,25 +64,7 @@ namespace EFCore.NamingConventions.Internal public virtual void ProcessPropertyAdded( IConventionPropertyBuilder propertyBuilder, IConventionContext context) - { - var entityType = propertyBuilder.Metadata.DeclaringEntityType; - var property = propertyBuilder.Metadata; - - propertyBuilder.HasColumnName(_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) - { - propertyBuilder.HasColumnName( - _namingNameRewriter.RewriteName(property.GetColumnName(identifier.Value)), identifier.Value); - } - } - } + => RewriteColumnName(propertyBuilder); public void ProcessForeignKeyOwnershipChanged(IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) { @@ -102,27 +82,11 @@ namespace EFCore.NamingConventions.Internal 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) + // We've previously set rewritten column names when the entity was originally added (before becoming owned). + // These need to be rewritten again to include the owner prefix. + foreach (var property in ownedEntityType.GetProperties()) { - 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 - } - } + RewriteColumnName(property.Builder); } } } @@ -135,40 +99,50 @@ namespace EFCore.NamingConventions.Internal return; } + var entityType = entityTypeBuilder.Metadata; + // The table's name is changing - rewrite keys, index names - if (entityTypeBuilder.Metadata.FindPrimaryKey() is IConventionKey primaryKey) + if (entityType.FindPrimaryKey() is IConventionKey primaryKey) { - primaryKey.Builder.HasName(_namingNameRewriter.RewriteName(primaryKey.GetDefaultName())); + if (entityType.BaseType is not null + && entityType.GetTableName() != entityType.BaseType.GetTableName()) + { + // It's not yet possible to set the PK name with TPT, see https://github.com/dotnet/efcore/issues/23444. + primaryKey.Builder.HasNoAnnotation(RelationalAnnotationNames.Name); + } + else + { + primaryKey.Builder.HasName(_namingNameRewriter.RewriteName(primaryKey.GetDefaultName())); + } } - foreach (var foreignKey in entityTypeBuilder.Metadata.GetForeignKeys()) + foreach (var foreignKey in entityType.GetForeignKeys()) { foreignKey.Builder.HasConstraintName(_namingNameRewriter.RewriteName(foreignKey.GetDefaultName())); } - foreach (var index in entityTypeBuilder.Metadata.GetIndexes()) + foreach (var index in entityType.GetIndexes()) { index.Builder.HasDatabaseName(_namingNameRewriter.RewriteName(index.GetDefaultDatabaseName())); } - if (oldAnnotation?.Value is null && - annotation?.Value is not null && - entityTypeBuilder.Metadata.FindOwnership() is IConventionForeignKey ownership && + if (annotation?.Value is not null && + entityType.FindOwnership() is IConventionForeignKey ownership && (string)annotation.Value != ownership.PrincipalEntityType.GetTableName()) { // 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) + foreach (var property in entityType.GetProperties() + .Except(entityType.FindPrimaryKey().Properties) .Where(p => p.Builder.CanSetColumnName(null))) { - property.Builder.HasColumnName(_namingNameRewriter.RewriteName(property.GetDefaultColumnBaseName())); + RewriteColumnName(property.Builder); } // 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) + if (entityType.FindPrimaryKey() is IConventionKey key) { key.Builder.HasName(_namingNameRewriter.RewriteName(key.GetDefaultName())); } @@ -224,5 +198,33 @@ namespace EFCore.NamingConventions.Internal } } } + + private void RewriteColumnName(IConventionPropertyBuilder propertyBuilder) + { + var property = propertyBuilder.Metadata; + var entityType = property.DeclaringEntityType; + + // Remove any previous setting of the column name we may have done, so we can get the default recalculated below. + property.Builder.HasNoAnnotation(RelationalAnnotationNames.ColumnName); + + // TODO: The following is a temporary hack. We should probably just always set the relational override below, + // but https://github.com/dotnet/efcore/pull/23834 +#pragma warning disable 618 + propertyBuilder.HasColumnName(_namingNameRewriter.RewriteName(property.GetColumnName())); +#pragma warning restore 618 + + foreach (var storeObjectType in _storeObjectTypes) + { + var identifier = StoreObjectIdentifier.Create(entityType, storeObjectType); + if (identifier is null) + continue; + + if (property.GetColumnNameConfigurationSource(identifier.Value) == ConfigurationSource.Convention) + { + propertyBuilder.HasColumnName( + _namingNameRewriter.RewriteName(property.GetColumnName(identifier.Value)), identifier.Value); + } + } + } } }