From ae6c9b1f2550eecd0789582b25d9db9ecebbc49f Mon Sep 17 00:00:00 2001 From: abigailvp Date: Tue, 12 Aug 2025 14:24:35 +0200 Subject: [PATCH 1/2] feature/tpt adding tests #23 --- .../Schema/Inheritance/TablePerType.cs | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 EntityFramework.Explained/Schema/Inheritance/TablePerType.cs diff --git a/EntityFramework.Explained/Schema/Inheritance/TablePerType.cs b/EntityFramework.Explained/Schema/Inheritance/TablePerType.cs new file mode 100644 index 0000000..74d8dde --- /dev/null +++ b/EntityFramework.Explained/Schema/Inheritance/TablePerType.cs @@ -0,0 +1,120 @@ +using EntityFramework.Explained._Tools.Helpers; +using Microsoft.EntityFrameworkCore; +using QuickPulse.Explains; +using QuickPulse.Explains.Text; + +namespace EntityFramework.Explained.Schema.Conventions; + +[DocFile] +public class TablePerType +{ + public class AnimalServerDbContext : DbContext where T : class + { + public DbSet Animals => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlServer("DoesNotMatter"); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().UseTptMappingStrategy(); + + modelBuilder.Entity() + .ToTable("Dogs"); + + modelBuilder.Entity() + .ToTable("Cats"); + + } + } + + public class AnimalSqliteDbContext : DbContext where T : class + { + public DbSet Animals => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseSqlite("DoesNotMatter"); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().UseTptMappingStrategy(); + + modelBuilder.Entity() + .ToTable("Dogs"); + + modelBuilder.Entity() + .ToTable("Cats"); + } + } + + public class Animal + { + public int Id { get; set; } + public string isPet { get; set; } + } + + public class Cat : Animal + { + public int numberOfMeows { get; set; } + } + + public class Dog : Animal + { + public int numberOfBarks { get; set; } + } + + [Fact] + [DocHeader("Sql Server")] + [DocContent("Creates tables per type classes: Animal, Dog and Cat using UseTptMappingStrategy() and ToTable() method. UseTptMappingStrategy() is necessary because else EF Core thinks you are using TPH. The tabels are one on one related with the property Id.")] + public void SqlServer_Uses_TPT_With_Mapping_strategy() + { + using var context = new AnimalServerDbContext(); + var sql = context.Database.GenerateCreateScript(); + var reader = LinesReader.FromText(sql); + + Assert.Equal("CREATE TABLE [Animals] (", reader.SkipToLineContaining("Animals")); + Assert.Equal(" [Id] int NOT NULL IDENTITY,", reader.NextLine()); + Assert.Equal(" [isPet] nvarchar(max) NOT NULL,", reader.NextLine()); + Assert.Equal(" CONSTRAINT [PK_Animals] PRIMARY KEY ([Id])", reader.NextLine()); + + Assert.Equal("CREATE TABLE [Cats] (", reader.SkipToLineContaining("Cats")); + Assert.Equal(" [Id] int NOT NULL,", reader.NextLine()); + Assert.Equal(" [numberOfMeows] int NOT NULL,", reader.NextLine()); + Assert.Equal(" CONSTRAINT [PK_Cats] PRIMARY KEY ([Id]),", reader.NextLine()); + Assert.Equal(" CONSTRAINT [FK_Cats_Animals_Id] FOREIGN KEY ([Id]) REFERENCES [Animals] ([Id]) ON DELETE CASCADE", reader.NextLine()); + + Assert.Equal("CREATE TABLE [Dogs] (", reader.SkipToLineContaining("Dogs")); + Assert.Equal(" [Id] int NOT NULL,", reader.NextLine()); + Assert.Equal(" [numberOfBarks] int NOT NULL,", reader.NextLine()); + Assert.Equal(" CONSTRAINT [PK_Dogs] PRIMARY KEY ([Id]),", reader.NextLine()); + Assert.Equal(" CONSTRAINT [FK_Dogs_Animals_Id] FOREIGN KEY ([Id]) REFERENCES [Animals] ([Id]) ON DELETE CASCADE", reader.NextLine()); + + } + + [Fact] + [DocHeader("Sqlite")] + [DocContent("Creates tables per type classes: Animal, Dog and Cat using UseTptMappingStrategy() and ToTable() method. UseTptMappingStrategy() is necessary because else EF Core thinks you are using TPH. The tabels are one on one related with the property Id.")] + public void Sqlite_Uses_Tpt_With_Mapping_strategy() + { + using var context = new AnimalSqliteDbContext(); + var sql = context.Database.GenerateCreateScript(); + var reader = LinesReader.FromText(sql); + + Assert.Equal("CREATE TABLE \"Animals\" (", reader.SkipToLineContaining("Animals")); + Assert.Equal(" \"Id\" INTEGER NOT NULL CONSTRAINT \"PK_Animals\" PRIMARY KEY AUTOINCREMENT,", reader.NextLine()); + Assert.Equal(" \"isPet\" TEXT NOT NULL", reader.NextLine()); + + Assert.Equal("CREATE TABLE \"Cats\" (", reader.SkipToLineContaining("Cats")); + Assert.Equal(" \"Id\" INTEGER NOT NULL CONSTRAINT \"PK_Cats\" PRIMARY KEY AUTOINCREMENT,", reader.NextLine()); + Assert.Equal(" \"numberOfMeows\" INTEGER NOT NULL,", reader.NextLine()); + Assert.Equal(" CONSTRAINT \"FK_Cats_Animals_Id\" FOREIGN KEY (\"Id\") REFERENCES \"Animals\" (\"Id\") ON DELETE CASCADE", reader.NextLine()); + + Assert.Equal("CREATE TABLE \"Dogs\" (", reader.SkipToLineContaining("Dogs")); + Assert.Equal(" \"Id\" INTEGER NOT NULL CONSTRAINT \"PK_Dogs\" PRIMARY KEY AUTOINCREMENT,", reader.NextLine()); + Assert.Equal(" \"numberOfBarks\" INTEGER NOT NULL,", reader.NextLine()); + Assert.Equal(" CONSTRAINT \"FK_Dogs_Animals_Id\" FOREIGN KEY (\"Id\") REFERENCES \"Animals\" (\"Id\") ON DELETE CASCADE", reader.NextLine()); + + } +} \ No newline at end of file From 9c4ecee33c1b1751bedb56333f0371efbfcb08aa Mon Sep 17 00:00:00 2001 From: abigailvp Date: Wed, 13 Aug 2025 11:15:13 +0200 Subject: [PATCH 2/2] feat(Schema): improving documentation #23 --- .../Schema/Inheritance/TablePerType.cs | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/EntityFramework.Explained/Schema/Inheritance/TablePerType.cs b/EntityFramework.Explained/Schema/Inheritance/TablePerType.cs index 74d8dde..b8c006f 100644 --- a/EntityFramework.Explained/Schema/Inheritance/TablePerType.cs +++ b/EntityFramework.Explained/Schema/Inheritance/TablePerType.cs @@ -1,13 +1,33 @@ -using EntityFramework.Explained._Tools.Helpers; using Microsoft.EntityFrameworkCore; using QuickPulse.Explains; using QuickPulse.Explains.Text; + namespace EntityFramework.Explained.Schema.Conventions; [DocFile] +[DocFileHeader("Inheritance:`Table per type`")] +[DocContent("**If base class is made and derived classes too, table per type is generated like this:**")] +[DocExample(typeof(AnimalServerDbContext))] public class TablePerType { + public class Animal + { + public int Id { get; set; } + public string isPet { get; set; } + } + + public class Cat : Animal + { + public int numberOfMeows { get; set; } + } + + public class Dog : Animal + { + public int numberOfBarks { get; set; } + } + + [CodeExample] public class AnimalServerDbContext : DbContext where T : class { public DbSet Animals => Set(); @@ -18,7 +38,6 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - modelBuilder.Entity().UseTptMappingStrategy(); modelBuilder.Entity() .ToTable("Dogs"); @@ -39,36 +58,18 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - modelBuilder.Entity().UseTptMappingStrategy(); modelBuilder.Entity() .ToTable("Dogs"); - modelBuilder.Entity() .ToTable("Cats"); } } - public class Animal - { - public int Id { get; set; } - public string isPet { get; set; } - } - - public class Cat : Animal - { - public int numberOfMeows { get; set; } - } - - public class Dog : Animal - { - public int numberOfBarks { get; set; } - } - [Fact] [DocHeader("Sql Server")] - [DocContent("Creates tables per type classes: Animal, Dog and Cat using UseTptMappingStrategy() and ToTable() method. UseTptMappingStrategy() is necessary because else EF Core thinks you are using TPH. The tabels are one on one related with the property Id.")] - public void SqlServer_Uses_TPT_With_Mapping_strategy() + [DocContent("Sql Server creates tables for the base AND derived classes using the ToTable() method. The tables are 1:1 related with each other with the property Id. UseTptMappingStrategy() is recommended by Microsoft but this doesn't work here.")] + public void SqlServer_Creates_TPT() { using var context = new AnimalServerDbContext(); var sql = context.Database.GenerateCreateScript(); @@ -95,8 +96,8 @@ public void SqlServer_Uses_TPT_With_Mapping_strategy() [Fact] [DocHeader("Sqlite")] - [DocContent("Creates tables per type classes: Animal, Dog and Cat using UseTptMappingStrategy() and ToTable() method. UseTptMappingStrategy() is necessary because else EF Core thinks you are using TPH. The tabels are one on one related with the property Id.")] - public void Sqlite_Uses_Tpt_With_Mapping_strategy() + [DocContent("Sqlite creates tables for the base AND derived classes (=tpt) using the ToTable() method. The tables are 1:1 related with each other with the property Id. UseTptMappingStrategy() is not compatible with Sqlite. It will only create one table of the base class.")] + public void Sqlite_Creates_TPT() { using var context = new AnimalSqliteDbContext(); var sql = context.Database.GenerateCreateScript(); @@ -117,4 +118,6 @@ public void Sqlite_Uses_Tpt_With_Mapping_strategy() Assert.Equal(" CONSTRAINT \"FK_Dogs_Animals_Id\" FOREIGN KEY (\"Id\") REFERENCES \"Animals\" (\"Id\") ON DELETE CASCADE", reader.NextLine()); } + + } \ No newline at end of file