What are owned entities and table splitting in EF Core?
Quick Answer
Owned entities are value objects with no identity of their own that belong to a parent entity (`OwnsOne`/`OwnsMany`); by default their columns are stored in the owner's table (table splitting). Table splitting more generally maps multiple entity types to a single table sharing a primary key. These let you model rich value objects (e.g., Address) cleanly without separate tables or extra joins.
Detailed Answer
Owned Entities Owned types are value objects that belong to another entity and share its lifetime.
// Value object
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
public string Country { get; set; }
}
// Entity owning the value object
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public Address ShippingAddress { get; set; }
public Address BillingAddress { get; set; }
}
// Configuration
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.OwnsOne(c => c.ShippingAddress, sa =>
{
sa.Property(a => a.Street).HasColumnName("ShippingStreet");
sa.Property(a => a.City).HasColumnName("ShippingCity");
});
modelBuilder.Entity()
.OwnsOne(c => c.BillingAddress, ba =>
{
ba.Property(a => a.Street).HasColumnName("BillingStreet");
ba.Property(a => a.City).HasColumnName("BillingCity");
});
}
Result: All columns in same table:
Customers Table:
Id | Name | ShippingStreet | ShippingCity | ... | BillingStreet | BillingCity | ...
Owned Collections:
public class Order
{
public int Id { get; set; }
public ICollection Items { get; set; }
}
public class OrderItem
{
public string ProductName { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.OwnsMany(o => o.Items, item =>
{
item.WithOwner().HasForeignKey("OrderId");
item.Property("Id");
item.HasKey("Id");
});
}
Owned Types in Separate Table:
modelBuilder.Entity()
.OwnsOne(c => c.ShippingAddress)
.ToTable("ShippingAddresses");
Table Splitting Multiple entity types share the same table.
public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public OrderDetails Details { get; set; }
}
public class OrderDetails
{
public int OrderId { get; set; }
public string Notes { get; set; }
public string ShippingMethod { get; set; }
public DateTime? DeliveryDate { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.ToTable("Orders");
modelBuilder.Entity()
.ToTable("Orders"); // Same table!
// Configure relationship
modelBuilder.Entity()
.HasOne(o => o.Details)
.WithOne()
.HasForeignKey(d => d.OrderId);
// Shared primary key
modelBuilder.Entity()
.HasKey(d => d.OrderId);
}
Result: Single table with columns from both entities:
Orders Table:
Id | OrderDate | Notes | ShippingMethod | DeliveryDate
Benefits of Table Splitting:
- Logical separation of concerns in code
- Single table in database
- Can load entities independently
- Useful for large tables with many columns
Usage Example:
// Load only main entity
var order = context.Orders
.AsNoTracking()
.First();
// Load with details
var orderWithDetails = context.Orders
.Include(o => o.Details)
.First();
Owned Entity Navigation:
// Query owned entity
var customersInNewYork = context.Customers
.Where(c => c.ShippingAddress.City == "New York")
.ToList();
Key Differences:
| Feature | Owned Entities | Table Splitting |
|---|---|---|
| Relationship | One-to-one or one-to-many | One-to-one only |
| Identity | No separate identity | Separate entities |
| Querying | Part of owner | Can query independently |
| Use Case | Value objects | Logical separation |