EF Core 中实现值对象


前言

在 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 TypesComplex 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 中正确实现领域驱动设计中的值对象,保持业务模型的纯净性和持久化的有效性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值