EF Core 中实现值对象

发布于:2025-06-25 ⋅ 阅读:(24) ⋅ 点赞:(0)


前言

在 Entity Framework Core 中实现值对象(Value Object)主要有两种方式,根据 EF Core 版本选择合适的方法。

一、核心概念

  • 值对象特点:
    • 无唯一标识(ID)
    • 不可变(immutable)
    • 通过属性值定义相等性
    • 属于聚合实体的一部分(内嵌在实体中)

二、实现方式

方式一(使用 Owned Entity Types)

1.定义值对象类(不可变设计)

  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.在实体中引用值对象

  1. 代码示例
    public class Customer
    {
        public int Id { get; private set; }
        public Address ShippingAddress { get; private set; } // 值对象属性
        
        // 设置发送地址的方法(保持不可变性)
        public void SetShippingAddress(Address address)
            => ShippingAddress = address;
    }
    

3.在 DbContext 中配置

  1. 代码示例
    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.标记值对象类

  1. 代码示例
    [ComplexType] // EF Core 8+ 特性
    public record Address(string Street, string City, string PostalCode);
    

2.实体中使用值对象(同 Owned Types)

  1. 代码示例

    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 集合

总结

  • 新项目(EF Core 8+):优先使用 [ComplexType]
  • 旧项目(EF Core <8):使用 OwnsOne/OwnsMany
  • 始终重写值对象的 Equals()GetHashCode()
  • 推荐使用 record 类型简化不可变对象实现

通过以上方法,您可以在 EF Core 中正确实现领域驱动设计中的值对象,保持业务模型的纯净性和持久化的有效性。


网站公告

今日签到

点亮在社区的每一天
去签到