C# CQRS与文件事件 C#如何将文件上传、修改、删除作为事件源

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

文件系统变化怎么变成 CQRS 里的事件

Windows 上的

FileSystemWatcher
是最直接的入口,但它不是事件总线,也不能直接塞进 CQRS 的
IEvent
流程里。你得在它触发后,把原始通知包装成领域事件,再交给事件总线(比如 MediatR 或自建的
IEventPublisher
)分发。

常见错误是直接在

Changed
回调里做业务逻辑或保存数据库——这会卡住文件监听线程,导致漏事件、重复触发,甚至
FileSystemWatcher
自动停止。

只在回调里提取关键信息:路径、变更类型(
Created
/
Changed
/
Deleted
)、时间戳、是否是目录
Task.Run
await
转到后台处理,避免阻塞
FileSystemWatcher
的内部线程池
对同一文件的连续修改(比如保存 Word 文档)会触发多次
Changed
,加个简单去重缓存(如
ConcurrentDictionary<string datetime></string>
),500ms 内相同路径只发一次
FileModifiedEvent

CQRS 事件类该怎么设计才不踩坑

别把

FileSystemEventArgs
直接当领域事件用。CQRS 要求事件是不可变、语义清晰、面向业务的——
FileRenamedEvent
FileSystemChangedEvent
更好理解,也更利于后续重放或审计。

容易忽略的是版本和序列号:文件操作没有天然顺序,但 CQRS 事件流必须有序。建议在事件基类里加

SequenceNumber
(由事件存储生成)和
OccurredAtUtc
(用
DateTime.UtcNow
,别用
Now
)。

事件类必须是 public、无参构造、所有属性 get/set,否则序列化(如 JSON.NET 或 MessagePack)可能失败 路径字段统一用
string
,不要存
FileInfo
FileStream
——它们不能跨进程/序列化
删除事件要包含原文件大小、哈希(如果之前上传时计算过),否则审计时无法确认删的是哪个版本

上传/修改/删除动作如何与文件事件对齐

用户点击“上传”不是文件事件的起点,而是命令(

UploadFileCommand
)。真正的事件源有两个:命令执行成功后显式发布
FileUploadedEvent
,以及
FileSystemWatcher
捕获到磁盘写入完成后的
Created
事件。二者要能关联上——靠同一个
CorrelationId
字段。

典型场景:Web API 接收上传 → 保存到

uploads/
目录 → 发布
FileUploadedEvent
(含
CorrelationId
)→
FileSystemWatcher
监听到该路径创建 → 发布带相同
CorrelationId
FileSyncedEvent
。这样就能追踪“用户上传”到“磁盘落盘”的完整链路。

不要依赖
FileSystemWatcher
Created
等于“上传完成”——大文件写入可能分块,
Created
只表示文件句柄打开,内容未必写完
修改场景同理:先发
FileUpdatedCommand
,服务端改完再写磁盘,最后由监听器补发
FileUpdatedEvent
,而非监听
Changed
就发
删除操作必须走命令(
DeleteFileCommand
),禁止前端直删磁盘;否则事件流断裂,审计日志缺失操作人、原因等上下文

FileSystemWatcher 在生产环境为什么经常失效

它不是为高可靠事件总线设计的:缓冲区默认只有 8KB,超量就丢事件;监视网络路径(SMB)基本不可靠;权限不足时静默失败,不抛异常;进程重启后监听丢失——这些都导致事件空洞。

真正能落地的方式是“双源校验”:以定期扫描(如每分钟查

Directory.GetFiles
+
FileInfo.LastWriteTimeUtc
)作为兜底,和
FileSystemWatcher
输出做比对。差异项补发事件,并记录告警。

务必设置
NotifyFilter
,只监听需要的类型(如
NotifyFilters.FileName | NotifyFilters.LastWrite
),减少内核通知压力
启用
IncludeSubdirectories = true
时,子目录新增会触发两次事件(父目录
Created
+ 子目录
Created
),需在去重逻辑里一并处理
Linux/macOS 不支持
FileSystemWatcher
(.NET 6+ 有部分改进但仍有缺陷),跨平台项目必须用
System.IO.Pipelines
+
inotify
或第三方库如
Libuv
替代
事情说清了就结束。文件事件做 CQRS 源头最难的不是监听,而是让“磁盘发生了什么”和“业务认为发生了什么”始终对得上——中间差的那层映射逻辑,没人能帮你写。

相关推荐