Esempio n. 1
0
        /// <summary>
        /// This runs the events before, during, and after the base SaveChanges method is run
        /// </summary>
        /// <param name="context">The current DbContext</param>
        /// <param name="callBaseSaveChanges">A function that is linked to the base SaveChanges in your DbContext</param>
        /// <returns>Returns the status with the numUpdated number from SaveChanges</returns>
        public IStatusGeneric <int> RunEventsBeforeDuringAfterSaveChanges(DbContext context, Func <int> callBaseSaveChanges)
        {
            IStatusGeneric <int> RunTransactionWithDuringSaveChangesEvents()
            {
                var localStatus = new StatusGenericHandler <int>();

                //If there is a current transaction then use that, otherwise
                using var transaction = context.Database.CurrentTransaction == null
                    ? context.Database.BeginTransaction()
                    : null;

                var duringPreValueTask = _eachEventRunner.RunDuringSaveChangesEventsAsync(context, false, false);

                if (!duringPreValueTask.IsCompleted)
                {
                    throw new InvalidOperationException("Can only run sync tasks");
                }
                localStatus.CombineStatuses(duringPreValueTask.Result);

                var transactionSaveChanges = CallSaveChangesWithExceptionHandler(context, callBaseSaveChanges);

                if (localStatus.CombineStatuses(transactionSaveChanges).HasErrors)
                {
                    return(localStatus);
                }

                localStatus.SetResult(transactionSaveChanges.Result);

                var duringPostValueTask = _eachEventRunner.RunDuringSaveChangesEventsAsync(context, true, false);

                if (!duringPostValueTask.IsCompleted)
                {
                    throw new InvalidOperationException("Can only run sync tasks");
                }
                if (localStatus.CombineStatuses(duringPostValueTask.Result).HasErrors)
                {
                    return(localStatus);
                }

                transaction?.Commit();

                return(localStatus);
            }

            var status          = new StatusGenericHandler <int>();
            var hasDuringEvents = _eachEventRunner.SetupDuringEvents(context);

            var beforeValueTask = _eachEventRunner.RunBeforeSaveChangesEventsAsync(context, false);

            if (!beforeValueTask.IsCompleted)
            {
                throw new InvalidOperationException("Can only run sync tasks");
            }
            status.CombineStatuses(beforeValueTask.Result);
            if (!status.IsValid)
            {
                return(status);
            }

            context.ChangeTracker.DetectChanges();

            //This runs any actions adding to the config that match this DbContext type
            RunAnyAfterDetectChangesActions(context);

            //Call SaveChanges with catch for exception handler
            IStatusGeneric <int> callSaveChangesStatus;

            if (!hasDuringEvents)
            {
                //No need for a transaction as no During event. Therefore just call SaveChanges
                callSaveChangesStatus = CallSaveChangesWithExceptionHandler(context, callBaseSaveChanges);
            }
            else if (context.Database.CurrentTransaction == null && context.Database.CreateExecutionStrategy().RetriesOnFailure)
            {
                //There is no existing transactions AND we have to handle retries, then we need to wrap the transaction in a retry
                callSaveChangesStatus = context.Database.CreateExecutionStrategy().Execute(RunTransactionWithDuringSaveChangesEvents);
                context.ClearDuringEvents();  //clear During events after a successful transaction
            }
            else
            {
                callSaveChangesStatus = RunTransactionWithDuringSaveChangesEvents();
                context.ClearDuringEvents();  //clear During events after a successful transaction
            }

            if (status.CombineStatuses(callSaveChangesStatus).HasErrors)
            {
                return(status);
            }

            //Copy over the saveChanges result
            status.SetResult(callSaveChangesStatus.Result);

            var afterValueTask = _eachEventRunner.RunAfterSaveChangesEventsAsync(context, false);

            if (!afterValueTask.IsCompleted && !afterValueTask.IsFaulted)
            {
                throw new InvalidOperationException("Can only run sync tasks");
            }
            if (afterValueTask.IsFaulted)
            {
                throw afterValueTask.Result;
            }

            return(status);
        }
        /// <summary>
        /// This runs the events before, during and after the base SaveChangesAsync method is run
        /// </summary>
        /// <param name="context">The current DbContext</param>
        /// <param name="callBaseSaveChangesAsync">A function that is linked to the base SaveChangesAsync in your DbContext</param>
        /// <param name="cancellationToken"></param>
        /// <returns>Returns the status with the numUpdated number from SaveChanges</returns>
        public async Task <IStatusGeneric <int> > RunEventsBeforeDuringAfterSaveChangesAsync(DbContext context,
                                                                                             Func <Task <int> > callBaseSaveChangesAsync, CancellationToken cancellationToken)
        {
            var eachEventRunner = new RunEachTypeOfEvents(_serviceProvider, _logger, _config);

            async Task <IStatusGeneric <int> > RunTransactionWithDuringSaveChangesEventsAsync()
            {
                var localStatus = new StatusGenericHandler <int>();

                //If there is a current transaction then use that, otherwise
                using var transaction = context.Database.CurrentTransaction == null
                    ? await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false)
                    : null;

                var duringPreStatus = await eachEventRunner.RunDuringSaveChangesEventsAsync(false, true)
                                      .ConfigureAwait(false);

                localStatus.CombineStatuses(duringPreStatus);

                var transactionSaveChanges = await CallSaveChangesWithExceptionHandlerAsync(context, callBaseSaveChangesAsync)
                                             .ConfigureAwait(false);

                if (localStatus.CombineStatuses(transactionSaveChanges).HasErrors)
                {
                    return(localStatus);
                }

                localStatus.SetResult(transactionSaveChanges.Result);

                var duringPostStatus = await eachEventRunner.RunDuringSaveChangesEventsAsync(true, true)
                                       .ConfigureAwait(false);

                if (localStatus.CombineStatuses(duringPostStatus).HasErrors)
                {
                    return(localStatus);
                }

                if (transaction != null)
                {
                    await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
                }

                return(localStatus);
            }

            var status = new StatusGenericHandler <int>();

            status.CombineStatuses(await eachEventRunner.RunBeforeSaveChangesEventsAsync(context, true).ConfigureAwait(false));
            if (!status.IsValid)
            {
                return(status);
            }

            var hasDuringEvents = !_config.NotUsingDuringSaveHandlers && eachEventRunner.SetupDuringEvents(context);

            context.ChangeTracker.DetectChanges();

            //This runs any actions adding to the config that match this DbContext type
            RunAnyAfterDetectChangesActions(context);

            //Call SaveChangesAsync with catch for exception handler
            IStatusGeneric <int> callSaveChangesStatus;

            if (!hasDuringEvents)
            {
                //No need for a transaction as no During event. Therefore just call SaveChanges
                callSaveChangesStatus = await CallSaveChangesWithExceptionHandlerAsync(context, callBaseSaveChangesAsync)
                                        .ConfigureAwait(false);
            }
            else if (context.Database.CurrentTransaction == null && context.Database.CreateExecutionStrategy().RetriesOnFailure)
            {
                //There is no existing transactions AND we have to handle retries, then we need to wrap the transaction in a retry
                callSaveChangesStatus = await context.Database.CreateExecutionStrategy().ExecuteAsync(async x =>
                                                                                                      await RunTransactionWithDuringSaveChangesEventsAsync().ConfigureAwait(false), cancellationToken);

                context.ClearDuringEvents();  //clear During events after a successful transaction
            }
            else
            {
                callSaveChangesStatus = await RunTransactionWithDuringSaveChangesEventsAsync().ConfigureAwait(false);

                context.ClearDuringEvents();  //clear During events after a successful transaction
            }

            if (status.CombineStatuses(callSaveChangesStatus).HasErrors)
            {
                return(status);
            }

            //Copy over the saveChanges result
            status.SetResult(callSaveChangesStatus.Result);

            await eachEventRunner.RunAfterSaveChangesEventsAsync(context, true);

            return(status);
        }