private async Task AppendToStreamExpectedVersionAny( StreamIdInfo streamIdInfo, int expectedVersion, NewStreamEvent[] events, CancellationToken cancellationToken) { var sqlDataRecords = CreateSqlDataRecords(events); using (var connection = _createConnection()) { await connection.OpenAsync(cancellationToken).NotOnCapturedContext(); using (var command = new SqlCommand(_scripts.AppendStreamExpectedVersionAny, connection)) { command.Parameters.AddWithValue("streamId", streamIdInfo.Hash); command.Parameters.AddWithValue("streamIdOriginal", streamIdInfo.Id); var eventsParam = CreateNewEventsSqlParameter(sqlDataRecords); command.Parameters.Add(eventsParam); try { await command .ExecuteNonQueryAsync(cancellationToken) .NotOnCapturedContext(); } 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_Events_StreamIdInternal_Id")) { // Idempotency handling. Check if the events have already been written. var page = await ReadStreamInternal( streamIdInfo.Id, StreamVersion.Start, events.Length, ReadDirection.Forward, connection, cancellationToken) .NotOnCapturedContext(); if (events.Length > page.Events.Length) { throw new WrongExpectedVersionException( Messages.AppendFailedWrongExpectedVersion(streamIdInfo.Id, expectedVersion), ex); } for (int i = 0; i < Math.Min(events.Length, page.Events.Length); i++) { if (events[i].EventId != page.Events[i].EventId) { throw new WrongExpectedVersionException( Messages.AppendFailedWrongExpectedVersion(streamIdInfo.Id, expectedVersion), ex); } } return; } if (ex.IsUniqueConstraintViolation()) { throw new WrongExpectedVersionException( Messages.AppendFailedWrongExpectedVersion(streamIdInfo.Id, expectedVersion), ex); } throw; } } } }
private async Task <StreamEventsPage> ReadStreamInternal( string streamId, int start, int count, ReadDirection direction, SqlConnection connection, CancellationToken cancellationToken) { var streamIdInfo = new StreamIdInfo(streamId); var streamVersion = start == StreamVersion.End ? int.MaxValue : start; // To read backwards from end, need to use int MaxValue string commandText; Func <List <StreamEvent>, 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", streamIdInfo.Hash); command.Parameters.AddWithValue("count", count + 1); //Read extra row to see if at end or not command.Parameters.AddWithValue("StreamVersion", streamVersion); var streamEvents = new List <StreamEvent>(); var reader = await command.ExecuteReaderAsync(cancellationToken).NotOnCapturedContext(); await reader.ReadAsync(cancellationToken).NotOnCapturedContext(); var doesNotExist = reader.IsDBNull(0); if (doesNotExist) { return(new StreamEventsPage( streamId, PageReadStatus.StreamNotFound, start, -1, -1, direction, isEndOfStream: true)); } // Read IsDeleted result set var isDeleted = reader.GetBoolean(0); if (isDeleted) { return(new StreamEventsPage( streamId, PageReadStatus.StreamDeleted, 0, 0, 0, direction, isEndOfStream: true)); } // Read Events result set await reader.NextResultAsync(cancellationToken).NotOnCapturedContext(); 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 jsonData = reader.GetString(5); var jsonMetadata = reader.GetString(6); var streamEvent = new StreamEvent( streamId, eventId, streamVersion1, ordinal, created, type, jsonData, jsonMetadata); streamEvents.Add(streamEvent); } // Read last event revision result set await reader.NextResultAsync(cancellationToken).NotOnCapturedContext(); await reader.ReadAsync(cancellationToken).NotOnCapturedContext(); var lastStreamVersion = reader.GetInt32(0); var isEnd = true; if (streamEvents.Count == count + 1) { isEnd = false; streamEvents.RemoveAt(count); } return(new StreamEventsPage( streamId, PageReadStatus.Success, start, getNextSequenceNumber(streamEvents), lastStreamVersion, direction, isEnd, streamEvents.ToArray())); } }
private async Task AppendToStreamExpectedVersion( string streamId, int expectedVersion, NewStreamEvent[] events, StreamIdInfo streamIdHash, CancellationToken cancellationToken) { var sqlDataRecords = CreateSqlDataRecords(events); using (var connection = _createConnection()) { await connection.OpenAsync(cancellationToken).NotOnCapturedContext(); using (var command = new SqlCommand(_scripts.AppendStreamExpectedVersion, connection)) { command.Parameters.AddWithValue("streamId", streamIdHash.Hash); command.Parameters.AddWithValue("expectedStreamVersion", expectedVersion); var eventsParam = CreateNewEventsSqlParameter(sqlDataRecords); command.Parameters.Add(eventsParam); try { await command .ExecuteNonQueryAsync(cancellationToken) .NotOnCapturedContext(); } catch (SqlException ex) { if (ex.Errors.Count == 1) { var sqlError = ex.Errors[0]; if (sqlError.Message == "WrongExpectedVersion") { // Idempotency handling. Check if the events have already been written. var page = await ReadStreamInternal(streamId, expectedVersion + 1, // when reading for already written events, it's from the one after the expected events.Length, ReadDirection.Forward, connection, cancellationToken); if (events.Length > page.Events.Length) { throw new WrongExpectedVersionException( Messages.AppendFailedWrongExpectedVersion(streamId, expectedVersion), ex); } for (int i = 0; i < Math.Min(events.Length, page.Events.Length); i++) { if (events[i].EventId != page.Events[i].EventId) { throw new WrongExpectedVersionException( Messages.AppendFailedWrongExpectedVersion(streamId, expectedVersion), ex); } } return; } } if (ex.IsUniqueConstraintViolation()) { throw new WrongExpectedVersionException( Messages.AppendFailedWrongExpectedVersion(streamId, expectedVersion), ex); } throw; } } } }