protected override async Task <ReadStreamPage> ReadStreamForwardsInternal( string streamId, int start, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken) { using (var connection = _createConnection()) { await connection.OpenAsync(cancellationToken).ConfigureAwait(false); var streamIdInfo = new StreamIdInfo(streamId); return(await ReadStreamInternal( streamIdInfo.SqlStreamId, start, count, ReadDirection.Forward, prefetch, readNext, connection, null, cancellationToken)); } }
protected override async Task <ReadStreamPage> ReadStreamForwardsInternal( string streamId, int start, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken) { try { using (var connection = await OpenConnection(cancellationToken)) using (var transaction = await connection .BeginTransactionAsync(cancellationToken) .NotOnCapturedContext()) { var streamIdInfo = new StreamIdInfo(streamId); return(await ReadStreamInternal( streamIdInfo.MySqlStreamId, start, count, ReadDirection.Forward, prefetch, readNext, transaction, cancellationToken)); } } catch (MySqlException exception) when(exception.InnerException is ObjectDisposedException disposedException) { throw new ObjectDisposedException(disposedException.Message, exception); } }
protected override async Task <ReadStreamPage> ReadStreamBackwardsInternal( string streamId, int start, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken) { using (var connection = _createConnection()) { await connection.OpenAsync(cancellationToken).NotOnCapturedContext(); var streamIdInfo = new StreamIdInfo(streamId); var(page, _) = await ReadStreamInternal( streamIdInfo.SqlStreamId, start, count, ReadDirection.Backward, prefetch, readNext, connection, null, cancellationToken); return(page); } }
public async Task <ReadStreamPage> ReadStreamBackwards( string streamId, int fromVersionInclusive, int maxCount, bool prefetchJsonData, CancellationToken cancellationToken = default(CancellationToken)) { Ensure.That(streamId, nameof(streamId)).IsNotNullOrWhiteSpace(); Ensure.That(fromVersionInclusive, nameof(fromVersionInclusive)).IsGte(-1); Ensure.That(maxCount, nameof(maxCount)).IsGte(1); GuardAgainstDisposed(); cancellationToken.ThrowIfCancellationRequested(); if (Logger.IsDebugEnabled()) { Logger.DebugFormat("ReadStreamBackwards {streamId} from version {fromVersionInclusive} with max count " + "{maxCount}.", streamId, fromVersionInclusive, maxCount); } ReadNextStreamPage readNext = (nextVersion, ct) => ReadStreamBackwards(streamId, nextVersion, maxCount, prefetchJsonData, ct); var page = await ReadStreamBackwardsInternal(streamId, fromVersionInclusive, maxCount, prefetchJsonData, readNext, cancellationToken); return(await FilterExpired(page, readNext, cancellationToken)); }
public async Task <ReadStreamPage> ReadStreamBackwards( StreamId streamId, int fromVersionInclusive, int maxCount, bool prefetchJsonData = true, CancellationToken cancellationToken = default) { Ensure.That(fromVersionInclusive, nameof(fromVersionInclusive)).IsGte(-1); Ensure.That(maxCount, nameof(maxCount)).IsGte(1); GuardAgainstDisposed(); cancellationToken.ThrowIfCancellationRequested(); Logger.Debug( "ReadStreamBackwards {streamId} from version {fromVersionInclusive} with max count {maxCount}.", streamId, fromVersionInclusive, maxCount); ReadNextStreamPage readNext = (nextVersion, ct) => ReadStreamBackwards(streamId, nextVersion, maxCount, prefetchJsonData, ct); var page = await ReadStreamBackwardsInternal(streamId, fromVersionInclusive, maxCount, prefetchJsonData, readNext, cancellationToken); return(await FilterExpired(page, readNext, cancellationToken)); }
protected abstract Task <ReadStreamPage> ReadStreamForwardsInternal( string streamId, int start, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken);
protected override async Task <ReadStreamPage> ReadStreamBackwardsInternal( string streamId, int fromVersionInclusive, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken) { using (var connection = await OpenConnection(cancellationToken)) using (var transaction = await connection .BeginTransactionAsync(cancellationToken) .NotOnCapturedContext()) { var streamIdInfo = new StreamIdInfo(streamId); return(await ReadStreamInternal( streamIdInfo.MySqlStreamId, fromVersionInclusive, count, ReadDirection.Backward, prefetch, readNext, transaction, cancellationToken)); } }
protected override async Task <ReadStreamPage> ReadStreamBackwardsInternal( string streamId, int fromVersionInclusive, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken) { try { using (var connection = await OpenConnection(cancellationToken)) using (var transaction = await connection .BeginTransactionAsync(cancellationToken) .ConfigureAwait(false)) { var streamIdInfo = new StreamIdInfo(streamId); return(await ReadStreamInternal( streamIdInfo.MySqlStreamId, fromVersionInclusive, count, ReadDirection.Backward, prefetch, readNext, transaction, cancellationToken)); } } catch (MySqlException exception) when(exception.InnerException is ObjectDisposedException disposedException) { throw new ObjectDisposedException(disposedException.Message, exception); } }
private async Task <ReadStreamPage> FilterExpired( ReadStreamPage page, ReadNextStreamPage readNext, CancellationToken cancellationToken) { if (page.StreamId.StartsWith("$")) { return(page); } int?maxAge = _metadataMaxAgeCache == null ? null : await _metadataMaxAgeCache.GetMaxAge(page.StreamId, cancellationToken); if (!maxAge.HasValue) { return(page); } var currentUtc = GetUtcNow(); var valid = new List <StreamMessage>(); foreach (var message in page.Messages) { if (message.CreatedUtc.AddSeconds(maxAge.Value) > currentUtc) { valid.Add(message); } else { PurgeExpiredMessage(message); } } return(new ReadStreamPage( page.StreamId, page.Status, page.FromStreamVersion, page.NextStreamVersion, page.LastStreamVersion, page.LastStreamPosition, page.ReadDirection, page.IsEnd, readNext, valid.ToArray())); }
protected override async Task <ReadStreamPage> ReadStreamForwardsInternal( string streamId, int fromVersion, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken) { GuardAgainstDisposed(); cancellationToken.ThrowIfCancellationRequested(); using (var connection = OpenConnection()) { var streamProperties = await connection.Streams(streamId) .Properties(initializeIfNotFound: false, cancellationToken); if (streamProperties == null) { // not found. return(new ReadStreamPage( streamId, PageReadStatus.StreamNotFound, fromVersion, StreamVersion.End, StreamVersion.End, StreamVersion.End, ReadDirection.Forward, true, readNext)); } return(await PrepareStreamResponse(connection, streamId, ReadDirection.Forward, fromVersion, prefetch, readNext, count, streamProperties.MaxAge)); } }
/// <summary> /// Initialized a new instance of <see cref="ReadStreamPage"/>/ /// </summary> /// <param name="streamId">The id of the stream that was read.</param> /// <param name="status">The <see cref="PageReadStatus"/> of the read operation.</param> /// <param name="fromStreamVersion">The version of the stream that read from.</param> /// <param name="nextStreamVersion">The next message version that can be read.</param> /// <param name="lastStreamVersion">The version of the last message in the stream.</param> /// <param name="lastStreamPosition">The position of the last message in the stream.</param> /// <param name="direction">The direction of the read operation.</param> /// <param name="isEnd">Whether or not this is the end of the stream.</param> /// <param name="readNext">An operation to read the next page.</param> /// <param name="messages">The messages read.</param> public ReadStreamPage( string streamId, PageReadStatus status, int fromStreamVersion, int nextStreamVersion, int lastStreamVersion, long lastStreamPosition, ReadDirection direction, bool isEnd, ReadNextStreamPage readNext = null, StreamMessage[] messages = null) { StreamId = streamId; Status = status; FromStreamVersion = fromStreamVersion; LastStreamVersion = lastStreamVersion; LastStreamPosition = lastStreamPosition; NextStreamVersion = nextStreamVersion; ReadDirection = direction; IsEnd = isEnd; Messages = messages ?? new StreamMessage[0]; _readNext = readNext ?? ((_, __) => throw new NotSupportedException()); }
protected override Task <ReadStreamPage> ReadStreamForwardsInternal(string streamId, int start, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken) { throw new NotImplementedException(); }
protected abstract Task <ReadStreamPage> ReadStreamBackwardsInternal( string streamId, int fromVersionInclusive, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken);
protected override Task <ReadStreamPage> ReadStreamBackwardsInternal( string streamId, int fromVersionInclusive, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken) { GuardAgainstDisposed(); cancellationToken.ThrowIfCancellationRequested(); using (_lock.UseReadLock()) { InMemoryStream stream; if (!_streams.TryGetValue(streamId, out stream)) { var notFound = new ReadStreamPage(streamId, PageReadStatus.StreamNotFound, fromVersionInclusive, -1, -1, ReadDirection.Backward, true, StreamMessage.EmptyArray, s_readNextNotFound); return(Task.FromResult(notFound)); } var messages = new List <StreamMessage>(); var i = fromVersionInclusive == StreamVersion.End ? stream.Messages.Count - 1 : fromVersionInclusive; while (i >= 0 && count > 0) { var inMemorymessage = stream.Messages[i]; StreamMessage message; if (prefetch) { message = new StreamMessage( streamId, inMemorymessage.MessageId, inMemorymessage.StreamVersion, inMemorymessage.Position, inMemorymessage.Created, inMemorymessage.Type, inMemorymessage.JsonMetadata, inMemorymessage.JsonData); } else { message = new StreamMessage( streamId, inMemorymessage.MessageId, inMemorymessage.StreamVersion, inMemorymessage.Position, inMemorymessage.Created, inMemorymessage.Type, inMemorymessage.JsonMetadata, ct => Task.Run(() => ReadMessageData(streamId, inMemorymessage.MessageId), ct)); messages.Add(message); } messages.Add(message); i--; count--; } var lastStreamVersion = stream.Messages.Last().StreamVersion; var nextStreamVersion = messages.Last().StreamVersion - 1; var endOfStream = nextStreamVersion < 0; var page = new ReadStreamPage( streamId, PageReadStatus.Success, fromVersionInclusive, nextStreamVersion, lastStreamVersion, ReadDirection.Backward, endOfStream, messages.ToArray(), readNext); return(Task.FromResult(page)); } }
private async Task <ReadStreamPage> ReadStreamInternal( SqlStreamId sqlStreamId, int start, int count, ReadDirection direction, bool prefetch, ReadNextStreamPage readNext, SqlConnection connection, CancellationToken cancellationToken) { // If the count is int.MaxValue, TSql will see it as a negative number. // Users shouldn't be using int.MaxValue in the first place anyway. count = count == int.MaxValue ? count - 1 : count; // To read backwards from end, need to use int MaxValue var streamVersion = start == StreamVersion.End ? int.MaxValue : start; string commandText; Func <List <StreamMessage>, int, int> getNextVersion; if (direction == ReadDirection.Forward) { commandText = prefetch ? _scripts.ReadStreamForwardWithData : _scripts.ReadStreamForward; getNextVersion = (events, lastVersion) => { if (events.Any()) { return(events.Last().StreamVersion + 1); } return(lastVersion + 1); }; } else { commandText = prefetch ? _scripts.ReadStreamBackwardWithData : _scripts.ReadStreamBackward; getNextVersion = (events, lastVersion) => { if (events.Any()) { return(events.Last().StreamVersion - 1); } return(-1); }; } using (var command = new SqlCommand(commandText, connection)) { command.Parameters.AddWithValue("streamId", sqlStreamId.Id); command.Parameters.AddWithValue("count", count + 1); //Read extra row to see if at end or not command.Parameters.AddWithValue("streamVersion", streamVersion); using (var reader = await command.ExecuteReaderAsync(cancellationToken).NotOnCapturedContext()) { await reader.ReadAsync(cancellationToken).NotOnCapturedContext(); if (reader.IsDBNull(0)) { return(new ReadStreamPage( sqlStreamId.IdOriginal, PageReadStatus.StreamNotFound, start, -1, -1, direction, true, StreamMessage.EmptyArray, readNext)); } var lastStreamVersion = reader.GetInt32(0); await reader.NextResultAsync(cancellationToken).NotOnCapturedContext(); var messages = new List <StreamMessage>(); while (await reader.ReadAsync(cancellationToken).NotOnCapturedContext()) { var streamVersion1 = reader.GetInt32(0); var ordinal = reader.GetInt64(1); var eventId = reader.GetGuid(2); var created = reader.GetDateTime(3); var type = reader.GetString(4); var jsonMetadata = reader.GetString(5); Func <CancellationToken, Task <string> > getJsonData; if (prefetch) { var jsonData = reader.GetString(6); getJsonData = _ => Task.FromResult(jsonData); } else { getJsonData = ct => GetJsonData(sqlStreamId.Id, streamVersion1, ct); } var message = new StreamMessage( sqlStreamId.IdOriginal, eventId, streamVersion1, ordinal, created, type, jsonMetadata, getJsonData); messages.Add(message); } var isEnd = true; if (messages.Count == count + 1) { isEnd = false; messages.RemoveAt(count); } return(new ReadStreamPage( sqlStreamId.IdOriginal, PageReadStatus.Success, start, getNextVersion(messages, lastStreamVersion), lastStreamVersion, direction, isEnd, messages.ToArray(), readNext)); } } }
private async Task <(ReadStreamPage, StreamMeta)> ReadStreamInternal( SqlStreamId sqlStreamId, int start, int count, ReadDirection direction, bool prefetch, ReadNextStreamPage readNext, SqlConnection connection, SqlTransaction transaction, CancellationToken cancellationToken) { // If the count is int.MaxValue, TSql will see it as a negative number. // Users shouldn't be using int.MaxValue in the first place anyway. count = count == int.MaxValue ? count - 1 : count; // To read backwards from end, need to use int MaxValue var streamVersion = start == StreamVersion.End ? int.MaxValue : start; string commandText; Func <List <(StreamMessage, int?)>, int, int> getNextVersion; if (direction == ReadDirection.Forward) { commandText = prefetch ? _scripts.ReadStreamForwardWithData : _scripts.ReadStreamForward; getNextVersion = (events, lastVersion) => events.Any() ? events.Last().Item1.StreamVersion + 1 : lastVersion + 1; } else { commandText = prefetch ? _scripts.ReadStreamBackwardWithData : _scripts.ReadStreamBackward; getNextVersion = (events, lastVersion) => events.Any() ? events.Last().Item1.StreamVersion - 1 : -1; } using (var command = new SqlCommand(commandText, connection, transaction)) { command.Parameters.Add(new SqlParameter("streamId", SqlDbType.Char, 42) { Value = sqlStreamId.Id }); command.Parameters.AddWithValue("count", count + 1); //Read extra row to see if at end or not command.Parameters.AddWithValue("streamVersion", streamVersion); using (var reader = await command.ExecuteReaderAsync(cancellationToken).NotOnCapturedContext()) { await reader.ReadAsync(cancellationToken).NotOnCapturedContext(); if (reader.IsDBNull(0)) { return(new ReadStreamPage( sqlStreamId.IdOriginal, PageReadStatus.StreamNotFound, start, -1, -1, -1, direction, true, readNext), StreamMeta.None); } var lastStreamVersion = reader.GetInt32(0); var lastStreamPosition = reader.GetInt64(1); var maxAge = reader.GetNullableInt32(2); var maxCount = reader.GetNullableInt32(3); await reader.NextResultAsync(cancellationToken).NotOnCapturedContext(); var messages = new List <(StreamMessage, int?)>(); while (await reader.ReadAsync(cancellationToken).NotOnCapturedContext()) { if (messages.Count == count) { messages.Add(default);
protected override async Task <ReadStreamPage> ReadStreamBackwardsInternal( string streamId, int fromStreamVersion, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken) { GuardAgainstDisposed(); cancellationToken.ThrowIfCancellationRequested(); var streamVersion = fromStreamVersion == StreamVersion.End ? int.MaxValue - 1 : fromStreamVersion; using (var connection = OpenConnection()) { var streamProperties = await connection.Streams(streamId) .Properties(initializeIfNotFound: false, cancellationToken); if (streamProperties == null) { // not found. return(new ReadStreamPage( streamId, PageReadStatus.StreamNotFound, fromStreamVersion, StreamVersion.End, StreamVersion.End, StreamVersion.End, ReadDirection.Forward, true, readNext)); } var position = connection.Streams(streamId) .AllStreamPosition(ReadDirection.Backward, streamVersion); // if no position, then need to return success with end of stream. if (position == null) { // not found. return(new ReadStreamPage( streamId, PageReadStatus.Success, fromStreamVersion, StreamVersion.End, StreamVersion.End, StreamVersion.End, ReadDirection.Backward, true, readNext)); } return(await PrepareStreamResponse(connection, streamId, ReadDirection.Backward, fromStreamVersion, prefetch, readNext, count, streamProperties.MaxAge)); } }
private Task <ReadStreamPage> PrepareStreamResponse( SqliteConnection connection, string streamId, ReadDirection direction, int fromVersion, bool prefetch, ReadNextStreamPage readNext, int maxRecords, int?maxAge) { // If the count is int.MaxValue, TSql will see it as a negative number. // Users shouldn't be using int.MaxValue in the first place anyway. maxRecords = maxRecords == int.MaxValue ? maxRecords - 1 : maxRecords; var streamVersion = fromVersion == StreamVersion.End ? int.MaxValue - 1 : fromVersion; int nextVersion = 0; var stream = connection.Streams(streamId); var header = stream .Properties() .GetAwaiter().GetResult(); var position = stream .AllStreamPosition(direction, streamVersion) .GetAwaiter().GetResult(); var remaining = stream .Length(direction, position, CancellationToken.None) .GetAwaiter().GetResult(); var messages = stream .Read(direction, position, prefetch, maxRecords) .GetAwaiter().GetResult() .Select(Message => (Message, maxAge)) .ToList(); var filtered = FilterExpired(messages); var isEnd = remaining - messages.Count <= 0; if (direction == ReadDirection.Forward) { if (messages.Any()) { nextVersion = messages.Last().Message.StreamVersion + 1; } else { nextVersion = header.Version + 1; } } else if (direction == ReadDirection.Backward) { if (streamVersion == int.MaxValue - 1 && !messages.Any()) { nextVersion = StreamVersion.End; } if (messages.Any()) { nextVersion = messages.Last().Message.StreamVersion - 1; } } var page = new ReadStreamPage( streamId, status: PageReadStatus.Success, fromStreamVersion: fromVersion, nextStreamVersion: nextVersion, lastStreamVersion: header.Version, lastStreamPosition: header.Position, direction: direction, isEnd: isEnd, readNext: readNext, messages: filtered.ToArray()); return(Task.FromResult(page)); }
private async Task <ReadStreamPage> ReadStreamInternal( MySqlStreamId streamId, int start, int count, ReadDirection direction, bool prefetch, ReadNextStreamPage readNext, MySqlTransaction transaction, CancellationToken cancellationToken) { // If the count is int.MaxValue, TSql will see it as a negative number. // Users shouldn't be using int.MaxValue in the first place anyway. count = count == int.MaxValue ? count - 1 : count; // To read backwards from end, need to use int MaxValue var streamVersion = start == StreamVersion.End ? int.MaxValue : start; var messages = new List <(StreamMessage message, int?maxAge)>(); string procedure; Func <List <StreamMessage>, int, int> getNextVersion; if (direction == ReadDirection.Forward) { procedure = prefetch ? _schema.ReadStreamForwardsWithData : _schema.ReadStreamForwards; getNextVersion = (events, lastVersion) => events.Any() ? events.Last().StreamVersion + 1 : lastVersion + 1; } else { procedure = prefetch ? _schema.ReadStreamBackwardsWithData : _schema.ReadStreamBackwards; getNextVersion = (events, lastVersion) => events.Any() ? events.Last().StreamVersion - 1 : -1; } using (var command = BuildStoredProcedureCall( procedure, transaction, Parameters.StreamId(streamId), Parameters.Count(count + 1), Parameters.Version(streamVersion))) using (var reader = await command .ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken) .NotOnCapturedContext()) { if (!reader.HasRows) { return(new ReadStreamPage( streamId.IdOriginal, PageReadStatus.StreamNotFound, start, -1, -1, -1, direction, true, readNext)); } if (messages.Count == count) { messages.Add(default);
protected override Task <ReadStreamPage> ReadStreamBackwardsInternal(string streamId, int fromVersionInclusive, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken) { throw new NotImplementedException(); }
protected override Task <ReadStreamPage> ReadStreamForwardsInternal(string streamId, int start, int count, bool prefetch, ReadNextStreamPage readNext, CancellationToken cancellationToken) { GuardAgainstDisposed(); cancellationToken.ThrowIfCancellationRequested(); using (_lock.UseReadLock()) { InMemoryStream stream; if (!_streams.TryGetValue(streamId, out stream)) { var notFound = new ReadStreamPage( streamId, PageReadStatus.StreamNotFound, start, -1, -1, ReadDirection.Forward, true, StreamMessage.EmptyArray, s_readNextNotFound); return(Task.FromResult(notFound)); } var messages = new List <StreamMessage>(); var i = start; while (i < stream.Messages.Count && count > 0) { var inMemorymessage = stream.Messages[i]; StreamMessage message; if (prefetch) { message = new StreamMessage( streamId, inMemorymessage.MessageId, inMemorymessage.StreamVersion, inMemorymessage.Position, inMemorymessage.Created, inMemorymessage.Type, inMemorymessage.JsonMetadata, inMemorymessage.JsonData); } else { message = new StreamMessage( streamId, inMemorymessage.MessageId, inMemorymessage.StreamVersion, inMemorymessage.Position, inMemorymessage.Created, inMemorymessage.Type, inMemorymessage.JsonMetadata, ct => Task.Run(() => ReadMessageData(streamId, inMemorymessage.MessageId), ct)); } messages.Add(message); i++; count--; } var lastStreamVersion = stream.CurrentVersion; int nextStreamVersion; if (lastStreamVersion == -1) { nextStreamVersion = 0; } else if (messages.Count == 0) { nextStreamVersion = lastStreamVersion + 1; } else { nextStreamVersion = messages.Last().StreamVersion + 1; } var endOfStream = i == stream.Messages.Count; var page = new ReadStreamPage( streamId, PageReadStatus.Success, start, nextStreamVersion, lastStreamVersion, ReadDirection.Forward, endOfStream, messages.ToArray(), readNext); return(Task.FromResult(page)); } }
private async Task <ReadStreamPage> ReadStreamInternal( PostgresqlStreamId streamId, int start, int count, ReadDirection direction, bool prefetch, ReadNextStreamPage readNext, NpgsqlTransaction transaction, CancellationToken cancellationToken) { // If the count is int.MaxValue, TSql will see it as a negative number. // Users shouldn't be using int.MaxValue in the first place anyway. count = count == int.MaxValue ? count - 1 : count; // To read backwards from end, need to use int MaxValue var streamVersion = start == StreamVersion.End ? int.MaxValue : start; var messages = new List <(StreamMessage message, int?maxAge)>(); Func <List <StreamMessage>, int, int> getNextVersion; if (direction == ReadDirection.Forward) { getNextVersion = (events, lastVersion) => { if (events.Any()) { return(events.Last().StreamVersion + 1); } return(lastVersion + 1); }; } else { getNextVersion = (events, lastVersion) => { if (events.Any()) { return(events.Last().StreamVersion - 1); } return(-1); }; } var refcursorSql = new StringBuilder(); using (var command = BuildFunctionCommand( _schema.Read, transaction, Parameters.StreamId(streamId), Parameters.Count(count + 1), Parameters.Version(streamVersion), Parameters.ReadDirection(direction), Parameters.Prefetch(prefetch))) using (var reader = await command .ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken) .ConfigureAwait(false)) { while (await reader.ReadAsync(cancellationToken).ConfigureAwait(false)) { refcursorSql.AppendLine(Schema.FetchAll(reader.GetString(0))); } } using (var command = new NpgsqlCommand(refcursorSql.ToString(), transaction.Connection, transaction)) using (var reader = await command .ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken) .ConfigureAwait(false)) { if (!reader.HasRows) { return(new ReadStreamPage( streamId.IdOriginal, PageReadStatus.StreamNotFound, start, -1, -1, -1, direction, true, readNext)); } if (messages.Count == count) { messages.Add(default);