void ValidateUniqueProperties(IContainSagaData saga) { var sagaMetaData = sagaModel.FindByEntityName(saga.GetType().FullName); if (!sagaMetaData.CorrelationProperties.Any()) { return; } var sagasFromSameType = from s in data where (s.Value.SagaEntity.GetType() == saga.GetType() && (s.Key != saga.Id)) select s.Value; foreach (var storedSaga in sagasFromSameType) { foreach (var correlationProperty in sagaMetaData.CorrelationProperties) { var uniqueProperty = saga.GetType().GetProperty(correlationProperty.Name); if (!uniqueProperty.CanRead) { continue; } var inComingSagaPropertyValue = uniqueProperty.GetValue(saga, null); var storedSagaPropertyValue = uniqueProperty.GetValue(storedSaga.SagaEntity, null); if (inComingSagaPropertyValue.Equals(storedSagaPropertyValue)) { var message = string.Format("Cannot store a saga. The saga with id '{0}' already has property '{1}' with value '{2}'.", storedSaga.SagaEntity.Id, uniqueProperty, storedSagaPropertyValue); throw new InvalidOperationException(message); } } } }
private void ValidateUniqueProperties(IContainSagaData saga) { var uniqueProperties = UniqueAttribute.GetUniqueProperties(saga.GetType()); if (!uniqueProperties.Any()) { return; } var sagasFromSameType = from s in data where (s.Value.SagaEntity.GetType() == saga.GetType() && (s.Key != saga.Id)) select s.Value; foreach (var storedSaga in sagasFromSameType) { foreach (var uniqueProperty in uniqueProperties) { if (uniqueProperty.CanRead) { var inComingSagaPropertyValue = uniqueProperty.GetValue(saga, null); var storedSagaPropertyValue = uniqueProperty.GetValue(storedSaga.SagaEntity, null); if (inComingSagaPropertyValue.Equals(storedSagaPropertyValue)) { throw new InvalidOperationException( string.Format("Cannot store a saga. The saga with id '{0}' already has property '{1}' with value '{2}'.", storedSaga.SagaEntity.Id, uniqueProperty, storedSagaPropertyValue)); } } } } }
static async Task SaveSagaWithCorrelationProperty(IContainSagaData sagaData, SagaCorrelationProperty correlationProperty, SagaPersisterAtomicOperations operations, SynchronizedStorageSession session) { var propertyValue = correlationProperty.Value; var indexStream = BuildSagaByIdStreamName(sagaData.GetType(), sagaData.Id); var dataStream = BuildSagaDataStreamName(sagaData.GetType(), propertyValue); var stateChangeEvent = new EventData(Guid.NewGuid(), SagaDataEventType, true, sagaData.ToJsonBytes(), new byte[0]); await operations.CreateIndex(indexStream, dataStream).ConfigureAwait(false); await session.AppendToStreamAsync(dataStream, ExpectedVersion.NoStream, stateChangeEvent).ConfigureAwait(false); }
private void EnsureUniqueIndex(IContainSagaData saga, KeyValuePair <string, object> uniqueProperty) { Contract.Requires(saga != null); var collection = this.mongoDatabase.GetCollection(saga.GetType().Name); var indexOptions = IndexOptions.SetName(uniqueProperty.Key).SetUnique(true).SetSparse(true); var result = collection.CreateIndex(IndexKeys.Ascending(uniqueProperty.Key), indexOptions); if (result.HasLastErrorMessage) { throw new InvalidOperationException( string.Format( "Unable to create unique index on {0}: {1}", saga.GetType().Name, uniqueProperty.Key)); } }
public async Task Save( IContainSagaData sagaData, SagaCorrelationProperty correlationProperty, SynchronizedStorageSession session, ContextBag context) { var sagaTypeName = sagaData.GetType().Name; var sagaDataWithVersion = sagaData as IHaveDocumentVersion; if (sagaDataWithVersion == null) { throw new InvalidOperationException( string.Format("Saga type {0} does not implement IHaveDocumentVersion", sagaTypeName)); } sagaDataWithVersion.DocumentVersion = 0; sagaDataWithVersion.ETag = sagaData.ComputeETag(); if (correlationProperty != null) { await this.EnsureUniqueIndex(sagaData, correlationProperty).ConfigureAwait(false); } var collection = this.mongoDatabase.GetCollection <BsonDocument>(sagaTypeName); await collection.InsertOneAsync(sagaData.ToBsonDocument()).ConfigureAwait(false); }
internal async Task Save(IContainSagaData sagaData, SynchronizedStorageSession session, object correlationId) { var sqlSession = session.SqlPersistenceSession(); var sagaInfo = sagaInfoCache.GetInfo(sagaData.GetType()); using (var command = sqlDialect.CreateCommand(sqlSession.Connection)) { command.Transaction = sqlSession.Transaction; command.CommandText = sagaInfo.SaveCommand; command.AddParameter("Id", sagaData.Id); var metadata = new Dictionary <string, string>(); if (sagaData.OriginalMessageId != null) { metadata.Add("OriginalMessageId", sagaData.OriginalMessageId); } if (sagaData.Originator != null) { metadata.Add("Originator", sagaData.Originator); } command.AddParameter("Metadata", Serializer.Serialize(metadata)); command.AddJsonParameter("Data", sqlDialect.BuildSagaData(command, sagaInfo, sagaData)); command.AddParameter("PersistenceVersion", StaticVersions.PersistenceVersion); command.AddParameter("SagaTypeVersion", sagaInfo.CurrentVersion); if (correlationId != null) { command.AddParameter("CorrelationId", correlationId); } AddTransitionalParameter(sagaData, sagaInfo, command); await command.ExecuteNonQueryEx().ConfigureAwait(false); } }
protected StorageAction(IContainSagaData sagaData, Dictionary <string, SagaStorageFile> sagaFiles, SagaManifestCollection sagaManifests) { this.sagaFiles = sagaFiles; this.sagaData = sagaData; this.sagaManifests = sagaManifests; sagaFileKey = $"{sagaData.GetType().FullName}{sagaData.Id}"; }
void AttachEntity(IContainSagaData sagaEntity) { sagaId = sagaEntity.Id; UpdateModified(); Instance.Entity = sagaEntity; SagaId = sagaEntity.Id.ToString(); var properties = sagaEntity.GetType().GetProperties(); SagaMetadata.CorrelationPropertyMetadata correlatedPropertyMetadata; if (Metadata.TryGetCorrelationProperty(out correlatedPropertyMetadata)) { var propertyInfo = properties.Single(p => p.Name == correlatedPropertyMetadata.Name); var propertyValue = propertyInfo.GetValue(sagaEntity); var defaultValue = GetDefault(propertyInfo.PropertyType); var hasValue = propertyValue != null && !propertyValue.Equals(defaultValue); correlationProperty = new CorrelationPropertyInfo { PropertyInfo = propertyInfo, Value = propertyValue, HasExistingValue = hasValue }; } }
void ValidateUniqueProperties(SagaCorrelationProperty correlationProperty, IContainSagaData saga) { var sagaType = saga.GetType(); var existingSagas = new List <VersionedSagaEntity>(); // ReSharper disable once LoopCanBeConvertedToQuery foreach (var s in data) { if (s.Value.SagaData.GetType() == sagaType && (s.Key != saga.Id)) { existingSagas.Add(s.Value); } } var uniqueProperty = sagaType.GetProperty(correlationProperty.Name); if (correlationProperty.Value == null) { var message = $"Cannot store saga with id '{saga.Id}' since the unique property '{uniqueProperty.Name}' has a null value."; throw new InvalidOperationException(message); } foreach (var storedSaga in existingSagas) { var storedSagaPropertyValue = uniqueProperty.GetValue(storedSaga.SagaData, null); if (Equals(correlationProperty.Value, storedSagaPropertyValue)) { var message = $"Cannot store a saga. The saga with id '{storedSaga.SagaData.Id}' already has property '{uniqueProperty.Name}'."; throw new InvalidOperationException(message); } } }
public void Update(IContainSagaData saga, string versionFieldName, int version) { var collection = GetCollection(saga.GetType()); var fbuilder = Builders <BsonDocument> .Filter; var filter = fbuilder.Eq("_id", saga.Id) & fbuilder.Eq(versionFieldName, version); var bsonDoc = saga.ToBsonDocument(); var ubuilder = Builders <BsonDocument> .Update; var update = ubuilder.Inc(versionFieldName, 1); foreach (var field in bsonDoc.Where(field => field.Name != versionFieldName && field.Name != "_id")) { update = update.Set(field.Name, field.Value); } var modifyResult = collection.FindOneAndUpdate( filter, update, new FindOneAndUpdateOptions <BsonDocument> { IsUpsert = false, ReturnDocument = ReturnDocument.After }); if (modifyResult == null) { throw new SagaMongoDbConcurrentUpdateException(version); } }
public async Task Save(IContainSagaData sagaData, SagaCorrelationProperty correlationProperty, SynchronizedStorageSession session, ContextBag context) { var documentSession = session.RavenSession(); if (sagaData == null) { return; } var container = new SagaDataContainer { Id = DocumentIdForSagaData(documentSession, sagaData), Data = sagaData }; if (correlationProperty == null) { return; } container.IdentityDocId = SagaUniqueIdentity.FormatId(sagaData.GetType(), correlationProperty.Name, correlationProperty.Value); await documentSession.StoreAsync(container, string.Empty, container.Id).ConfigureAwait(false); await documentSession.StoreAsync(new SagaUniqueIdentity { Id = container.IdentityDocId, SagaId = sagaData.Id, UniqueValue = correlationProperty.Value, SagaDocId = container.Id }, changeVector : string.Empty, id : container.IdentityDocId).ConfigureAwait(false); }
static Task SaveSagaWithoutCorrelationProperty(IContainSagaData sagaData, SynchronizedStorageSession session) { var indexStream = BuildSagaByIdStreamName(sagaData.GetType(), sagaData.Id); var stateChangeEvent = new EventData(Guid.NewGuid(), SagaDataEventType, true, sagaData.ToJsonBytes(), new byte[0]); return(session.AppendToStreamAsync(indexStream, ExpectedVersion.NoStream, stateChangeEvent)); }
/// <summary> /// Saves the saga entity to the persistence store. /// </summary> /// <param name="saga">The saga entity to save.</param> public void Save(IContainSagaData saga) { var sagaTypeName = saga.GetType().Name; if (!(saga is IHaveDocumentVersion)) { throw new InvalidOperationException( string.Format("Saga type {0} does not implement IHaveDocumentVersion", sagaTypeName)); } var uniqueProperty = UniqueAttribute.GetUniqueProperty(saga); if (uniqueProperty.HasValue) { this.EnsureUniqueIndex(saga, uniqueProperty.Value); CheckUniqueProperty(saga, uniqueProperty.Value); } var collection = this.mongoDatabase.GetCollection(sagaTypeName); var result = collection.Insert(saga); if (result.HasLastErrorMessage) { throw new InvalidOperationException(string.Format("Unable to save with id {0}", saga.Id)); } }
public async Task Save(IContainSagaData sagaData, SagaCorrelationProperty correlationProperty, SynchronizedStorageSession session, ContextBag context) { DocumentVersionAttribute.SetPropertyValue(sagaData, 0); await EnsureUniqueIndex(sagaData.GetType(), correlationProperty?.Name).ConfigureAwait(false); await _repo.Insert(sagaData).ConfigureAwait(false); }
/// <summary> /// Gets a single property that is marked with the <see cref="UniqueAttribute"/> for a <see cref="IContainSagaData"/>. /// </summary> /// <param name="entity">A saga entity.</param> /// <returns>A <see cref="PropertyInfo"/> of the property marked with a <see cref="UniqueAttribute"/> or null if not used.</returns> public static KeyValuePair <string, object>?GetUniqueProperty(IContainSagaData entity) { var prop = GetUniqueProperty(entity.GetType()); return(prop != null ? new KeyValuePair <string, object>(prop.Name, prop.GetValue(entity, null)) : (KeyValuePair <string, object>?)null); }
/// <summary> /// Gets a single property that is marked with the <see cref="UniqueAttribute"/> for a <see cref="IContainSagaData"/>. /// </summary> /// <param name="entity">A saga entity.</param> /// <returns>A <see cref="PropertyInfo"/> of the property marked with a <see cref="UniqueAttribute"/> or null if not used.</returns> public static KeyValuePair<string, object>? GetUniqueProperty(IContainSagaData entity) { var prop = GetUniqueProperty(entity.GetType()); return prop != null ? new KeyValuePair<string, object>(prop.Name, prop.GetValue(entity, null)) : (KeyValuePair<string, object>?) null; }
public Task Complete(IContainSagaData sagaData, SynchronizedStorageSession session, ContextBag context) { var query = Builders <BsonDocument> .Filter.Eq(MongoPersistenceConstants.IdPropertyName, sagaData.Id); var collection = this.mongoDatabase.GetCollection <BsonDocument>(sagaData.GetType().Name); return(collection.DeleteOneAsync(query)); }
void DeleteUniqueProperty(IContainSagaData saga, KeyValuePair <string, object> uniqueProperty) { var id = SagaUniqueIdentity.FormatId(saga.GetType(), uniqueProperty); Session.Advanced.Defer(new DeleteCommandData { Key = id }); }
public Task Update(IContainSagaData sagaData, SynchronizedStorageSession session, ContextBag context) { var versionProperty = DocumentVersionAttribute.GetProperty(sagaData); var classmap = BsonClassMap.LookupClassMap(sagaData.GetType()); var versionFieldName = GetFieldName(classmap, versionProperty.Key); return(_repo.Update(sagaData, versionFieldName, versionProperty.Value)); }
static byte[] Serialize(IContainSagaData sagaData) { var serializer = new DataContractSerializer(sagaData.GetType()); using (var ms = new MemoryStream()) { serializer.WriteObject(ms, sagaData); return(ms.ToArray()); } }
/// <summary> /// Sets a saga as completed and removes it from the active saga list /// in the persistence store. /// </summary> /// <param name="saga">The saga to complete.</param> public void Complete(IContainSagaData saga) { var query = Query.EQ("_id", saga.Id); var result = this.mongoDatabase.GetCollection(saga.GetType().Name).Remove(query); if (result.HasLastErrorMessage) { throw new InvalidOperationException( string.Format("Unable to find and remove saga with id {0}", saga.Id)); } }
public async Task Save(IContainSagaData sagaData, SagaCorrelationProperty correlationProperty, ISynchronizedStorageSession session, ContextBag context, CancellationToken cancellationToken = default) { var storageSession = (StorageSession)session; var sagaDataType = sagaData.GetType(); var document = sagaData.ToBsonDocument(); document.Add(versionElementName, 0); await storageSession.InsertOneAsync(sagaDataType, document, cancellationToken).ConfigureAwait(false); }
public void Update(IContainSagaData saga) { var sagaDataType = saga.GetType(); var versionProperty = DocumentVersionAttribute.GetDocumentVersionProperty(sagaDataType); var version = (int)versionProperty.GetValue(saga); var classmap = BsonClassMap.LookupClassMap(sagaDataType); var versionFieldName = GetFieldName(classmap, versionProperty.Name); _repo.Update(saga, versionFieldName, version); }
public Task Update(IContainSagaData sagaData, SynchronizedStorageSession session, ContextBag context) { var inMemSession = (InMemorySynchronizedStorageSession)session; inMemSession.Enlist(() => { data.AddOrUpdate(sagaData.Id, id => new VersionedSagaEntity(sagaData, $"{sagaData.GetType().FullName}.None.None"), // we can never end up here. (id, original) => new VersionedSagaEntity(sagaData, original.LockTokenKey, original)); }); return(TaskEx.CompletedTask); }
public void Update(IContainSagaData saga) { var versionProperty = DocumentVersionAttribute.GetDocumentVersionProperty(saga.GetType()); var version = (int)versionProperty.GetValue(saga); var classmap = BsonClassMap.LookupClassMap(saga.GetType()); var membermap = classmap.GetMemberMap(versionProperty.Name); var versionFieldName = membermap.ElementName; _repo.Update(saga, versionFieldName, version); }
/// <summary> /// Updates an existing saga entity in the persistence store. /// </summary> /// <param name="saga">The saga entity to updated.</param> public void Update(IContainSagaData saga) { var collection = this.mongoDatabase.GetCollection(saga.GetType().Name); var query = saga.MongoUpdateQuery(); var update = saga.MongoUpdate(); var result = collection.Update(query, update, UpdateFlags.None); if (!result.UpdatedExisting) { throw new InvalidOperationException(string.Format("Unable to update saga with id {0}", saga.Id)); } }
public static SagaCorrelationProperty GetMetadata <T>(IContainSagaData entity) { var metadata = SagaMetadata.Create(typeof(T)); if (!metadata.TryGetCorrelationProperty(out var correlatedProp)) { return(SagaCorrelationProperty.None); } var prop = entity.GetType().GetProperty(correlatedProp.Name); var value = prop.GetValue(entity); return(new SagaCorrelationProperty(correlatedProp.Name, value)); }
internal async Task Complete(IContainSagaData sagaData, SynchronizedStorageSession session, int concurrency) { var sagaInfo = sagaInfoCache.GetInfo(sagaData.GetType()); var sqlSession = session.SqlPersistenceSession(); using (var command = commandBuilder.CreateCommand(sqlSession.Connection)) { command.CommandText = sagaInfo.CompleteCommand; command.Transaction = sqlSession.Transaction; command.AddParameter("Id", sagaData.Id); command.AddParameter("Concurrency", concurrency); await command.ExecuteNonQueryAsync().ConfigureAwait(false); } }
public async Task Complete(IContainSagaData sagaData, ISynchronizedStorageSession session, ContextBag context, CancellationToken cancellationToken = default) { var storageSession = (StorageSession)session; var sagaDataType = sagaData.GetType(); var version = storageSession.RetrieveVersion(sagaDataType); var result = await storageSession.DeleteOneAsync(sagaDataType, filterBuilder.Eq(idElementName, sagaData.Id)& filterBuilder.Eq(versionElementName, version), cancellationToken).ConfigureAwait(false); if (result.DeletedCount != 1) { throw new Exception("Saga can't be completed because it was updated by another process."); } }
private async Task EnsureUniqueIndex(IContainSagaData saga, SagaCorrelationProperty correlationProperty) { Contract.Requires(saga != null); Contract.Requires(correlationProperty != null); var collection = this.mongoDatabase.GetCollection <BsonDocument>(saga.GetType().Name); await collection.Indexes.CreateOneAsync( new BsonDocumentIndexKeysDefinition <BsonDocument>(new BsonDocument(correlationProperty.Name, 1)), new CreateIndexOptions() { Unique = true }).ConfigureAwait(false); }
public async Task Update(IContainSagaData sagaData, ISynchronizedStorageSession session, ContextBag context, CancellationToken cancellationToken = default) { var storageSession = (StorageSession)session; var sagaDataType = sagaData.GetType(); var version = storageSession.RetrieveVersion(sagaDataType); var document = sagaData.ToBsonDocument().SetElement(new BsonElement(versionElementName, version + 1)); var result = await storageSession.ReplaceOneAsync(sagaDataType, filterBuilder.Eq(idElementName, sagaData.Id)& filterBuilder.Eq(versionElementName, version), document, cancellationToken).ConfigureAwait(false); if (result.ModifiedCount != 1) { throw new Exception($"The '{sagaDataType.Name}' saga with id '{sagaData.Id}' was updated by another process or no longer exists."); } }
/// <summary> /// Gets all the properties that are marked with the <see cref="UniqueAttribute"/> for a <see cref="IContainSagaData"/>. /// </summary> /// <param name="entity">A <see cref="IContainSagaData"/>.</param> /// <returns>A <see cref="IDictionary{TKey,TValue}"/> of property names and their values.</returns> public static IDictionary<string, object> GetUniqueProperties(IContainSagaData entity) { return GetUniqueProperties(entity.GetType()) .ToDictionary(p => p.Name, p => p.GetValue(entity, null)); }