Tutorials Menu

What are Convention?

Conventions are predefined rules or default behaviors that EF will apply automatically during the construction of the EF model. They simplify the development process by reducing the amount of code you need to write. Let's explore some of the most commonly used conventions.

Primary Key Convention

Entity Framework Code First automatically identifies a property as the primary key if it is named "ID" (case insensitive) or matches the class name followed by "ID". Additionally, if the primary key property is of a numeric type or GUID, it will be configured as an identity column by default, meaning its value will auto-increment by default.

public class Inventory
{
    public int InventoryID { get; set; } // Recognized as the primary key
    public string Name { get; set; }
    public int Quantity { get; set; }
}

Conventions for relationship discovery

Relationships between objects are created by navigational properties between the objects.

Entity Framework recognizes a property as a navigation property if the following conditions are met:

  • Accessibility
    • The property is public.
    • The property includes both a getter and a setter.
      • The setter can have any access modifier (e.g., private, public, protected ...).
      • The setter can also be init-only.
  • Type Requirements

    The property type:

    • must be a reference type.
    • must not have been explicitly configured as a primitive property type.
    • must not be mapped as a primitive type by the database provider in use.
    • must not be automatically convertible to a primitive type recognized by the database provider.
  • Other Constraints
    • The property must not be static.
    • The property must not be an indexer property.
public class Inventory
    {
        // Not discovered as reference navigations:
        public int InventoryID { get; set; } // Primary key
        public string Name { get; set; }
        public int Quantity { get; set; }
        public Uri? Uri { get; set; } 
        public string Description => $"Item: {Name}, Quantity: {Quantity}"; 

        // Discovered as reference navigations:
        public Warehouse Warehouse { get; set; } // One-to-one navigation property
    }
    
public class Warehouse
    {
        // Not discovered as reference navigations: 
        public int WarehouseID { get; set; } // Primary key
        public string Location { get; set; }
        public string ManagerName { get; set; }

        // Discovered as reference navigations:
        public Inventory Inventory { get; set; } // One-to-one navigation property
    }
    

Discovered as Navigation Properties:

  • Warehouse (in Inventory class):
    • The property is public.
    • It has a getter and a setter.
    • Its type (Warehouse) is a reference type that can be mapped as an entity.
    • It is not configured as a primitive property type.
    • It is not automatically convertible to a primitive type.
    • It is not static or an indexer property.
  • Inventory (in Warehouse class):
    • The property is public.
    • It has a getter and a setter.
    • Its type (Inventory) is a reference type that can be mapped as an entity.
    • It is not configured as a primitive property type.
    • It is not automatically convertible to a primitive type.
    • It is not static or an indexer property.

Not Discovered as Navigation Properties:

  • InventoryID - The property is a numeric type (int), which is treated as a primitive property type and is automatically mapped to a database column.
  • Name - The property is a string, which is a primitive type automatically mapped to a database column.
  • Description - The property does not have a setter.
  • Quantity - The property is a numeric type (int) that is mapped directly as a primitive column.
  • Uri - The property is of type Uri, which is automatically converted to a mapped primitive type.
  • Location - The property is a string, which is a primitive type automatically mapped to a database column.
  • ManagerName - The property is a string, which is a primitive type automatically mapped to a database column.
  • WarehouseID - The property is a numeric type (int), which is treated as a primitive property type and automatically mapped to a database column.

Collection Navigation Property in Entity Framework

A property is discovered as a collection navigation property if the property type is, or implements, IEnumerable<TEntity>, where TEntity:

  • The property must be a reference type.
  • The property must not be configured explicitly as a primitive property type.
  • The property must not be mapped as a primitive property type by the database provider being used.
  • The property must not be automatically convertible to a primitive property type mapped by the database provider being used.

Below is an example showcasing a one-to-many relationship between Suppliers and their Inventory items:

public class Inventory
{
    public int InventoryID { get; set; } // Primary key
    public string Name { get; set; } // Item name
    public int Quantity { get; set; } // Quantity in stock

    // Discovered as a reference navigation property:
    public Supplier Supplier { get; set; } = null!; // One inventory item belongs to one supplier
}
public class Supplier
{
    public int SupplierID { get; set; } // Primary key
    public string Name { get; set; } // Supplier name
    public string ContactNumber { get; set; } // Contact information

    // Discovered as a collection navigation property:
    public List<Inventory> Inventories { get; set; } = new(); // One supplier has many inventory items
}

Pairing navigations

Once a navigational property is discovered in an Entity (Entity A has a navigational property to Entity B), EF core will search for a pair inside the navigational property type (Is there navigational property in Entity B that points to Entity A). When such a connection is found a pair with bidirectional relationship is created.

There are 3 types of bidirectional connections:

  1. One-to-one - Both navigations are reference navigations.
  2. One-to-many - One of the connections is reference navigation, and the other one is a collection navigation.
  3. Many-to-many - Both navigations are collection navigations.

Creating Foreign keys

The foreign key is the identifier of the related record, serving as a reference to establish the connection between two entities. In contrast, the navigational property represents the actual related record, enabling access to its details. The foreign key is only the ID used to link the two records together.

To create a FK you can use any of the4 naming conventions:

  1. <navigation property name><principal key property name>
  2. public class School
    {
        public int Key { get; set; } // Principal Primary Key
        public string Name { get; set; } = string.Empty;
        public ICollection<Student> Students { get; } = new List<Student>(); // Collection navigation property
    }
    
    public class Student
    {
        public int Id { get; set; } // Primary Key
        public string FullName { get; set; } = string.Empty;
    
        public int? TechSchoolKey { get; set; } // Foreign Key
        public School? TechSchool { get; set; } // Reference navigation property
    }
    
  3. <navigation property name>Id
  4. public class School
    {
        public int Key { get; set; } // Principal Primary Key
        public string Name { get; set; } = string.Empty;
        public ICollection<Student> Students { get; } = new List<Student>(); // Collection navigation property
    }
    
    public class Student
    {
        public int Id { get; set; } // Primary Key
        public string FullName { get; set; } = string.Empty;
    
        public int? TechSchoolId { get; set; } // Foreign Key
        public School? TechSchool { get; set; } // Reference navigation property
    }
    
  5. <principal entity type name><principal key property name>
  6. public class School
    {
        public int Key { get; set; } // Principal Primary Key
        public string Name { get; set; } = string.Empty;
        public ICollection<Student> Students { get; } = new List<Student>(); // Collection navigation property
    }
    
    public class Student
    {
        public int Id { get; set; } // Primary Key
        public string FullName { get; set; } = string.Empty;
    
        public int? SchoolKey { get; set; } // Foreign Key
        public School? TechSchool { get; set; } // Reference navigation property
    }
    
  7. <principal entity type name>Id
  8. public class School
    {
        public int Key { get; set; } // Principal Primary Key
        public string Name { get; set; } = string.Empty;
        public ICollection<Student> Students { get; } = new List<Student>(); // Collection navigation property
    }
    
    public class Student
    {
        public int Id { get; set; } // Primary Key
        public string FullName { get; set; } = string.Empty;
    
        public int? SchoolId { get; set; } // Foreign Key
        public School? TechSchool { get; set; } // Reference navigation property
    }
    
    

When working with foreign key (FK) properties in Entity Framework, there are a few key rules regarding where they can be placed:

  1. Many-to-Many Relationships:

    In a many-to-many relationship, there is no single foreign key property. Both properties are collections, and they cannot be linked by just one foreign key. A junction table is typically used to manage the relationship between the two entities.

  2. One-to-Many Relationships:

    In a one-to-many relationship, the foreign key is placed on the "many" side. For example, in a relationship where one school has many students, the student entity would have a foreign key pointing to the school entity. The reverse is not possible because a school can be linked to many students, but a student can only belong to one school.

  3. One-to-One Relationships:

    In a one-to-one relationship, the foreign key is placed on the dependent entity. This entity holds a foreign key pointing to the principal entity, and the foreign key in the dependent entity will also be its primary key.

public class Employee
{
    // Employee is the principal entity and holds the primary key
    public int EmployeeId { get; set; }
    public string Name { get; set; }

    // Navigation property to the EmployeeProfile (dependent entity)
    public EmployeeProfile Profile { get; set; }
}

public class EmployeeProfile
{
    // EmployeeProfile is the dependent entity and holds the foreign key
    public int EmployeeProfileId { get; set; }
    public string Address { get; set; }
    public string PhoneNumber { get; set; }

    // Foreign key to the Employee entity (principal)
    public int EmployeeId { get; set; }

    // Navigation property to the Employee (principal entity)
    public Employee Employee { get; set; }
}

Shadow foreign key properties

If Entity Framework determines the dependent side of a relationship but does not find a foreign key property, it will automatically create a shadow property to represent the foreign key. A shadow property has the following characteristics:

  1. Type:The shadow property's type matches the primary key property type of the principal entity in the relationship. By default, the shadow property is nullable, making the relationship optional unless otherwise configured.
  2. Naming with Navigation Property:If the dependent entity includes a navigation property pointing to the principal entity, the shadow foreign key property is named by combining the name of the navigation property with the name of the principal key property (e.g., <navigation property name><principal key property name>).
  3. Naming without Navigation Property:If no navigation property exists on the dependent side, the shadow foreign key property is named by combining the name of the principal entity type with the name of the principal key property (e.g., <principal entity type name><principal key property name>).

Cascade delete

By default, Entity Framework applies the following conventions for cascading deletes:

Required Relationships:Automatically configured to enable cascade delete, meaning that deleting the principal entity will also delete the dependent entities.

Optional Relationships:Configured to not enable cascade delete, ensuring that the dependent entities remain intact when the principal entity is deleted.