In database design, a one-to-one (1:1) relationship occurs when a record in one table is associated with exactly one record in another table.
This type of relationship is useful for information segmentation (for example, entities that are closely related but are different things).
Suppose we have two entities:
User
Profile
Each user has exactly one profile, and each profile belongs to one user.
public class User
{
public int Id { get; set; }
public string Username { get; set; } // For login
public string Email { get; set; } // For authentication
public string PasswordHash { get; set; } // Security
public Profile Profile { get; set; } // Navigation property
}
public class Profile
{
public int Id { get; set; }
public string FullName { get; set; } // Full name
public DateTime BirthDate { get; set; } // Birth date
public string Bio { get; set; } // Short bio
public User User { get; set; } // Navigation property
}
Why not put everything in User?
In this example, we can decide to have the entities separated for data segregation (i.e., for cleanliness)
User
contains only the necessary information for system authentication and security,Profile
, on the other hand, holds data about the person associated with the User
Another clear example of a 1:1 relationship could be Employee
and Computer
. Even though an employee may have only one computer, and one computer belongs to a single person, they are separate entities.
Configuring the relationship
Naming conventions
Entity Framework can infer the 1:1 relationship if:
- Both entities have a navigation property pointing to the other.
- One of the entities has a foreign key that follows the convention
{PrimaryKeyName}Id
.
public class Profile
{
public int Id { get; set; }
public string FullName { get; set; }
public int UserId { get; set; } // Foreign key (UserId → User.Id)
public User User { get; set; } // Navigation property
}
EF automatically detects that Profile.UserId
is a foreign key of User.Id
With Data Annotations
If we do not follow the conventions, we can use attributes:
[ForeignKey]
on the foreign key
public class Profile
{
public int Id { get; set; }
public string FullName { get; set; }
[ForeignKey("User")] // Indicates that UserId is FK of User
public int UserId { get; set; }
public User User { get; set; }
}
[ForeignKey]
on the navigation property
public class Profile
{
public int Id { get; set; }
public string FullName { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")] // Indicates that UserId is the FK
public User User { get; set; }
}
With Fluent API
Or we can also configure it with Fluent API.
Foreign key in Profile
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOne(u => u.Profile) // A User has a Profile
.WithOne(p => p.User) // A Profile belongs to a User
.HasForeignKey<Profile>(p => p.UserId); // FK in Profile
}
Foreign key in User
(less common)
modelBuilder.Entity<Profile>()
.HasOne(p => p.User)
.WithOne(u => u.Profile)
.HasForeignKey<User>(u => u.ProfileId); // FK in User
Structure of the generated database
When applying a migration, EF creates the tables:
CREATE TABLE Users (
Id INT PRIMARY KEY IDENTITY,
Username NVARCHAR(100)
);
CREATE TABLE Profiles (
Id INT PRIMARY KEY IDENTITY,
FullName NVARCHAR(100),
UserId INT UNIQUE, -- UNIQUE guarantees the 1:1 relationship!
FOREIGN KEY (UserId) REFERENCES Users(Id)
);
UserId
in Profiles
is UNIQUE, which prevents the same user from having multiple profiles
Practical examples
Data Query
We retrieve a user with their profile:
using (var context = new AppDbContext())
{
// Explicit loading (2 queries)
var user = context.Users.First(u => u.Id == 1);
var profile = context.Profiles.First(p => p.UserId == user.Id);
// Eager loading (1 query with JOIN)
var userWithProfile = context.Users
.Include(u => u.Profile) // Automatic JOIN
.First(u => u.Id == 1);
Console.WriteLine($"User: {userWithProfile.Username}");
Console.WriteLine($"Full Name: {userWithProfile.Profile.FullName}");
}
Data Insertion
We create a user with their profile:
using (var context = new AppDbContext())
{
var newUser = new User { Username = "johndoe" };
var newProfile = new Profile { FullName = "John Doe" };
// We assign the relationship
newUser.Profile = newProfile;
context.Users.Add(newUser);
context.SaveChanges(); // Saves both records to the DB
}