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);
        }
Example #4
0
        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);
        }
    }
Example #7
0
 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}";
 }
Example #8
0
        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
                };
            }
        }
Example #9
0
        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);
                }
            }
        }
Example #10
0
        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);
            }
        }
Example #11
0
        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));
        }
Example #13
0
        /// <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));
            }
        }
Example #14
0
        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);
        }
Example #15
0
        /// <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);
        }
Example #16
0
        /// <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));
        }
Example #18
0
        void DeleteUniqueProperty(IContainSagaData saga, KeyValuePair <string, object> uniqueProperty)
        {
            var id = SagaUniqueIdentity.FormatId(saga.GetType(), uniqueProperty);

            Session.Advanced.Defer(new DeleteCommandData {
                Key = id
            });
        }
Example #19
0
        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));
        }
Example #20
0
        static byte[] Serialize(IContainSagaData sagaData)
        {
            var serializer = new DataContractSerializer(sagaData.GetType());

            using (var ms = new MemoryStream())
            {
                serializer.WriteObject(ms, sagaData);
                return(ms.ToArray());
            }
        }
Example #21
0
        /// <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));
            }
        }
Example #22
0
        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);
        }
Example #23
0
        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);
        }
Example #24
0
        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);
        }
Example #25
0
        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);
        }
Example #26
0
        /// <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));
        }
Example #28
0
    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);
        }
    }
Example #29
0
        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);
        }
Example #31
0
        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.");
            }
        }
        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
                };
            }
        }
Example #33
0
 /// <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));
 }