You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于EventSourcing的.NET应用聚合根事件变更内容存储方案咨询

基于聚合根的事件内容设计与EF Core实现方案

核心原则:聚焦聚合根边界内的精准变更

事件只需要记录聚合根标识、变更的具体对象/属性、变更后的值,不需要存储全量实体数据。比如修改Customer关联的Address某属性时,事件要包含:

  • CustomerId(聚合根唯一标识)
  • AddressId(关联子实体的唯一标识)
  • 变更的属性名(如Street)
  • 属性的新值

基于EF Core Change Tracker的实现步骤

1. 筛选聚合根及关联子实体的变更

在DbContext的SaveChanges方法中,遍历ChangeTracker,只处理聚合根实体(如Customer)和其边界内的子实体(如Address)的Modified状态条目。

2. 提取精准变更内容

对比实体的OriginalValues和CurrentValues,只提取真正发生变化的属性,避免冗余数据。对于子实体的变更,必须关联到所属的聚合根ID,确保事件归属清晰。

3. 定义强类型事件

不要用泛型或模糊的事件结构,针对不同变更场景创建强类型事件类,便于后续事件溯源和处理。

4. 原子性写入事件与数据库变更

确保数据库变更和事件写入在同一个事务内,避免数据不一致。

代码示例

强类型事件定义

// Address属性变更事件
public class CustomerAddressPropertyUpdatedEvent
{
    public Guid CustomerId { get; set; }
    public Guid AddressId { get; set; }
    public string PropertyName { get; set; }
    public object NewValue { get; set; }
    public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}

// Customer自身属性变更事件
public class CustomerPropertyUpdatedEvent
{
    public Guid CustomerId { get; set; }
    public string PropertyName { get; set; }
    public object NewValue { get; set; }
    public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}

DbContext中处理变更并生成事件

public class AppDbContext : DbContext
{
    private readonly IEventStore _eventStore;

    public AppDbContext(DbContextOptions<AppDbContext> options, IEventStore eventStore)
        : base(options)
    {
        _eventStore = eventStore;
    }

    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        var events = new List<object>();

        // 处理Customer关联的Address变更
        foreach (var entry in ChangeTracker.Entries<Address>()
            .Where(e => e.State == EntityState.Modified))
        {
            var address = entry.Entity;
            // 获取所属的Customer聚合根
            var customer = ChangeTracker.Entries<Customer>()
                .FirstOrDefault(c => c.Entity.Id == address.CustomerId)?.Entity;
            
            if (customer == null) continue;

            // 提取实际变更的属性
            var changedProperties = entry.OriginalValues.Properties
                .Where(p => !entry.OriginalValues[p].Equals(entry.CurrentValues[p]))
                .Select(p => new 
                { 
                    PropertyName = p.Name, 
                    NewValue = entry.CurrentValues[p] 
                });

            foreach (var prop in changedProperties)
            {
                events.Add(new CustomerAddressPropertyUpdatedEvent
                {
                    CustomerId = customer.Id,
                    AddressId = address.Id,
                    PropertyName = prop.PropertyName,
                    NewValue = prop.NewValue
                });
            }
        }

        // 处理Customer自身的属性变更
        foreach (var entry in ChangeTracker.Entries<Customer>()
            .Where(e => e.State == EntityState.Modified))
        {
            var customer = entry.Entity;
            var changedProperties = entry.OriginalValues.Properties
                .Where(p => !entry.OriginalValues[p].Equals(entry.CurrentValues[p]))
                .Select(p => new 
                { 
                    PropertyName = p.Name, 
                    NewValue = entry.CurrentValues[p] 
                });

            foreach (var prop in changedProperties)
            {
                events.Add(new CustomerPropertyUpdatedEvent
                {
                    CustomerId = customer.Id,
                    PropertyName = prop.PropertyName,
                    NewValue = prop.NewValue
                });
            }
        }

        // 开启事务保证数据库变更与事件写入原子性
        using var transaction = Database.BeginTransaction();
        try
        {
            var result = base.SaveChanges(acceptAllChangesOnSuccess);
            
            // 写入事件到EventStore
            foreach (var evnt in events)
            {
                _eventStore.AppendEvent(evnt);
            }

            transaction.Commit();
            return result;
        }
        catch
        {
            transaction.Rollback();
            throw;
        }
    }
}

关键注意事项

  • 聚合根边界清晰:确保子实体(如Address)无法脱离聚合根(Customer)单独存在,所有子实体的变更都归属于聚合根事件。
  • 避免过度反射:反射仅用于对比属性值,事件类型必须是强类型,降低后续维护成本。
  • 事件版本控制:如果后续事件结构需要调整,要添加版本号(如CustomerAddressPropertyUpdatedEventV2),避免溯源时出现解析错误。
  • 事件序列化:写入EventStore时,选择稳定的序列化方式(如JSON),确保事件可以长期解析。

内容的提问来源于stack exchange,提问作者William Peter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 08:30:17