EF Core - Adding a Foreign Key
By FoxLearn 2/6/2025 8:35:29 AM 7
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 theOrder
entity.- The
ForeignKey
attribute indicates thatCustomerId
is the foreign key referencing theCustomer
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.
- EF Core - Inheritance Mapping
- EF Core - Applying Migrations Programmatically
- EF Core - Creating a Database and Table
- EF Core - Database Schema Modifications
- EF Core - Adding a Computed Column
- EF Core - SELECT Queries Involving Multiple Tables
- EF Core - Aggregate SELECT queries
- EF Core - Basic SELECT Queries