From c25a831ec31d76470b4aa87d0de13b28d6453cfa Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Thu, 13 Oct 2022 10:01:06 -0700 Subject: [PATCH] Document TPC Fixes #3764 Fixes #3765 --- .gitignore | 1 + .../core/modeling/generated-properties.md | 2 +- entity-framework/core/modeling/inheritance.md | 264 +++++++++++++++++- .../core/modeling/keyless-entity-types.md | 4 +- entity-framework/core/modeling/keys.md | 2 +- .../core/modeling/owned-entities.md | 2 +- .../core/modeling/relationships.md | 3 + .../core/modeling/table-splitting.md | 14 +- .../performance/modeling-for-performance.md | 25 +- .../core/what-is-new/ef-core-7.0/whatsnew.md | 10 +- samples/core/Benchmarks/Inheritance.cs | 51 +++- .../NewInEFCore7/BlogsContext.cs | 2 +- .../Inheritance/FluentAPI/TPCConfiguration.cs | 54 ++++ 13 files changed, 399 insertions(+), 35 deletions(-) create mode 100644 samples/core/Modeling/Inheritance/FluentAPI/TPCConfiguration.cs diff --git a/.gitignore b/.gitignore index ac30dc6617..19bda18036 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ # SQLite databases *.sqlite +*.db # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/entity-framework/core/modeling/generated-properties.md b/entity-framework/core/modeling/generated-properties.md index 3f670c5100..355d3bae37 100644 --- a/entity-framework/core/modeling/generated-properties.md +++ b/entity-framework/core/modeling/generated-properties.md @@ -39,7 +39,7 @@ The above creates a *virtual* computed column, whose value is computed every tim By convention, non-composite primary keys of type short, int, long, or Guid are set up to have values generated for inserted entities if a value isn't provided by the application. Your database provider typically takes care of the necessary configuration; for example, a numeric primary key in SQL Server is automatically set up to be an IDENTITY column. -For more information, [see the documentation about keys](xref:core/modeling/keys). +For more information, [see the documentation about keys](xref:core/modeling/keys) and [guidance for specific inheritance mapping strategies](xref:core/modeling/inheritance#key-generation). ## Explicitly configuring value generation diff --git a/entity-framework/core/modeling/inheritance.md b/entity-framework/core/modeling/inheritance.md index a48785a853..fd90701ddd 100644 --- a/entity-framework/core/modeling/inheritance.md +++ b/entity-framework/core/modeling/inheritance.md @@ -2,7 +2,7 @@ title: Inheritance - EF Core description: How to configure entity type inheritance using Entity Framework Core author: AndriySvyryd -ms.date: 10/01/2020 +ms.date: 10/10/2022 uid: core/modeling/inheritance --- # Inheritance @@ -61,12 +61,15 @@ By default, when two sibling entity types in the hierarchy have a property with ## Table-per-type configuration > [!NOTE] -> The table-per-type (TPT) feature was introduced in EF Core 5.0. Table-per-concrete-type (TPC) is supported by EF6, but is not yet supported by EF Core. +> The table-per-type (TPT) feature was introduced in EF Core 5.0. In the TPT mapping pattern, all the types are mapped to individual tables. Properties that belong solely to a base type or derived type are stored in a table that maps to that type. Tables that map to derived types also store a foreign key that joins the derived table with the base table. [!code-csharp[Main](../../../samples/core/Modeling/Inheritance/FluentAPI/TPTConfiguration.cs?name=TPTConfiguration)] +> [!TIP] +> Instead of calling `ToTable` on each entity type you can call `modelBuilder.Entity().UseTptMappingStrategy()` on each root entity type and the table names will be generated by EF. + EF will create the following database schema for the model above. ```sql @@ -93,3 +96,260 @@ If you are employing bulk configuration you can retrieve the column name for a s > [!WARNING] > In many cases, TPT shows inferior performance when compared to TPH. [See the performance docs for more information](xref:core/performance/modeling-for-performance#inheritance-mapping). + +> [!CAUTION] +> Columns for a derived type are mapped to different tables, therefore composite FK constraints and indexes that use both the inherited and declared properties cannot be created in the database. + +## Table-per-concrete-type configuration + +> [!NOTE] +> The table-per-concrete-type (TPC) feature was introduced in EF Core 7.0. + +In the TPC mapping pattern, all the types are mapped to individual tables. Each table contains columns for all properties on the corresponding entity type. This addresses some common performance issues with the TPT strategy. + +> [!TIP] +> The EF Team demonstrated and talked in depth about TPC mapping in an episode of the [.NET Data Community Standup](https://aka.ms/efstandups). As with all Community Standup episodes, you can [watch the TPC episode now on YouTube](https://youtu.be/HaL6DKW1mrg). + +[!code-csharp[Main](../../../samples/core/Modeling/Inheritance/FluentAPI/TPCConfiguration.cs?name=TPCConfiguration)] + +> [!TIP] +> Instead of calling `ToTable` on each entity type just calling `modelBuilder.Entity().UseTpcMappingStrategy()` on each root entity type will generate the table names by convention. + +EF will create the following database schema for the model above. + +```sql +CREATE TABLE [Blogs] ( + [BlogId] int NOT NULL DEFAULT (NEXT VALUE FOR [BlogSequence]), + [Url] nvarchar(max) NULL, + CONSTRAINT [PK_Blogs] PRIMARY KEY ([BlogId]) +); + +CREATE TABLE [RssBlogs] ( + [BlogId] int NOT NULL DEFAULT (NEXT VALUE FOR [BlogSequence]), + [Url] nvarchar(max) NULL, + [RssUrl] nvarchar(max) NULL, + CONSTRAINT [PK_RssBlogs] PRIMARY KEY ([BlogId]) +); +``` + +### TPC database schema + +The TPC strategy is similar to the TPT strategy except that a different table is created for every *concrete* type in the hierarchy, but tables are **not** created for *abstract* types - hence the name “table-per-concrete-type”. As with TPT, the table itself indicates the type of the object saved. However, unlike TPT mapping, each table contains columns for every property in the concrete type and its base types. TPC database schemas are denormalized. + +For example, consider mapping this hierarchy: + + +[!code-csharp[AnimalsHierarchy](../../../samples/core/Miscellaneous/NewInEFCore7/TpcInheritanceSample.cs?name=AnimalsHierarchy)] + +When using SQL Server, the tables created for this hierarchy are: + +```sql +CREATE TABLE [Cats] ( + [Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]), + [Name] nvarchar(max) NOT NULL, + [FoodId] uniqueidentifier NULL, + [Vet] nvarchar(max) NULL, + [EducationLevel] nvarchar(max) NOT NULL, + CONSTRAINT [PK_Cats] PRIMARY KEY ([Id])); + +CREATE TABLE [Dogs] ( + [Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]), + [Name] nvarchar(max) NOT NULL, + [FoodId] uniqueidentifier NULL, + [Vet] nvarchar(max) NULL, + [FavoriteToy] nvarchar(max) NOT NULL, + CONSTRAINT [PK_Dogs] PRIMARY KEY ([Id])); + +CREATE TABLE [FarmAnimals] ( + [Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]), + [Name] nvarchar(max) NOT NULL, + [FoodId] uniqueidentifier NULL, + [Value] decimal(18,2) NOT NULL, + [Species] nvarchar(max) NOT NULL, + CONSTRAINT [PK_FarmAnimals] PRIMARY KEY ([Id])); + +CREATE TABLE [Humans] ( + [Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]), + [Name] nvarchar(max) NOT NULL, + [FoodId] uniqueidentifier NULL, + [FavoriteAnimalId] int NULL, + CONSTRAINT [PK_Humans] PRIMARY KEY ([Id])); +``` + +Notice that: + +- There are no tables for the `Animal` or `Pet` types, since these are `abstract` in the object model. Remember that C# does not allow instances of abstract types, and there is therefore no situation where an abstract type instance will be saved to the database. +- The mapping of properties in base types is repeated for each concrete type. For example, every table has a `Name` column, and both Cats and Dogs have a `Vet` column. + +- Saving some data into this database results in the following: + +**Cats table** + +| Id | Name | FoodId | Vet | EducationLevel | +|:----|:-------|:-------------------------------------|:---------------------|:---------------| +| 1 | Alice | 99ca3e98-b26d-4a0c-d4ae-08da7aca624f | Pengelly | MBA | +| 2 | Mac | 99ca3e98-b26d-4a0c-d4ae-08da7aca624f | Pengelly | Preschool | +| 8 | Baxter | 5dc5019e-6f72-454b-d4b0-08da7aca624f | Bothell Pet Hospital | BSc | + +**Dogs table** + +| Id | Name | FoodId | Vet | FavoriteToy | +|:----|:------|:-------------------------------------|:---------|:-------------| +| 3 | Toast | 011aaf6f-d588-4fad-d4ac-08da7aca624f | Pengelly | Mr. Squirrel | + +**FarmAnimals table** + +| Id | Name | FoodId | Value | Species | +|:----|:------|:-------------------------------------|:-------|:-----------------------| +| 4 | Clyde | 1d495075-f527-4498-d4af-08da7aca624f | 100.00 | Equus africanus asinus | + +**Humans table** + +| Id | Name | FoodId | FavoriteAnimalId | +|:----|:-------|:-------------------------------------|:----------------------| +| 5 | Wendy | 5418fd81-7660-432f-d4b1-08da7aca624f | 2 | +| 6 | Arthur | 59b495d4-0414-46bf-d4ad-08da7aca624f | 1 | +| 9 | Katie | null | 8 | + +Notice that unlike with TPT mapping, all the information for a single object is contained in a single table. And, unlike with TPH mapping, there is no combination of column and row in any table where that is never used by the model. We'll see below how these characteristics can be important for queries and storage. + +### Key generation + +The inheritance mapping strategy chosen has consequences for how primary key values are generated and managed. Keys in TPH are easy, since each entity instance is represented by a single row in a single table. Any kind of key value generation can be used, and no additional constraints are needed. + +For the TPT strategy, there is always a row in the table mapped to the base type of the hierarchy. Any kind of key generation can be used on this row, and the keys for other tables are linked to this table using foreign key constraints. + +Things get a bit more complicated for TPC. First, it’s important to understand that EF Core requires that all entities in a hierarchy have a unique key value, even if the entities have different types. For example, using our example model, a Dog cannot have the same Id key value as a Cat. Second, unlike TPT, there is no common table that can act as the single place where key values live and can be generated. This means a simple `Identity` column cannot be used. + +For databases that support sequences, key values can be generated by using a single sequence referenced in the default constraint for each table. This is the strategy used in the TPC tables shown above, where each table has the following: + +```sql +[Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]) +``` + +`AnimalSequence` is a database sequence created by EF Core. This strategy is used by default for TPC hierarchies when using the EF Core database provider for SQL Server. Database providers for other databases that support sequences should have a similar default. Other key generation strategies that use sequences, such as Hi-Lo patterns, may also be used with TPC. + +While standard Identity columns don't work with TPC, it is possible to use Identity columns if each table is configured with an appropriate seed and increment such that the values generated for each table will never conflict. For example: + + +[!code-csharp[UsingIdentity](../../../samples/core/Miscellaneous/NewInEFCore7/TpcInheritanceSample.cs?name=UsingIdentity)] + +> [!IMPORTANT] +> Using this strategy makes it harder to add derived types later as it requires the total number of types in the hierarchy to be known beforehand. + +SQLite does not support sequences or Identity seed/increment, and hence integer key value generation is not supported when using SQLite with the TPC strategy. However, client-side generation or globally unique keys - such as GUIDs - are supported on any database, including SQLite. + +### Foreign key constraints + +The TPC mapping strategy creates a denormalized SQL schema - this is one reason why some database purists are against it. For example, consider the foreign key column `FavoriteAnimalId`. The value in this column must match the primary key value of some animal. This can be enforced in the database with a simple FK constraint when using TPH or TPT. For example: + +```sql +CONSTRAINT [FK_Animals_Animals_FavoriteAnimalId] FOREIGN KEY ([FavoriteAnimalId]) REFERENCES [Animals] ([Id]) +``` + +But when using TPC, the primary key for any given animal is stored in the table corresponding to the concrete type of that animal. For example, a cat's primary key is stored in the `Cats.Id` column, while a dog's primary key is stored in the `Dogs.Id` column, and so on. This means an FK constraint cannot be created for this relationship. + +In practice, this is not a problem as long as the application does not attempt to insert invalid data. For example, if all the data is inserted by EF Core and uses navigations to relate entities, then it is guaranteed that the FK column will contain valid PK values at all times. + +## Summary and guidance + +In summary, TPH is usually fine for most applications, and is a good default for a wide range of scenarios, so don't add the complexity of TPC if you don't need it. Specifically, if your code will mostly query for entities of many types, such as writing queries against the base type, then lean towards TPH over TPC. + +That being said, TPC is also a good mapping strategy to use when your code will mostly query for entities of a single leaf type and your benchmarks show an improvement compared with TPH. + +Use TPT only if constrained to do so by external factors. diff --git a/entity-framework/core/modeling/keyless-entity-types.md b/entity-framework/core/modeling/keyless-entity-types.md index 4b8d84ccfb..3b970512be 100644 --- a/entity-framework/core/modeling/keyless-entity-types.md +++ b/entity-framework/core/modeling/keyless-entity-types.md @@ -2,7 +2,7 @@ title: Keyless Entity Types - EF Core description: How to configure keyless entity types using Entity Framework Core author: AndriySvyryd -ms.date: 11/15/2021 +ms.date: 10/10/2022 uid: core/modeling/keyless-entity-types --- # Keyless Entity Types @@ -42,6 +42,8 @@ However, they are different from regular entity types in that they: - Entities cannot contain navigation properties to keyless entity types. - Need to be configured with a `[Keyless]` data annotation or a `.HasNoKey()` method call. - May be mapped to a _defining query_. A defining query is a query declared in the model that acts as a data source for a keyless entity type. +- Can have a hierarchy, but it must be mapped as TPH. +- Cannot use table splitting or entity splitting. ## Usage scenarios diff --git a/entity-framework/core/modeling/keys.md b/entity-framework/core/modeling/keys.md index da9e88598a..68a4ca81c8 100644 --- a/entity-framework/core/modeling/keys.md +++ b/entity-framework/core/modeling/keys.md @@ -36,7 +36,7 @@ You can also configure multiple properties to be the key of an entity - this is ## Value generation -For non-composite numeric and GUID primary keys, EF Core sets up value generation for you by convention. For example, a numeric primary key in SQL Server is automatically set up to be an IDENTITY column. For more information, see [the documentation on value generation](xref:core/modeling/generated-properties). +For non-composite numeric and GUID primary keys, EF Core sets up value generation for you by convention. For example, a numeric primary key in SQL Server is automatically set up to be an IDENTITY column. For more information, see [the documentation on value generation](xref:core/modeling/generated-properties) and [guidance for specific inheritance mapping strategies](xref:core/modeling/inheritance#key-generation). ## Primary key name diff --git a/entity-framework/core/modeling/owned-entities.md b/entity-framework/core/modeling/owned-entities.md index 5deaf8523a..d44bf8930d 100644 --- a/entity-framework/core/modeling/owned-entities.md +++ b/entity-framework/core/modeling/owned-entities.md @@ -33,7 +33,7 @@ If the `ShippingAddress` property is private in the `Order` type, you can use th The model above is mapped to the following database schema: -![Sceenshot of the database model for entity containing owned reference](_static/owned-entities-ownsone.png) +![Screenshot of the database model for entity containing owned reference](_static/owned-entities-ownsone.png) See the [full sample project](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Modeling/OwnedEntities) for more context. diff --git a/entity-framework/core/modeling/relationships.md b/entity-framework/core/modeling/relationships.md index 0626575d6e..d08114c33d 100644 --- a/entity-framework/core/modeling/relationships.md +++ b/entity-framework/core/modeling/relationships.md @@ -146,6 +146,9 @@ After the navigation property has been created, you may need to further configur [!code-csharp[Main](../../../samples/core/Modeling/Relationships/FluentAPI/NavigationConfiguration.cs?name=NavigationConfiguration&highlight=7-9)] +> [!TIP] +> Non-collection navigations can also be marked as required, see [Required one-to-one dependents](xref:core/modeling/relationships#one-to-one) for more information. + > [!NOTE] > This call cannot be used to create a navigation property. It is only used to configure a navigation property which has been previously created by defining a relationship or from a convention. diff --git a/entity-framework/core/modeling/table-splitting.md b/entity-framework/core/modeling/table-splitting.md index 3f05d1ae11..a4e831289b 100644 --- a/entity-framework/core/modeling/table-splitting.md +++ b/entity-framework/core/modeling/table-splitting.md @@ -2,7 +2,7 @@ title: Table Splitting - EF Core description: How to configure table splitting using Entity Framework Core author: AndriySvyryd -ms.date: 11/15/2021 +ms.date: 10/10/2022 uid: core/modeling/table-splitting --- # Table Splitting @@ -38,6 +38,8 @@ Saving and querying entities using table splitting is done in the same way as ot If all of the columns used by a dependent entity are `NULL` in the database, then no instance for it will be created when queried. This allows modeling an optional dependent entity, where the relationship property on the principal would be null. Note that this would also happen if all of the dependent's properties are optional and set to `null`, which might not be expected. +However, the additional check can impact query performance. In addition, if the dependent entity type has dependents of its own, then determining whether an instance should be created becomes non-trivial. To avoid these issues the dependent entity type can be marked as required, see [Required one-to-one dependents](xref:core/modeling/relationships#one-to-one) for more information. + ## Concurrency tokens If any of the entity types sharing a table has a concurrency token then it must be included in all other entity types as well. This is necessary in order to avoid a stale concurrency token value when only one of the entities mapped to the same table is updated. @@ -45,3 +47,13 @@ If any of the entity types sharing a table has a concurrency token then it must To avoid exposing the concurrency token to the consuming code, it's possible the create one as a [shadow property](xref:core/modeling/shadow-properties): [!code-csharp[TableSplittingConfiguration](../../../samples/core/Modeling/TableSplitting/TableSplittingContext.cs?name=ConcurrencyToken&highlight=2)] + +## Inheritance + +It's recommended to read [the dedicated page on inheritance](xref:core/modeling/inheritance) before continuing with this section. + +The dependent types using table splitting can have an inheritance hierarchy, but there are some limitations: + +- The dependent entity type __cannot__ use TPC mapping as the derived types wouldn't be able to map to the same table. +- The dependent entity type __can__ use TPT mapping, but only the root entity type can use table splitting. +- If the principal entity type uses TPC, then only the entity types that don't have any descendants can use table splitting. Otherwise, the dependent columns would need to be duplicated on the tables corresponding to the derived types, complicating all interactions. diff --git a/entity-framework/core/performance/modeling-for-performance.md b/entity-framework/core/performance/modeling-for-performance.md index e77c130f1c..910d273365 100644 --- a/entity-framework/core/performance/modeling-for-performance.md +++ b/entity-framework/core/performance/modeling-for-performance.md @@ -2,7 +2,7 @@ title: Modeling for Performance - EF Core description: Modeling efficiently when using Entity Framework Core author: roji -ms.date: 12/1/2020 +ms.date: 10/10/2022 uid: core/performance/modeling-for-performance --- # Modeling for Performance @@ -39,22 +39,29 @@ EF doesn't currently provide any specific API for creating or maintaining views, It's recommended to read [the dedicated page on inheritance](xref:core/modeling/inheritance) before continuing with this section. -EF Core currently supports two techniques for mapping an inheritance model to a relational database: +EF Core currently supports three techniques for mapping an inheritance model to a relational database: -* **Table-per-hierarchy** (TPH), in which an entire .NET hierarchy of classes is mapped to a single database table +* **Table-per-hierarchy** (TPH), in which an entire .NET hierarchy of classes is mapped to a single database table. * **Table-per-type** (TPT), in which each type in the .NET hierarchy is mapped to a different table in the database. +* **Table-per-concrete-type** (TPC), in which each concrete type in the .NET hierarchy is mapped to a different table in the database, where each table contains columns for all properties of the corresponding type. The choice of inheritance mapping technique can have a considerable impact on application performance - it's recommended to carefully measure before committing to a choice. -People sometimes choose TPT because it appears to be the "cleaner" technique; a separate table for each .NET type makes the database schema look similar to the .NET type hierarchy. In addition, since TPH must represent the entire hierarchy in a single table, rows have *all* columns regardless of the type actually being held in the row, and unrelated columns are always empty and unused. Aside from seeming to be an "unclean" mapping technique, many believe that these empty columns take up considerable space in the database and may hurt performance as well. +Intuitively, TPT might seem like the "cleaner" technique; a separate table for each .NET type makes the database schema look similar to the .NET type hierarchy. In addition, since TPH must represent the entire hierarchy in a single table, rows have *all* columns regardless of the type actually being held in the row, and unrelated columns are always empty and unused. Aside from seeming to be an "unclean" mapping technique, many believe that these empty columns take up considerable space in the database and may hurt performance as well. + +> [!TIP] +> If your database system supports it (e.g. SQL Server), then consider using "sparse columns" for TPH columns that will be rarely populated. However, measuring shows that TPT is in most cases the inferior mapping technique from a performance standpoint; where all data in TPH comes from a single table, TPT queries must join together multiple tables, and joins are one of the primary sources of performance issues in relational databases. Databases also generally tend to deal well with empty columns, and features such as [SQL Server sparse columns](/sql/relational-databases/tables/use-sparse-columns) can reduce this overhead even further. +TPC has similar performance characteristics to TPH, but is slightly slower when selecting entities of all types as this involves several tables. However, TPC really excels when querying for entities of a single leaf type - the query only uses a single table and needs no filtering. + For a concrete example, [see this benchmark](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Benchmarks/Inheritance.cs) which sets up a simple model with a 7-type hierarchy; 5000 rows are seeded for each type - totalling 35000 rows - and the benchmark simply loads all rows from the database: -| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | -|------- |---------:|--------:|--------:|----------:|----------:|----------:|----------:| -| TPH | 132.3 ms | 2.29 ms | 2.03 ms | 8000.0000 | 3000.0000 | 1250.0000 | 44.49 MB | -| TPT | 201.3 ms | 3.32 ms | 3.10 ms | 9000.0000 | 4000.0000 | - | 61.84 MB | +| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Allocated | +|------- |---------:|--------:|---------:|----------:|----------:|----------:| +| TPH | 149.0 ms | 3.38 ms | 9.80 ms | 4000.0000 | 1000.0000 | 40 MB | +| TPT | 312.9 ms | 6.17 ms | 10.81 ms | 9000.0000 | 3000.0000 | 75 MB | +| TPC | 158.2 ms | 3.24 ms | 8.88 ms | 5000.0000 | 2000.0000 | 46 MB | -As can be seen, TPH is considerably more efficient than TPT for this scenario. Note that actual results always depend on the specific query being executed and the number of tables in the hierarchy, so other queries may show a different performance gap; you're encouraged to use this benchmark code as a template for testing other queries. +As can be seen, TPH and TPC are considerably more efficient than TPT for this scenario. Note that actual results always depend on the specific query being executed and the number of tables in the hierarchy, so other queries may show a different performance gap; you're encouraged to use this benchmark code as a template for testing other queries. diff --git a/entity-framework/core/what-is-new/ef-core-7.0/whatsnew.md b/entity-framework/core/what-is-new/ef-core-7.0/whatsnew.md index 3124dc7fbb..b33c794a2e 100644 --- a/entity-framework/core/what-is-new/ef-core-7.0/whatsnew.md +++ b/entity-framework/core/what-is-new/ef-core-7.0/whatsnew.md @@ -1190,11 +1190,11 @@ Notice that: **Humans table** -| Id | Name | FoodId | FavoriteAnimalId | -|:----|:-------|:-------------------------------------|:---------------------------------------------------------------------------| -| 5 | Wendy | 5418fd81-7660-432f-d4b1-08da7aca624f | -2147482646 // See [#28654](https://github.com/dotnet/efcore/issues/28654) | -| 6 | Arthur | 59b495d4-0414-46bf-d4ad-08da7aca624f | -2147482644 // See [#28654](https://github.com/dotnet/efcore/issues/28654) | -| 9 | Katie | null | -2147482640 // See [#28654](https://github.com/dotnet/efcore/issues/28654) | +| Id | Name | FoodId | FavoriteAnimalId | +|:----|:-------|:-------------------------------------|:----------------------| +| 5 | Wendy | 5418fd81-7660-432f-d4b1-08da7aca624f | 2 | +| 6 | Arthur | 59b495d4-0414-46bf-d4ad-08da7aca624f | 1 | +| 9 | Katie | null | 8 | Notice that, unlike with TPT mapping, all the information for a single object is contained in a single table. And, unlike with TPH mapping, there is no combination of column and row in any table where that is never used by the model. We'll see below how these characteristics can be important for queries and storage. diff --git a/samples/core/Benchmarks/Inheritance.cs b/samples/core/Benchmarks/Inheritance.cs index 5409c39167..dbc45195b6 100644 --- a/samples/core/Benchmarks/Inheritance.cs +++ b/samples/core/Benchmarks/Inheritance.cs @@ -32,6 +32,17 @@ public void SetupTPT() Console.WriteLine("Setup complete."); } + [GlobalSetup(Target = nameof(TPC))] + public void SetupTPC() + { + Console.WriteLine("Setting up database..."); + using var context = new TPCContext(); + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + context.SeedData(RowsPerEntityType); + Console.WriteLine("Setup complete."); + } + [Benchmark] public List TPH() { @@ -48,6 +59,14 @@ public List TPT() return context.Roots.ToList(); } + [Benchmark] + public List TPC() + { + using var context = new TPCContext(); + + return context.Roots.ToList(); + } + public abstract class InheritanceContext : DbContext { public DbSet Roots { get; set; } @@ -55,6 +74,16 @@ public abstract class InheritanceContext : DbContext protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True"); + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + } + public void SeedData(int rowsPerEntityType) { Set().AddRange(Enumerable.Range(0, rowsPerEntityType).Select(i => new Root { RootProperty = i })); @@ -69,28 +98,24 @@ public void SeedData(int rowsPerEntityType) } public class TPHContext : InheritanceContext + { + } + + public class TPTContext : InheritanceContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity(); - modelBuilder.Entity(); - modelBuilder.Entity(); - modelBuilder.Entity(); - modelBuilder.Entity(); - modelBuilder.Entity(); + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().UseTptMappingStrategy(); } } - public class TPTContext : InheritanceContext + public class TPCContext : InheritanceContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().ToTable("Child1"); - modelBuilder.Entity().ToTable("Child1A"); - modelBuilder.Entity().ToTable("Child1B"); - modelBuilder.Entity().ToTable("Child2"); - modelBuilder.Entity().ToTable("Child2A"); - modelBuilder.Entity().ToTable("Child2B"); + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().UseTpcMappingStrategy(); } } diff --git a/samples/core/Miscellaneous/NewInEFCore7/BlogsContext.cs b/samples/core/Miscellaneous/NewInEFCore7/BlogsContext.cs index e2cd029496..a8baa0c521 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/BlogsContext.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/BlogsContext.cs @@ -183,7 +183,7 @@ protected BlogsContext(bool useSqlite = false) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => (UseSqlite - ? optionsBuilder.UseSqlite(@$"DataSource={GetType().Name}") + ? optionsBuilder.UseSqlite(@$"DataSource={GetType().Name}.db") : optionsBuilder.UseSqlServer( @$"Server=(localdb)\mssqllocaldb;Database={GetType().Name}", sqlServerOptionsBuilder => sqlServerOptionsBuilder.UseNetTopologySuite())) diff --git a/samples/core/Modeling/Inheritance/FluentAPI/TPCConfiguration.cs b/samples/core/Modeling/Inheritance/FluentAPI/TPCConfiguration.cs new file mode 100644 index 0000000000..e14a4a60f0 --- /dev/null +++ b/samples/core/Modeling/Inheritance/FluentAPI/TPCConfiguration.cs @@ -0,0 +1,54 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace EFModeling.Inheritance.FluentAPI.TPCConfiguration; + +public class MyContext : DbContext +{ + public MyContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Blogs { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + #region TPCConfiguration + modelBuilder.Entity().UseTpcMappingStrategy() + .ToTable("Blogs"); + modelBuilder.Entity() + .ToTable("RssBlogs"); + #endregion + + #region Metadata + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { + var tableIdentifier = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table); + + Console.WriteLine($"{entityType.DisplayName()}\t\t{tableIdentifier}"); + Console.WriteLine(" Property\tColumn"); + + foreach (var property in entityType.GetProperties()) + { + var columnName = property.GetColumnName(tableIdentifier.Value); + Console.WriteLine($" {property.Name,-10}\t{columnName}"); + } + + Console.WriteLine(); + } + #endregion + } +} + +public class Blog +{ + public int BlogId { get; set; } + public string Url { get; set; } +} + +public class RssBlog : Blog +{ + public string RssUrl { get; set; } +} \ No newline at end of file