Unlike One-to-Many, a One-to-One relationship is strict mapping. A User can only have exactly one IdentityPassport. These are typically used to split a massive "God Table" with 150 columns into smaller, manageable tables for performance reasons (Vertical Partitioning).
Because the relationship is 1:1, there are NO collections (List<T>) involved. Both sides hold a single reference to the other.
public class User // The Principal (The entity that can exist on its own)
{
public int Id { get; set; }
public string Name { get; set; }
// Reference Navigation
public IdentityPassport Passport { get; set; }
}
public class IdentityPassport // The Dependent (Cannot exist without a User)
{
public int Id { get; set; }
public string PassportNumber { get; set; }
// Navigation and Foreign Key to the parent
public int UserId { get; set; }
public User User { get; set; }
}
Setting up 1:1 relationships natively via EF Conventions is highly prone to erratic errors because EF Core struggles to figure out which table is the "Parent" and which table is the "Child." We must explicitly tell the Fluent API who holds the Foreign Key.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>(entity =>
{
// Read out loud: "A user HAS ONE Passport...
entity.HasOne(u => u.Passport)
// WITH ONE User attached back to the Passport...
.WithOne(p => p.User)
// And the IdentityPassport table is the one that possesses the Foreign Key column!
.HasForeignKey<IdentityPassport>(p => p.UserId);
});
}
<IdentityPassport> generic marker attached to the HasForeignKey method. In a 1:1 relationship, either table COULD theoretically hold the foreign key. You MUST tell the system which side gets the SQL column.
Because it's a 1:1 relationship, a separate UserId column feels redundant. The Passport's own Primary Key can just be the User's ID. If the User is ID #5, the Passport is forced to be ID #5.
public class SecureVault
{
// Both the Primary Key AND the Foreign Key simultaneously!
public int UserId { get; set; }
public User User { get; set; }
}
// Fluent API configuration
modelBuilder.Entity<User>()
.HasOne(u => u.SecureVault)
.WithOne(s => s.User)
.HasForeignKey<SecureVault>(s => s.UserId);
modelBuilder.Entity<SecureVault>().HasKey(s => s.UserId);
Q: "Why would we ever split a User's columns (e.g., BloodType, PassportImage) into a separate 1:1 Table instead of just keeping them as nullable columns on the primary User table?"
Architect Answer: "The decision revolves around SQL Server's 8KB Page limits and network I/O optimization. If the `User` table contains massive byte[] array columns for passport images and biometric data, standard LINQ queries (`Select * from Users`) will drag extreme amounts of data across the network even when the UI just wants to display a username. By 'Vertically Partitioning' the heavy unstructured data into a separate 1:1 table (`IdentityPassport`), the main `User` table stays incredibly lightweight, narrow, and fits densely into CPU Caches. We explicitly `.Include(u => u.Passport)` ONLY when the UI specifically requires the heavy biometric data."