Refactor exceptions to include profileId context

Refactored `JobException` and derived classes (`InactiveProfileException`, `JobHttpException`, `JobSqlException`) to include `profileId` as a required parameter in their constructors. This ensures consistent context for exceptions related to job execution.

Updated behaviors (`CheckQueryExecutionBehavior`, `MainQueryExecutionBehavior`, `ReCRequestExecutionBehavior`) to use the new constructors, passing `profileId` where applicable. Improved exception messages for better debugging context.

Simplified property initialization and enhanced XML documentation for clarity.
This commit is contained in:
2026-08-03 13:36:42 +02:00
parent eb060aa54e
commit 90916f6d03
7 changed files with 107 additions and 116 deletions

View File

@@ -6,42 +6,31 @@ namespace ECMJobRunner.Application.Common.Exceptions
/// Exception thrown when attempting to execute a job for an inactive profile /// Exception thrown when attempting to execute a job for an inactive profile
/// Extends JobException with profile-specific context /// Extends JobException with profile-specific context
/// </summary> /// </summary>
public class InactiveProfileException : JobException /// <remarks>
/// Initializes a new instance of InactiveProfileException
/// </remarks>
/// <param name="profileId">ID of the inactive profile</param>
/// <param name="profileName">Name of the inactive profile (nullable)</param>
/// <param name="batchId">Unique batch identifier for tracking</param>
/// <remarks>
/// Use this exception when:
/// - Attempting to trigger DEX job batch for an inactive profile
/// - Attempting to execute individual jobs from an inactive profile
/// - Profile is deactivated during execution
/// </remarks>
public class InactiveProfileException(long profileId, string? profileName, string batchId) : JobException(
profileId,
jobName: "Profile Execution",
processName: "Profile Active Status Validation",
batchId: batchId,
reason: "The profile is marked as inactive and cannot be executed",
innerException: null,
("Profile ID", profileId.ToString(), false),
("Profile Name", profileName, true))
{ {
/// <summary>
/// Initializes a new instance of InactiveProfileException
/// </summary>
/// <param name="profileId">ID of the inactive profile</param>
/// <param name="profileName">Name of the inactive profile (nullable)</param>
/// <param name="batchId">Unique batch identifier for tracking</param>
/// <remarks>
/// Use this exception when:
/// - Attempting to trigger DEX job batch for an inactive profile
/// - Attempting to execute individual jobs from an inactive profile
/// - Profile is deactivated during execution
/// </remarks>
public InactiveProfileException(long profileId, string? profileName, string batchId)
: base(
jobName: "Profile Execution",
processName: "Profile Active Status Validation",
batchId: batchId,
reason: "The profile is marked as inactive and cannot be executed",
innerException: null,
("Profile ID", profileId.ToString(), false),
("Profile Name", profileName, true))
{
ProfileId = profileId;
ProfileName = profileName;
}
/// <summary>
/// Gets the ID of the inactive profile
/// </summary>
public long ProfileId { get; }
/// <summary> /// <summary>
/// Gets the name of the inactive profile (nullable) /// Gets the name of the inactive profile (nullable)
/// </summary> /// </summary>
public string? ProfileName { get; } public string? ProfileName { get; } = profileName;
} }
} }

View File

@@ -7,53 +7,54 @@ namespace ECMJobRunner.Application.Common.Exceptions
/// Base exception class for job execution failures /// Base exception class for job execution failures
/// Provides a flexible structure for capturing job context and detailed error information /// Provides a flexible structure for capturing job context and detailed error information
/// </summary> /// </summary>
public class JobException : Exception /// <remarks>
{ /// Initializes a new instance of JobException with detailed context information
/// <summary> /// </remarks>
/// Initializes a new instance of JobException with detailed context information /// <param name="profileId">Identifier of the profile associated with the job</param>
/// </summary> /// <param name="jobName">Name of the job that failed (e.g., "SQL Main Query", "ReC Request")</param>
/// <param name="jobName">Name of the job that failed (e.g., "SQL Main Query", "ReC Request")</param> /// <param name="processName">Name of the process/stage being executed (e.g., "MainQueryExecution", "CheckQueryValidation")</param>
/// <param name="processName">Name of the process/stage being executed (e.g., "MainQueryExecution", "CheckQueryValidation")</param> /// <param name="batchId">Unique batch identifier for tracking the execution</param>
/// <param name="batchId">Unique batch identifier for tracking the execution</param> /// <param name="reason">Human-readable reason for the failure (nullable)</param>
/// <param name="reason">Human-readable reason for the failure (nullable)</param> /// <param name="innerException">The underlying exception that caused the failure (nullable)</param>
/// <param name="innerException">The underlying exception that caused the failure (nullable)</param> /// <param name="details">Additional contextual details as name-value pairs with optional null-handling</param>
/// <param name="details">Additional contextual details as name-value pairs with optional null-handling</param> /// <remarks>
/// <remarks> /// The details parameter accepts tuples with:
/// The details parameter accepts tuples with: /// - Name: Display name of the detail
/// - Name: Display name of the detail /// - Value: String value of the detail (nullable)
/// - Value: String value of the detail (nullable) /// - IgnoreIfNull: If true, the detail is omitted from the message when value is null
/// - IgnoreIfNull: If true, the detail is omitted from the message when value is null /// </remarks>
/// </remarks> public class JobException(long profileId, string jobName, string processName, string batchId, string? reason, Exception? innerException, params (string Name, string? Value, bool IgnoreIfNull)[] details)
public JobException(string jobName, string processName, string batchId, string? reason, Exception? innerException, params (string Name, string? Value, bool IgnoreIfNull)[] details) : Exception(
: base( CreateMessage(jobName,
CreateMessage(jobName,
[ [
("Profile Id", profileId.ToString(), false),
("Job Name", jobName, false),
("Process Name", processName, false), ("Process Name", processName, false),
("Batch Id", batchId, false), ("Batch Id", batchId, false),
("Reason", reason, true), ("Reason", reason, true),
..details ..details
]), ]),
innerException) innerException)
{ {
JobName = jobName; /// <summary>
ProcessName = processName; /// Gets the profile identifier associated with the job execution
BatchId = batchId; /// </summary>
} public long ProfileId { get; } = profileId;
/// <summary> /// <summary>
/// Gets the name of the job that failed /// Gets the name of the job that failed
/// </summary> /// </summary>
public string JobName { get; } public string JobName { get; } = jobName;
/// <summary> /// <summary>
/// Gets the name of the process/stage that was being executed when the failure occurred /// Gets the name of the process/stage that was being executed when the failure occurred
/// </summary> /// </summary>
public string ProcessName { get; } public string ProcessName { get; } = processName;
/// <summary> /// <summary>
/// Gets the unique batch identifier for tracking the execution /// Gets the unique batch identifier for tracking the execution
/// </summary> /// </summary>
public string BatchId { get; } public string BatchId { get; } = batchId;
/// <summary> /// <summary>
/// Generates a formatted error message with job context and details /// Generates a formatted error message with job context and details

View File

@@ -6,41 +6,36 @@ namespace ECMJobRunner.Application.Common.Exceptions
/// Exception for HTTP client-related job failures (e.g., ReC API calls, REST requests) /// Exception for HTTP client-related job failures (e.g., ReC API calls, REST requests)
/// Extends JobException with client library and method context /// Extends JobException with client library and method context
/// </summary> /// </summary>
public class JobHttpException : JobException /// <remarks>
/// Initializes a new instance of JobHttpException with HTTP client context
/// </remarks>
/// <param name="profileId">Identifier of the profile associated with the job</param>
/// <param name="jobName">Name of the job that failed (e.g., "ReC Request", "API Call")</param>
/// <param name="processName">Name of the process/stage being executed</param>
/// <param name="batchId">Unique batch identifier for tracking</param>
/// <param name="reason">Human-readable reason for the failure (nullable)</param>
/// <param name="clientLibrary">Name of the HTTP client library used (e.g., "ReC.Client", "HttpClient") (nullable)</param>
/// <param name="clientMethod">Name of the client method that failed (e.g., "ExecuteAsync", "PostAsync") (nullable)</param>
/// <param name="innerException">The underlying exception that caused the failure (nullable)</param>
/// <remarks>
/// Use this exception for HTTP-related failures such as:
/// - ReC API request failures
/// - REST API communication errors
/// - HTTP client timeout/network issues
/// - Authentication/authorization failures
/// </remarks>
public class JobHttpException(long profileId, string jobName, string processName, string batchId, string? reason, string? clientLibrary, string? clientMethod, Exception? innerException)
: JobException(profileId, jobName, processName, batchId, reason, innerException, ("Client Library", clientLibrary, true), ("Client Method", clientMethod, true))
{ {
/// <summary>
/// Initializes a new instance of JobHttpException with HTTP client context
/// </summary>
/// <param name="jobName">Name of the job that failed (e.g., "ReC Request", "API Call")</param>
/// <param name="processName">Name of the process/stage being executed</param>
/// <param name="batchId">Unique batch identifier for tracking</param>
/// <param name="reason">Human-readable reason for the failure (nullable)</param>
/// <param name="clientLibrary">Name of the HTTP client library used (e.g., "ReC.Client", "HttpClient") (nullable)</param>
/// <param name="clientMethod">Name of the client method that failed (e.g., "ExecuteAsync", "PostAsync") (nullable)</param>
/// <param name="innerException">The underlying exception that caused the failure (nullable)</param>
/// <remarks>
/// Use this exception for HTTP-related failures such as:
/// - ReC API request failures
/// - REST API communication errors
/// - HTTP client timeout/network issues
/// - Authentication/authorization failures
/// </remarks>
public JobHttpException(string jobName, string processName, string batchId, string? reason, string? clientLibrary, string? clientMethod, Exception? innerException) : base(jobName, processName, batchId, reason, innerException,
("Client Library", clientLibrary, true),
("Client Method", clientMethod, true))
{
ClientLibrary = clientLibrary;
ClientMethod = clientMethod;
}
/// <summary> /// <summary>
/// Gets the name of the HTTP client library that was used (e.g., "ReC.Client", "HttpClient") /// Gets the name of the HTTP client library that was used (e.g., "ReC.Client", "HttpClient")
/// </summary> /// </summary>
public string? ClientLibrary { get; } public string? ClientLibrary { get; } = clientLibrary;
/// <summary> /// <summary>
/// Gets the name of the client method that failed (e.g., "ExecuteAsync", "PostAsync") /// Gets the name of the client method that failed (e.g., "ExecuteAsync", "PostAsync")
/// </summary> /// </summary>
public string? ClientMethod { get; } public string? ClientMethod { get; } = clientMethod;
} }
} }

View File

@@ -6,33 +6,30 @@ namespace ECMJobRunner.Application.Common.Exceptions
/// Exception for SQL query execution failures /// Exception for SQL query execution failures
/// Extends JobException with SQL query context for debugging /// Extends JobException with SQL query context for debugging
/// </summary> /// </summary>
public class JobSqlException : JobException /// <remarks>
/// Initializes a new instance of JobSqlException with SQL query context
/// </remarks>
/// <param name="profileId">Identifier of the profile associated with the job</param>
/// <param name="jobName">Name of the job that failed (e.g., "Main Query Execution", "Check Query")</param>
/// <param name="processName">Name of the process/stage being executed</param>
/// <param name="batchId">Unique batch identifier for tracking</param>
/// <param name="reason">Human-readable reason for the failure (nullable)</param>
/// <param name="query">The SQL query that failed (nullable, for debugging purposes)</param>
/// <param name="innerException">The underlying SQL exception (nullable)</param>
/// <remarks>
/// Use this exception for SQL-related failures such as:
/// - Main query execution errors
/// - Check query validation failures
/// - Database connection issues
/// - SQL syntax errors
/// - Query timeout exceptions
///
/// The Query property can be logged for debugging but should be handled carefully
/// to avoid exposing sensitive data in production logs.
/// </remarks>
public class JobSqlException(long profileId, string jobName, string processName, string batchId, string? reason, string? query, Exception? innerException)
: JobException(profileId, jobName, processName, batchId, reason, innerException, ("Query", query, true))
{ {
/// <summary>
/// Initializes a new instance of JobSqlException with SQL query context
/// </summary>
/// <param name="jobName">Name of the job that failed (e.g., "Main Query Execution", "Check Query")</param>
/// <param name="processName">Name of the process/stage being executed</param>
/// <param name="batchId">Unique batch identifier for tracking</param>
/// <param name="reason">Human-readable reason for the failure (nullable)</param>
/// <param name="query">The SQL query that failed (nullable, for debugging purposes)</param>
/// <param name="innerException">The underlying SQL exception (nullable)</param>
/// <remarks>
/// Use this exception for SQL-related failures such as:
/// - Main query execution errors
/// - Check query validation failures
/// - Database connection issues
/// - SQL syntax errors
/// - Query timeout exceptions
///
/// The Query property can be logged for debugging but should be handled carefully
/// to avoid exposing sensitive data in production logs.
/// </remarks>
public JobSqlException(string jobName, string processName, string batchId, string? reason, string? query, Exception? innerException) : base(jobName, processName, batchId, reason, innerException,
("Query", query, true))
{
Query = query;
}
/// <summary> /// <summary>
/// Gets the SQL query that failed (nullable) /// Gets the SQL query that failed (nullable)
@@ -41,6 +38,6 @@ namespace ECMJobRunner.Application.Common.Exceptions
/// This property is marked as virtual to allow derived classes to customize query handling /// This property is marked as virtual to allow derived classes to customize query handling
/// (e.g., sanitizing sensitive data, truncating long queries) /// (e.g., sanitizing sensitive data, truncating long queries)
/// </remarks> /// </remarks>
public virtual string? Query { get; } public virtual string? Query { get; } = query;
} }
} }

View File

@@ -71,6 +71,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
{ {
if (result is null) if (result is null)
throw new JobSqlException( throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName: "Triggering DEX", jobName: "Triggering DEX",
processName: "Check Query", processName: "Check Query",
batchId: command.BatchId, batchId: command.BatchId,
@@ -79,6 +80,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
innerException: null); innerException: null);
else if (result.ReturnValue <= 0) else if (result.ReturnValue <= 0)
throw new JobSqlException( throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName: "Triggering DEX", jobName: "Triggering DEX",
processName: "Check Query", processName: "Check Query",
batchId: command.BatchId, batchId: command.BatchId,
@@ -91,6 +93,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
{ {
if (_options.Error.CheckQuery.OnExecution == ErrorAction.Stop) if (_options.Error.CheckQuery.OnExecution == ErrorAction.Stop)
throw new JobSqlException( throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName: "Triggering DEX", jobName: "Triggering DEX",
processName: "Check Query", processName: "Check Query",
batchId: command.BatchId, batchId: command.BatchId,
@@ -103,6 +106,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
else if (_options.Error.CheckQuery.IfNullOrWhiteSpace == ErrorAction.Stop) else if (_options.Error.CheckQuery.IfNullOrWhiteSpace == ErrorAction.Stop)
{ {
throw new JobSqlException( throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName:"Triggering DEX", jobName:"Triggering DEX",
processName:"Check Query", processName:"Check Query",
batchId:command.BatchId, batchId:command.BatchId,

View File

@@ -71,6 +71,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
{ {
if (result is null) if (result is null)
throw new JobSqlException( throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName: "Triggering DEX", jobName: "Triggering DEX",
processName: "Main Query", processName: "Main Query",
batchId: command.BatchId, batchId: command.BatchId,
@@ -79,6 +80,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
innerException: null); innerException: null);
else if (result.ReturnValue is not null) else if (result.ReturnValue is not null)
throw new JobSqlException( throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName: "Triggering DEX", jobName: "Triggering DEX",
processName: "Main Query", processName: "Main Query",
batchId: command.BatchId, batchId: command.BatchId,
@@ -90,6 +92,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
{ {
if (_options.Error.MainQuery.OnExecution == ErrorAction.Stop) if (_options.Error.MainQuery.OnExecution == ErrorAction.Stop)
throw new JobSqlException( throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName: "Triggering DEX", jobName: "Triggering DEX",
processName: "Main Query", processName: "Main Query",
batchId: command.BatchId, batchId: command.BatchId,
@@ -101,6 +104,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
else if (_options.Error.MainQuery.IfNullOrWhiteSpace == ErrorAction.Stop) else if (_options.Error.MainQuery.IfNullOrWhiteSpace == ErrorAction.Stop)
{ {
throw new JobSqlException( throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName: "Triggering DEX", jobName: "Triggering DEX",
processName: "Main Query", processName: "Main Query",
batchId: command.BatchId, batchId: command.BatchId,

View File

@@ -67,6 +67,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
if (_options.Error.ReCRequest.OnSending == ErrorAction.Stop) if (_options.Error.ReCRequest.OnSending == ErrorAction.Stop)
{ {
throw new JobHttpException( throw new JobHttpException(
profileId: command.Job.ProfileId,
jobName: "Triggering DEX", jobName: "Triggering DEX",
processName: "ReC Http Request", processName: "ReC Http Request",
batchId: command.BatchId, batchId: command.BatchId,