Skip to content

Commit

Permalink
Add documentation for required dependents
Browse files Browse the repository at this point in the history
Fixes #2559
Fixes #2320
  • Loading branch information
AndriySvyryd committed Oct 1, 2020
1 parent 97f1a4f commit 84c9763
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 19 deletions.
28 changes: 12 additions & 16 deletions entity-framework/core/modeling/owned-entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ If the `ShippingAddress` property is private in the `Order` type, you can use th

See the [full sample project](https://github.com/dotnet/EntityFramework.Docs/tree/master/samples/core/Modeling/OwnedEntities) for more context.

> [!TIP]
> The owned entity type can be marked as required, see [Required one-to-one dependents](xref:core/modeling/relationships#one-to-one) for more information.
## Implicit keys

Owned types configured with `OwnsOne` or discovered through a reference navigation always have a one-to-one relationship with the owner, therefore they don't need their own key values as the foreign key values are unique. In the previous example, the `StreetAddress` type does not need to define a key property.
Expand All @@ -42,9 +45,6 @@ In order to understand how EF Core tracks these objects, it is useful to know th

## Collections of owned types

> [!NOTE]
> This feature is new in EF Core 2.2.
To configure a collection of owned types use `OwnsMany` in `OnModelCreating`.

Owned types need a primary key. If there are no good candidates properties on the .NET type, EF Core can try to create one. However, when owned types are defined through a collection, it isn't enough to just create a shadow property to act as both the foreign key into the owner and the primary key of the owned instance, as we do for `OwnsOne`: there can be multiple owned type instances for each owner, and hence the key of the owner isn't enough to provide a unique identity for each owned instance.
Expand All @@ -54,26 +54,23 @@ The two most straightforward solutions to this are:
- Defining a surrogate primary key on a new property independent of the foreign key that points to the owner. The contained values would need to be unique across all owners (e.g. if Parent {1} has Child {1}, then Parent {2} cannot have Child {1}), so the value doesn't have any inherent meaning. Since the foreign key is not part of the primary key its values can be changed, so you could move a child from one parent to another one, however this usually goes against aggregate semantics.
- Using the foreign key and an additional property as a composite key. The additional property value now only needs to be unique for a given parent (so if Parent {1} has Child {1,1} then Parent {2} can still have Child {2,1}). By making the foreign key part of the primary key the relationship between the owner and the owned entity becomes immutable and reflects aggregate semantics better. This is what EF Core does by default.

In this example we'll use the `Distributor` class:
In this example we'll use the `Distributor` class.

[!code-csharp[Distributor](../../../samples/core/Modeling/OwnedEntities/Distributor.cs?name=Distributor)]

By default the primary key used for the owned type referenced through the `ShippingCenters` navigation property will be `("DistributorId", "Id")` where `"DistributorId"` is the FK and `"Id"` is a unique `int` value.

To configure a different PK call `HasKey`:
To configure a different primary key call `HasKey`.

[!code-csharp[OwnsMany](../../../samples/core/Modeling/OwnedEntities/OwnedEntityContext.cs?name=OwnsMany)]

> [!NOTE]
> Before EF Core 3.0 `WithOwner()` method didn't exist so this call should be removed. Also the primary key was not discovered automatically so it always had to be specified.
## Mapping owned types with table splitting

When using relational databases, by default reference owned types are mapped to the same table as the owner. This requires splitting the table in two: some columns will be used to store the data of the owner, and some columns will be used to store data of the owned entity. This is a common feature known as [table splitting](xref:core/modeling/table-splitting).

By default, EF Core will name the database columns for the properties of the owned entity type following the pattern _Navigation_OwnedEntityProperty_. Therefore the `StreetAddress` properties will appear in the 'Orders' table with the names 'ShippingAddress_Street' and 'ShippingAddress_City'.

You can use the `HasColumnName` method to rename those columns:
You can use the `HasColumnName` method to rename those columns.

[!code-csharp[ColumnNames](../../../samples/core/Modeling/OwnedEntities/OwnedEntityContext.cs?name=ColumnNames)]

Expand All @@ -86,7 +83,7 @@ An owned entity type can be of the same .NET type as another owned entity type,

In those cases, the property pointing from the owner to the owned entity becomes the _defining navigation_ of the owned entity type. From the perspective of EF Core, the defining navigation is part of the type's identity alongside the .NET type.

For example, in the following class `ShippingAddress` and `BillingAddress` are both of the same .NET type, `StreetAddress`:
For example, in the following class `ShippingAddress` and `BillingAddress` are both of the same .NET type, `StreetAddress`.

[!code-csharp[OrderDetails](../../../samples/core/Modeling/OwnedEntities/OrderDetails.cs?name=OrderDetails)]

Expand Down Expand Up @@ -138,16 +135,15 @@ Some of these limitations are fundamental to how owned entity types work, but so

### By-design restrictions

- You cannot create a `DbSet<T>` for an owned type
- You cannot call `Entity<T>()` with an owned type on `ModelBuilder`
- You cannot create a `DbSet<T>` for an owned type.
- You cannot call `Entity<T>()` with an owned type on `ModelBuilder`.
- Instances of owned entity types cannot be shared by multiple owners (this is a well-known scenario for value objects that cannot be implemented using owned entity types).

### Current shortcomings

- Owned entity types cannot have inheritance hierarchies
- Reference navigations to owned entity types cannot be null unless they are explicitly mapped to a separate table from the owner
- Instances of owned entity types cannot be shared by multiple owners (this is a well-known scenario for value objects that cannot be implemented using owned entity types)

### Shortcomings in previous versions

- In EF Core 2.0, navigations to owned entity types cannot be declared in derived entity types unless the owned entities are explicitly mapped to a separate table from the owner hierarchy. This limitation has been removed in EF Core 2.1
- In EF Core 2.0 and 2.1 only reference navigations to owned types were supported. This limitation has been removed in EF Core 2.2
- In EF Core 2.x reference navigations to owned entity types cannot be null unless they are explicitly mapped to a separate table from the owner.
- In EF Core 3.x the columns for owned entity types mapped to the same table as the owner are always marked as nullable.
19 changes: 18 additions & 1 deletion entity-framework/core/modeling/relationships.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@ If you only have one navigation property then there are parameterless overloads

### Configuring navigation properties

After the navigation property has been created, you may need to further configure it. In EFCore 5.0, new Fluent API is added to allow you to perform that configuration.
> [!NOTE]
> This feature was added in EF Core 5.0.
After the navigation property has been created, you may need to further configure it.

[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/NavigationConfiguration.cs?name=NavigationConfiguration&highlight=7-9)]

Expand Down Expand Up @@ -218,6 +221,8 @@ If you want the foreign key to reference a property other than the primary key,

You can use the Fluent API to configure whether the relationship is required or optional. Ultimately this controls whether the foreign key property is required or optional. This is most useful when you are using a shadow state foreign key. If you have a foreign key property in your entity class then the requiredness of the relationship is determined based on whether the foreign key property is required or optional (see [Required and Optional properties](xref:core/modeling/entity-properties#required-and-optional-properties) for more information).

The foreign key properties are located on the dependent entity type, so if they are configured as required it means that every dependent entity is required to have a corresponding principal entity.

[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/Required.cs?name=Required&highlight=6)]

> [!NOTE]
Expand Down Expand Up @@ -248,6 +253,18 @@ When configuring the foreign key you need to specify the dependent entity type -

[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/OneToOne.cs?name=OneToOne&highlight=11)]

The dependent side is considered optional by default, but can be configured as required. However EF will not validate whether a dependent entity was provided, so this configuration will only make a difference when the database mapping allows it to be enforced. A common scenario for this are reference owned types that use table splitting by default.

[!code-csharp[Main](../../../samples/core/Modeling/OwnedEntities/OwnedEntityContext.cs?name=Required&highlight=11-12)]

With this configuration the columns corresponding to `ShippingAddress` will be marked as non-nullable in the database.

> [!NOTE]
> If you are using [non-nullable reference types](/dotnet/csharp/nullable-references) calling `IsRequired` is not necessary.
> [!NOTE]
> The ability to configure whether the dependent is required was added in EF Core 5.0.
### Many-to-many

Many-to-many relationships without an entity class to represent the join table are not yet supported. However, you can represent a many-to-many relationship by including an entity class for the join table and mapping two separate one-to-many relationships.
Expand Down
4 changes: 2 additions & 2 deletions samples/core/Modeling/OwnedEntities/OwnedEntities.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.0-preview.5.20278.2">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.0-rc.1.20451.13">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.0-preview.5.20278.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.0-rc.1.20451.13" />
</ItemGroup>

</Project>
16 changes: 16 additions & 0 deletions samples/core/Modeling/OwnedEntities/OwnedEntityContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
});
#endregion

#region Required
modelBuilder.Entity<Order>(ob =>
{
ob.OwnsOne(
o => o.ShippingAddress,
sa =>
{
sa.Property(p => p.Street).IsRequired();
sa.Property(p => p.City).IsRequired();
});
ob.Navigation(o => o.ShippingAddress)
.IsRequired();
});
#endregion

#region OwnsOneNested
modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od =>
{
Expand Down

0 comments on commit 84c9763

Please sign in to comment.