/// <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); }