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
/// Extends JobException with profile-specific context
/// </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>
/// Gets the name of the inactive profile (nullable)
/// </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
/// Provides a flexible structure for capturing job context and detailed error information
/// </summary>
public class JobException : Exception
{
/// <summary>
/// Initializes a new instance of JobException with detailed context information
/// </summary>
/// <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="batchId">Unique batch identifier for tracking the execution</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="details">Additional contextual details as name-value pairs with optional null-handling</param>
/// <remarks>
/// The details parameter accepts tuples with:
/// - Name: Display name of the detail
/// - Value: String value of the detail (nullable)
/// - IgnoreIfNull: If true, the detail is omitted from the message when value is null
/// </remarks>
public JobException(string jobName, string processName, string batchId, string? reason, Exception? innerException, params (string Name, string? Value, bool IgnoreIfNull)[] details)
: base(
CreateMessage(jobName,
/// <remarks>
/// Initializes a new instance of JobException with detailed context information
/// </remarks>
/// <param name="profileId">Identifier of the profile associated with the job</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="batchId">Unique batch identifier for tracking the execution</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="details">Additional contextual details as name-value pairs with optional null-handling</param>
/// <remarks>
/// The details parameter accepts tuples with:
/// - Name: Display name of the detail
/// - Value: String value of the detail (nullable)
/// - IgnoreIfNull: If true, the detail is omitted from the message when value is null
/// </remarks>
public class JobException(long profileId, string jobName, string processName, string batchId, string? reason, Exception? innerException, params (string Name, string? Value, bool IgnoreIfNull)[] details)
: Exception(
CreateMessage(jobName,
[
("Profile Id", profileId.ToString(), false),
("Job Name", jobName, false),
("Process Name", processName, false),
("Batch Id", batchId, false),
("Reason", reason, true),
..details
]),
innerException)
{
JobName = jobName;
ProcessName = processName;
BatchId = batchId;
}
innerException)
{
/// <summary>
/// Gets the profile identifier associated with the job execution
/// </summary>
public long ProfileId { get; } = profileId;
/// <summary>
/// Gets the name of the job that failed
/// </summary>
public string JobName { get; }
public string JobName { get; } = jobName;
/// <summary>
/// Gets the name of the process/stage that was being executed when the failure occurred
/// </summary>
public string ProcessName { get; }
public string ProcessName { get; } = processName;
/// <summary>
/// Gets the unique batch identifier for tracking the execution
/// </summary>
public string BatchId { get; }
public string BatchId { get; } = batchId;
/// <summary>
/// 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)
/// Extends JobException with client library and method context
/// </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>
/// Gets the name of the HTTP client library that was used (e.g., "ReC.Client", "HttpClient")
/// </summary>
public string? ClientLibrary { get; }
public string? ClientLibrary { get; } = clientLibrary;
/// <summary>
/// Gets the name of the client method that failed (e.g., "ExecuteAsync", "PostAsync")
/// </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
/// Extends JobException with SQL query context for debugging
/// </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>
/// 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
/// (e.g., sanitizing sensitive data, truncating long queries)
/// </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)
throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName: "Triggering DEX",
processName: "Check Query",
batchId: command.BatchId,
@@ -79,6 +80,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
innerException: null);
else if (result.ReturnValue <= 0)
throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName: "Triggering DEX",
processName: "Check Query",
batchId: command.BatchId,
@@ -91,6 +93,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
{
if (_options.Error.CheckQuery.OnExecution == ErrorAction.Stop)
throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName: "Triggering DEX",
processName: "Check Query",
batchId: command.BatchId,
@@ -103,6 +106,7 @@ namespace ECMJobRunner.Application.Profiles.Commands.Behaviors
else if (_options.Error.CheckQuery.IfNullOrWhiteSpace == ErrorAction.Stop)
{
throw new JobSqlException(
profileId: command.Job.ProfileId,
jobName:"Triggering DEX",
processName:"Check Query",
batchId:command.BatchId,

View File

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

View File

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