C# Event Sourcing事件溯源方法 C#如何基于事件来构建系统状态

来源:这里教程网 时间:2026-02-21 17:41:07 作者:

Event Sourcing 是什么,它不等于“加一堆 event”

Event Sourcing 的核心不是给类加

public event EventHandler<orderplaced></orderplaced>
,而是把状态变更**全部建模为不可变事件流**,系统当前状态由重放这些事件得到。你写的是“发生了什么”,而不是“现在是什么”。如果误以为只是用
event
关键字发通知,后续做快照、回溯、调试时会发现根本没法还原历史——因为那些事件没被持久化、没带完整上下文、没按时间顺序严格排序。

如何定义和存储事件(C# 实操要点)

事件是纯数据容器,必须满足:不可变、可序列化、带唯一 ID 和时间戳。不要在事件里存引用类型或业务逻辑。

record
(C# 9+)定义事件,比如
public record OrderPlaced(Guid OrderId, decimal Amount, DateTime OccurredAt);
存储时必须保证顺序和幂等性:推荐用支持有序追加的存储,如 PostgreSQL 的表(带
sequence
timestamp
排序字段)、SQL Server 的
IDENTITY
列,或专用事件存储如 EventStoreDB
避免用 Entity Framework 直接映射事件——它默认按主键查,而事件查询主场景是“按聚合 ID + 版本号范围读”,EF 容易生成低效 SQL

如何从事件重建聚合根状态(Apply 方法陷阱)

聚合根的

Apply
方法负责将事件“应用”到内存状态,但常见错误是让它承担副作用(如发邮件、调外部 API),或者让
Apply
修改非当前聚合的状态。

Apply
必须是纯函数式:只修改
this
的字段,不访问数据库、不抛异常(除非校验失败)、不触发其他事件
加载聚合时,先 new 出空实例,再按版本升序依次调用
Apply(event)
,顺序错一帧,状态就错到底
别在
Apply
里做复杂计算——比如“计算累计积分”,应改为事件自带
CumulativePoints
字段,否则重放时浮点误差或逻辑变更会导致状态不一致

为什么需要快照(Snapshot),以及怎么安全地用

当一个聚合产生上千个事件,每次重建都重放太慢。快照就是某个版本的状态“照片”,但它不是可选优化,而是实际生产中必须处理的环节。

快照应在聚合版本达到阈值(如 100)后自动创建,保存聚合 ID、快照版本(即该快照对应最后那个事件的版本号)、序列化后的状态 加载时,先查最新快照,再查该快照版本之后的所有事件,合并重放——注意:快照本身不能替代事件,事件仍是唯一真相源 快照格式必须向后兼容:如果改了聚合字段,旧快照反序列化会失败。建议用显式 DTO(如
OrderSnapshotV1
)而非直接序列化聚合根

最常被忽略的是事件版本与快照版本的对齐逻辑——差一个版本,就可能漏掉一次状态变更。这事关数据正确性,不是性能问题。

相关推荐