diff --git a/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs b/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs index 695f780..029f11b 100644 --- a/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs +++ b/EFCore.NamingConventions.Test/NameRewritingConventionTest.cs @@ -14,552 +14,551 @@ using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace EFCore.NamingConventions.Test +namespace EFCore.NamingConventions.Test; + +public class NameRewritingConventionTest { - public class NameRewritingConventionTest + [Fact] + public void Table() { - [Fact] - public void Table() - { - var entityType = BuildEntityType(b => b.Entity()); - Assert.Equal("sample_entity", entityType.GetTableName()); - } + var entityType = BuildEntityType(b => b.Entity()); + Assert.Equal("sample_entity", entityType.GetTableName()); + } - [Fact] - public void Column() + [Fact] + public void Column() + { + 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_with_turkish_culture() + { + 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 }) { - 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_with_turkish_culture() - { - 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("sample_entity_id", entityType.FindProperty(nameof(SampleEntity.SampleEntityId)) - .GetColumnName(StoreObjectIdentifier.Create(entityType, type)!.Value)); - } - } - - [Fact] - public void Primary_key() - { - var entityType = BuildEntityType(b => b.Entity()); - Assert.Equal("pk_sample_entity", entityType.FindPrimaryKey()!.GetName()); - } - - [Fact] - public void Primary_key_on_table_with_explicit_name() - { - var entityType = BuildEntityType(b => b.Entity().ToTable("some_explicit_name")); - - Assert.Equal("pk_some_explicit_name", entityType.FindPrimaryKey()!.GetName()); - } - - [Fact] - public void Alternative_key() - { - var entityType = BuildEntityType( - 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 Foreign_key() - { - 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() - { - var entityType = BuildEntityType(b => b.Entity().HasIndex(s => s.SomeProperty)); - Assert.Equal("ix_sample_entity_some_property", entityType.GetIndexes().Single().GetDatabaseName()); - } - - [Fact] - public void TPH() - { - var model = BuildModel(b => - { - b.Entity(); - b.Entity(); - }); - - var parentEntityType = model.FindEntityType(typeof(Parent)); - var childEntityType = model.FindEntityType(typeof(Child)); - - 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] - public void TPT() - { - var model = BuildModel(b => - { - b.Entity().ToTable("parent"); - b.Entity().ToTable("child"); - }); - - var parentEntityType = model.FindEntityType(typeof(Parent)); - var childEntityType = model.FindEntityType(typeof(Child)); - - 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 primaryKey = parentEntityType.FindPrimaryKey(); - Assert.Same(primaryKey, childEntityType.FindPrimaryKey()); - - Assert.Equal("PK_parent", primaryKey.GetName()); - - // For the following, see #112 - var parentStoreObjectIdentifier = StoreObjectIdentifier.Create(parentEntityType, StoreObjectType.Table).Value; - var childStoreObjectIdentifier = StoreObjectIdentifier.Create(childEntityType, StoreObjectType.Table).Value; - Assert.Equal("PK_parent", primaryKey.GetName(parentStoreObjectIdentifier)); - Assert.Equal("PK_child", primaryKey.GetName(childStoreObjectIdentifier)); - } - - [Fact] - public void TPT_reversed_configuration() - { - var model = BuildModel(b => - { - b.Entity().ToTable("child"); - b.Entity().ToTable("parent"); - }); - - var parentEntityType = model.FindEntityType(typeof(Parent)); - var childEntityType = model.FindEntityType(typeof(Child)); - - 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 primaryKey = parentEntityType.FindPrimaryKey(); - Assert.Same(primaryKey, childEntityType.FindPrimaryKey()); - - Assert.Equal("PK_parent", primaryKey.GetName()); - - // For the following, see #112 - var parentStoreObjectIdentifier = StoreObjectIdentifier.Create(parentEntityType, StoreObjectType.Table).Value; - var childStoreObjectIdentifier = StoreObjectIdentifier.Create(childEntityType, StoreObjectType.Table).Value; - Assert.Equal("PK_parent", primaryKey.GetName(parentStoreObjectIdentifier)); - Assert.Equal("PK_child", primaryKey.GetName(childStoreObjectIdentifier)); - } - - [Fact] - public void TPH_with_owned() - { - var model = BuildModel(b => - { - b.Entity(); - b.Entity().OwnsOne(c => c.Owned); - }); - - var parentEntityType = model.FindEntityType(typeof(Parent)); - var childEntityType = model.FindEntityType(typeof(ChildWithOwned)); - var ownedEntityType = model.FindEntityType(typeof(Owned)); - - 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()); - - Assert.Equal("parent", ownedEntityType.GetTableName()); - Assert.Equal("owned_owned_property", ownedEntityType.FindProperty(nameof(Owned.OwnedProperty)) - .GetColumnName(StoreObjectIdentifier.Create(ownedEntityType, StoreObjectType.Table)!.Value)); - } - - [Fact] - public void TPT_with_owned() - { - var model = BuildModel(b => - { - b.Entity().ToTable("parent"); - b.Entity( - e => - { - e.ToTable("child"); - e.OwnsOne(c => c.Owned); - }); - }); - - var parentEntityType = model.FindEntityType(typeof(Parent)); - var childEntityType = model.FindEntityType(typeof(ChildWithOwned)); - var ownedEntityType = model.FindEntityType(typeof(Owned)); - - 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(ChildWithOwned.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()); - - Assert.Equal("child", ownedEntityType.GetTableName()); - Assert.Equal("owned_owned_property", ownedEntityType.FindProperty(nameof(Owned.OwnedProperty)) - .GetColumnName(StoreObjectIdentifier.Create(ownedEntityType, StoreObjectType.Table)!.Value)); - } - - [Fact] - public void Table_splitting1() - { - 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().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 Table_splitting_and_explicit_owner_table() - { - var model = BuildModel(b => - { - b.Entity().HasOne(s1 => s1.S2).WithOne(s2 => s2.S1).HasForeignKey(s2 => s2.Id); - b.Entity().ToTable("split_table"); - b.Entity().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_with_table_splitting_and_explicit_owner_table() - { - var model = BuildModel( - b => b.Entity( - e => - { - e.OwnsOne(o => o.Owned); - e.ToTable("destination_table"); - })); - - var ownerEntityType = model.FindEntityType(typeof(Owner)); - var ownedEntityType = model.FindEntityType(typeof(Owned)); - - Assert.Equal("destination_table", ownerEntityType.GetTableName()); - Assert.Equal("destination_table", 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_destination_table", ownerKey.GetName()); - Assert.Equal("pk_destination_table", ownedKey.GetName()); - Assert.Equal("id", ownerKey.Properties.Single().GetColumnName(table)); - Assert.Equal("id", ownedKey.Properties.Single().GetColumnName(table)); - } - - [Fact] - public void Owned_entity_twice_with_table_splitting_and_explicit_owner_table() - { - var model = BuildModel( - b => b.Entity( - e => - { - e.OwnsOne("owned1", o => o.Owned); - e.OwnsOne("owned2", o => o.Owned2); - e.ToTable("destination_table"); - })); - - var ownerEntityType = model.FindEntityType(typeof(Owner)); - var owned1EntityType = model.FindEntityType("owned1"); - var owned2EntityType = model.FindEntityType("owned2"); - - Assert.Equal("destination_table", ownerEntityType.GetTableName()); - Assert.Equal("destination_table", owned1EntityType.GetTableName()); - Assert.Equal("destination_table", owned2EntityType.GetTableName()); - var table = StoreObjectIdentifier.Create(ownerEntityType, StoreObjectType.Table)!.Value; - Assert.Equal(table, StoreObjectIdentifier.Create(owned1EntityType, StoreObjectType.Table)!.Value); - Assert.Equal(table, StoreObjectIdentifier.Create(owned2EntityType, StoreObjectType.Table)!.Value); - - Assert.Equal("owned_owned_property", owned1EntityType.FindProperty(nameof(Owned.OwnedProperty)).GetColumnName(table)); - Assert.Equal("owned2_owned_property", owned2EntityType.FindProperty(nameof(Owned.OwnedProperty)).GetColumnName(table)); - - var (ownerKey, owned1Key, owned2Key) = - (ownerEntityType.FindPrimaryKey(), owned1EntityType.FindPrimaryKey(), owned1EntityType.FindPrimaryKey()); - Assert.Equal("pk_destination_table", ownerKey.GetName()); - Assert.Equal("pk_destination_table", owned1Key.GetName()); - Assert.Equal("pk_destination_table", owned2Key.GetName()); - Assert.Equal("id", ownerKey.Properties.Single().GetColumnName(table)); - Assert.Equal("id", owned1Key.Properties.Single().GetColumnName(table)); - Assert.Equal("id", owned2Key.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)); - } - - [Fact] - public void Owned_entity_withs_OwnsMany() - { - var model = BuildModel(b => b.Entity().OwnsMany(b => b.Posts)); - var ownedEntityType = model.FindEntityType(typeof(Post)); - - Assert.Equal("post", ownedEntityType.GetTableName()); - Assert.Equal("pk_post", ownedEntityType.FindPrimaryKey().GetName()); - Assert.Equal("post_title", ownedEntityType.FindProperty("PostTitle") - .GetColumnName(StoreObjectIdentifier.Create(ownedEntityType, StoreObjectType.Table)!.Value)); - } - - [Fact] - public void Not_mapped_to_table() - { - var entityType = BuildEntityType(b => b.Entity().ToSqlQuery("SELECT foobar")); - - Assert.Null(entityType.GetTableName()); - } - - 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 - .CreateContextServices() - .GetRequiredService() - .CreateConventionSet(); - - var optionsBuilder = new DbContextOptionsBuilder(); - SqliteTestHelpers.Instance.UseProviderOptions(optionsBuilder); - optionsBuilder.UseSnakeCaseNamingConvention(culture); - var plugin = new NamingConventionSetPlugin(optionsBuilder.Options); - plugin.ModifyConventions(conventionSet); - - var modelBuilder = new ModelBuilder(conventionSet); - builderAction(modelBuilder); - return modelBuilder.FinalizeModel(); - } - - public class SampleEntity - { - public int SampleEntityId { get; set; } - public int SomeProperty { get; set; } - } - - 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 string PostTitle { 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 ChildWithOwned : Parent - { - public int ChildProperty { get; set; } - public Owned Owned { 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; } - [NotMapped] - public Owned Owned2 { get; set; } - } - - public class Owned - { - public int OwnedProperty { get; set; } + .GetColumnName(StoreObjectIdentifier.Create(entityType, type)!.Value)); } } -} + + [Fact] + public void Primary_key() + { + var entityType = BuildEntityType(b => b.Entity()); + Assert.Equal("pk_sample_entity", entityType.FindPrimaryKey()!.GetName()); + } + + [Fact] + public void Primary_key_on_table_with_explicit_name() + { + var entityType = BuildEntityType(b => b.Entity().ToTable("some_explicit_name")); + + Assert.Equal("pk_some_explicit_name", entityType.FindPrimaryKey()!.GetName()); + } + + [Fact] + public void Alternative_key() + { + var entityType = BuildEntityType( + 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 Foreign_key() + { + 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() + { + var entityType = BuildEntityType(b => b.Entity().HasIndex(s => s.SomeProperty)); + Assert.Equal("ix_sample_entity_some_property", entityType.GetIndexes().Single().GetDatabaseName()); + } + + [Fact] + public void TPH() + { + var model = BuildModel(b => + { + b.Entity(); + b.Entity(); + }); + + var parentEntityType = model.FindEntityType(typeof(Parent)); + var childEntityType = model.FindEntityType(typeof(Child)); + + 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] + public void TPT() + { + var model = BuildModel(b => + { + b.Entity().ToTable("parent"); + b.Entity().ToTable("child"); + }); + + var parentEntityType = model.FindEntityType(typeof(Parent)); + var childEntityType = model.FindEntityType(typeof(Child)); + + 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 primaryKey = parentEntityType.FindPrimaryKey(); + Assert.Same(primaryKey, childEntityType.FindPrimaryKey()); + + Assert.Equal("PK_parent", primaryKey.GetName()); + + // For the following, see #112 + var parentStoreObjectIdentifier = StoreObjectIdentifier.Create(parentEntityType, StoreObjectType.Table).Value; + var childStoreObjectIdentifier = StoreObjectIdentifier.Create(childEntityType, StoreObjectType.Table).Value; + Assert.Equal("PK_parent", primaryKey.GetName(parentStoreObjectIdentifier)); + Assert.Equal("PK_child", primaryKey.GetName(childStoreObjectIdentifier)); + } + + [Fact] + public void TPT_reversed_configuration() + { + var model = BuildModel(b => + { + b.Entity().ToTable("child"); + b.Entity().ToTable("parent"); + }); + + var parentEntityType = model.FindEntityType(typeof(Parent)); + var childEntityType = model.FindEntityType(typeof(Child)); + + 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 primaryKey = parentEntityType.FindPrimaryKey(); + Assert.Same(primaryKey, childEntityType.FindPrimaryKey()); + + Assert.Equal("PK_parent", primaryKey.GetName()); + + // For the following, see #112 + var parentStoreObjectIdentifier = StoreObjectIdentifier.Create(parentEntityType, StoreObjectType.Table).Value; + var childStoreObjectIdentifier = StoreObjectIdentifier.Create(childEntityType, StoreObjectType.Table).Value; + Assert.Equal("PK_parent", primaryKey.GetName(parentStoreObjectIdentifier)); + Assert.Equal("PK_child", primaryKey.GetName(childStoreObjectIdentifier)); + } + + [Fact] + public void TPH_with_owned() + { + var model = BuildModel(b => + { + b.Entity(); + b.Entity().OwnsOne(c => c.Owned); + }); + + var parentEntityType = model.FindEntityType(typeof(Parent)); + var childEntityType = model.FindEntityType(typeof(ChildWithOwned)); + var ownedEntityType = model.FindEntityType(typeof(Owned)); + + 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()); + + Assert.Equal("parent", ownedEntityType.GetTableName()); + Assert.Equal("owned_owned_property", ownedEntityType.FindProperty(nameof(Owned.OwnedProperty)) + .GetColumnName(StoreObjectIdentifier.Create(ownedEntityType, StoreObjectType.Table)!.Value)); + } + + [Fact] + public void TPT_with_owned() + { + var model = BuildModel(b => + { + b.Entity().ToTable("parent"); + b.Entity( + e => + { + e.ToTable("child"); + e.OwnsOne(c => c.Owned); + }); + }); + + var parentEntityType = model.FindEntityType(typeof(Parent)); + var childEntityType = model.FindEntityType(typeof(ChildWithOwned)); + var ownedEntityType = model.FindEntityType(typeof(Owned)); + + 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(ChildWithOwned.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()); + + Assert.Equal("child", ownedEntityType.GetTableName()); + Assert.Equal("owned_owned_property", ownedEntityType.FindProperty(nameof(Owned.OwnedProperty)) + .GetColumnName(StoreObjectIdentifier.Create(ownedEntityType, StoreObjectType.Table)!.Value)); + } + + [Fact] + public void Table_splitting1() + { + 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().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 Table_splitting_and_explicit_owner_table() + { + var model = BuildModel(b => + { + b.Entity().HasOne(s1 => s1.S2).WithOne(s2 => s2.S1).HasForeignKey(s2 => s2.Id); + b.Entity().ToTable("split_table"); + b.Entity().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_with_table_splitting_and_explicit_owner_table() + { + var model = BuildModel( + b => b.Entity( + e => + { + e.OwnsOne(o => o.Owned); + e.ToTable("destination_table"); + })); + + var ownerEntityType = model.FindEntityType(typeof(Owner)); + var ownedEntityType = model.FindEntityType(typeof(Owned)); + + Assert.Equal("destination_table", ownerEntityType.GetTableName()); + Assert.Equal("destination_table", 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_destination_table", ownerKey.GetName()); + Assert.Equal("pk_destination_table", ownedKey.GetName()); + Assert.Equal("id", ownerKey.Properties.Single().GetColumnName(table)); + Assert.Equal("id", ownedKey.Properties.Single().GetColumnName(table)); + } + + [Fact] + public void Owned_entity_twice_with_table_splitting_and_explicit_owner_table() + { + var model = BuildModel( + b => b.Entity( + e => + { + e.OwnsOne("owned1", o => o.Owned); + e.OwnsOne("owned2", o => o.Owned2); + e.ToTable("destination_table"); + })); + + var ownerEntityType = model.FindEntityType(typeof(Owner)); + var owned1EntityType = model.FindEntityType("owned1"); + var owned2EntityType = model.FindEntityType("owned2"); + + Assert.Equal("destination_table", ownerEntityType.GetTableName()); + Assert.Equal("destination_table", owned1EntityType.GetTableName()); + Assert.Equal("destination_table", owned2EntityType.GetTableName()); + var table = StoreObjectIdentifier.Create(ownerEntityType, StoreObjectType.Table)!.Value; + Assert.Equal(table, StoreObjectIdentifier.Create(owned1EntityType, StoreObjectType.Table)!.Value); + Assert.Equal(table, StoreObjectIdentifier.Create(owned2EntityType, StoreObjectType.Table)!.Value); + + Assert.Equal("owned_owned_property", owned1EntityType.FindProperty(nameof(Owned.OwnedProperty)).GetColumnName(table)); + Assert.Equal("owned2_owned_property", owned2EntityType.FindProperty(nameof(Owned.OwnedProperty)).GetColumnName(table)); + + var (ownerKey, owned1Key, owned2Key) = + (ownerEntityType.FindPrimaryKey(), owned1EntityType.FindPrimaryKey(), owned1EntityType.FindPrimaryKey()); + Assert.Equal("pk_destination_table", ownerKey.GetName()); + Assert.Equal("pk_destination_table", owned1Key.GetName()); + Assert.Equal("pk_destination_table", owned2Key.GetName()); + Assert.Equal("id", ownerKey.Properties.Single().GetColumnName(table)); + Assert.Equal("id", owned1Key.Properties.Single().GetColumnName(table)); + Assert.Equal("id", owned2Key.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)); + } + + [Fact] + public void Owned_entity_withs_OwnsMany() + { + var model = BuildModel(b => b.Entity().OwnsMany(b => b.Posts)); + var ownedEntityType = model.FindEntityType(typeof(Post)); + + Assert.Equal("post", ownedEntityType.GetTableName()); + Assert.Equal("pk_post", ownedEntityType.FindPrimaryKey().GetName()); + Assert.Equal("post_title", ownedEntityType.FindProperty("PostTitle") + .GetColumnName(StoreObjectIdentifier.Create(ownedEntityType, StoreObjectType.Table)!.Value)); + } + + [Fact] + public void Not_mapped_to_table() + { + var entityType = BuildEntityType(b => b.Entity().ToSqlQuery("SELECT foobar")); + + Assert.Null(entityType.GetTableName()); + } + + 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 + .CreateContextServices() + .GetRequiredService() + .CreateConventionSet(); + + var optionsBuilder = new DbContextOptionsBuilder(); + SqliteTestHelpers.Instance.UseProviderOptions(optionsBuilder); + optionsBuilder.UseSnakeCaseNamingConvention(culture); + var plugin = new NamingConventionSetPlugin(optionsBuilder.Options); + plugin.ModifyConventions(conventionSet); + + var modelBuilder = new ModelBuilder(conventionSet); + builderAction(modelBuilder); + return modelBuilder.FinalizeModel(); + } + + public class SampleEntity + { + public int SampleEntityId { get; set; } + public int SomeProperty { get; set; } + } + + 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 string PostTitle { 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 ChildWithOwned : Parent + { + public int ChildProperty { get; set; } + public Owned Owned { 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; } + [NotMapped] + public Owned Owned2 { get; set; } + } + + public class Owned + { + public int OwnedProperty { get; set; } + } +} \ No newline at end of file diff --git a/EFCore.NamingConventions.Test/RewriterTest.cs b/EFCore.NamingConventions.Test/RewriterTest.cs index c2487af..2fc3df4 100644 --- a/EFCore.NamingConventions.Test/RewriterTest.cs +++ b/EFCore.NamingConventions.Test/RewriterTest.cs @@ -2,33 +2,32 @@ using System.Globalization; using EFCore.NamingConventions.Internal; using Xunit; -namespace EFCore.NamingConventions.Test +namespace EFCore.NamingConventions.Test; + +public class RewriterTest { - public class RewriterTest - { - [Fact] - public void SnakeCase() - => Assert.Equal("full_name", - new SnakeCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName")); + [Fact] + public void SnakeCase() + => Assert.Equal("full_name", + new SnakeCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName")); - [Fact] - public void UpperSnakeCase() - => Assert.Equal("FULL_NAME", - new UpperSnakeCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName")); + [Fact] + public void UpperSnakeCase() + => Assert.Equal("FULL_NAME", + new UpperSnakeCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName")); - [Fact] - public void LowerCase() - => Assert.Equal("fullname", - new LowerCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName")); + [Fact] + public void LowerCase() + => Assert.Equal("fullname", + new LowerCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName")); - [Fact] - public void CamelCase() - => Assert.Equal("fullName", - new CamelCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName")); + [Fact] + public void CamelCase() + => Assert.Equal("fullName", + new CamelCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName")); - [Fact] - public void UpperCase() - => Assert.Equal("FULLNAME", - new UpperCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName")); - } -} + [Fact] + public void UpperCase() + => Assert.Equal("FULLNAME", + new UpperCaseNameRewriter(CultureInfo.InvariantCulture).RewriteName("FullName")); +} \ No newline at end of file diff --git a/EFCore.NamingConventions.Test/TestUtilities/SqliteTestHelpers.cs b/EFCore.NamingConventions.Test/TestUtilities/SqliteTestHelpers.cs index ebe4a9d..aefb59c 100644 --- a/EFCore.NamingConventions.Test/TestUtilities/SqliteTestHelpers.cs +++ b/EFCore.NamingConventions.Test/TestUtilities/SqliteTestHelpers.cs @@ -7,24 +7,23 @@ using Microsoft.EntityFrameworkCore.Sqlite.Diagnostics.Internal; using Microsoft.Extensions.DependencyInjection; // ReSharper disable once CheckNamespace -namespace Microsoft.EntityFrameworkCore.TestUtilities +namespace Microsoft.EntityFrameworkCore.TestUtilities; + +public class SqliteTestHelpers : TestHelpers { - public class SqliteTestHelpers : TestHelpers + protected SqliteTestHelpers() { - protected SqliteTestHelpers() - { - } + } - public static SqliteTestHelpers Instance { get; } = new(); + public static SqliteTestHelpers Instance { get; } = new(); - public override IServiceCollection AddProviderServices(IServiceCollection services) - => services.AddEntityFrameworkSqlite(); + public override IServiceCollection AddProviderServices(IServiceCollection services) + => services.AddEntityFrameworkSqlite(); - public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder) - => optionsBuilder.UseSqlite(new SqliteConnection("Data Source=:memory:")); + public override void UseProviderOptions(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlite(new SqliteConnection("Data Source=:memory:")); #pragma warning disable EF1001 - public override LoggingDefinitions LoggingDefinitions { get; } = new SqliteLoggingDefinitions(); + public override LoggingDefinitions LoggingDefinitions { get; } = new SqliteLoggingDefinitions(); #pragma warning restore EF1001 - } } diff --git a/EFCore.NamingConventions/Check.cs b/EFCore.NamingConventions/Check.cs index c7f2e88..b8e4d7b 100644 --- a/EFCore.NamingConventions/Check.cs +++ b/EFCore.NamingConventions/Check.cs @@ -6,105 +6,104 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Diagnostics; // ReSharper disable once CheckNamespace -namespace Microsoft.EntityFrameworkCore +namespace Microsoft.EntityFrameworkCore; + +[DebuggerStepThrough] +internal static class Check { - [DebuggerStepThrough] - internal static class Check + [ContractAnnotation("value:null => halt")] + public static T NotNull([NoEnumeration] T value, [InvokerParameterName] [NotNull] string parameterName) { - [ContractAnnotation("value:null => halt")] - public static T NotNull([NoEnumeration] T value, [InvokerParameterName] [NotNull] string parameterName) + if (ReferenceEquals(value, null)) { - if (ReferenceEquals(value, null)) - { - NotEmpty(parameterName, nameof(parameterName)); + NotEmpty(parameterName, nameof(parameterName)); - throw new ArgumentNullException(parameterName); - } - - return value; + throw new ArgumentNullException(parameterName); } - [ContractAnnotation("value:null => halt")] - public static T NotNull( - [NoEnumeration] T value, - [InvokerParameterName] [NotNull] string parameterName, - [NotNull] string propertyName) - { - if (ReferenceEquals(value, null)) - { - NotEmpty(parameterName, nameof(parameterName)); - NotEmpty(propertyName, nameof(propertyName)); - - throw new ArgumentException(CoreStrings.ArgumentPropertyNull(propertyName, parameterName)); - } - - return value; - } - - [ContractAnnotation("value:null => halt")] - public static IReadOnlyList NotEmpty(IReadOnlyList value, [InvokerParameterName] [NotNull] string parameterName) - { - NotNull(value, parameterName); - - if (value.Count == 0) - { - NotEmpty(parameterName, nameof(parameterName)); - - throw new ArgumentException(AbstractionsStrings.CollectionArgumentIsEmpty(parameterName)); - } - - return value; - } - - [ContractAnnotation("value:null => halt")] - public static string NotEmpty(string value, [InvokerParameterName] [NotNull] string parameterName) - { - Exception e = null; - if (ReferenceEquals(value, null)) - { - e = new ArgumentNullException(parameterName); - } - else if (value.Trim().Length == 0) - { - e = new ArgumentException(AbstractionsStrings.ArgumentIsEmpty(parameterName)); - } - - if (e != null) - { - NotEmpty(parameterName, nameof(parameterName)); - - throw e; - } - - return value; - } - - public static string NullButNotEmpty([CanBeNull] string value, [InvokerParameterName] [NotNull] string parameterName) - { - if (!ReferenceEquals(value, null) - && (value.Length == 0)) - { - NotEmpty(parameterName, nameof(parameterName)); - - throw new ArgumentException(AbstractionsStrings.ArgumentIsEmpty(parameterName)); - } - - return value; - } - - public static IReadOnlyList HasNoNulls(IReadOnlyList value, [InvokerParameterName] [NotNull] string parameterName) - where T : class - { - NotNull(value, parameterName); - - if (value.Any(e => e == null)) - { - NotEmpty(parameterName, nameof(parameterName)); - - throw new ArgumentException(parameterName); - } - - return value; - } + return value; } -} + + [ContractAnnotation("value:null => halt")] + public static T NotNull( + [NoEnumeration] T value, + [InvokerParameterName] [NotNull] string parameterName, + [NotNull] string propertyName) + { + if (ReferenceEquals(value, null)) + { + NotEmpty(parameterName, nameof(parameterName)); + NotEmpty(propertyName, nameof(propertyName)); + + throw new ArgumentException(CoreStrings.ArgumentPropertyNull(propertyName, parameterName)); + } + + return value; + } + + [ContractAnnotation("value:null => halt")] + public static IReadOnlyList NotEmpty(IReadOnlyList value, [InvokerParameterName] [NotNull] string parameterName) + { + NotNull(value, parameterName); + + if (value.Count == 0) + { + NotEmpty(parameterName, nameof(parameterName)); + + throw new ArgumentException(AbstractionsStrings.CollectionArgumentIsEmpty(parameterName)); + } + + return value; + } + + [ContractAnnotation("value:null => halt")] + public static string NotEmpty(string value, [InvokerParameterName] [NotNull] string parameterName) + { + Exception e = null; + if (ReferenceEquals(value, null)) + { + e = new ArgumentNullException(parameterName); + } + else if (value.Trim().Length == 0) + { + e = new ArgumentException(AbstractionsStrings.ArgumentIsEmpty(parameterName)); + } + + if (e != null) + { + NotEmpty(parameterName, nameof(parameterName)); + + throw e; + } + + return value; + } + + public static string NullButNotEmpty([CanBeNull] string value, [InvokerParameterName] [NotNull] string parameterName) + { + if (!ReferenceEquals(value, null) + && (value.Length == 0)) + { + NotEmpty(parameterName, nameof(parameterName)); + + throw new ArgumentException(AbstractionsStrings.ArgumentIsEmpty(parameterName)); + } + + return value; + } + + public static IReadOnlyList HasNoNulls(IReadOnlyList value, [InvokerParameterName] [NotNull] string parameterName) + where T : class + { + NotNull(value, parameterName); + + if (value.Any(e => e == null)) + { + NotEmpty(parameterName, nameof(parameterName)); + + throw new ArgumentException(parameterName); + } + + return value; + } +} \ No newline at end of file diff --git a/EFCore.NamingConventions/CodeAnnotations.cs b/EFCore.NamingConventions/CodeAnnotations.cs index 8a11e30..704b17b 100644 --- a/EFCore.NamingConventions/CodeAnnotations.cs +++ b/EFCore.NamingConventions/CodeAnnotations.cs @@ -1,108 +1,107 @@ using System; // ReSharper disable once CheckNamespace -namespace JetBrains.Annotations +namespace JetBrains.Annotations; + +[AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | + AttributeTargets.Property | AttributeTargets.Delegate | + AttributeTargets.Field)] +internal sealed class NotNullAttribute : Attribute { - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | - AttributeTargets.Property | AttributeTargets.Delegate | - AttributeTargets.Field)] - internal sealed class NotNullAttribute : Attribute +} + +[AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | + AttributeTargets.Property | AttributeTargets.Delegate | + AttributeTargets.Field)] +internal sealed class CanBeNullAttribute : Attribute +{ +} + +[AttributeUsage(AttributeTargets.Parameter)] +internal sealed class InvokerParameterNameAttribute : Attribute +{ +} + +[AttributeUsage(AttributeTargets.Parameter)] +internal sealed class NoEnumerationAttribute : Attribute +{ +} + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +internal sealed class ContractAnnotationAttribute : Attribute +{ + public string Contract { get; } + + public bool ForceFullStates { get; } + + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) { } - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | - AttributeTargets.Property | AttributeTargets.Delegate | - AttributeTargets.Field)] - internal sealed class CanBeNullAttribute : Attribute + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) { - } - - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class InvokerParameterNameAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class NoEnumerationAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - internal sealed class ContractAnnotationAttribute : Attribute - { - public string Contract { get; } - - public bool ForceFullStates { get; } - - public ContractAnnotationAttribute([NotNull] string contract) - : this(contract, false) - { - } - - public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) - { - Contract = contract; - ForceFullStates = forceFullStates; - } - } - - [AttributeUsage(AttributeTargets.All)] - internal sealed class UsedImplicitlyAttribute : Attribute - { - public UsedImplicitlyAttribute() - : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) - { - } - - public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) - : this(useKindFlags, ImplicitUseTargetFlags.Default) - { - } - - public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) - : this(ImplicitUseKindFlags.Default, targetFlags) - { - } - - public UsedImplicitlyAttribute( - ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) - { - UseKindFlags = useKindFlags; - TargetFlags = targetFlags; - } - - public ImplicitUseKindFlags UseKindFlags { get; } - public ImplicitUseTargetFlags TargetFlags { get; } - } - - [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Delegate)] - internal sealed class StringFormatMethodAttribute : Attribute - { - public StringFormatMethodAttribute([NotNull] string formatParameterName) - => FormatParameterName = formatParameterName; - - [NotNull] - public string FormatParameterName { get; } - } - - [Flags] - internal enum ImplicitUseKindFlags - { - Default = Access | Assign | InstantiatedWithFixedConstructorSignature, - Access = 1, - Assign = 2, - InstantiatedWithFixedConstructorSignature = 4, - InstantiatedNoFixedConstructorSignature = 8 - } - - [Flags] - internal enum ImplicitUseTargetFlags - { - Default = Itself, - Itself = 1, - Members = 2, - WithMembers = Itself | Members + Contract = contract; + ForceFullStates = forceFullStates; } } + +[AttributeUsage(AttributeTargets.All)] +internal sealed class UsedImplicitlyAttribute : Attribute +{ + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) + { + } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) + { + } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) + { + } + + public UsedImplicitlyAttribute( + ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; } + public ImplicitUseTargetFlags TargetFlags { get; } +} + +[AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Delegate)] +internal sealed class StringFormatMethodAttribute : Attribute +{ + public StringFormatMethodAttribute([NotNull] string formatParameterName) + => FormatParameterName = formatParameterName; + + [NotNull] + public string FormatParameterName { get; } +} + +[Flags] +internal enum ImplicitUseKindFlags +{ + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + Access = 1, + Assign = 2, + InstantiatedWithFixedConstructorSignature = 4, + InstantiatedNoFixedConstructorSignature = 8 +} + +[Flags] +internal enum ImplicitUseTargetFlags +{ + Default = Itself, + Itself = 1, + Members = 2, + WithMembers = Itself | Members +} \ No newline at end of file diff --git a/EFCore.NamingConventions/Internal/CamelCaseNameRewriter.cs b/EFCore.NamingConventions/Internal/CamelCaseNameRewriter.cs index e68b496..83829cc 100644 --- a/EFCore.NamingConventions/Internal/CamelCaseNameRewriter.cs +++ b/EFCore.NamingConventions/Internal/CamelCaseNameRewriter.cs @@ -1,14 +1,13 @@ using System.Globalization; -namespace EFCore.NamingConventions.Internal +namespace EFCore.NamingConventions.Internal; + +public class CamelCaseNameRewriter : INameRewriter { - public class CamelCaseNameRewriter : INameRewriter - { - private readonly CultureInfo _culture; + private readonly CultureInfo _culture; - public CamelCaseNameRewriter(CultureInfo culture) => _culture = culture; + public CamelCaseNameRewriter(CultureInfo culture) => _culture = culture; - public string RewriteName(string name) => - string.IsNullOrEmpty(name) ? name: char.ToLower(name[0], _culture) + name.Substring(1); - } -} + public string RewriteName(string name) => + string.IsNullOrEmpty(name) ? name: char.ToLower(name[0], _culture) + name.Substring(1); +} \ No newline at end of file diff --git a/EFCore.NamingConventions/Internal/INameRewriter.cs b/EFCore.NamingConventions/Internal/INameRewriter.cs index d52a551..f21b7d6 100644 --- a/EFCore.NamingConventions/Internal/INameRewriter.cs +++ b/EFCore.NamingConventions/Internal/INameRewriter.cs @@ -1,7 +1,6 @@ -namespace EFCore.NamingConventions.Internal +namespace EFCore.NamingConventions.Internal; + +public interface INameRewriter { - public interface INameRewriter - { - string RewriteName(string name); - } -} + string RewriteName(string name); +} \ No newline at end of file diff --git a/EFCore.NamingConventions/Internal/LowerCaseNameRewriter.cs b/EFCore.NamingConventions/Internal/LowerCaseNameRewriter.cs index 7a55144..cfe9c4e 100644 --- a/EFCore.NamingConventions/Internal/LowerCaseNameRewriter.cs +++ b/EFCore.NamingConventions/Internal/LowerCaseNameRewriter.cs @@ -1,12 +1,11 @@ using System.Globalization; -namespace EFCore.NamingConventions.Internal -{ - public class LowerCaseNameRewriter : INameRewriter - { - private readonly CultureInfo _culture; +namespace EFCore.NamingConventions.Internal; - public LowerCaseNameRewriter(CultureInfo culture) => _culture = culture; - public string RewriteName(string name) => name.ToLower(_culture); - } -} +public class LowerCaseNameRewriter : INameRewriter +{ + private readonly CultureInfo _culture; + + public LowerCaseNameRewriter(CultureInfo culture) => _culture = culture; + public string RewriteName(string name) => name.ToLower(_culture); +} \ No newline at end of file diff --git a/EFCore.NamingConventions/Internal/NameRewritingConvention.cs b/EFCore.NamingConventions/Internal/NameRewritingConvention.cs index 9f502d3..b35a544 100644 --- a/EFCore.NamingConventions/Internal/NameRewritingConvention.cs +++ b/EFCore.NamingConventions/Internal/NameRewritingConvention.cs @@ -5,278 +5,277 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Conventions; -namespace EFCore.NamingConventions.Internal +namespace EFCore.NamingConventions.Internal; + +public class NameRewritingConvention : + IEntityTypeAddedConvention, + IEntityTypeAnnotationChangedConvention, + IPropertyAddedConvention, + IForeignKeyOwnershipChangedConvention, + IKeyAddedConvention, + IForeignKeyAddedConvention, + IIndexAddedConvention, + IEntityTypeBaseTypeChangedConvention, + IModelFinalizingConvention { - public class NameRewritingConvention : - IEntityTypeAddedConvention, - IEntityTypeAnnotationChangedConvention, - IPropertyAddedConvention, - IForeignKeyOwnershipChangedConvention, - IKeyAddedConvention, - IForeignKeyAddedConvention, - IIndexAddedConvention, - IEntityTypeBaseTypeChangedConvention, - IModelFinalizingConvention + private static readonly StoreObjectType[] _storeObjectTypes + = { StoreObjectType.Table, StoreObjectType.View, StoreObjectType.Function, StoreObjectType.SqlQuery }; + + private readonly INameRewriter _namingNameRewriter; + + public NameRewritingConvention(INameRewriter nameRewriter) + => _namingNameRewriter = nameRewriter; + + public virtual void ProcessEntityTypeAdded( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionContext context) { - private static readonly StoreObjectType[] _storeObjectTypes - = { StoreObjectType.Table, StoreObjectType.View, StoreObjectType.Function, StoreObjectType.SqlQuery }; + var entityType = entityTypeBuilder.Metadata; - private readonly INameRewriter _namingNameRewriter; - - public NameRewritingConvention(INameRewriter nameRewriter) - => _namingNameRewriter = nameRewriter; - - public virtual void ProcessEntityTypeAdded( - IConventionEntityTypeBuilder entityTypeBuilder, - IConventionContext context) + // 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) { - var entityType = entityTypeBuilder.Metadata; + entityTypeBuilder.ToTable(_namingNameRewriter.RewriteName(entityType.GetTableName()), entityType.GetSchema()); - // 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) + if (entityType.GetViewNameConfigurationSource() == ConfigurationSource.Convention) { - entityTypeBuilder.ToTable(_namingNameRewriter.RewriteName(entityType.GetTableName()), entityType.GetSchema()); - - if (entityType.GetViewNameConfigurationSource() == ConfigurationSource.Convention) - { - entityTypeBuilder.ToView(_namingNameRewriter.RewriteName(entityType.GetViewName()), entityType.GetViewSchema()); - } + entityTypeBuilder.ToView(_namingNameRewriter.RewriteName(entityType.GetViewName()), entityType.GetViewSchema()); } } + } - public void ProcessEntityTypeBaseTypeChanged( - IConventionEntityTypeBuilder entityTypeBuilder, - IConventionEntityType newBaseType, - IConventionEntityType oldBaseType, - IConventionContext context) + public void ProcessEntityTypeBaseTypeChanged( + IConventionEntityTypeBuilder entityTypeBuilder, + IConventionEntityType newBaseType, + IConventionEntityType oldBaseType, + IConventionContext context) + { + var entityType = entityTypeBuilder.Metadata; + + if (newBaseType is null) { - var entityType = entityTypeBuilder.Metadata; + // 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); + } + } - if (newBaseType is null) + public virtual void ProcessPropertyAdded( + IConventionPropertyBuilder propertyBuilder, + IConventionContext context) + => RewriteColumnName(propertyBuilder); + + public void ProcessForeignKeyOwnershipChanged(IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) + { + var foreignKey = relationshipBuilder.Metadata; + var ownedEntityType = foreignKey.DeclaringEntityType; + + // An entity type is becoming owned - this is a bit complicated. + // Unless it's a collection navigation, or the owned entity table name was explicitly set by the user, this triggers table + // splitting, which means we need to undo rewriting which we've done previously. + if (foreignKey.IsOwnership + && !foreignKey.GetNavigation(false).IsCollection + && ownedEntityType.GetTableNameConfigurationSource() != ConfigurationSource.Explicit) + { + // 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.Builder.HasNoAnnotation(RelationalAnnotationNames.TableName); + ownedEntityType.Builder.HasNoAnnotation(RelationalAnnotationNames.Schema); + + ownedEntityType.FindPrimaryKey()?.Builder.HasNoAnnotation(RelationalAnnotationNames.Name); + + // 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()) { - // The entity is getting removed from a hierarchy. Set the (rewritten) TableName. - entityTypeBuilder.ToTable(_namingNameRewriter.RewriteName(entityType.GetTableName()), entityType.GetSchema()); + RewriteColumnName(property.Builder); + } + } + } + + public void ProcessEntityTypeAnnotationChanged( + IConventionEntityTypeBuilder entityTypeBuilder, + string name, + IConventionAnnotation annotation, + IConventionAnnotation oldAnnotation, + IConventionContext context) + { + var entityType = entityTypeBuilder.Metadata; + + // If the View/SqlQuery/Function name is being set on the entity type, and its table name is set by convention, then we assume + // we're the one who set the table name back when the entity type was originally added. We now undo this as the entity type + // should only be mapped to the View/SqlQuery/Function. + if (name is RelationalAnnotationNames.ViewName or RelationalAnnotationNames.SqlQuery or RelationalAnnotationNames.FunctionName + && annotation.Value is not null + && entityType.GetTableNameConfigurationSource() == ConfigurationSource.Convention) + { + entityType.SetTableName(null); + } + + if (name != RelationalAnnotationNames.TableName + || StoreObjectIdentifier.Create(entityType, StoreObjectType.Table) is not StoreObjectIdentifier tableIdentifier) + { + return; + } + + // The table's name is changing - rewrite keys, index names + + if (entityType.FindPrimaryKey() is IConventionKey primaryKey) + { + // We need to rewrite the PK name. + // However, this isn't yet supported with TPT, see https://github.com/dotnet/efcore/issues/23444. + // So we need to check if the entity is within a TPT hierarchy, or is an owned entity within a TPT hierarchy. + + var rootType = entityType.GetRootType(); + var isTPT = rootType.GetDerivedTypes().FirstOrDefault() is { } derivedType + && derivedType.GetTableName() != rootType.GetTableName(); + + if (entityType.FindRowInternalForeignKeys(tableIdentifier).FirstOrDefault() is null && !isTPT) + { + primaryKey.Builder.HasName(_namingNameRewriter.RewriteName(primaryKey.GetDefaultName())); } 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) - => RewriteColumnName(propertyBuilder); - - public void ProcessForeignKeyOwnershipChanged(IConventionForeignKeyBuilder relationshipBuilder, IConventionContext context) - { - var foreignKey = relationshipBuilder.Metadata; - var ownedEntityType = foreignKey.DeclaringEntityType; - - // An entity type is becoming owned - this is a bit complicated. - // Unless it's a collection navigation, or the owned entity table name was explicitly set by the user, this triggers table - // splitting, which means we need to undo rewriting which we've done previously. - if (foreignKey.IsOwnership - && !foreignKey.GetNavigation(false).IsCollection - && ownedEntityType.GetTableNameConfigurationSource() != ConfigurationSource.Explicit) - { - // 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.Builder.HasNoAnnotation(RelationalAnnotationNames.TableName); - ownedEntityType.Builder.HasNoAnnotation(RelationalAnnotationNames.Schema); - - ownedEntityType.FindPrimaryKey()?.Builder.HasNoAnnotation(RelationalAnnotationNames.Name); - - // 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()) + // This hierarchy is being transformed into TPT via the explicit setting of the table name. + // We not only have to reset our own key name, but also the parents'. Otherwise, the parent's key name + // is used as the child's (see RelationalKeyExtensions.GetName), and we get a "duplicate key name in database" error + // since both parent and child have the same key name; + foreach (var type in entityType.GetRootType().GetDerivedTypesInclusive()) { - RewriteColumnName(property.Builder); - } - } - } - - public void ProcessEntityTypeAnnotationChanged( - IConventionEntityTypeBuilder entityTypeBuilder, - string name, - IConventionAnnotation annotation, - IConventionAnnotation oldAnnotation, - IConventionContext context) - { - var entityType = entityTypeBuilder.Metadata; - - // If the View/SqlQuery/Function name is being set on the entity type, and its table name is set by convention, then we assume - // we're the one who set the table name back when the entity type was originally added. We now undo this as the entity type - // should only be mapped to the View/SqlQuery/Function. - if (name is RelationalAnnotationNames.ViewName or RelationalAnnotationNames.SqlQuery or RelationalAnnotationNames.FunctionName - && annotation.Value is not null - && entityType.GetTableNameConfigurationSource() == ConfigurationSource.Convention) - { - entityType.SetTableName(null); - } - - if (name != RelationalAnnotationNames.TableName - || StoreObjectIdentifier.Create(entityType, StoreObjectType.Table) is not StoreObjectIdentifier tableIdentifier) - { - return; - } - - // The table's name is changing - rewrite keys, index names - - if (entityType.FindPrimaryKey() is IConventionKey primaryKey) - { - // We need to rewrite the PK name. - // However, this isn't yet supported with TPT, see https://github.com/dotnet/efcore/issues/23444. - // So we need to check if the entity is within a TPT hierarchy, or is an owned entity within a TPT hierarchy. - - var rootType = entityType.GetRootType(); - var isTPT = rootType.GetDerivedTypes().FirstOrDefault() is { } derivedType - && derivedType.GetTableName() != rootType.GetTableName(); - - if (entityType.FindRowInternalForeignKeys(tableIdentifier).FirstOrDefault() is null && !isTPT) - { - primaryKey.Builder.HasName(_namingNameRewriter.RewriteName(primaryKey.GetDefaultName())); - } - else - { - // This hierarchy is being transformed into TPT via the explicit setting of the table name. - // We not only have to reset our own key name, but also the parents'. Otherwise, the parent's key name - // is used as the child's (see RelationalKeyExtensions.GetName), and we get a "duplicate key name in database" error - // since both parent and child have the same key name; - foreach (var type in entityType.GetRootType().GetDerivedTypesInclusive()) + if (type.FindPrimaryKey() is IConventionKey pk) { - if (type.FindPrimaryKey() is IConventionKey pk) - { - pk.Builder.HasNoAnnotation(RelationalAnnotationNames.Name); - } + pk.Builder.HasNoAnnotation(RelationalAnnotationNames.Name); } } } - - foreach (var foreignKey in entityType.GetForeignKeys()) - { - foreignKey.Builder.HasConstraintName(_namingNameRewriter.RewriteName(foreignKey.GetDefaultName())); - } - - foreach (var index in entityType.GetIndexes()) - { - index.Builder.HasDatabaseName(_namingNameRewriter.RewriteName(index.GetDefaultDatabaseName())); - } - - 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 entityType.GetProperties() - .Except(entityType.FindPrimaryKey().Properties) - .Where(p => p.Builder.CanSetColumnName(null))) - { - 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 (entityType.FindPrimaryKey() is IConventionKey key) - { - key.Builder.HasName(_namingNameRewriter.RewriteName(key.GetDefaultName())); - } - } } - public void ProcessForeignKeyAdded( - IConventionForeignKeyBuilder relationshipBuilder, - IConventionContext context) - => relationshipBuilder.HasConstraintName(_namingNameRewriter.RewriteName(relationshipBuilder.Metadata.GetDefaultName())); - - public void ProcessKeyAdded(IConventionKeyBuilder keyBuilder, IConventionContext context) - => keyBuilder.HasName(_namingNameRewriter.RewriteName(keyBuilder.Metadata.GetName())); - - public void ProcessIndexAdded( - IConventionIndexBuilder indexBuilder, - IConventionContext context) - => indexBuilder.HasDatabaseName(_namingNameRewriter.RewriteName(indexBuilder.Metadata.GetDefaultDatabaseName())); - - /// - /// EF Core's runs at model finalization time, and adds entity type prefixes to - /// clashing columns. These prefixes also needs to be rewritten by us, so we run after that convention to do that. - /// - public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext context) + foreach (var foreignKey in entityType.GetForeignKeys()) { - foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + foreignKey.Builder.HasConstraintName(_namingNameRewriter.RewriteName(foreignKey.GetDefaultName())); + } + + foreach (var index in entityType.GetIndexes()) + { + index.Builder.HasDatabaseName(_namingNameRewriter.RewriteName(index.GetDefaultDatabaseName())); + } + + 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 entityType.GetProperties() + .Except(entityType.FindPrimaryKey().Properties) + .Where(p => p.Builder.CanSetColumnName(null))) { - foreach (var property in entityType.GetProperties()) + 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 (entityType.FindPrimaryKey() is IConventionKey key) + { + key.Builder.HasName(_namingNameRewriter.RewriteName(key.GetDefaultName())); + } + } + } + + public void ProcessForeignKeyAdded( + IConventionForeignKeyBuilder relationshipBuilder, + IConventionContext context) + => relationshipBuilder.HasConstraintName(_namingNameRewriter.RewriteName(relationshipBuilder.Metadata.GetDefaultName())); + + public void ProcessKeyAdded(IConventionKeyBuilder keyBuilder, IConventionContext context) + => keyBuilder.HasName(_namingNameRewriter.RewriteName(keyBuilder.Metadata.GetName())); + + public void ProcessIndexAdded( + IConventionIndexBuilder indexBuilder, + IConventionContext context) + => indexBuilder.HasDatabaseName(_namingNameRewriter.RewriteName(indexBuilder.Metadata.GetDefaultDatabaseName())); + + /// + /// EF Core's runs at model finalization time, and adds entity type prefixes to + /// clashing columns. These prefixes also needs to be rewritten by us, so we run after that convention to do that. + /// + public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext context) + { + foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()) + { + foreach (var property in entityType.GetProperties()) + { + var columnName = property.GetColumnBaseName(); + if (columnName.StartsWith(entityType.ShortName() + '_', StringComparison.Ordinal)) { - var columnName = property.GetColumnBaseName(); + property.Builder.HasColumnName( + _namingNameRewriter.RewriteName(entityType.ShortName()) + columnName.Substring(entityType.ShortName().Length)); + } + + foreach (var storeObjectType in _storeObjectTypes) + { + var identifier = StoreObjectIdentifier.Create(entityType, storeObjectType); + if (identifier is null) + { + continue; + } + + if (property.GetColumnNameConfigurationSource(identifier.Value) != ConfigurationSource.Convention) + { + continue; + } + + columnName = property.GetColumnName(identifier.Value); if (columnName.StartsWith(entityType.ShortName() + '_', StringComparison.Ordinal)) { property.Builder.HasColumnName( - _namingNameRewriter.RewriteName(entityType.ShortName()) + columnName.Substring(entityType.ShortName().Length)); + _namingNameRewriter.RewriteName(entityType.ShortName()) + + columnName.Substring(entityType.ShortName().Length)); } - - foreach (var storeObjectType in _storeObjectTypes) - { - var identifier = StoreObjectIdentifier.Create(entityType, storeObjectType); - if (identifier is null) - { - continue; - } - - if (property.GetColumnNameConfigurationSource(identifier.Value) != ConfigurationSource.Convention) - { - continue; - } - - columnName = property.GetColumnName(identifier.Value); - if (columnName.StartsWith(entityType.ShortName() + '_', StringComparison.Ordinal)) - { - property.Builder.HasColumnName( - _namingNameRewriter.RewriteName(entityType.ShortName()) - + columnName.Substring(entityType.ShortName().Length)); - } - } - } - } - } - - 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 - var baseColumnName = StoreObjectIdentifier.Create(property.DeclaringEntityType, StoreObjectType.Table) is { } tableIdentifier - ? property.GetDefaultColumnName(tableIdentifier) - : property.GetDefaultColumnBaseName(); - propertyBuilder.HasColumnName(_namingNameRewriter.RewriteName(baseColumnName)); - - 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); } } } } + + 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 + var baseColumnName = StoreObjectIdentifier.Create(property.DeclaringEntityType, StoreObjectType.Table) is { } tableIdentifier + ? property.GetDefaultColumnName(tableIdentifier) + : property.GetDefaultColumnBaseName(); + propertyBuilder.HasColumnName(_namingNameRewriter.RewriteName(baseColumnName)); + + 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); + } + } + } } diff --git a/EFCore.NamingConventions/Internal/NamingConvention.cs b/EFCore.NamingConventions/Internal/NamingConvention.cs index 305420d..801298a 100644 --- a/EFCore.NamingConventions/Internal/NamingConvention.cs +++ b/EFCore.NamingConventions/Internal/NamingConvention.cs @@ -1,12 +1,11 @@ -namespace EFCore.NamingConventions.Internal +namespace EFCore.NamingConventions.Internal; + +public enum NamingConvention { - public enum NamingConvention - { - None, - SnakeCase, - LowerCase, - CamelCase, - UpperCase, - UpperSnakeCase - } + None, + SnakeCase, + LowerCase, + CamelCase, + UpperCase, + UpperSnakeCase } diff --git a/EFCore.NamingConventions/Internal/NamingConventionSetPlugin.cs b/EFCore.NamingConventions/Internal/NamingConventionSetPlugin.cs index 335ff6f..add3bc3 100644 --- a/EFCore.NamingConventions/Internal/NamingConventionSetPlugin.cs +++ b/EFCore.NamingConventions/Internal/NamingConventionSetPlugin.cs @@ -5,44 +5,43 @@ using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using JetBrains.Annotations; -namespace EFCore.NamingConventions.Internal +namespace EFCore.NamingConventions.Internal; + +public class NamingConventionSetPlugin : IConventionSetPlugin { - public class NamingConventionSetPlugin : IConventionSetPlugin + private readonly IDbContextOptions _options; + public NamingConventionSetPlugin([NotNull] IDbContextOptions options) => _options = options; + + public ConventionSet ModifyConventions(ConventionSet conventionSet) { - private readonly IDbContextOptions _options; - public NamingConventionSetPlugin([NotNull] IDbContextOptions options) => _options = options; - - public ConventionSet ModifyConventions(ConventionSet conventionSet) + var extension = _options.FindExtension(); + var namingStyle = extension.NamingConvention; + var culture = extension.Culture; + if (namingStyle == NamingConvention.None) { - var extension = _options.FindExtension(); - var namingStyle = extension.NamingConvention; - var culture = extension.Culture; - if (namingStyle == NamingConvention.None) - { - return conventionSet; - } - - var convention = new NameRewritingConvention(namingStyle switch - { - NamingConvention.SnakeCase => new SnakeCaseNameRewriter(culture ?? CultureInfo.InvariantCulture), - NamingConvention.LowerCase => new LowerCaseNameRewriter(culture ?? CultureInfo.InvariantCulture), - NamingConvention.CamelCase => new CamelCaseNameRewriter(culture ?? CultureInfo.InvariantCulture), - NamingConvention.UpperCase => new UpperCaseNameRewriter(culture ?? CultureInfo.InvariantCulture), - NamingConvention.UpperSnakeCase => new UpperSnakeCaseNameRewriter(culture ?? CultureInfo.InvariantCulture), - _ => throw new ArgumentOutOfRangeException("Unhandled enum value: " + namingStyle) - }); - - conventionSet.EntityTypeAddedConventions.Add(convention); - conventionSet.EntityTypeAnnotationChangedConventions.Add(convention); - conventionSet.PropertyAddedConventions.Add(convention); - conventionSet.ForeignKeyOwnershipChangedConventions.Add(convention); - conventionSet.KeyAddedConventions.Add(convention); - conventionSet.ForeignKeyAddedConventions.Add(convention); - conventionSet.IndexAddedConventions.Add(convention); - conventionSet.EntityTypeBaseTypeChangedConventions.Add(convention); - conventionSet.ModelFinalizingConventions.Add(convention); - return conventionSet; } + + var convention = new NameRewritingConvention(namingStyle switch + { + NamingConvention.SnakeCase => new SnakeCaseNameRewriter(culture ?? CultureInfo.InvariantCulture), + NamingConvention.LowerCase => new LowerCaseNameRewriter(culture ?? CultureInfo.InvariantCulture), + NamingConvention.CamelCase => new CamelCaseNameRewriter(culture ?? CultureInfo.InvariantCulture), + NamingConvention.UpperCase => new UpperCaseNameRewriter(culture ?? CultureInfo.InvariantCulture), + NamingConvention.UpperSnakeCase => new UpperSnakeCaseNameRewriter(culture ?? CultureInfo.InvariantCulture), + _ => throw new ArgumentOutOfRangeException("Unhandled enum value: " + namingStyle) + }); + + conventionSet.EntityTypeAddedConventions.Add(convention); + conventionSet.EntityTypeAnnotationChangedConventions.Add(convention); + conventionSet.PropertyAddedConventions.Add(convention); + conventionSet.ForeignKeyOwnershipChangedConventions.Add(convention); + conventionSet.KeyAddedConventions.Add(convention); + conventionSet.ForeignKeyAddedConventions.Add(convention); + conventionSet.IndexAddedConventions.Add(convention); + conventionSet.EntityTypeBaseTypeChangedConventions.Add(convention); + conventionSet.ModelFinalizingConventions.Add(convention); + + return conventionSet; } -} +} \ No newline at end of file diff --git a/EFCore.NamingConventions/Internal/NamingConventionsOptionsExtension.cs b/EFCore.NamingConventions/Internal/NamingConventionsOptionsExtension.cs index e95d3d1..3a35c37 100644 --- a/EFCore.NamingConventions/Internal/NamingConventionsOptionsExtension.cs +++ b/EFCore.NamingConventions/Internal/NamingConventionsOptionsExtension.cs @@ -6,144 +6,143 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; using JetBrains.Annotations; -namespace EFCore.NamingConventions.Internal +namespace EFCore.NamingConventions.Internal; + +public class NamingConventionsOptionsExtension : IDbContextOptionsExtension { - public class NamingConventionsOptionsExtension : IDbContextOptionsExtension + private DbContextOptionsExtensionInfo _info; + private NamingConvention _namingConvention; + private CultureInfo _culture; + + public NamingConventionsOptionsExtension() {} + protected NamingConventionsOptionsExtension([NotNull] NamingConventionsOptionsExtension copyFrom) { - private DbContextOptionsExtensionInfo _info; - private NamingConvention _namingConvention; - private CultureInfo _culture; + _namingConvention = copyFrom._namingConvention; + _culture = copyFrom._culture; + } - public NamingConventionsOptionsExtension() {} - protected NamingConventionsOptionsExtension([NotNull] NamingConventionsOptionsExtension copyFrom) + public virtual DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this); + + protected virtual NamingConventionsOptionsExtension Clone() => new(this); + + internal virtual NamingConvention NamingConvention => _namingConvention; + internal virtual CultureInfo Culture => _culture; + + public virtual NamingConventionsOptionsExtension WithoutNaming() + { + var clone = Clone(); + clone._namingConvention = NamingConvention.None; + return clone; + } + + public virtual NamingConventionsOptionsExtension WithSnakeCaseNamingConvention(CultureInfo culture = null) + { + var clone = Clone(); + clone._namingConvention = NamingConvention.SnakeCase; + clone._culture = culture; + return clone; + } + + public virtual NamingConventionsOptionsExtension WithLowerCaseNamingConvention(CultureInfo culture = null) + { + var clone = Clone(); + clone._namingConvention = NamingConvention.LowerCase; + clone._culture = culture; + return clone; + } + + public virtual NamingConventionsOptionsExtension WithUpperCaseNamingConvention(CultureInfo culture = null) + { + var clone = Clone(); + clone._namingConvention = NamingConvention.UpperCase; + clone._culture = culture; + return clone; + } + + public virtual NamingConventionsOptionsExtension WithUpperSnakeCaseNamingConvention(CultureInfo culture = null) + { + var clone = Clone(); + clone._namingConvention = NamingConvention.UpperSnakeCase; + clone._culture = culture; + return clone; + } + + public virtual NamingConventionsOptionsExtension WithCamelCaseNamingConvention(CultureInfo culture = null) + { + var clone = Clone(); + clone._namingConvention = NamingConvention.CamelCase; + clone._culture = culture; + return clone; + } + + public void Validate(IDbContextOptions options) {} + + public void ApplyServices(IServiceCollection services) + => services.AddEntityFrameworkNamingConventions(); + + private sealed class ExtensionInfo : DbContextOptionsExtensionInfo + { + private string _logFragment; + + public ExtensionInfo(IDbContextOptionsExtension extension) : base(extension) {} + + private new NamingConventionsOptionsExtension Extension + => (NamingConventionsOptionsExtension)base.Extension; + + public override bool IsDatabaseProvider => false; + + public override string LogFragment { - _namingConvention = copyFrom._namingConvention; - _culture = copyFrom._culture; - } - - public virtual DbContextOptionsExtensionInfo Info => _info ??= new ExtensionInfo(this); - - protected virtual NamingConventionsOptionsExtension Clone() => new(this); - - internal virtual NamingConvention NamingConvention => _namingConvention; - internal virtual CultureInfo Culture => _culture; - - public virtual NamingConventionsOptionsExtension WithoutNaming() - { - var clone = Clone(); - clone._namingConvention = NamingConvention.None; - return clone; - } - - public virtual NamingConventionsOptionsExtension WithSnakeCaseNamingConvention(CultureInfo culture = null) - { - var clone = Clone(); - clone._namingConvention = NamingConvention.SnakeCase; - clone._culture = culture; - return clone; - } - - public virtual NamingConventionsOptionsExtension WithLowerCaseNamingConvention(CultureInfo culture = null) - { - var clone = Clone(); - clone._namingConvention = NamingConvention.LowerCase; - clone._culture = culture; - return clone; - } - - public virtual NamingConventionsOptionsExtension WithUpperCaseNamingConvention(CultureInfo culture = null) - { - var clone = Clone(); - clone._namingConvention = NamingConvention.UpperCase; - clone._culture = culture; - return clone; - } - - public virtual NamingConventionsOptionsExtension WithUpperSnakeCaseNamingConvention(CultureInfo culture = null) - { - var clone = Clone(); - clone._namingConvention = NamingConvention.UpperSnakeCase; - clone._culture = culture; - return clone; - } - - public virtual NamingConventionsOptionsExtension WithCamelCaseNamingConvention(CultureInfo culture = null) - { - var clone = Clone(); - clone._namingConvention = NamingConvention.CamelCase; - clone._culture = culture; - return clone; - } - - public void Validate(IDbContextOptions options) {} - - public void ApplyServices(IServiceCollection services) - => services.AddEntityFrameworkNamingConventions(); - - private sealed class ExtensionInfo : DbContextOptionsExtensionInfo - { - private string _logFragment; - - public ExtensionInfo(IDbContextOptionsExtension extension) : base(extension) {} - - private new NamingConventionsOptionsExtension Extension - => (NamingConventionsOptionsExtension)base.Extension; - - public override bool IsDatabaseProvider => false; - - public override string LogFragment + get { - get + if (_logFragment == null) { - if (_logFragment == null) + var builder = new StringBuilder(); + + builder.Append(Extension._namingConvention switch { - var builder = new StringBuilder(); + NamingConvention.SnakeCase => "using snake-case naming ", + NamingConvention.LowerCase => "using lower case naming", + NamingConvention.UpperCase => "using upper case naming", + NamingConvention.UpperSnakeCase => "using upper snake-case naming", + NamingConvention.CamelCase => "using camel-case naming", + _ => throw new ArgumentOutOfRangeException("Unhandled enum value: " + Extension._namingConvention) + }); - builder.Append(Extension._namingConvention switch - { - NamingConvention.SnakeCase => "using snake-case naming ", - NamingConvention.LowerCase => "using lower case naming", - NamingConvention.UpperCase => "using upper case naming", - NamingConvention.UpperSnakeCase => "using upper snake-case naming", - NamingConvention.CamelCase => "using camel-case naming", - _ => throw new ArgumentOutOfRangeException("Unhandled enum value: " + Extension._namingConvention) - }); - - if (Extension._culture is null) - { - builder - .Append(" (culture=") - .Append(Extension._culture) - .Append(")"); - } - - _logFragment = builder.ToString(); + if (Extension._culture is null) + { + builder + .Append(" (culture=") + .Append(Extension._culture) + .Append(")"); } - return _logFragment; + _logFragment = builder.ToString(); } + + return _logFragment; } + } - public override int GetServiceProviderHashCode() + public override int GetServiceProviderHashCode() + { + var hashCode = Extension._namingConvention.GetHashCode(); + hashCode = (hashCode * 3) ^ (Extension._culture?.GetHashCode() ?? 0); + return hashCode; + } + + public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) + => other is ExtensionInfo; + + public override void PopulateDebugInfo(IDictionary debugInfo) + { + debugInfo["Naming:UseNamingConvention"] + = Extension._namingConvention.GetHashCode().ToString(CultureInfo.InvariantCulture); + if (Extension._culture != null) { - var hashCode = Extension._namingConvention.GetHashCode(); - hashCode = (hashCode * 3) ^ (Extension._culture?.GetHashCode() ?? 0); - return hashCode; - } - - public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) - => other is ExtensionInfo; - - public override void PopulateDebugInfo(IDictionary debugInfo) - { - debugInfo["Naming:UseNamingConvention"] - = Extension._namingConvention.GetHashCode().ToString(CultureInfo.InvariantCulture); - if (Extension._culture != null) - { - debugInfo["Naming:Culture"] - = Extension._culture.GetHashCode().ToString(CultureInfo.InvariantCulture); - } + debugInfo["Naming:Culture"] + = Extension._culture.GetHashCode().ToString(CultureInfo.InvariantCulture); } } } -} +} \ No newline at end of file diff --git a/EFCore.NamingConventions/Internal/SnakeCaseNameRewriter.cs b/EFCore.NamingConventions/Internal/SnakeCaseNameRewriter.cs index 80228dd..dbb4c7c 100644 --- a/EFCore.NamingConventions/Internal/SnakeCaseNameRewriter.cs +++ b/EFCore.NamingConventions/Internal/SnakeCaseNameRewriter.cs @@ -2,37 +2,37 @@ using System; using System.Globalization; using System.Text; -namespace EFCore.NamingConventions.Internal +namespace EFCore.NamingConventions.Internal; + +public class SnakeCaseNameRewriter : INameRewriter { - public class SnakeCaseNameRewriter : INameRewriter + private readonly CultureInfo _culture; + + public SnakeCaseNameRewriter(CultureInfo culture) => _culture = culture; + + public virtual string RewriteName(string name) { - private readonly CultureInfo _culture; - - public SnakeCaseNameRewriter(CultureInfo culture) => _culture = culture; - - public virtual string RewriteName(string name) + if (string.IsNullOrEmpty(name)) { - if (string.IsNullOrEmpty(name)) + return name; + } + + var builder = new StringBuilder(name.Length + Math.Min(2, name.Length / 5)); + var previousCategory = default(UnicodeCategory?); + + for (var currentIndex = 0; currentIndex < name.Length; currentIndex++) + { + var currentChar = name[currentIndex]; + if (currentChar == '_') { - return name; + builder.Append('_'); + previousCategory = null; + continue; } - var builder = new StringBuilder(name.Length + Math.Min(2, name.Length / 5)); - var previousCategory = default(UnicodeCategory?); - - for (var currentIndex = 0; currentIndex < name.Length; currentIndex++) + var currentCategory = char.GetUnicodeCategory(currentChar); + switch (currentCategory) { - var currentChar = name[currentIndex]; - if (currentChar == '_') - { - builder.Append('_'); - previousCategory = null; - continue; - } - - var currentCategory = char.GetUnicodeCategory(currentChar); - switch (currentCategory) - { case UnicodeCategory.UppercaseLetter: case UnicodeCategory.TitlecaseLetter: if (previousCategory == UnicodeCategory.SpaceSeparator || @@ -63,13 +63,12 @@ namespace EFCore.NamingConventions.Internal previousCategory = UnicodeCategory.SpaceSeparator; } continue; - } - - builder.Append(currentChar); - previousCategory = currentCategory; } - return builder.ToString(); + builder.Append(currentChar); + previousCategory = currentCategory; } + + return builder.ToString(); } -} +} \ No newline at end of file diff --git a/EFCore.NamingConventions/Internal/UpperCaseNameRewriter.cs b/EFCore.NamingConventions/Internal/UpperCaseNameRewriter.cs index b398043..8717592 100644 --- a/EFCore.NamingConventions/Internal/UpperCaseNameRewriter.cs +++ b/EFCore.NamingConventions/Internal/UpperCaseNameRewriter.cs @@ -1,12 +1,11 @@ using System.Globalization; -namespace EFCore.NamingConventions.Internal -{ - public class UpperCaseNameRewriter : INameRewriter - { - private readonly CultureInfo _culture; +namespace EFCore.NamingConventions.Internal; - public UpperCaseNameRewriter(CultureInfo culture) => _culture = culture; - public string RewriteName(string name) => name.ToUpper(_culture); - } -} +public class UpperCaseNameRewriter : INameRewriter +{ + private readonly CultureInfo _culture; + + public UpperCaseNameRewriter(CultureInfo culture) => _culture = culture; + public string RewriteName(string name) => name.ToUpper(_culture); +} \ No newline at end of file diff --git a/EFCore.NamingConventions/Internal/UpperSnakeCaseNameRewriter.cs b/EFCore.NamingConventions/Internal/UpperSnakeCaseNameRewriter.cs index 7b6df2b..65d7e1d 100644 --- a/EFCore.NamingConventions/Internal/UpperSnakeCaseNameRewriter.cs +++ b/EFCore.NamingConventions/Internal/UpperSnakeCaseNameRewriter.cs @@ -1,13 +1,12 @@ using System.Globalization; -namespace EFCore.NamingConventions.Internal +namespace EFCore.NamingConventions.Internal; + +public class UpperSnakeCaseNameRewriter : SnakeCaseNameRewriter { - public class UpperSnakeCaseNameRewriter : SnakeCaseNameRewriter - { - private readonly CultureInfo _culture; + private readonly CultureInfo _culture; - public UpperSnakeCaseNameRewriter(CultureInfo culture) : base(culture) => _culture = culture; + public UpperSnakeCaseNameRewriter(CultureInfo culture) : base(culture) => _culture = culture; - public override string RewriteName(string name) => base.RewriteName(name).ToUpper(_culture); - } -} + public override string RewriteName(string name) => base.RewriteName(name).ToUpper(_culture); +} \ No newline at end of file diff --git a/EFCore.NamingConventions/NamingConventionsExtensions.cs b/EFCore.NamingConventions/NamingConventionsExtensions.cs index fc94176..96d250f 100644 --- a/EFCore.NamingConventions/NamingConventionsExtensions.cs +++ b/EFCore.NamingConventions/NamingConventionsExtensions.cs @@ -4,103 +4,102 @@ using JetBrains.Annotations; using EFCore.NamingConventions.Internal; // ReSharper disable once CheckNamespace -namespace Microsoft.EntityFrameworkCore +namespace Microsoft.EntityFrameworkCore; + +public static class NamingConventionsExtensions { - 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)); + Check.NotNull(optionsBuilder, nameof(optionsBuilder)); - var extension = (optionsBuilder.Options.FindExtension() - ?? new NamingConventionsOptionsExtension()) - .WithSnakeCaseNamingConvention(culture); + var extension = (optionsBuilder.Options.FindExtension() + ?? new NamingConventionsOptionsExtension()) + .WithSnakeCaseNamingConvention(culture); - ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); - return optionsBuilder; - } - - 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) - { - Check.NotNull(optionsBuilder, nameof(optionsBuilder)); - - var extension = (optionsBuilder.Options.FindExtension() - ?? new NamingConventionsOptionsExtension()) - .WithLowerCaseNamingConvention(culture); - - ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); - - return optionsBuilder; - } - - 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) - { - Check.NotNull(optionsBuilder, nameof(optionsBuilder)); - - var extension = (optionsBuilder.Options.FindExtension() - ?? new NamingConventionsOptionsExtension()) - .WithUpperCaseNamingConvention(culture); - - ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); - - return optionsBuilder; - } - - 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) - { - Check.NotNull(optionsBuilder, nameof(optionsBuilder)); - - var extension = (optionsBuilder.Options.FindExtension() - ?? new NamingConventionsOptionsExtension()) - .WithUpperSnakeCaseNamingConvention(culture); - - ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); - - return optionsBuilder; - } - - public static DbContextOptionsBuilder UseUpperSnakeCaseNamingConvention( - [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) - where TContext : DbContext - => (DbContextOptionsBuilder)UseUpperSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture); - - public static DbContextOptionsBuilder UseCamelCaseNamingConvention( - [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) - { - Check.NotNull(optionsBuilder, nameof(optionsBuilder)); - - var extension = (optionsBuilder.Options.FindExtension() - ?? new NamingConventionsOptionsExtension()) - .WithCamelCaseNamingConvention(culture); - - ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); - - return optionsBuilder; - } - - public static DbContextOptionsBuilder UseCamelCaseNamingConvention( - [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) - where TContext : DbContext - => (DbContextOptionsBuilder)UseCamelCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture); + return optionsBuilder; } -} + + 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) + { + Check.NotNull(optionsBuilder, nameof(optionsBuilder)); + + var extension = (optionsBuilder.Options.FindExtension() + ?? new NamingConventionsOptionsExtension()) + .WithLowerCaseNamingConvention(culture); + + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + + return optionsBuilder; + } + + 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) + { + Check.NotNull(optionsBuilder, nameof(optionsBuilder)); + + var extension = (optionsBuilder.Options.FindExtension() + ?? new NamingConventionsOptionsExtension()) + .WithUpperCaseNamingConvention(culture); + + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + + return optionsBuilder; + } + + 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) + { + Check.NotNull(optionsBuilder, nameof(optionsBuilder)); + + var extension = (optionsBuilder.Options.FindExtension() + ?? new NamingConventionsOptionsExtension()) + .WithUpperSnakeCaseNamingConvention(culture); + + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + + return optionsBuilder; + } + + public static DbContextOptionsBuilder UseUpperSnakeCaseNamingConvention( + [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) + where TContext : DbContext + => (DbContextOptionsBuilder)UseUpperSnakeCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture); + + public static DbContextOptionsBuilder UseCamelCaseNamingConvention( + [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) + { + Check.NotNull(optionsBuilder, nameof(optionsBuilder)); + + var extension = (optionsBuilder.Options.FindExtension() + ?? new NamingConventionsOptionsExtension()) + .WithCamelCaseNamingConvention(culture); + + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + + return optionsBuilder; + } + + public static DbContextOptionsBuilder UseCamelCaseNamingConvention( + [NotNull] this DbContextOptionsBuilder optionsBuilder, CultureInfo culture = null) + where TContext : DbContext + => (DbContextOptionsBuilder)UseCamelCaseNamingConvention((DbContextOptionsBuilder)optionsBuilder, culture); +} \ No newline at end of file diff --git a/EFCore.NamingConventions/NamingConventionsServiceCollectionExtensions.cs b/EFCore.NamingConventions/NamingConventionsServiceCollectionExtensions.cs index 9ba170d..e03255c 100644 --- a/EFCore.NamingConventions/NamingConventionsServiceCollectionExtensions.cs +++ b/EFCore.NamingConventions/NamingConventionsServiceCollectionExtensions.cs @@ -5,38 +5,37 @@ using EFCore.NamingConventions.Internal; using JetBrains.Annotations; // ReSharper disable once CheckNamespace -namespace Microsoft.Extensions.DependencyInjection +namespace Microsoft.Extensions.DependencyInjection; + +/// +/// Extension methods for . +/// +public static class NamingConventionsServiceCollectionExtensions { /// - /// Extension methods for . + /// + /// Adds the services required for applying naming conventions in Entity Framework Core. + /// You use this method when using dependency injection in your application, such as with ASP.NET. + /// For more information on setting up dependency injection, see http://go.microsoft.com/fwlink/?LinkId=526890. + /// + /// + /// You only need to use this functionality when you want Entity Framework to resolve the services it uses + /// from an external dependency injection container. If you are not using an external + /// dependency injection container, Entity Framework will take care of creating the services it requires. + /// /// - public static class NamingConventionsServiceCollectionExtensions + /// The to add services to. + /// + /// The same service collection so that multiple calls can be chained. + /// + public static IServiceCollection AddEntityFrameworkNamingConventions( + [NotNull] this IServiceCollection serviceCollection) { - /// - /// - /// Adds the services required for applying naming conventions in Entity Framework Core. - /// You use this method when using dependency injection in your application, such as with ASP.NET. - /// For more information on setting up dependency injection, see http://go.microsoft.com/fwlink/?LinkId=526890. - /// - /// - /// You only need to use this functionality when you want Entity Framework to resolve the services it uses - /// from an external dependency injection container. If you are not using an external - /// dependency injection container, Entity Framework will take care of creating the services it requires. - /// - /// - /// The to add services to. - /// - /// The same service collection so that multiple calls can be chained. - /// - public static IServiceCollection AddEntityFrameworkNamingConventions( - [NotNull] this IServiceCollection serviceCollection) - { - Check.NotNull(serviceCollection, nameof(serviceCollection)); + Check.NotNull(serviceCollection, nameof(serviceCollection)); - new EntityFrameworkServicesBuilder(serviceCollection) - .TryAdd(); + new EntityFrameworkServicesBuilder(serviceCollection) + .TryAdd(); - return serviceCollection; - } + return serviceCollection; } -} +} \ No newline at end of file