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