private void ValidateEntry(TransactionState state)
        {
            Assert(state != null);
            Assert(state.Version >= 1);
            Assert(state.Status.IsValid());
            Assert(state.Operations != null);
            Assert(state.Id != 0);

            var lastId = 0L;

            for (var i = 0; i < state.Operations.Length; i++)
            {
                var operation = state.Operations[i];

                Assert(operation.OperationType.IsValid());
                Assert(operation.EntryType != null);
                Assert(operation.ExpectedVersion != null, operation.ExpectedVersion >= 0);
                Assert(state.Status == TransactionStatus.Prepare, operation.State == OperationState.Unapplied);
                Assert(state.Status == TransactionStatus.Initial, operation.State == OperationState.Unapplied);
                Assert(state.Status == TransactionStatus.Committed, operation.State == OperationState.Applied);
                Assert(state.Status == TransactionStatus.CleanedUp, operation.State == OperationState.Applied);
                Assert(state.Status == TransactionStatus.Aborted, operation.State == OperationState.Unapplied);
                Assert(lastId < operation.Id);

                var predicate = DataPropertyHelper.CompilePredicate(operation.EntryType, operation.Entry);
                Assert(!state.Operations.Skip(i + 1).Any(p => p.EntryType == operation.EntryType && predicate(p.Entry)));

                lastId = operation.Id;
            }
        }
        public ITransactionState AddOperation(ITransactionState state, OperationType operationType, Type entryType, object entry, int?expectedVersion, out IOperation operation)
        {
            var convertedState = ToTransactionState(state);

            if (convertedState.Status != TransactionStatus.Initial)
            {
                throw new InvalidOperationException("Cannot modify a this after it has started.");
            }

            if (entryType == null)
            {
                throw new ArgumentNullException(nameof(entryType));
            }

            if (entry == null)
            {
                throw new ArgumentNullException(nameof(entry));
            }



#if DEBUG
            ValidateEntry(convertedState);
#endif

            if (!operationType.IsValid())
            {
                throw new ArgumentException($"The argument must be one of the values defined in { typeof(OperationType).FullName }.", nameof(operationType));
            }

            if (expectedVersion < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(expectedVersion));
            }

            if (entryType.IsGenericTypeDefinition)
            {
                throw new ArgumentException("The argument must not be an open generic type.", nameof(entryType));
            }

            if (!entryType.IsClass || (entryType.IsGenericType && entryType.GetGenericTypeDefinition() == typeof(Nullable <>)))
            {
                throw new ArgumentException("The argument must be a reference type.", nameof(entryType));
            }

            if (!entryType.IsAssignableFrom(entry.GetType()))
            {
                throw new ArgumentException($"The specified entry must be of a type assignale to '{nameof(entryType)}'.");
            }

            var predicate = DataPropertyHelper.CompilePredicate(entryType, entry);

            if (convertedState.Operations.Any(p => p.EntryType == entryType && predicate(p.Entry)))
            {
                throw new InvalidOperationException("The this cannot have multiple operations for a single entry.");
            }

            var operationId = GetNextOperationId(convertedState);

            var op = new Operation(convertedState.Id, operationId, operationType, entryType, entry, expectedVersion, OperationState.Unapplied);
            operation = op;

            return(new TransactionState(convertedState.Id, convertedState.Version + 1, TransactionStatus.Initial, convertedState.Operations.Add(op)));
        }