EF Core - Adding a Foreign Key

By FoxLearn 2/6/2025 8:35:29 AM   7
In Entity Framework Core (EF Core), adding a foreign key involves creating a relationship between two entities (tables). You can define the foreign key either through data annotations in your model class or by using the Fluent API in your DbContext.

In this article, I will walk through how to add a foreign key in EF Core and demonstrate how foreign keys affect data operations like inserting, deleting, and enforcing data integrity constraints.

How to Add a Foreign Key

You can use the ForeignKey attribute to specify a foreign key relationship between two entities.

Let's say you have two entities: Order and Customer, and you want to establish a relationship where an Order belongs to a Customer.

In the Order model, we’ll add a CustomerId property and the ForeignKey attribute to represent the relationship:

For example, Using Data Annotations:

using System.ComponentModel.DataAnnotations.Schema;

public class Order
{
    [Key]
    public int Id { get; set; }

    [ForeignKey("FK_Customer")]
    public int CustomerId { get; set; }

    [Required]
    public decimal TotalAmount { get; set; }

    [Required]
    public DateTime OrderDate { get; set; }
}

In the Customer model, we will create a collection of Orders to represent the inverse relationship:

public class Customer
{
    [Key]
    public int Id { get; set; }

    [Required]
    [MaxLength(100)]
    public string Name { get; set; }

    [Required]
    [MaxLength(200)]
    public string Email { get; set; }

    public List<Order> Orders { get; set; }
}

In this example:

  • CustomerId is the foreign key in the Order entity.
  • The ForeignKey attribute indicates that CustomerId is the foreign key referencing the Customer entity.

You can also use the Fluent API inside the OnModelCreating method in your DbContext to configure the foreign key relationship.

For example, Using Fluent API:

public class ApplicationDbContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    public DbSet<Order> Orders { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // Configure the relationship between Order and Customer
        modelBuilder.Entity<Order>()
            .HasOne(o => o.Customer)  // Order has one Customer
            .WithMany(c => c.Orders)  // Customer has many Orders
            .HasForeignKey(o => o.CustomerId);  // CustomerId is the foreign key
    }
}

After defining the foreign key, you will need to create and apply a migration to update your database schema.

Run these commands in the Package Manager Console:

Add-Migration AddCustomerForeignKeyToOrder
Update-Database

or use the .NET CLI:

dotnet ef migrations add AddCustomerOrderForeignKey
dotnet ef database update

Generated Migration Code

In the migration file, you will see the logic for creating the foreign key and linking the tables together:

public partial class AddCustomerOrderForeignKey : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Order",
            columns: table => new
            {
                Id = table.Column<int>(type: "int", nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                CustomerId = table.Column<int>(type: "int", nullable: false),
                TotalAmount = table.Column<decimal>(type: "decimal(18,2)", nullable: false),
                OrderDate = table.Column<DateTime>(type: "datetime2", nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Order", x => x.Id);
                table.ForeignKey(
                    name: "FK_Order_Customers_CustomerId",
                    column: x => x.CustomerId,
                    principalTable: "Customers",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });

        migrationBuilder.CreateIndex(
            name: "IX_Order_CustomerId",
            table: "Order",
            column: "CustomerId");
    }

    // Down() not shown
}

Apply the migration:

dotnet ef database update

Inserting Data with a Foreign Key

A foreign key ensures referential integrity between related records. A Customer has many Orders, and an Order must link to an existing Customer. If you try to insert an Order that doesn’t link to a valid Customer, you’ll encounter a constraint violation.

Foreign Key Preventing Invalid Inserts

Here’s an example that tries to insert an Order with a non-existent CustomerId:

using (var context = new ECommerceContext(connectionString))
{
    context.Add(new Order
    {
        TotalAmount = 99.99m,
        OrderDate = DateTime.Now,
        CustomerId = 10, // Non-existent CustomerId
    });

    context.SaveChanges();
}

This will throw an exception:

Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. 
—> Microsoft.Data.SqlClient.SqlException (0x80131904): 
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Order_Customers_CustomerId".

EF Core Automatically Linking Objects for Insertion

You can add an Order by adding it to the Customer’s Orders collection, and EF Core will automatically manage the CustomerId:

using (var context = new ECommerceContext(connectionString))
{
    context.Add(new Customer
    {
        Name = "Jane Doe",
        Email = "[email protected]",
        Orders = new List<Order>
        {
            new Order
            {
                TotalAmount = 199.99m,
                OrderDate = DateTime.Now,
            }
        }
    });

    context.SaveChanges();
}

Notice that here, the CustomerId is not explicitly set. EF Core generates the CustomerId automatically when the Customer object is saved, linking the Order to the correct Customer.

Explicitly Setting the Foreign Key ID for Insertion

Alternatively, you can manually set the CustomerId when inserting an Order:

using (var context = new ECommerceContext(connectionString))
{
    var customer = new Customer
    {
        Name = "John Smith",
        Email = "[email protected]"
    };

    context.Add(customer);
    context.SaveChanges(); // Save the customer first to generate the ID

    var order = new Order
    {
        TotalAmount = 49.99m,
        OrderDate = DateTime.Now,
        CustomerId = customer.Id, // Explicitly set the foreign key
    };

    context.Add(order);
    context.SaveChanges();
}

This ensures that the correct CustomerId is set on the Order before it is saved.

Cascading Deletes

By default, EF Core configures foreign keys with cascading deletes. This means if a Customer is deleted, all associated Orders will also be deleted automatically. The migration code for cascading deletes looks like this:

table.ForeignKey(
    name: "FK_Order_Customers_CustomerId",
    column: x => x.CustomerId,
    principalTable: "Customers",
    principalColumn: "Id",
    onDelete: ReferentialAction.Cascade);

Changing Delete Behavior

If you don’t want to use cascading deletes, you can modify the delete behavior by overriding OnModelCreating in your DbContext:

public class ECommerceContext : DbContext
{
    private readonly string _connectionString;
    public ECommerceContext(string connectionString)
    {
        _connectionString = connectionString;
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        foreach (var entity in modelBuilder.Model.GetEntityTypes())
        {
            foreach (var fk in entity.GetForeignKeys())
            {
                fk.DeleteBehavior = DeleteBehavior.Restrict;
            }
        }
    }

    public DbSet<Customer> Customers { get; set; }
    public DbSet<Order> Orders { get; set; }
}

Now the foreign keys will not delete associated records automatically when a Customer is deleted.

Generate the migration for this change:

dotnet ef migrations add ModifyDeleteBehavior

Apply the migration:

dotnet ef database update

This will configure the foreign key with a Restrict behavior, meaning you cannot delete a Customer that has linked Orders.

This example demonstrates how to implement a foreign key using EF Core, insert related data, handle invalid inserts, and configure delete behavior.