/// <summary>
        /// Asynchronously executes the submit flow.
        /// </summary>
        /// <param name="context">
        /// The submit context.
        /// </param>
        /// <param name="cancellationToken">
        /// A cancellation token.
        /// </param>
        /// <returns>
        /// A task that represents the asynchronous
        /// operation whose result is a submit result.
        /// </returns>
        public static async Task<SubmitResult> SubmitAsync(
            SubmitContext context, CancellationToken cancellationToken)
        {
            Ensure.NotNull(context, "context");

            var preparer = context.GetHookHandler<IChangeSetPreparer>();
            if (preparer == null)
            {
                throw new NotSupportedException(Resources.ChangeSetPreparerMissing);
            }

            await preparer.PrepareAsync(context, cancellationToken);

            if (context.Result != null)
            {
                return context.Result;
            }

            ChangeSet eventsChangeSet = context.ChangeSet;

            IEnumerable<ChangeSetEntry> currentChangeSetItems;
            int outerLoopCount = 0;

            do
            {
                outerLoopCount++;
                int innerLoopCount = 0;
                do
                {
                    innerLoopCount++;
                    eventsChangeSet.AnEntityHasChanged = false;
                    currentChangeSetItems = eventsChangeSet.Entries.ToArray();

                    if (eventsChangeSet.AnEntityHasChanged)
                    {
                        eventsChangeSet.AnEntityHasChanged = false;
                        currentChangeSetItems = eventsChangeSet.Entries.ToArray();
                    }

                    await PerformValidate(context, currentChangeSetItems, cancellationToken);

                    await PerformPreEvent(context, currentChangeSetItems, cancellationToken);
                }
                while (eventsChangeSet.AnEntityHasChanged && (innerLoopCount < MaxLoop));

                VerifyNoEntityHasChanged(eventsChangeSet);

                await PerformPersist(context, currentChangeSetItems, cancellationToken);

                eventsChangeSet.Entries.Clear();

                await PerformPostEvent(context, currentChangeSetItems, cancellationToken);
            }
            while (eventsChangeSet.AnEntityHasChanged && (outerLoopCount < MaxLoop));

            VerifyNoEntityHasChanged(eventsChangeSet);

            return context.Result;
        }
 private static async Task PerformPostEvent(
     SubmitContext context,
     IEnumerable<ChangeSetEntry> changeSetItems,
     CancellationToken cancellationToken)
 {
     foreach (ChangeSetEntry entry in changeSetItems)
     {
         var filter = context.GetHookHandler<IChangeSetEntryFilter>();
         if (filter != null)
         {
             await filter.OnExecutedEntryAsync(context, entry, cancellationToken);
         }
     }
 }
        private static async Task PerformPersist(
            SubmitContext context,
            IEnumerable<ChangeSetEntry> changeSetItems,
            CancellationToken cancellationToken)
        {
            // Once the change is persisted, the EntityState is lost.
            // In order to invoke the correct post-CUD event, remember which action was performed on the entity.
            foreach (ChangeSetEntry item in changeSetItems)
            {
                if (item.Type == ChangeSetEntryType.DataModification)
                {
                    DataModificationEntry dataModification = (DataModificationEntry)item;
                    if (dataModification.IsNew)
                    {
                        dataModification.AddAction = AddAction.Inserting;
                    }
                    else if (dataModification.IsUpdate)
                    {
                        dataModification.AddAction = AddAction.Updating;
                    }
                    else if (dataModification.IsDelete)
                    {
                        dataModification.AddAction = AddAction.Removing;
                    }
                }
            }

            var executor = context.GetHookHandler<ISubmitExecutor>();
            if (executor == null)
            {
                throw new NotSupportedException(Resources.SubmitExecutorMissing);
            }

            context.Result = await executor.ExecuteSubmitAsync(context, cancellationToken);
        }
        private static async Task PerformPreEvent(
            SubmitContext context,
            IEnumerable<ChangeSetEntry> changeSetItems,
            CancellationToken cancellationToken)
        {
            foreach (ChangeSetEntry entry in changeSetItems)
            {
                if (entry.ChangeSetEntityState == DynamicChangeSetEntityState.Validated)
                {
                    entry.ChangeSetEntityState = DynamicChangeSetEntityState.PreEventing;

                    var filter = context.GetHookHandler<IChangeSetEntryFilter>();
                    if (filter != null)
                    {
                        await filter.OnExecutingEntryAsync(context, entry, cancellationToken);
                    }

                    if (entry.ChangeSetEntityState == DynamicChangeSetEntityState.PreEventing)
                    {
                        // if the state is still the intermediate state,
                        // the entity was not changed during processing
                        // and can move to the next step
                        entry.ChangeSetEntityState = DynamicChangeSetEntityState.PreEvented;
                    }
                    else if (entry.ChangeSetEntityState == DynamicChangeSetEntityState.Changed /*&&
                        entity.Details.EntityState == originalEntityState*/)
                    {
                        entry.ChangeSetEntityState = DynamicChangeSetEntityState.ChangedWithinOwnPreEventing;
                    }
                }
            }
        }
        private static async Task InvokeValidators(
            SubmitContext context,
            IEnumerable<ChangeSetEntry> changeSetItems,
            CancellationToken cancellationToken)
        {
            var validator = context.GetHookHandler<IChangeSetEntryValidator>();
            if (validator == null)
            {
                return;
            }

            ValidationResults validationResults = new ValidationResults();

            foreach (ChangeSetEntry entry in changeSetItems.Where(i => i.HasChanged()))
            {
                await validator.ValidateEntityAsync(context, entry, validationResults, cancellationToken);
            }

            if (validationResults.HasErrors)
            {
                string validationErrorMessage = Resources.ValidationFailsTheOperation;
                throw new ValidationException(validationErrorMessage)
                {
                    ValidationResults = validationResults.Errors
                };
            }
        }
        private static async Task InvokeAuthorizers(
            SubmitContext context,
            IEnumerable<ChangeSetEntry> changeSetItems,
            CancellationToken cancellationToken)
        {
            var authorizer = context.GetHookHandler<IChangeSetEntryAuthorizer>();
            if (authorizer == null)
            {
                return;
            }

            foreach (ChangeSetEntry entry in changeSetItems.Where(i => i.HasChanged()))
            {
                if (!await authorizer.AuthorizeAsync(context, entry, cancellationToken))
                {
                    var message = DefaultSubmitHandler.GetAuthorizeFailedMessage(entry);
                    throw new SecurityException(message);
                }
            }
        }