One-to-one relationships with Entity Framework
May 13 2024 03:40
One-to-one relationships are used when one entity is associated with at most one other entity. For example, a ProductEntity has one ProductDetailEntity, and a ProductDetailEntity belongs to only one ProductEntity
In this example, ProductDetails links to Product via ProductId (foreign key) which is primary key in Products table
ProductId is both primary/foreign key of ProductDetails table
Config unidirectional relationship
A unidirectional relationship has only an owning side
public class ProductEntity
{
public int Id { get; set; }
public required string Url { get; set; }
public required string Name { get; set; }
public string? Description { get; set; }
public decimal Price { get; set; }
public DateTimeOffset CreatedDate { get; set; }
public virtual ProductDetailEntity? Details { get; set; }
}
public class ProductDetailEntity
{
public int ProductId { get; set; }
public int Width { get; init; }
public int Height { get; init; }
public int Depth { get; init; }
public decimal Weight { get; init; }
}
// Fluent API config in DBContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ProductEntity>(
e =>
{
e.ToTable("Products");
e.HasKey(x => new { x.Id }).HasName("PK_ProductId");
e.Property(x => x.Id).ValueGeneratedOnAdd();
e.Property(x => x.Url).HasMaxLength(255);
e.Property(x => x.Name).HasMaxLength(255);
e.Property(x => x.Price).HasPrecision(18, 2);
e.Property(x => x.CreatedDate).HasColumnType("datetimeoffset");
e.Property(x => x.Description).HasColumnType("text").HasMaxLength(255);
// Relation config
e.HasOne(product => product.Details)
.WithOne()
.HasForeignKey<ProductDetailEntity>(productDetail => productDetail.ProductId)
.IsRequired(false);
}
);
}
Enable bidirectional relationship
Bidirectional relationships allow us to navigate to the child/parent entity from the current entity
public class ProductDetailEntity
{
public int ProductId { get; set; }
public int Width { get; init; }
public int Height { get; init; }
public int Depth { get; init; }
public decimal Weight { get; init; }
// Add parent entity
public virtual ProductEntity Product { get; init; } = null!;
}
Relation config change to
e.HasOne(product => product.Details)
.WithOne(productDetail => productDetail.Product)
.HasForeignKey<ProductDetailEntity>(productDetail => productDetail.ProductId)
.IsRequired();
SQL generated by Entity Framework core
SELECT [p].[Id], [p].[CreatedDate], [p].[Description], [p].[Name], [p].[Price], [p].[Url], [p0].[ProductId], [p0].[Depth], [p0].[Height], [p0].[Weight], [p0].[Width]
FROM [Products] AS [p]
LEFT JOIN [ProductDetails] AS [p0] ON [p].[Id] = [p0].[ProductId]
More details from Microsoft document: One-to-one relationships
Full project example available on Github
Delete comment
Confirm delete comment
Pham Duc Minh
Da Nang, Vietnam