In the second part of this series, we have learned how to configure non-relational properties in Entity Framework Core. So as a logical continuation, this article will be dedicated to learning about database relationships configuration with Entity Framework Core (EF Core Relationships).

We will show you how to create additional entities in the database model and how to create relationships between them. We are going to use all three ways: by Convention, Data Annotations and Fluent API, to create those relationships.

You can download the source code for this article on our GitHub repository.

To see all the basic instructions and complete navigation for this series, visit Entity Framework Core with ASP.NET Core Tutorial.

This is what we are going to learn about:

EF Core Relationships – Concepts and Navigational Properties

Right now, we have only one entity (model) class, the Student class, but soon enough we are going to create the rest of the database model in our application. But before we do that, it is quite important to understand some basic concepts when working with relational databases and models.

When we create a relationship between two entities, one of them becomes the Principal entity and another one is the Dependent entity. The Principal entity is the main entity in a relationship. It contains a primary key as a property that the dependent entity refers to via the foreign key. The Dependent entity, from the other side, is the entity that holds the foreign key that refers to the principal entity’s primary key.

Our entity classes will contain Navigational properties which are the properties containing a single class or a collection of classes that EF Core uses to link entity classes.

Additionally, let’s explain the Required and Optional relationships in EF Core. The required relationship is a relationship where a foreign key cannot be null. This means that the principal entity must exist. The optional relationship is a relationship where a foreign key could be null and therefore the principal entity can be missing.

Configuring One-to-One Relationship

The one-to-one relationship means that a row in one table can only relate to one row in another table in a relationship. This is not that common relationship because it is usually handled as “all the data in one table”, but sometimes (when we want to separate our entities) it is useful to divide data into two tables.

The easiest way to configure this type of relationship is to use by Convention approach, and that is exactly what we are going to do. So let’s first create another class in the Entities project, named StudentDetails:

Now, to establish a relationship between the Student and the StudentDetails classes we need to add a reference navigation property at both sides. So, let’s first modify the Student class:

And let’s modify the StudentDetails class:

We can see that the Student class has a reference navigation property towards the StudentDetails class and the StudentDetails class has a foreign key and the navigation property Student.

As a result, we can create a new migration and apply it:

This is the result:

One to one - EF Core Relationships

Excellent, this works great.

Additional Explanation

If we take a look at the first article of this series, we are going to see that we had to create a DbSet<T> property for the Student class in order to be created in the database. But as we can see, we haven’t done the same thing for the StudentDetails class but it is still created in db.

Why is that?

Well, as we explained in the first article, EF Core searches for all the public DbSet<T> properties in the DbContext class to create tables in the database. Then it searches for all the public properties in the T class to map the columns. But it also searches for all the public navigational properties in the T class and creates additional tables and columns related to the type of the navigation property. So, in our example, in the Student class, EF Core finds the StudentDetails navigation property and creates an additional table with its columns.

One-to-Many Relationship Configuration

In this section, we are going to learn how to create One to Many relationships with all three ways. So, before we start, let’s create an additional model class Evaluation in the Entities project:

Let’s continue on.

Using by Convention Approach to Create One-to-Many Relationship

Let’s take a look at the different conventions which automatically configure the one-to-many relationship between the Student and Evaluation classes.

The first approach includes the navigation property in the principal entity, the Student class:

We have in the ApplicationContext class a DbSet<Student> property and as we explained, EF Core searches through the Student class to find all the navigational properties to create appropriate tables in the database.

Another way to create a One-to-Many relationship is by adding a Student property in the Evaluation class without ICollection<Evaluation> property in the Student class:

For this approach to work, we have to add a DbSet<Evaluation> Evaluations property in the ApplicationContext class.

The third approach by Convention is to use a combination of the previous ones. So, we can add the ICollection<Evaluation> Evaluations navigational property to the Student class and add the Student Student navigational property in the Evaluation class. Of course, with this approach, we don’t need the DbSet<Evaluation> Evaluations property in the ApplicationContext class.

This is the result of any of these three approaches:

Optional relationship - EF Core Relationships

We can see that the relationship was properly created, but our foreign key is a nullable field. This is because both navigational properties have a default value of null. This relationship is also called an Optional Relationship (we have talked about it in the first part of this article).

If we want to create a required relationship between the Student and Evaluation entities, we have to include the foreign key into the Evaluation class:

Now when we execute our migration, we are going to see the following result:

Required relationship - EF Core Relationships

It is obvious that our relationship is now required.

Data Annotations Approach

The Data Annotations approach contains only two attributes related to relationships. The [ForeignKey] and [InverseProperty] attributes.

The [ForeignKey] attribute allows us to define a foreign key for a navigational property in the model class. So, let’s modify the Evaluation class by adding this attribute:

We have applied the [ForeignKey] attribute on top of the StudentId property(which is a foreign key in this class) giving it a name of the navigational property Student. But it also works the other way around:

The ForeignKey attribute takes one parameter of type string. If the foreign key is a composite key then the ForeignKey attribute should look like this:

[ForeignKey(“Property1”, “Property2”)].

Whichever way we choose, the result is going to be the same as with the “by Convention” approach. We are going to have a required relationship created between these two tables:

Required relationship - EF Core Relationships

Fluent API approach for the One-to-Many Configuration

To create a One-to-Many relationship with this approach, we need to remove the [ForeignKey] attribute from the Evaluation class and to modify the StudentConfiguration class by adding this code:

With a code like this, we inform EF Core that our Student entity (the builder object is of type EntityTypeBuilder<Student>) can be in a relationship with many Evaluation entities. We also state that the Evaluation is in a relationship with only one Student entity. Finally, we provide information about the foreign key in this relationship.

The result is going to be the same:

Required relationship - EF Core Relationships

We need to mention one thing here.

For the database model like we’ve defined, we don’t need to have the HasForeignKey method. That’s because the foreign key property in the Evaluation class has the same type and the same name as the primary key in the Student class. This means that by Convention this relation would still be the required one. But if we had a foreign key with a different name, StudId for example, then the HasForeignKey method would be needed because otherwise, EF core would create an optional relationship between Evaluation and Student classes.

Many-to-Many Relationship Configuration

Before we start explaining how to configure this relationship, let’s create the required classes in the Entities project:

Now, we can modify the Student and Subject classes by providing the navigational property for each class towards the StudentSubject class:

In Entity Framework Core, we have to create a joining entity for a joining table (StudentSubject). This class contains the foreign keys and navigational properties from the Student and Subject classes. Furthermore, the Student and Subject classes both have navigational ICollection properties towards the StudentSubject class. So basically, the Many-to-Many relationship is just two One-To-Many relationships.

We have created our entities, and now we have to create the required configuration. For that, let’s create the StudentSubjectConfiguration class in the Entities/Configuration folder:

As we said, the Many-to-Many is just two One-to-Many EF Core relationships and that’s exactly what we configure in our code. We create a primary key for the StudentSubject table which is, in this case, a composite key. After the primary key configuration, we use a familiar code to create relationship configurations.

Now, we have to modify the OnModelBuilder method in the ApplicationContext class:

After these modifications, we can create a migration and apply it:

PM> Add-Migration ManyToManyRelationship

PM> Update-Database

This is the result:

Many to many relationship

Excellent work. Let’s press on.

OnDelete Method

The OnDelete method configures the delete actions between relational entities. We can add this method to the end of the relationship configuration to decide on how delete actions will execute.

The values that can be used in the OnDelete method are:

  • Restrict – The delete action isn’t applied to dependent entities. This means that we can’t delete the principal entity if it has related dependent entity.
  • SetNull – The dependent entity isn’t deleted but its foreign key property is set to null.
  • ClientSetNull – If EF Core tracks dependent entity its foreign key is set to null and that entity is not deleted. If it doesn’t track dependent entity, the database rules apply.
  • Cascade – The dependent entity is deleted with the principal entity.

If we look at our entities Student and Evaluation, we are going to see that we have a required relationship between them. For this type of relationship, the Cascade deleting action is configured by default.

We can also see that from the code in our migration file:

Cascasde delete

We can change this type of behavior by modifying the configuration code in the StudentConfiguration class:

Let’s create another migration:

PM> Add-Migration StudentEvaluationRestrictDelete

And take a look at the migration generated code:

Restrict delete

Great job.

Conclusion

Configuring EF Core Relationships in our database model is a very important part of the modeling process.

We have seen that EF Core provides us with several ways to achieve that and to make the process as easy as it can be.

Now that we know how to establish relationships in our database, we can continue to the next article where we are going to learn how to access data from the database.

If you have enjoyed reading this article and if you would like to receive the notifications about the freshly published .NET Core content we encourage you to subscribe to our blog.