From de1a6180ce772f0bb5a5ff2866fe0aec1c78c62c Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 8 Nov 2022 11:11:58 -0800 Subject: [PATCH] Document entity splitting Fixes #3848 --- .../core/modeling/table-splitting.md | 138 ++++++++++++++++-- .../core/what-is-new/ef-core-7.0/whatsnew.md | 2 +- entity-framework/toc.yml | 2 +- .../NewInEFCore7/ModelBuildingSample.cs | 10 +- 4 files changed, 140 insertions(+), 12 deletions(-) diff --git a/entity-framework/core/modeling/table-splitting.md b/entity-framework/core/modeling/table-splitting.md index a4e831289b..4909f100c1 100644 --- a/entity-framework/core/modeling/table-splitting.md +++ b/entity-framework/core/modeling/table-splitting.md @@ -1,15 +1,22 @@ --- -title: Table Splitting - EF Core -description: How to configure table splitting using Entity Framework Core +title: Advanced table mapping - EF Core +description: How to configure table splitting and entity splitting using Entity Framework Core. author: AndriySvyryd ms.date: 10/10/2022 uid: core/modeling/table-splitting --- -# Table Splitting + +# Advanced table mapping + +EF Core offers a lot of flexibility when it comes to mapping entity types to tables in a database. This becomes even more useful when you need to use a database that wasn't created by EF. + +The below techniques are described in terms of tables, but the same result can be achieved when mapping to views as well. + +## Table splitting EF Core allows to map two or more entities to a single row. This is called _table splitting_ or _table sharing_. -## Configuration +### Configuration To use table splitting the entity types need to be mapped to the same table, have the primary keys mapped to the same columns and at least one relationship configured between the primary key of one entity type and another in the same table. @@ -28,19 +35,19 @@ In addition to the required configuration we call `Property(o => o.Status).HasCo > [!TIP] > See the [full sample project](https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Modeling/TableSplitting) for more context. -## Usage +### Usage Saving and querying entities using table splitting is done in the same way as other entities: [!code-csharp[Usage](../../../samples/core/Modeling/TableSplitting/Program.cs?name=Usage)] -## Optional dependent entity +### Optional dependent entity 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 +### 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. @@ -48,7 +55,7 @@ To avoid exposing the concurrency token to the consuming code, it's possible the [!code-csharp[TableSplittingConfiguration](../../../samples/core/Modeling/TableSplitting/TableSplittingContext.cs?name=ConcurrencyToken&highlight=2)] -## Inheritance +### Inheritance It's recommended to read [the dedicated page on inheritance](xref:core/modeling/inheritance) before continuing with this section. @@ -56,4 +63,117 @@ The dependent types using table splitting can have an inheritance hierarchy, but - 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. +- 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. + +## Entity splitting + +EF Core allows to map an entity to rows in two or more tables. This is called _entity splitting_. + +### Configuration + +For example, consider a database with three tables that hold customer data: + +- A `Customers` table for customer information +- A `PhoneNumbers` table for the customer's phone number +- An `Addresses` table for the customer's address + +Here are definitions for these tables in SQL Server: + +```sql +CREATE TABLE [Customers] ( + [Id] int NOT NULL IDENTITY, + [Name] nvarchar(max) NOT NULL, + CONSTRAINT [PK_Customers] PRIMARY KEY ([Id]) +); + +CREATE TABLE [PhoneNumbers] ( + [CustomerId] int NOT NULL, + [PhoneNumber] nvarchar(max) NULL, + CONSTRAINT [PK_PhoneNumbers] PRIMARY KEY ([CustomerId]), + CONSTRAINT [FK_PhoneNumbers_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE +); + +CREATE TABLE [Addresses] ( + [CustomerId] int NOT NULL, + [Street] nvarchar(max) NOT NULL, + [City] nvarchar(max) NOT NULL, + [PostCode] nvarchar(max) NULL, + [Country] nvarchar(max) NOT NULL, + CONSTRAINT [PK_Addresses] PRIMARY KEY ([CustomerId]), + CONSTRAINT [FK_Addresses_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE +); +``` + +Each of these tables would typically be mapped to their own entity type, with relationships between the types. However, if all three tables are always used together, then it can be more convenient to map them all to a single entity type. For example: + + +[!code-csharp[CombinedCustomer](../../../samples/core/Miscellaneous/NewInEFCore7/ModelBuildingSample.cs?name=CombinedCustomer)] + +This is achieved in EF7 by calling `SplitToTable` for each split in the entity type. For example, the following code splits the `Customer` entity type to the `Customers`, `PhoneNumbers`, and `Addresses` tables shown above: + + +[!code-csharp[EntitySplitting](../../../samples/core/Miscellaneous/NewInEFCore7/ModelBuildingSample.cs?name=EntitySplitting)] + +Notice also that, if necessary, different column names can be specified for each of the tables. + +### Configuring the linking foreign key + +The FK linking the mapped tables is targeting the same properties on which it is declared. Normally it wouldn't be created in the database, as it would be redundant. But there's an exception for when the entity type is mapped to more than one table. To change its facets you can use the normal [relationship Fluent API](xref:core/modeling/relationships#foreign-key): + + +[!code-csharp[LinkingForeignKey](../../../samples/core/Miscellaneous/NewInEFCore7/ModelBuildingSample.cs?name=LinkingForeignKey)] + +### Limitations + +- Entity splitting can't be used for entity types in hierarchies. +- For any row in the main table there must be a row in each of the split tables (the fragments are not optional). 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 e1e15c7ae6..c19908a152 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 @@ -3979,7 +3979,7 @@ This is achieved in EF7 by calling `SplitToTable` for each split in the entity t }); }); --> -[!code-csharp[TableSplitting](../../../../samples/core/Miscellaneous/NewInEFCore7/ModelBuildingSample.cs?name=TableSplitting)] +[!code-csharp[EntitySplitting](../../../../samples/core/Miscellaneous/NewInEFCore7/ModelBuildingSample.cs?name=EntitySplitting)] Notice also that, if necessary, different primary key column names can be specified for each of the tables. diff --git a/entity-framework/toc.yml b/entity-framework/toc.yml index ca8e92acb7..63715927e0 100644 --- a/entity-framework/toc.yml +++ b/entity-framework/toc.yml @@ -152,7 +152,7 @@ href: core/modeling/data-seeding.md - name: Entity type constructors href: core/modeling/constructors.md - - name: Table splitting + - name: Advanced table mapping href: core/modeling/table-splitting.md - name: Owned entity types href: core/modeling/owned-entities.md diff --git a/samples/core/Miscellaneous/NewInEFCore7/ModelBuildingSample.cs b/samples/core/Miscellaneous/NewInEFCore7/ModelBuildingSample.cs index ece11c6866..c285e06ed1 100644 --- a/samples/core/Miscellaneous/NewInEFCore7/ModelBuildingSample.cs +++ b/samples/core/Miscellaneous/NewInEFCore7/ModelBuildingSample.cs @@ -493,7 +493,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder) { - #region TableSplitting + #region EntitySplitting modelBuilder.Entity( entityBuilder => { @@ -518,6 +518,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) }); }); #endregion + + #region LinkingForeignKey + modelBuilder.Entity() + .HasOne() + .WithOne() + .HasForeignKey(a => a.Id) + .OnDelete(DeleteBehavior.Restrict); + #endregion #region OwnedTemporalTable modelBuilder