EF Core的Owned Types(所属类型)是处理复杂类型映射的核心功能,它允许我们把一个复杂的值对象作为另一个实体的一部分,自动将其属性映射到对应的数据库表中,不需要单独为复杂类型创建独立的DbSet和主键。

Owned Types的核心特性
Owned Types本质上是一种特殊配置的实体类型,它有以下核心特点:
- 没有独立的标识,它的生命周期完全依附于所属的主实体
- 不需要定义自己的主键,EF Core会自动处理其存储结构
- 可以映射到主实体的同一张表(表拆分),也可以映射到独立的关联表
- 支持嵌套,即Owned Types内部还可以包含其他的Owned Types
基础使用示例
我们首先定义一个常见的业务场景:用户实体包含地址信息,地址是一个包含省、市、街道、邮编的复杂类型,我们用Owned Types来映射这个结构。
定义实体和复杂类型
先定义用户实体和地址复杂类型:
// 地址复杂类型,不需要定义主键
public class Address
{
public string Province { get; set; }
public string City { get; set; }
public string Street { get; set; }
public string ZipCode { get; set; }
}
// 用户主实体
public class User
{
public int Id { get; set; }
public string Name { get; set; }
// 引用地址复杂类型
public Address HomeAddress { get; set; }
public Address WorkAddress { get; set; }
}
配置Owned Types
在DbContext的OnModelCreating方法中配置Address为Owned Type:
public class AppDbContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 配置Address为所属类型
modelBuilder.Entity<User>().OwnsOne(
u => u.HomeAddress,
address =>
{
// 可以单独配置地址属性的列名
address.Property(a => a.Province).HasColumnName("HomeProvince");
address.Property(a => a.City).HasColumnName("HomeCity");
address.Property(a => a.Street).HasColumnName("HomeStreet");
address.Property(a => a.ZipCode).HasColumnName("HomeZipCode");
});
// 配置第二个地址属性
modelBuilder.Entity<User>().OwnsOne(
u => u.WorkAddress,
address =>
{
address.Property(a => a.Province).HasColumnName("WorkProvince");
address.Property(a => a.City).HasColumnName("WorkCity");
address.Property(a => a.Street).HasColumnName("WorkStreet");
address.Property(a => a.ZipCode).HasColumnName("WorkZipCode");
});
// 也可以直接使用Entity方法配置所属类型
// modelBuilder.Entity<User>().OwnsOne<Address>(u => u.HomeAddress);
}
}
生成的表结构
上述配置会将Address的所有属性映射到User表的对应列中,生成的Users表结构如下:
| 列名 | 类型 | 说明 |
|---|---|---|
| Id | int | 用户主键 |
| Name | nvarchar | 用户名称 |
| HomeProvince | nvarchar | 家庭地址省份 |
| HomeCity | nvarchar | 家庭地址城市 |
| HomeStreet | nvarchar | 家庭地址街道 |
| HomeZipCode | nvarchar | 家庭地址邮编 |
| WorkProvince | nvarchar | 工作地址省份 |
| WorkCity | nvarchar | 工作地址城市 |
| WorkStreet | nvarchar | 工作地址街道 |
| WorkZipCode | nvarchar | 工作地址邮编 |
映射到独立表
如果不想将Owned Types的属性合并到主表中,也可以配置它映射到独立的关联表,此时EF Core会自动生成外键关联:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().OwnsOne(
u => u.HomeAddress,
address =>
{
// 配置映射到独立的表,表名可以自定义
address.ToTable("UserHomeAddresses");
// 配置外键列名
address.WithOwner().HasForeignKey("UserId");
});
}
这样就会生成UserHomeAddresses表,包含UserId作为外键关联Users表,同时存储所有地址属性。
集合类型的Owned Types
如果主实体包含Owned Types的集合,EF Core也支持映射,此时会自动映射到独立的关联表:
// 用户实体包含多个联系方式
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public List<Contact> Contacts { get; set; }
}
// 联系方式复杂类型
public class Contact
{
public string Type { get; set; }
public string Number { get; set; }
}
// 配置集合类型的Owned Types
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().OwnsMany(
u => u.Contacts,
contact =>
{
// 配置独立表名
contact.ToTable("UserContacts");
// 集合类型的Owned Types需要显式定义主键
contact.HasKey(c => c.Id);
contact.Property(c => c.Id).ValueGeneratedOnAdd();
});
}
注意事项
- Owned Types不能单独被查询,必须依附于所属的主实体查询
- 修改Owned Types的属性后,EF Core会自动跟踪变更并更新对应的数据库字段
- 如果Owned Types内部还有嵌套的复杂类型,需要逐层配置OwnsOne或者OwnsMany
- 映射到独立表时,外键默认是必需的,不能设置为可空,除非主实体的所属属性是可空的
Owned Types非常适合映射那些没有独立业务身份、只作为其他实体一部分的值对象,能够有效简化复杂类型的映射配置,减少冗余代码。
EF_CoreOwned_Types复杂类型映射实体配置修改时间:2026-06-14 14:09:39