/// <summary> /// Increment the sequence number of the event stream /// </summary> private async Task IncrementSequence(string writeStreamLeaseId = "") { if (null != EventStreamBlob) { bool exists = await EventStreamBlob.ExistsAsync(); if (exists) { await EventStreamBlob.FetchAttributesAsync(); int sequenceNumber; if (int.TryParse(EventStreamBlob.Metadata[METADATA_SEQUENCE], out sequenceNumber)) { sequenceNumber += 1; EventStreamBlob.Metadata[METADATA_SEQUENCE] = $"{sequenceNumber }"; // and commit it back AccessCondition condition = AccessCondition.GenerateEmptyCondition(); if (!string.IsNullOrWhiteSpace(writeStreamLeaseId)) { condition.LeaseId = writeStreamLeaseId; } await EventStreamBlob.SetMetadataAsync(condition, null, new Microsoft.Azure.Storage.OperationContext()); } } } }
/// <summary> /// Delete the blob file containing the event stream /// </summary> public void DeleteStream() { if (null != EventStreamBlob) { EventStreamBlob.Delete(Microsoft.Azure.Storage.Blob.DeleteSnapshotsOption.IncludeSnapshots); } }
public async Task <IEnumerable <IEventContext> > GetEventsWithContext(int StartingSequenceNumber = 0, DateTime?effectiveDateTime = null) { if (null != EventStreamBlob) { if (await EventStreamBlob.ExistsAsync()) { using (System.IO.Stream rawStream = await GetUnderlyingStream()) { if (!(rawStream.Position >= rawStream.Length)) { List <IEventContext> ret = new List <IEventContext>(); foreach (BlobBlockJsonWrappedEvent record in BlobBlockJsonWrappedEvent.FromBinaryStream(rawStream)) { if (null != record) { if (record.SequenceNumber >= StartingSequenceNumber) { if ((!effectiveDateTime.HasValue) || (record.WriteTime <= effectiveDateTime.Value)) { ret.Add(record); } } } } return(ret); } } } } return(Enumerable.Empty <IEventContext>()); }
/// <summary> /// Make sure the blob info attributes are up to date /// </summary> /// <remarks> /// This is similar in concept to FileSystemInfo.Refresh /// </remarks> public async Task Refresh() { if (EventStreamBlob != null) { bool exists = await EventStreamBlob.ExistsAsync(); if (exists) { // just refresh the attributes await EventStreamBlob.FetchAttributesAsync(); } else { exists = await EventStreamBlob.Container.ExistsAsync(); if (!exists) { await EventStreamBlob.Container.CreateAsync(); } await EventStreamBlob.CreateOrReplaceAsync(); // Set the original metadata EventStreamBlob.Metadata[METATDATA_DOMAIN] = DomainName; EventStreamBlob.Metadata[METADATA_ENTITY_TYPE_NAME] = EntityTypeName; EventStreamBlob.Metadata[METADATA_INSTANCE_KEY] = InstanceKey; EventStreamBlob.Metadata[METADATA_SEQUENCE] = @"0"; EventStreamBlob.Metadata[METADATA_DATE_CREATED] = DateTime.UtcNow.ToString("O"); // and commit it back await EventStreamBlob.SetMetadataAsync(); } } }
/// <summary> /// Delete the blob file containing the event stream /// </summary> public async Task DeleteStream() { if (null != EventStreamBlob) { AccessCondition condition = AccessCondition.GenerateEmptyCondition(); await EventStreamBlob.DeleteAsync(Microsoft.Azure.Storage.Blob.DeleteSnapshotsOption.IncludeSnapshots, condition, null, new Microsoft.Azure.Storage.OperationContext()); } }
/// <summary> /// Gets the current top sequence number of the event stream /// </summary> public async Task <int> GetSequenceNumber() { if (null != EventStreamBlob) { bool exists = await EventStreamBlob.ExistsAsync(); if (exists) { await EventStreamBlob.FetchAttributesAsync(); int sequenceNumber; if (int.TryParse(EventStreamBlob.Metadata[METADATA_SEQUENCE], out sequenceNumber)) { return(sequenceNumber); } } } return(0); }
private async Task <System.IO.Stream> GetUnderlyingStream() { if (null != EventStreamBlob) { System.IO.MemoryStream targetStream = new System.IO.MemoryStream(); try { await EventStreamBlob.DownloadToStreamAsync(targetStream); } catch (StorageException exBlob) { throw new EventStreamReadException(this, 0, "Unable to access the underlying event stream", innerException: exBlob, source: nameof(BlobEventStreamReader)); } targetStream.Seek(0, System.IO.SeekOrigin.Begin); return(targetStream); } return(null); }
/// <summary> /// Append the event to the end of the event stream /// </summary> /// <param name="eventInstance"> /// The event to append to the end of the event stream /// </param> /// <param name="expectedTopSequenceNumber"> /// if this is set to > 0 and the event stream is further on then a consistency issue has arisen and the /// event should not be written but rather throw an error /// </param> /// <param name="eventVersionNumber"> /// The version number to add to the event wrapper /// </param> /// <param name="streamConstraint"> /// An additional constrain that must be satisfied by the event stream in order to persist the event /// </param> /// <returns></returns> public async Task <IAppendResult> AppendEvent(IEvent eventInstance, int expectedTopSequenceNumber = 0, int eventVersionNumber = 1, EventStreamExistenceConstraint streamConstraint = EventStreamExistenceConstraint.Loose) { if (base.EventStreamBlob != null) { // acquire a lease for the blob.. string writeStreamLeaseId = null; if (await Exists()) { writeStreamLeaseId = await base.EventStreamBlob.AcquireLeaseAsync(TimeSpan.FromSeconds(15)); } int nextSequence = await base.GetSequenceNumber() + 1; if (expectedTopSequenceNumber > 0) { // check against actual top sequence number if ((expectedTopSequenceNumber + 1) < nextSequence) { throw new EventStreamWriteException(this, (nextSequence - 1), message: $"Out of sequence write - expected seqeunce number {expectedTopSequenceNumber }", source: "Blob Event Stream Writer"); } } string eventName = ""; if (null != eventInstance) { eventName = EventNameAttribute.GetEventName(eventInstance.GetType()); } // create an access condition AccessCondition condition = AccessCondition.GenerateEmptyCondition(); if (streamConstraint == EventStreamExistenceConstraint.MustBeNew) { condition = AccessCondition.GenerateIfNotExistsCondition(); } if (streamConstraint == EventStreamExistenceConstraint.MustExist) { condition = AccessCondition.GenerateIfExistsCondition(); } if (!string.IsNullOrWhiteSpace(writeStreamLeaseId)) { condition.LeaseId = writeStreamLeaseId; } // default the writer context if it is not already set if (null == _writerContext) { _writerContext = WriteContext.DefaultWriterContext(); } BlobBlockJsonWrappedEvent evtToWrite = BlobBlockJsonWrappedEvent.Create(eventName, nextSequence, eventVersionNumber, null, eventInstance, _writerContext); try { // Create it if it doesn't exist and initialsie the metadata await base.Refresh(); Microsoft.Azure.Storage.OperationContext context = new Microsoft.Azure.Storage.OperationContext() { }; await EventStreamBlob.AppendBlockAsync(new System.IO.MemoryStream(Encoding.UTF8.GetBytes(evtToWrite.ToJSonText())), "", condition, null, // use the default blob request options context ); } catch (Microsoft.Azure.Storage.StorageException exBlob) { throw new EventStreamWriteException(this, (nextSequence - 1), message: "Failed to save an event to the event stream", source: "Blob Event Stream Writer", innerException: exBlob); } await IncrementSequence(writeStreamLeaseId); if (!string.IsNullOrWhiteSpace(writeStreamLeaseId)) { // and release the lease await base.EventStreamBlob.ReleaseLeaseAsync(condition); } int sequence = await base.GetSequenceNumber(); return(new AppendResult((sequence == 0), sequence)); } else { return(null); } }