.NET应用如何无异常读取EventStore中版本为0或不存在的流?
无异常读取EventStore流版本的实现方案
针对使用EventStore.Client.Grpc.Streams 23.1.0时,读取不存在的流会抛出异常的问题,可通过以下无异常方式判断流状态,实现业务逻辑:
核心思路
利用ReadStreamAsync方法的throwOnStreamNotFound参数(设为false)避免流不存在时抛出异常;通过读取流的事件判断流状态:
- 若返回的事件集合为空,说明流不存在或版本为0(无事件),此时发送
TenantCreated事件 - 若存在事件,说明流版本>0,不发送事件
代码实现
using EventStore.Client; using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; public class EventStoreTenantHandler { private readonly EventStoreClient _client; public EventStoreTenantHandler(EventStoreClient client) { _client = client; } public async Task ProcessTenantStreamAsync(string tenantStreamId, CancellationToken cancellationToken = default) { // 反向读取流的最后1个事件,禁用流不存在时的异常抛出 var streamEvents = _client.ReadStreamAsync( Direction.Backwards, tenantStreamId, StreamRevision.End, maxCount: 1, throwOnStreamNotFound: false, cancellationToken: cancellationToken); // 判断流是否存在且有事件(版本>0) var hasExistingEvents = await streamEvents.AnyAsync(cancellationToken); if (!hasExistingEvents) { // 构造TenantCreated事件 var tenantCreatedEvent = new EventData( Uuid.NewUuid(), "TenantCreated", JsonSerializer.SerializeToUtf8Bytes(new { TenantId = tenantStreamId, CreatedAt = DateTime.UtcNow })); // 写入事件:兼容流不存在或版本0的场景 await _client.AppendToStreamAsync( tenantStreamId, ExpectedRevision.Any, new[] { tenantCreatedEvent }, cancellationToken: cancellationToken); } } }
关键细节说明
throwOnStreamNotFound: false:禁用流不存在时的异常抛出,返回空事件集合,避免异常带来的性能开销- 反向读取最后1个事件:相比读取全部事件,反向读取最后1个事件更高效,只需判断是否存在事件即可确定版本是否>0
- 并发安全处理:使用
ExpectedRevision.Any写入事件,即使检查流状态后有并发写入,也不会导致冲突(若其他进程已写入TenantCreated事件,本次写入会被忽略,符合业务逻辑)
注意事项
- 需要引用
System.Linq.Async包以使用IAsyncEnumerable的AnyAsync()扩展方法 - 若需严格保证仅第一次写入成功(避免重复事件),可将
ExpectedRevision.Any替换为ExpectedRevision.NoStream,并捕获WrongExpectedVersionException,在异常中检查流版本是否为0后再次尝试写入(该场景极少,视业务需求选择)
内容的提问来源于stack exchange,提问作者Maxim Kitsenko
相关产品推荐
相关产品推荐

