文章目录
前言
在 Entity Framework Core 中实现值对象(Value Object)主要有两种方式,根据 EF Core 版本选择合适的方法。
一、核心概念
- 值对象特点:
- 无唯一标识(ID)
- 不可变(immutable)
- 通过属性值定义相等性
- 属于聚合实体的一部分(内嵌在实体中)
二、实现方式
方式一(使用 Owned Entity Types)
1.定义值对象类(不可变设计)
代码示例:
public record Address // 使用 record 类型自动实现不可变性和值相等性 { public string Street { get; init; } public string City { get; init; } public string PostalCode { get; init; } // 构造函数(强制完整初始化) public Address(string street, string city, string postalCode) { Street = street; City = city; PostalCode = postalCode; } }
2.在实体中引用值对象
- 代码示例
public class Customer { public int Id { get; private set; } public Address ShippingAddress { get; private set; } // 值对象属性 // 设置发送地址的方法(保持不可变性) public void SetShippingAddress(Address address) => ShippingAddress = address; }
3.在 DbContext 中配置
- 代码示例
internal class CustomerConfig : IEntityTypeConfiguration<Customer> { public void Configure(EntityTypeBuilder<Customer> builder) { builder.OwnsOne(b => b.ShippingAddress, la => { la.Property(w => w.Street).HasMaxLength(100); la.Property(w=>w.City).HasMaxLength(20); la.Property(w=>w.PostalCode).HasColumnType("varchar(255)"); //可选:配置列名,入不配置,以默认方式命名列,如:ShippingAddress_Street la.Property(w => w.Street).HasColumnName("ShippingStreet"); la.Property(w=>w.City).HasColumnName("ShippingCity"); la.Property(w=>w.PostalCode).HasColumnName("ShippingPostalCode"); }); } }
方式二(Complex Types,适用于EF Core 8.0+)
1.标记值对象类
- 代码示例
[ComplexType] // EF Core 8+ 特性 public record Address(string Street, string City, string PostalCode);
2.实体中使用值对象(同 Owned Types)
代码示例
public class Customer { public int Id { get; set; } public Address ShippingAddress { get; set; } }
3.自动映射(无需在 DbContext 中额外配置)
默认列名如:ShippingAddress_Street, ShippingAddress_City 等
如需自定义列名仍需配置:
internal class CustomerConfig : IEntityTypeConfiguration<Customer> { public void Configure(EntityTypeBuilder<Customer> builder) { builder.ComplexProperty(e => e.ShippingAddress, la => { la.Property(c => c.Street).HasColumnName("ShippingStreet"); }); } }
三、两种方式对比
特性 | Owned Types | Complex Types (EF Core 8+) |
---|---|---|
EF Core 版本 | ≥ 2.0 | ≥ 8.0 |
数据库存储 | 内联在父实体表中 | 同左 |
配置复杂度 | 需手动配置 OwnsOne | 自动配置(可选自定义) |
嵌套值对象支持 | 支持 | 支持 |
变更跟踪 | 作为父实体一部分 | 同左 |
DDD 语义匹配度 | 良好 | 更准确 |
四、重要注意事项
- 不可变性设计
- 使用 init 或 private set
- 避免公共 setter
- 通过构造函数/方法整体替换值对象
- 数据库列名
- 默认命名:ShippingAddress_Street
- 推荐显式配置列名避免歧义
- 空值处理
- 值对象属性可为 null
- 业务逻辑需验证非空性
- 值对象集合
- 使用 OwnsMany(Owned Types):
builder.OwnsMany(e => e.LineItems, la => { la.ToTable("OrderLineItems"); });
- EF Core 8+ 暂不支持 ComplexType 集合
- 使用 OwnsMany(Owned Types):
总结
- 新项目(EF Core 8+):优先使用 [ComplexType]
- 旧项目(EF Core <8):使用 OwnsOne/OwnsMany
- 始终重写值对象的 Equals() 和 GetHashCode()
- 推荐使用 record 类型简化不可变对象实现
通过以上方法,您可以在 EF Core 中正确实现领域驱动设计中的值对象,保持业务模型的纯净性和持久化的有效性。