diff --git a/ECMJobRunner.Application/DEXJob/TriggeringDEXJobBatchCommand.cs b/ECMJobRunner.Application/DEXJob/TriggeringDEXJobBatchCommand.cs
new file mode 100644
index 0000000..35c82c3
--- /dev/null
+++ b/ECMJobRunner.Application/DEXJob/TriggeringDEXJobBatchCommand.cs
@@ -0,0 +1,73 @@
+using ECMJobRunner.Domain.Interfaces;
+using MediatR;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ECMJobRunner.Application.DEXJob
+{
+ ///
+ /// Command to trigger DEX job batch for a profile
+ /// Executes all SQL jobs associated with a profile ID
+ ///
+ public class TriggeringDEXJobBatchCommand : IRequest
+ {
+ ///
+ /// Profile ID to trigger all associated SQL jobs
+ ///
+ public int ProfileId { get; set; }
+ }
+
+ ///
+ /// Handler for TriggeringDEXJobBatchCommand
+ /// Retrieves all SQL jobs for a profile and executes them sequentially
+ ///
+ public class TriggeringDEXJobBatchCommandHandler(IProfileSqlJobRepository jobRepo, ISender sender)
+ : IRequestHandler
+ {
+ ///
+ /// Handles the TriggeringDEXJobBatchCommand
+ /// Creates a unique batch ID and triggers individual job commands
+ ///
+ /// The command containing the profile ID
+ /// Cancellation token
+ /// Unit value indicating completion
+ public async Task Handle(TriggeringDEXJobBatchCommand request, CancellationToken cancellationToken)
+ {
+ var jobs = await jobRepo.FindAsync(j => j.ProfileId == request.ProfileId, cancellationToken);
+
+ var batchId = CreateBatchId();
+
+ foreach (var job in jobs)
+ {
+ await sender.Send(new TriggeringDEXJobCommand
+ {
+ Job = job,
+ BatchId = batchId
+ }, cancellationToken);
+ }
+
+ return Unit.Value;
+ }
+
+ ///
+ /// Creates a unique batch ID based on current timestamp
+ ///
+ /// 20-character timestamp string (yyyyMMddHHmmssfffffff truncated to 20 chars)
+ public static string CreateBatchId()
+ {
+ // Format: "yyyy" : Year (4 digits)
+ // "MM" : Month (2 digits)
+ // "dd" : Day (2 digits)
+ // "HH" : Hour (24-hour format, 2 digits)
+ // "mm" : Minute (2 digits)
+ // "ss" : Second (2 digits)
+ // "fffffff": Fractions of a second / 100-nanoseconds (7 digits)
+
+ string fullBatchId = DateTime.Now.ToString("yyyyMMddHHmmssfffffff");
+
+ // Limit to 20 characters as per specification
+ return fullBatchId.Substring(0, 20);
+ }
+ }
+}
diff --git a/ECMJobRunner.Application/DEXJob/TriggeringDEXJobCommand.cs b/ECMJobRunner.Application/DEXJob/TriggeringDEXJobCommand.cs
new file mode 100644
index 0000000..6ba5c1e
--- /dev/null
+++ b/ECMJobRunner.Application/DEXJob/TriggeringDEXJobCommand.cs
@@ -0,0 +1,131 @@
+using ECMJobRunner.Application.Common.Constants;
+using ECMJobRunner.Application.Common.Dtos;
+using ECMJobRunner.Application.Common.Exceptions;
+using ECMJobRunner.Application.Common.Interfaces;
+using ECMJobRunner.Application.Common.Options;
+using ECMJobRunner.Domain.Entities;
+using MediatR;
+using Microsoft.Extensions.Options;
+using ReC.Client;
+using ReC.Client.Api;
+using System;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ECMJobRunner.Application.DEXJob
+{
+ ///
+ /// Command to trigger a single DEX job execution
+ /// Executes main query, check query, and ReC request in sequence
+ ///
+ public class TriggeringDEXJobCommand : IRequest
+ {
+ ///
+ /// The SQL job to execute
+ ///
+ public ProfileSqlJob Job { get; set; } = null!;
+
+ ///
+ /// Unique batch identifier for this execution
+ ///
+ public string BatchId { get; set; } = null!;
+ }
+
+ ///
+ /// Handler for TriggeringDEXJobCommand
+ /// Orchestrates execution of main query, check query, and ReC HTTP request
+ ///
+ public class TriggeringDEXJobCommandHandler(
+ ISQLExecutor executor,
+ IOptions options,
+ ReCClient reCClient)
+ : IRequestHandler
+ {
+ private readonly DexJobOptions _options = options.Value;
+
+ ///
+ /// Handles the TriggeringDEXJobCommand
+ /// Executes main query, check query, and ReC request according to configuration
+ ///
+ /// The command containing job and batch ID
+ /// Cancellation token
+ /// Unit value indicating completion
+ public async Task Handle(TriggeringDEXJobCommand request, CancellationToken cancel)
+ {
+ #region Main Query
+ if (!string.IsNullOrWhiteSpace(request.Job.SqlMainQuery))
+ {
+ var sqlMainQuery = Regex.Replace(
+ request.Job.SqlMainQuery!,
+ _options.Placeholders.BatchId.Pattern,
+ request.BatchId,
+ _options.Placeholders.BatchId.RegexOptions);
+
+ try
+ {
+ var result = await executor.ExecuteQueryAsync(sqlMainQuery, cancel);
+ if (_options.Error.MainQuery.OnUnexpectedResult == ErrorAction.Stop)
+ {
+ if (result is null)
+ throw new DEXJobException("SQL Main Query", request.BatchId, sqlMainQuery, "Main Query returned nothing.");
+ else if (result.ReturnValue is not null)
+ throw new DEXJobException("SQL Main Query", request.BatchId, sqlMainQuery, $"The query unexpectedly returned the value {result.ReturnValue}. The expected value was null.");
+ }
+ }
+ catch (Exception ex)
+ {
+ if (_options.Error.MainQuery.OnExecution == ErrorAction.Stop)
+ throw new DEXJobException("SQL Main Query", request.BatchId, sqlMainQuery, ex);
+ }
+ }
+ else if (_options.Error.MainQuery.IfNullOrWhiteSpace == ErrorAction.Stop)
+ {
+ throw new DEXJobException("SQL Main Query", request.BatchId, null, "SQL Main Query is null or empty");
+ }
+ #endregion
+
+ #region Check Query
+ if (!string.IsNullOrWhiteSpace(request.Job.SqlCheckQuery))
+ {
+ var sqlCheckQuery = Regex.Replace(
+ request.Job.SqlCheckQuery!,
+ _options.Placeholders.BatchId.Pattern,
+ request.BatchId,
+ _options.Placeholders.BatchId.RegexOptions);
+
+ try
+ {
+ var result = await executor.ExecuteQueryAsync(sqlCheckQuery, cancel);
+ if (_options.Error.CheckQuery.OnUnexpectedResult == ErrorAction.Stop)
+ {
+ if (result is null)
+ throw new DEXJobException("SQL Check Query", request.BatchId, sqlCheckQuery, "Check Query returned nothing.");
+ else if (result.ReturnValue <= 0)
+ throw new DEXJobException("SQL Check Query", request.BatchId, sqlCheckQuery, $"The query unexpectedly returned the value {result.ReturnValue}. The expected value was any value greater than 0.");
+ }
+ }
+ catch (Exception ex)
+ {
+ if (_options.Error.CheckQuery.OnExecution == ErrorAction.Stop)
+ throw new DEXJobException("SQL Check Query", request.BatchId, sqlCheckQuery, ex);
+ }
+ }
+ else if (_options.Error.CheckQuery.IfNullOrWhiteSpace == ErrorAction.Stop)
+ {
+ throw new DEXJobException("SQL Check Query", request.BatchId, null, "SQL Check Query is null or empty");
+ }
+ #endregion
+
+ #region ReC Request
+ long profileId = request.Job.ProfileId;
+ await reCClient.RecActions.InvokeAsync(profileId, new InvokeReferences()
+ {
+ BatchId = request.BatchId, // Fixed: Use request.BatchId instead of profileId.ToString()
+ }, cancel);
+ #endregion
+
+ return Unit.Value; // Fixed: Added missing return statement
+ }
+ }
+}