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

.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);
        }
    }
}

关键细节说明

  1. throwOnStreamNotFound: false:禁用流不存在时的异常抛出,返回空事件集合,避免异常带来的性能开销
  2. 反向读取最后1个事件:相比读取全部事件,反向读取最后1个事件更高效,只需判断是否存在事件即可确定版本是否>0
  3. 并发安全处理:使用ExpectedRevision.Any写入事件,即使检查流状态后有并发写入,也不会导致冲突(若其他进程已写入TenantCreated事件,本次写入会被忽略,符合业务逻辑)

注意事项

  • 需要引用System.Linq.Async包以使用IAsyncEnumerable的AnyAsync()扩展方法
  • 若需严格保证仅第一次写入成功(避免重复事件),可将ExpectedRevision.Any替换为ExpectedRevision.NoStream,并捕获WrongExpectedVersionException,在异常中检查流版本是否为0后再次尝试写入(该场景极少,视业务需求选择)

内容的提问来源于stack exchange,提问作者Maxim Kitsenko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 10:52:38