private async Task DeleteStreamAnyVersion(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            CancellationToken cancellationToken)
        {
            bool aStreamIsDeleted;

            using (var command = new SqlCommand(_scripts.DeleteStreamAnyVersion, connection, transaction))
            {
                command.Parameters.Add(new SqlParameter("streamId", SqlDbType.Char, 42)
                {
                    Value = sqlStreamId.Id
                });
                var i = await command
                        .ExecuteScalarAsync(cancellationToken)
                        .NotOnCapturedContext();

                aStreamIsDeleted = (int)i > 0;
            }

            if (aStreamIsDeleted && !_settings.DisableDeletionTracking)
            {
                var streamDeletedEvent = CreateStreamDeletedMessage(sqlStreamId.IdOriginal);
                await AppendToStreamExpectedVersionAny(
                    connection,
                    transaction,
                    SqlStreamId.Deleted,
                    new[] { streamDeletedEvent },
                    cancellationToken);
            }
        }
Пример #2
0
        private Task <MsSqlAppendResult> AppendToStreamInternal(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            GuardAgainstDisposed();

            return(RetryOnDeadLock(() =>
            {
                if (expectedVersion == ExpectedVersion.Any)
                {
                    return AppendToStreamExpectedVersionAny(
                        connection,
                        transaction,
                        sqlStreamId,
                        messages,
                        cancellationToken);
                }
                if (expectedVersion == ExpectedVersion.NoStream)
                {
                    return AppendToStreamExpectedVersionNoStream(
                        connection,
                        transaction,
                        sqlStreamId,
                        messages,
                        cancellationToken);
                }
                if (expectedVersion == ExpectedVersion.EmptyStream)
                {
                    return AppendToStreamExpectedVersion(
                        connection,
                        transaction,
                        sqlStreamId,
                        -1,
                        messages,
                        cancellationToken);
                }
                return AppendToStreamExpectedVersion(
                    connection,
                    transaction,
                    sqlStreamId,
                    expectedVersion,
                    messages,
                    cancellationToken);
            }));
        }
Пример #3
0
        private async Task <int> GetStreamVersionOfMessageId(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            Guid messageId,
            CancellationToken cancellationToken)
        {
            using (var command = new SqlCommand(_scripts.GetStreamVersionOfMessageId, connection, transaction))
            {
                command.Parameters.AddWithValue("streamId", sqlStreamId.Id);
                command.Parameters.AddWithValue("messageId", messageId);

                var result = await command.ExecuteScalarAsync(cancellationToken)
                             .NotOnCapturedContext();

                return((int)result);
            }
        }
Пример #4
0
        public StreamIdInfo(string idOriginal)
        {
            Ensure.That(idOriginal, "streamId").IsNotNullOrWhiteSpace();

            string id;
            Guid   _;

            if (Guid.TryParse(idOriginal, out _))
            {
                id = idOriginal; //If the ID is a GUID, don't bother hashing it.
            }
            else
            {
                using (var sha1 = SHA1.Create())
                {
                    var hashBytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(idOriginal));
                    id = BitConverter.ToString(hashBytes).Replace("-", string.Empty);
                }
            }
            SqlStreamId         = new SqlStreamId(id, idOriginal);
            MetadataSqlStreamId = new SqlStreamId("$$" + id, "$$" + idOriginal);
        }
Пример #5
0
        private async Task <int> GetStreamVersionOfMessageId(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            Guid messageId,
            CancellationToken cancellationToken)
        {
            using (var command = new SqlCommand(_scripts.GetStreamVersionOfMessageId, connection, transaction))
            {
                command.CommandTimeout = _commandTimeout;
                command.Parameters.Add(new SqlParameter("streamId", SqlDbType.Char, 42)
                {
                    Value = sqlStreamId.Id
                });
                command.Parameters.AddWithValue("messageId", messageId);

                var result = await command.ExecuteScalarAsync(cancellationToken)
                             .ConfigureAwait(false);

                return((int)result);
            }
        }
        private async Task <Tuple <int?, int> > AppendToStreamInternal(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            GuardAgainstDisposed();

            if (expectedVersion == ExpectedVersion.Any)
            {
                // Deadlock can occur when creating the stream for the first time and multiple threads attempting to
                // append with expected version any.
                return(await RetryOnDeadLock(() => AppendToStreamExpectedVersionAny(
                                                 connection,
                                                 transaction,
                                                 sqlStreamId,
                                                 messages,
                                                 cancellationToken)));
            }
            if (expectedVersion == ExpectedVersion.NoStream)
            {
                return(await AppendToStreamExpectedVersionNoStream(
                           connection,
                           transaction,
                           sqlStreamId,
                           messages,
                           cancellationToken));
            }
            return(await AppendToStreamExpectedVersion(
                       connection,
                       transaction,
                       sqlStreamId,
                       expectedVersion,
                       messages,
                       cancellationToken));
        }
Пример #7
0
        private async Task <int?> AppendToStreamInternal(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            CheckIfDisposed();

            if (expectedVersion == ExpectedVersion.Any)
            {
                // Deadlock can occur when creating the stream for the first time.
                return(await RetryOnDeadLock(() => AppendToStreamExpectedVersionAny(
                                                 connection,
                                                 transaction,
                                                 sqlStreamId,
                                                 messages,
                                                 cancellationToken)));
            }
            if (expectedVersion == ExpectedVersion.NoStream)
            {
                return(await AppendToStreamExpectedVersionNoStream(
                           connection,
                           transaction,
                           sqlStreamId,
                           messages,
                           cancellationToken));
            }
            return(await AppendToStreamExpectedVersion(
                       connection,
                       transaction,
                       sqlStreamId,
                       expectedVersion,
                       messages,
                       cancellationToken));
        }
Пример #8
0
        private async Task <MsSqlAppendResult> AppendToStreamExpectedVersion(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            int expectedVersion,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            var sqlDataRecords = CreateSqlDataRecords(messages);

            using (var command = new SqlCommand(_scripts.AppendStreamExpectedVersion, connection, transaction))
            {
                command.Parameters.Add(new SqlParameter("streamId", SqlDbType.Char, 42)
                {
                    Value = sqlStreamId.Id
                });
                command.Parameters.AddWithValue("expectedStreamVersion", expectedVersion);
                var eventsParam = CreateNewMessagesSqlParameter(sqlDataRecords);
                command.Parameters.Add(eventsParam);

                try
                {
                    using (var reader = await command
                                        .ExecuteReaderAsync(cancellationToken)
                                        .NotOnCapturedContext())
                    {
                        await reader.ReadAsync(cancellationToken).NotOnCapturedContext();

                        var currentVersion  = reader.GetInt32(0);
                        var currentPosition = reader.GetInt64(1);
                        int?maxCount        = null;

                        await reader.NextResultAsync(cancellationToken);

                        if (await reader.ReadAsync(cancellationToken).NotOnCapturedContext())
                        {
                            var jsonData        = reader.GetString(0);
                            var metadataMessage = SimpleJson.DeserializeObject <MetadataMessage>(jsonData);
                            maxCount = metadataMessage.MaxCount;
                        }

                        return(new MsSqlAppendResult(maxCount, currentVersion, currentPosition));
                    }
                }
                catch (SqlException ex)
                {
                    if (ex.Errors.Count == 1)
                    {
                        var sqlError = ex.Errors[0];
                        if (sqlError.Message == "WrongExpectedVersion")
                        {
                            // Idempotency handling. Check if the Messages have already been written.

                            var page = await ReadStreamInternal(
                                sqlStreamId,
                                expectedVersion + 1,
                                // when reading for already written Messages, it's from the one after the expected
                                messages.Length,
                                ReadDirection.Forward,
                                false,
                                null,
                                connection,
                                transaction,
                                cancellationToken);

                            if (messages.Length > page.Messages.Length)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, expectedVersion),
                                          ex);
                            }

                            // Iterate all messages an check to see if all message ids match
                            for (int i = 0; i < Math.Min(messages.Length, page.Messages.Length); i++)
                            {
                                if (messages[i].MessageId != page.Messages[i].MessageId)
                                {
                                    throw new WrongExpectedVersionException(
                                              ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, expectedVersion),
                                              ex);
                                }
                            }

                            return(new MsSqlAppendResult(
                                       null,
                                       page.LastStreamVersion,
                                       page.LastStreamPosition));
                        }
                    }
                    if (ex.IsUniqueConstraintViolation())
                    {
                        throw new WrongExpectedVersionException(
                                  ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, expectedVersion),
                                  ex);
                    }
                    throw;
                }
            }
        }
Пример #9
0
        private async Task <MsSqlAppendResult> AppendToStreamExpectedVersionNoStream(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            using (var command = new SqlCommand(_scripts.AppendStreamExpectedVersionNoStream, connection, transaction))
            {
                command.Parameters.Add(new SqlParameter("streamId", SqlDbType.Char, 42)
                {
                    Value = sqlStreamId.Id
                });
                command.Parameters.AddWithValue("streamIdOriginal", sqlStreamId.IdOriginal);

                if (messages.Length != 0)
                {
                    var sqlDataRecords = CreateSqlDataRecords(messages);
                    var eventsParam    = CreateNewMessagesSqlParameter(sqlDataRecords);
                    command.Parameters.Add(eventsParam);
                    command.Parameters.AddWithValue("hasMessages", true);
                }
                else
                {
                    // Must use a null value for the table-valued param if there are no records
                    var eventsParam = CreateNewMessagesSqlParameter(null);
                    command.Parameters.Add(eventsParam);
                    command.Parameters.AddWithValue("hasMessages", false);
                }

                try
                {
                    using (var reader = await command
                                        .ExecuteReaderAsync(cancellationToken)
                                        .NotOnCapturedContext())
                    {
                        await reader.ReadAsync(cancellationToken).NotOnCapturedContext();

                        var currentVersion  = reader.GetInt32(0);
                        var currentPosition = reader.GetInt64(1);
                        int?maxCount        = null;

                        if (await reader.ReadAsync(cancellationToken).NotOnCapturedContext())
                        {
                            var jsonData        = reader.GetString(0);
                            var metadataMessage = SimpleJson.DeserializeObject <MetadataMessage>(jsonData);
                            maxCount = metadataMessage.MaxCount;
                        }
                        return(new MsSqlAppendResult(maxCount, currentVersion, currentPosition));
                    }
                }
                catch (SqlException ex)
                {
                    // Check for unique constraint violation on
                    // https://technet.microsoft.com/en-us/library/aa258747%28v=sql.80%29.aspx
                    if (ex.IsUniqueConstraintViolationOnIndex("IX_Streams_Id"))
                    {
                        // Idempotency handling. Check if the Messages have already been written.
                        var page = await ReadStreamInternal(
                            sqlStreamId,
                            StreamVersion.Start,
                            messages.Length,
                            ReadDirection.Forward,
                            false,
                            null,
                            connection,
                            transaction,
                            cancellationToken)
                                   .NotOnCapturedContext();

                        if (messages.Length > page.Messages.Length)
                        {
                            throw new WrongExpectedVersionException(
                                      ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal,
                                                                                     ExpectedVersion.NoStream),
                                      ex);
                        }

                        for (int i = 0; i < Math.Min(messages.Length, page.Messages.Length); i++)
                        {
                            if (messages[i].MessageId != page.Messages[i].MessageId)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal,
                                                                                         ExpectedVersion.NoStream),
                                          ex);
                            }
                        }

                        return(new MsSqlAppendResult(
                                   null,
                                   page.LastStreamVersion,
                                   page.LastStreamPosition));
                    }

                    if (ex.IsUniqueConstraintViolation())
                    {
                        throw new WrongExpectedVersionException(
                                  ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal,
                                                                                 ExpectedVersion.NoStream),
                                  ex);
                    }

                    throw;
                }
            }
        }
Пример #10
0
        private async Task <MsSqlAppendResult> AppendToStreamExpectedVersionAny(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            using (var command = new SqlCommand(_scripts.AppendStreamExpectedVersionAny, connection, transaction))
            {
                command.CommandTimeout = _commandTimeout;
                command.Parameters.Add(new SqlParameter("streamId", SqlDbType.Char, 42)
                {
                    Value = sqlStreamId.Id
                });
                command.Parameters.AddWithValue("streamIdOriginal", sqlStreamId.IdOriginal);

                if (messages.Any())
                {
                    var sqlDataRecords = CreateSqlDataRecords(messages);
                    var eventsParam    = CreateNewMessagesSqlParameter(sqlDataRecords);
                    command.Parameters.Add(eventsParam);
                    command.Parameters.AddWithValue("hasMessages", true);
                }
                else
                {
                    // Must use a null value for the table-valued param if there are no records
                    var eventsParam = CreateNewMessagesSqlParameter(null);
                    command.Parameters.Add(eventsParam);
                    command.Parameters.AddWithValue("hasMessages", false);
                }

                try
                {
                    using (var reader = await command
                                        .ExecuteReaderAsync(cancellationToken)
                                        .ConfigureAwait(false))
                    {
                        await reader.ReadAsync(cancellationToken).ConfigureAwait(false);

                        var currentVersion  = reader.GetInt32(0);
                        var currentPosition = reader.GetInt64(1);
                        var maxCount        = reader.GetNullableInt32(2);

                        return(new MsSqlAppendResult(maxCount, currentVersion, currentPosition, false));
                    }
                }

                // Check for unique constraint violation on
                // https://technet.microsoft.com/en-us/library/aa258747%28v=sql.80%29.aspx
                catch (SqlException ex)
                    when(ex.IsUniqueConstraintViolationOnIndex("IX_Messages_StreamIdInternal_Id"))
                    {
                        var streamVersion = await GetStreamVersionOfMessageId(
                            connection,
                            transaction,
                            sqlStreamId,
                            messages[0].MessageId,
                            cancellationToken);

                        // Idempotency handling. Check if the Messages have already been written.
                        var(page, meta) = await ReadStreamInternal(
                            sqlStreamId,
                            streamVersion,
                            messages.Length,
                            ReadDirection.Forward,
                            false,
                            null,
                            connection,
                            transaction,
                            cancellationToken)
                                          .ConfigureAwait(false);

                        if (messages.Length > page.Messages.Length)
                        {
                            throw new WrongExpectedVersionException(
                                      ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                                      sqlStreamId.IdOriginal,
                                      ExpectedVersion.Any,
                                      ex);
                        }

                        for (int i = 0; i < Math.Min(messages.Length, page.Messages.Length); i++)
                        {
                            if (messages[i].MessageId != page.Messages[i].MessageId)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                                          sqlStreamId.IdOriginal,
                                          ExpectedVersion.Any,
                                          ex);
                            }
                        }

                        return(new MsSqlAppendResult(
                                   meta.MaxCount,
                                   page.LastStreamVersion,
                                   page.LastStreamPosition,
                                   true));
                    }
                catch (SqlException ex) when(ex.IsUniqueConstraintViolation())
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                              sqlStreamId.IdOriginal,
                              ExpectedVersion.Any,
                              ex);
                }
            }
        }
Пример #11
0
        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);
        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));
                }
            }
        }
Пример #13
0
        private async Task <int?> AppendToStreamExpectedVersionAny(
            SqlConnection connection,
            SqlTransaction transaction,
            SqlStreamId sqlStreamId,
            NewStreamMessage[] messages,
            CancellationToken cancellationToken)
        {
            using (var command = new SqlCommand(_scripts.AppendStreamExpectedVersionAny, connection, transaction))
            {
                command.Parameters.AddWithValue("streamId", sqlStreamId.Id);
                command.Parameters.AddWithValue("streamIdOriginal", sqlStreamId.IdOriginal);
                var eventsParam = CreateNewMessagesSqlParameter(CreateSqlDataRecords(messages));
                command.Parameters.Add(eventsParam);

                try
                {
                    using (var reader = await command
                                        .ExecuteReaderAsync(cancellationToken)
                                        .NotOnCapturedContext())
                    {
                        if (await reader.ReadAsync(cancellationToken).NotOnCapturedContext())
                        {
                            var jsonData        = reader.GetString(0);
                            var metadataMessage = SimpleJson.DeserializeObject <MetadataMessage>(jsonData);
                            return(metadataMessage.MaxCount);
                        }
                    }
                }

                // Check for unique constraint violation on
                // https://technet.microsoft.com/en-us/library/aa258747%28v=sql.80%29.aspx
                catch (SqlException ex)
                    when(ex.IsUniqueConstraintViolationOnIndex("IX_Messages_StreamIdInternal_Id"))
                    {
                        // Idempotency handling. Check if the Messages have already been written.
                        var page = await ReadStreamInternal(
                            sqlStreamId,
                            StreamVersion.Start,
                            messages.Length,
                            ReadDirection.Forward,
                            connection,
                            cancellationToken)
                                   .NotOnCapturedContext();

                        if (messages.Length > page.Messages.Length)
                        {
                            throw new WrongExpectedVersionException(
                                      ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                                      ex);
                        }

                        for (int i = 0; i < Math.Min(messages.Length, page.Messages.Length); i++)
                        {
                            if (messages[i].MessageId != page.Messages[i].MessageId)
                            {
                                throw new WrongExpectedVersionException(
                                          ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                                          ex);
                            }
                        }
                    }
                catch (SqlException ex) when(ex.IsUniqueConstraintViolation())
                {
                    throw new WrongExpectedVersionException(
                              ErrorMessages.AppendFailedWrongExpectedVersion(sqlStreamId.IdOriginal, ExpectedVersion.Any),
                              ex);
                }
                return(null);
            }
        }
Пример #14
0
        private async Task <StreamMessagesPage> ReadStreamInternal(
            SqlStreamId sqlStreamId,
            int start,
            int count,
            ReadDirection direction,
            SqlConnection connection,
            CancellationToken cancellationToken)
        {
            // To read backwards from end, need to use int MaxValue
            var    streamVersion = start == StreamVersion.End ? int.MaxValue : start;
            string commandText;
            Func <List <StreamMessage>, int> getNextSequenceNumber;

            if (direction == ReadDirection.Forward)
            {
                commandText           = _scripts.ReadStreamForward;
                getNextSequenceNumber = events => events.Last().StreamVersion + 1;
            }
            else
            {
                commandText           = _scripts.ReadStreamBackward;
                getNextSequenceNumber = events => events.Last().StreamVersion - 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);

                var messages = new List <StreamMessage>();

                using (var reader = await command.ExecuteReaderAsync(cancellationToken).NotOnCapturedContext())
                {
                    if (!await reader.ReadAsync(cancellationToken).NotOnCapturedContext())
                    {
                        return(new StreamMessagesPage(
                                   sqlStreamId.IdOriginal,
                                   PageReadStatus.StreamNotFound,
                                   start,
                                   -1,
                                   -1,
                                   direction,
                                   isEndOfStream: true));
                    }

                    // Read Messages result set
                    do
                    {
                        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 jsonData       = reader.GetString(5);
                        var jsonMetadata   = reader.GetString(6);

                        var message = new StreamMessage(
                            sqlStreamId.IdOriginal,
                            eventId,
                            streamVersion1,
                            ordinal,
                            created,
                            type,
                            jsonData,
                            jsonMetadata);

                        messages.Add(message);
                    } while(await reader.ReadAsync(cancellationToken).NotOnCapturedContext());

                    // Read last message revision result set
                    await reader.NextResultAsync(cancellationToken).NotOnCapturedContext();

                    await reader.ReadAsync(cancellationToken).NotOnCapturedContext();

                    var lastStreamVersion = reader.GetInt32(0);

                    var isEnd = true;
                    if (messages.Count == count + 1)
                    {
                        isEnd = false;
                        messages.RemoveAt(count);
                    }

                    return(new StreamMessagesPage(
                               sqlStreamId.IdOriginal,
                               PageReadStatus.Success,
                               start,
                               getNextSequenceNumber(messages),
                               lastStreamVersion,
                               direction,
                               isEnd,
                               messages.ToArray()));
                }
            }
        }