/// <summary> /// Iteratively executes the business logic until the application is stopped. /// </summary> /// <param name="stoppingToken">The <see cref="CancellationToken"/> that can be used to stop execution of the application.</param> /// <returns></returns> protected override async Task ExecuteAsync(CancellationToken stoppingToken) { MethodBase methodBase = MethodBase.GetCurrentMethod(); logger.Trace($"Begin {methodBase.ReflectedType.Name}.{methodBase.Name}"); // This is the loop containing all of the business logic that is executed iteratively throughout the lifeime of the application. while (!stoppingToken.IsCancellationRequested) { // Abort if waiting for restoration of connectivity to the MyGeotab server or to the database. if (StateMachine.CurrentState == State.Waiting) { continue; } if (initializationCompleted == false) { PerformInitializationTasks(); continue; } try { logger.Trace($"Started iteration of {methodBase.ReflectedType.Name}.{methodBase.Name}"); // Only proceed if the DVIRLogManipulatorIntervalSeconds has elapsed since the last iteration was initiated. if (Globals.TimeIntervalHasElapsed(lastIterationStartTimeUtc, Globals.DateTimeIntervalType.Seconds, Globals.ConfigurationManager.DVIRLogManipulatorIntervalSeconds)) { lastIterationStartTimeUtc = DateTime.UtcNow; using (var cancellationTokenSource = new CancellationTokenSource()) { try { // Retrieve a batch of DbDVIRDefectUpdate entities from the database. var dbDVIRDefectUpdates = await DbDVIRDefectUpdateService.GetAllAsync(connectionInfo, commandTimeout, DVIRDefectUpdateBatchSize); int dbDVIRDefectUpdateCount = dbDVIRDefectUpdates.Count(); int processedDbDVIRDefectUpdateCount = 0; int failedDbDVIRDefectUpdateCount = 0; if (dbDVIRDefectUpdates.Any()) { logger.Info($"Retrieved {dbDVIRDefectUpdateCount} records from DVIRDefectUpdates table. Processing..."); } else { logger.Debug($"No records retrieved from DVIRDefectUpdates table."); continue; } // Process each of the retrieved DbDVIRDefectUpdate entities. foreach (var dbDVIRDefectUpdate in dbDVIRDefectUpdates) { try { // Get the subject DVIRLog from the MyGeotab database. var dvirLogResultList = await MyGeotabApiUtility.GetAsync <DVIRLog>(Globals.MyGeotabAPI, new DVIRLogSearch { Id = Id.Create(dbDVIRDefectUpdate.DVIRLogId) }); if (dvirLogResultList.Any()) { var dvirLogToUpdate = dvirLogResultList.First(); // Get the subject DVIRDefect to be updated. var dvirDefectToUpdate = dvirLogToUpdate.DVIRDefects.Where(dvirDefect => dvirDefect.Id == Id.Create(dbDVIRDefectUpdate.DVIRDefectId)).FirstOrDefault(); if (dvirDefectToUpdate == null) { throw new ArgumentException($"No DVIRDefect with the sepecified Id is associated with the subject DVIRLog in the MyGeotab database."); } else { bool defectRemarkAddedToDVIRDefectToUpdate = AddDefectRemark(dvirDefectToUpdate, dbDVIRDefectUpdate); bool repairStatusUpdatedOnDVIRDefectToUpdate = UpdateDVIRDefectRepairStatus(dvirDefectToUpdate, dbDVIRDefectUpdate); if (defectRemarkAddedToDVIRDefectToUpdate == false && repairStatusUpdatedOnDVIRDefectToUpdate == false) { throw new ArgumentException($"No action to take because insufficent data was provided to either add a DefectRemark or update the RepairStatus of the subject DVIRDefect."); } var deleteCurrentDbDVIRDefectUpdateRecord = false; try { // Update the DVIRLog in the MyGeotab database. var result = await MyGeotabApiUtility.SetAsync <DVIRLog>(Globals.MyGeotabAPI, dvirLogToUpdate); deleteCurrentDbDVIRDefectUpdateRecord = true; processedDbDVIRDefectUpdateCount += 1; } catch (MyGeotabConnectionException) { // Pass the MyGeotabConnectionException along so that the connectivity loss can be addressed and the subject DbDVIRDefectUpdate can be re-tried once connectivity is restored. throw; } catch (Exception exception) { // Flag the current DbDVIRDefectUpdateRecord for deletion since this is not a connectivity-related exception and the same exception could occur repeatedly if the adapter is restarted and this record remains in the adapter database. deleteCurrentDbDVIRDefectUpdateRecord = true; throw new Exception($"MyGeotab Exception encountered on Set<DVIRLog> call.", exception); } finally { if (deleteCurrentDbDVIRDefectUpdateRecord == true) { // Delete the current DbDVIRDefectUpdate from the DVIRDefectUpdates table. await DbDVIRDefectUpdateService.DeleteAsync(connectionInfo, dbDVIRDefectUpdate, commandTimeout); } } } } else { throw new Exception($"No DVIRLog with the specified Id exists in the MyGeotab database."); } } catch (Exception dvirLogUpdateException) { try { failedDbDVIRDefectUpdateCount += 1; var failureMessage = GenerateMessageForDVIRLogUpdateException(dvirLogUpdateException); // Insert a record into the FailedDVIRDefectUpdates table. DbFailedDVIRDefectUpdate dbFailedDVIRDefectUpdate = ObjectMapper.GetDbFailedDVIRDefectUpdate(dbDVIRDefectUpdate, failureMessage); dbFailedDVIRDefectUpdate.DatabaseWriteOperationType = Common.DatabaseWriteOperationType.Insert; dbFailedDVIRDefectUpdate.RecordCreationTimeUtc = DateTime.UtcNow; await DbFailedDVIRDefectUpdateService.InsertAsync(connectionInfo, dbFailedDVIRDefectUpdate, commandTimeout); // Delete the current DbDVIRDefectUpdate from the DVIRDefectUpdates table. await DbDVIRDefectUpdateService.DeleteAsync(connectionInfo, dbDVIRDefectUpdate, commandTimeout); } catch (Exception ex) { throw new Exception($"Exception encountered while writing to {ConfigurationManager.DbFailedDVIRDefectUpdatesTableName} table or deleting from {ConfigurationManager.DbDVIRDefectUpdatesTableName} table.", ex); } } } logger.Info($"Of the {dbDVIRDefectUpdateCount} records from DVIRDefectUpdates table, {processedDbDVIRDefectUpdateCount} were successfully processed and {failedDbDVIRDefectUpdateCount} failed. Copies of any failed records have been inserted into the FailedDVIRDefectUpdates table for reference."); } catch (TaskCanceledException taskCanceledException) { string errorMessage = $"Task was cancelled. TaskCanceledException: \nMESSAGE [{taskCanceledException.Message}]; \nSOURCE [{taskCanceledException.Source}]; \nSTACK TRACE [{taskCanceledException.StackTrace}]"; logger.Warn(errorMessage); } } } else { logger.Debug($"Propagation of DVIRLog changes from tables in the adapter database to the associated MyGeotab database has not been initiated on this iteration; {Globals.ConfigurationManager.DVIRLogManipulatorIntervalSeconds} seconds have not passed since the process was last initiated."); } logger.Trace($"Completed iteration of {methodBase.ReflectedType.Name}.{methodBase.Name}"); } catch (OperationCanceledException) { string errorMessage = $"{nameof(DVIRLogManipulator)} process cancelled."; logger.Warn(errorMessage); throw new Exception(errorMessage); } catch (DatabaseConnectionException databaseConnectionException) { HandleException(databaseConnectionException, NLog.LogLevel.Error, $"{nameof(DVIRLogManipulator)} process caught an exception"); } catch (MyGeotabConnectionException myGeotabConnectionException) { HandleException(myGeotabConnectionException, NLog.LogLevel.Error, $"{nameof(DVIRLogManipulator)} process caught an exception"); } catch (Exception ex) { // If an exception hasn't been handled to this point, log it and kill the process. HandleException(ex, NLog.LogLevel.Fatal, $"******** {nameof(DVIRLogManipulator)} process caught an unhandled exception and will self-terminate now."); System.Diagnostics.Process.GetCurrentProcess().Kill(); } } logger.Trace($"End {methodBase.ReflectedType.Name}.{methodBase.Name}"); }
/// <summary> /// Inserts a <see cref="DbFailedDVIRDefectUpdate"/> entity into the database. /// </summary> /// <param name="connectionInfo">The database connection information.</param> /// <param name="dbFailedDVIRDefectUpdate">A <see cref="DbFailedDVIRDefectUpdate"/> entity to be inserted.</param> /// <param name="commandTimeout">The number of seconds before command execution timeout.</param> /// <returns></returns> public static async Task <long> InsertAsync(ConnectionInfo connectionInfo, DbFailedDVIRDefectUpdate dbFailedDVIRDefectUpdate, int commandTimeout) { return(await new DbFailedDVIRDefectUpdateRepository().InsertAsync(connectionInfo, dbFailedDVIRDefectUpdate, commandTimeout)); }