One-To-Many relationship

Description Article
Table of Contents
Summary

Configuring  one-to-many relationships with Code First Model.

Description

Overview

When entity classes follow the Entity Framework conventions, it is not required to configure One-To-Many relationships neither using DataAnnotations or Fluent API

Nevertheless these relationships should be configured as Foreign Key Associations (Refer to this article for description).

Even so, when foreign keys are added to the entities classes, explicitly configuration is only required if theses new properties does not follow the Entity Framework conventions.

Database Diagram:

Operations
Configuring relationship using DataAnnotation attributes

At database relational model:

  • The “many” side table contains a foreign key column to the “one” table’s primary key;

At the business object/configuration mapping model:

  • The foreign key properties are configured using the ForeignKey attribute to clarify the developer intention, and alternatively:

    • A ForeignKey attribute is applied on each foreign key property, along with the name of the navigation property that represents the relationship; OR
    • A ForeignKey attribute is applied to the navigation property along with the name of the foreign key property for the relationship

According the conventions, if the foreign key is of type Nullable<TPrimaryKeyTYPE>, the relationship would remain optional, otherwise it would be considered as required (Refer to Working with relationships: Overview for detailed description of foreign key behavior)

If bidirectional then the child entity class has a navigation property to the parent class type.

How to do it

Domain classes definition and mapping:

public class Student
{
  public Student() { }

  public int StudentId { get; set; }
  public string StudentName { get; set; }

  public int StdandardRefId { get; set; }

  [ForeignKey("StandardRefId")]
  public virtual Standard { get; set; }

}

public class Standard
{
  public Standard()
  {
    studentsList = new List<Student>();
  }

  public int StandardId { get; set; }

  public string Description { get; set; }

  public virtual ICollection<Student> Students { get; set; }
}
Configuring relationship using Fluent API

At database relational model:

  • The “many” side table contains a foreign key column to the “one” table’s primary key;

At the business object/configuration mapping model:

  • The navigation property in the parent entity object (the one side) is configured as a collection of entities using the HasMany() method
  • The navigation property in the child entity object is configured as required/optional using the WithRequired()/WithOptional() method(s). Thus the FK is marked as non-nullable/nullable on the child table
  • The property used as foreign key in the child entity object (the many side) is set using the HasForeignKey(). This is only required in case of a unconvencional name
  • Cascade Delete can be enabled or disabled through the use of the WillCascadeOnDelete() method

According the conventions, if the foreign key is of type Nullable, the relationship would remain optional, otherwise it would be considered as required.

If unidirectional, then:

  1. The navigation property to the parent class type in the child entity object can be removed
  2. The WithRequired()/WithOptional() method(s) are replaced by the overload without parameters.

If no foreign key property is added to the child entity class and a specific foreign key column, defined on the database table and inexistent at object level, should be considered, then the Map() method must be applied. This method replaces HasForeignKey() when used.

The pattern to use is:

Map(x => x.MapKey(“DATABASE_FOREIGNKEYNAME”))

Remarks

If a foreign key on the dependent entity is not nullable, then Code First sets CASCADE DELETE on the relationship.

If a foreign key on the dependent entity is nullable, then Code First does not set CASCADE DELETE on the relationship, and when the parent is deleted the foreign key will be set to null.

These cascade delete conventions can be ignored by using:

modelBuilder.Conventions.Remove()
modelBuilder.Conventions.Remove()
How to do it

Domain classes definition and mapping:

public class Student
{
  public Student() { }

  public int StudentId { get; set; }
  public string StudentName { get; set; }

  public int StandardRefId { get; set; }

  public virtual Standard { get; set; }

}

public class Standard
{
  public Standard()
  {
    studentsList = new List<Student>();
  }

  public int StandardId { get; set; }

  public string Description { get; set; }

  public virtual ICollection<Student> Students { get; set; }
}

Relationship configuration on DbContext (Option 1):

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Standard>()
              .HasMany<Student>(s => s.Students)
              .WithRequired(std => std.Standard)
              .HasForeignKey(std => std.StandardRefId)
              .WillCascadeOnDelete(false);
}

Relationship configuration on DbContext (Option 2):

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<Student>()
              .HasRequired<Standard>(std => std.Standard)
              .WithMany(s => s.Students)
              .HasForeignKey(std => std.StandardRefId)
              .WillCascadeOnDelete(false);

}