Skip to content

Commit

Permalink
Merge relational/fk-constraints into relationships
Browse files Browse the repository at this point in the history
Plus some general cleanup.

Part of #1669
  • Loading branch information
roji committed Dec 19, 2019
1 parent 06919aa commit 3ea4913
Show file tree
Hide file tree
Showing 20 changed files with 68 additions and 75 deletions.
5 changes: 5 additions & 0 deletions .openpublishing.redirection.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,11 @@
"source_path": "entity-framework/core/modeling/relational/indexes.md",
"redirect_url": "/ef/core/modeling/indexes",
"redirect_document_id": false
},
{
"source_path": "entity-framework/core/modeling/relational/fk-constraints.md",
"redirect_url": "/ef/core/modeling/relationships#foreign-key-constraint-name",
"redirect_document_id": false
}
]
}
26 changes: 0 additions & 26 deletions entity-framework/core/modeling/relational/fk-constraints.md

This file was deleted.

70 changes: 43 additions & 27 deletions entity-framework/core/modeling/relationships.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ There are a number of terms used to describe relationships

The following code shows a one-to-many relationship between `Blog` and `Post`

[!code-csharp[Main](../../../samples/core/Modeling/Conventions/Relationships/Full.cs#Entities)]
[!code-csharp[Main](../../../samples/core/Modeling/Conventions/Relationships/Full.cs#Full)]

* `Post` is the dependent entity

Expand Down Expand Up @@ -65,13 +65,13 @@ The most common pattern for relationships is to have navigation properties defin

* If a pair of navigation properties is found between two types, then they will be configured as inverse navigation properties of the same relationship.

* If the dependent entity contains a property with a name mathing one of these patterns then it will be configured as the foreign key:
* If the dependent entity contains a property with a name matching one of these patterns then it will be configured as the foreign key:
* `<navigation property name><principal key property name>`
* `<navigation property name>Id`
* `<principal entity name><principal key property name>`
* `<principal entity name>Id`

[!code-csharp[Main](../../../samples/core/Modeling/Conventions/Relationships/Full.cs?name=Entities&highlight=6,15,16)]
[!code-csharp[Main](../../../samples/core/Modeling/Conventions/Relationships/Full.cs?name=Full&highlight=6,15-16)]

In this example the highlighted properties will be used to configure the relationship.

Expand All @@ -85,7 +85,7 @@ In this example the highlighted properties will be used to configure the relatio

While it is recommended to have a foreign key property defined in the dependent entity class, it is not required. If no foreign key property is found, a [shadow foreign key property](shadow-properties.md) will be introduced with the name `<navigation property name><principal key property name>` or `<principal entity name><principal key property name>` if no navigation is present on the dependent type.

[!code-csharp[Main](../../../samples/core/Modeling/Conventions/Relationships/NoForeignKey.cs?name=Entities&highlight=6,15)]
[!code-csharp[Main](../../../samples/core/Modeling/Conventions/Relationships/NoForeignKey.cs?name=NoForeignKey&highlight=6,15)]

In this example the shadow foreign key is `BlogId` because prepending the navigation name would be redundant.

Expand All @@ -96,7 +96,7 @@ In this example the shadow foreign key is `BlogId` because prepending the naviga

Including just one navigation property (no inverse navigation, and no foreign key property) is enough to have a relationship defined by convention. You can also have a single navigation property and a foreign key property.

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

### Limitations

Expand All @@ -112,17 +112,17 @@ See [Cascade Delete](../saving/cascade-delete.md) for more details about the dif

## Manual configuration

#### [Fluent API](#tab/fluent-api)
### [Fluent API](#tab/fluent-api)

To configure a relationship in the Fluent API, you start by identifying the navigation properties that make up the relationship. `HasOne` or `HasMany` identifies the navigation property on the entity type you are beginning the configuration on. You then chain a call to `WithOne` or `WithMany` to identify the inverse navigation. `HasOne`/`WithOne` are used for reference navigation properties and `HasMany`/`WithMany` are used for collection navigation properties.

[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/NoForeignKey.cs?highlight=14-16)]
[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/NoForeignKey.cs?name=NoForeignKey&highlight=8-10)]

#### [Data annotations](#tab/data-annotations)
### [Data annotations](#tab/data-annotations)

You can use the Data Annotations to configure how navigation properties on the dependent and principal entities pair up. This is typically done when there is more than one pair of navigation properties between two entity types.

[!code-csharp[Main](../../../samples/core/Modeling/DataAnnotations/Relationships/InverseProperty.cs?highlight=33,36)]
[!code-csharp[Main](../../../samples/core/Modeling/DataAnnotations/Relationships/InverseProperty.cs?name=InverseProperty&highlight=20,23)]

> [!NOTE]
> You can only use [Required] on properties on the dependent entity to impact the requiredness of the relationship. [Required] on the navigation from the principal entity is usually ignored, but it may cause the entity to become the dependent one.
Expand All @@ -136,21 +136,27 @@ You can use the Data Annotations to configure how navigation properties on the d

If you only have one navigation property then there are parameterless overloads of `WithOne` and `WithMany`. This indicates that there is conceptually a reference or collection on the other end of the relationship, but there is no navigation property included in the entity class.

[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/OneNavigation.cs?highlight=14-16)]
[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/OneNavigation.cs?name=OneNavigation&highlight=8-10)]

### Foreign key

#### [Fluent API](#tab/fluent-api)
#### [Fluent API (simple key)](#tab/fluent-api-simple-key)

You can use the Fluent API to configure which property should be used as the foreign key property for a given relationship.
You can use the Fluent API to configure which property should be used as the foreign key property for a given relationship:

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

#### [Data annotations](#tab/data-annotations)
#### [Fluent API (composite key)](#tab/fluent-api-composite-key)

You can use the Data Annotations to configure which property should be used as the foreign key property for a given relationship. This is typically done when the foreign key property is not discovered by convention.
You can use the Fluent API to configure which properties should be used as the composite foreign key properties for a given relationship:

[!code-csharp[Main](../../../samples/core/Modeling/DataAnnotations/Relationships/ForeignKey.cs?highlight=30)]
[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/CompositeForeignKey.cs?name=CompositeForeignKey&highlight=13)]

#### [Data annotations (simple key)](#tab/data-annotations-simple-key)

You can use the Data Annotations to configure which property should be used as the foreign key property for a given relationship. This is typically done when the foreign key property is not discovered by convention:

[!code-csharp[Main](../../../samples/core/Modeling/DataAnnotations/Relationships/ForeignKey.cs?name=ForeignKey&highlight=17)]

> [!TIP]
> The `[ForeignKey]` annotation can be placed on either navigation property in the relationship. It does not need to go on the navigation property in the dependent entity class.
Expand All @@ -160,38 +166,48 @@ You can use the Data Annotations to configure which property should be used as t
---

The following code shows how to configure a composite foreign key.

[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/CompositeForeignKey.cs?highlight=20)]
#### Shadow foreign key

You can use the string overload of `HasForeignKey(...)` to configure a shadow property as a foreign key (see [Shadow Properties](shadow-properties.md) for more information). We recommend explicitly adding the shadow property to the model before using it as a foreign key (as shown below).

[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/ShadowForeignKey.cs#Sample)]
[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/ShadowForeignKey.cs?name=ShadowForeignKey&highlight=10,16)]

#### Foreign key constraint name

By convention, when targeting a relational database, foreign key constraints are named FK_<dependent type name>_<principal type name>_<foreign key property name>. For composite foreign keys <foreign key property name> becomes an underscore separated list of foreign key property names.

You can also configure the constraint name as follows:

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

### Without navigation property

You don't necessarily need to provide a navigation property. You can simply provide a foreign key on one side of the relationship.

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

### Principal key

If you want the foreign key to reference a property other than the primary key, you can use the Fluent API to configure the principal key property for the relationship. The property that you configure as the principal key will automatically be setup as an [alternate key](alternate-keys.md).

#### [Simple key](#tab/simple-key)

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

The following code shows how to configure a composite principal key.
#### [Composite key](#tab/composite-key)

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

> [!WARNING]
> The order in which you specify principal key properties must match the order in which they are specified for the foreign key.
---

### Required and optional relationships

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](required-optional.md) for more information).

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

> [!NOTE]
> Calling `IsRequired(false)` also makes the foreign key property optional unless it's configured otherwise.
Expand All @@ -202,15 +218,15 @@ You can use the Fluent API to configure the cascade delete behavior for a given

See [Cascade Delete](../saving/cascade-delete.md) for a detailed discussion of each option.

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

## Other relationship patterns

### One-to-one

One to one relationships have a reference navigation property on both sides. They follow the same conventions as one-to-many relationships, but a unique index is introduced on the foreign key property to ensure only one dependent is related to each principal.

[!code-csharp[Main](../../../samples/core/Modeling/Conventions/Relationships/OneToOne.cs?name=Property&highlight=6,15,16)]
[!code-csharp[Main](../../../samples/core/Modeling/Conventions/Relationships/OneToOne.cs?name=OneToOne&highlight=6,15-16)]

> [!NOTE]
> EF will choose one of the entities to be the dependent based on its ability to detect a foreign key property. If the wrong entity is chosen as the dependent, you can use the Fluent API to correct this.
Expand All @@ -225,4 +241,4 @@ When configuring the foreign key you need to specify the dependent entity type -

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.

[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/ManyToMany.cs?name=ManyToMany&highlight=11,12,13,14,16,17,18,19,39,40,41,42,43,44,45,46)]
[!code-csharp[Main](../../../samples/core/Modeling/FluentAPI/Relationships/ManyToMany.cs?name=ManyToMany&highlight=11-14,16-19,39-46)]
2 changes: 0 additions & 2 deletions entity-framework/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,6 @@
href: core/modeling/relational/sequences.md
- name: Default Values
href: core/modeling/relational/default-values.md
- name: Foreign Key Constraints
href: core/modeling/relational/fk-constraints.md
- name: Alternate Keys (Unique Constraints)
href: core/modeling/relational/unique-constraints.md
- name: Inheritance (Relational Database)
Expand Down
2 changes: 1 addition & 1 deletion samples/core/Modeling/Conventions/Relationships/Full.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class MyContext : DbContext
public DbSet<Post> Posts { get; set; }
}

#region Entities
#region Full
public class Blog
{
public int BlogId { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class MyContext : DbContext
public DbSet<Post> Posts { get; set; }
}

#region Entities
#region NoForeignKey
public class Blog
{
public int BlogId { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class MyContext : DbContext
public DbSet<Post> Posts { get; set; }
}

#region Entities
#region OneNavigation
public class Blog
{
public int BlogId { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class MyContext : DbContext
public DbSet<BlogImage> BlogImages { get; set; }
}

#region Property
#region OneToOne
public class Blog
{
public int BlogId { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class MyContext : DbContext
public DbSet<Post> Posts { get; set; }
}

#region Entities
#region ForeignKey
public class Blog
{
public int BlogId { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class MyContext : DbContext
public DbSet<User> Users { get; set; }
}

#region Entities
#region InverseProperty
public class Post
{
public int PostId { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@

namespace EFModeling.FluentAPI.Relationships.CascadeDelete
{
#region CascadeDelete
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }

#region CascadeDelete
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.OnDelete(DeleteBehavior.Cascade);
}
#endregion
}

public class Blog
Expand All @@ -36,5 +37,4 @@ public class Post
public int? BlogId { get; set; }
public Blog Blog { get; set; }
}
#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace EFModeling.Configuring.DataAnnotations.Samples.Relationships.CompositeForeignKey
{
#region Model
#region CompositeForeignKey
class MyContext : DbContext
{
public DbSet<Car> Cars { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace EFModeling.Configuring.DataAnnotations.Samples.Relationships.CompositePrincipalKey
{
#region Composite
#region CompositePrincipalKey
class MyContext : DbContext
{
public DbSet<Car> Cars { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;

namespace EFModeling.FluentAPI.RelationshipConstraintName
namespace EFModeling.FluentAPI.ConstraintName
{
#region Constraint
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }

#region ConstraintName
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
Expand All @@ -17,6 +17,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.HasForeignKey(p => p.BlogId)
.HasConstraintName("ForeignKey_Post_Blog");
}
#endregion
}

public class Blog
Expand All @@ -36,5 +37,4 @@ public class Post
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace EFModeling.Configuring.DataAnnotations.Samples.Relationships.ForeignKey
{
#region Model
#region ForeignKey
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace EFModeling.FluentAPI.Relationships.NoForeignKey
{
#region Model
#region NoForeignKey
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace EFModeling.FluentAPI.Relationships.NoNavigation
{
#region Model
#region NoNavigation
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace EFModeling.FluentAPI.Relationships.OneNavigation
{
#region Model
#region OneNavigation
class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
Expand Down
Loading

0 comments on commit 3ea4913

Please sign in to comment.