From 22bb585f6023393b34f57c5b3489b7f210c9a849 Mon Sep 17 00:00:00 2001 From: Developer 02 Date: Thu, 22 Jan 2026 01:45:50 +0100 Subject: [PATCH] Refactor SQL exception handling and config structure Simplify SQL exception tracking by replacing error message mappings with a list of relevant error numbers in appsettings.json. Remove custom error message logic and related classes, introducing SqlExceptionOptions to hold tracked error codes. --- src/ReC.API/appsettings.json | 6 +- .../Common/Options/SqlExceptionOptions.cs | 6 ++ .../Options/SqlExceptionTranslatorOptions.cs | 24 -------- .../Procedures/SqlExceptionTranslator.cs | 61 ------------------- 4 files changed, 8 insertions(+), 89 deletions(-) create mode 100644 src/ReC.Application/Common/Options/SqlExceptionOptions.cs delete mode 100644 src/ReC.Application/Common/Options/SqlExceptionTranslatorOptions.cs delete mode 100644 src/ReC.Application/Common/Procedures/SqlExceptionTranslator.cs diff --git a/src/ReC.API/appsettings.json b/src/ReC.API/appsettings.json index 1f02e0a..150bb3c 100644 --- a/src/ReC.API/appsettings.json +++ b/src/ReC.API/appsettings.json @@ -10,10 +10,8 @@ }, "SqlExceptionTranslator": { "ErrorMessages": { - "515": "{Operation} '{Target}' failed because a required field was not provided or was null. Verify mandatory values and retry.", - "547": "{Operation} '{Target}' failed because one or more referenced entities do not exist or violate relational rules. Please verify identifiers and constraints.", - "2601": "{Operation} '{Target}' failed because the data conflicts with a unique constraint. Remove duplicate values and retry.", - "2627": "{Operation} '{Target}' failed because the data conflicts with a unique constraint. Remove duplicate values and retry." + // https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlexception.number + "SqlExceptionNumber": [ 515 , 547, 2601, 2627] } }, "AddedWho": "ReC.API", diff --git a/src/ReC.Application/Common/Options/SqlExceptionOptions.cs b/src/ReC.Application/Common/Options/SqlExceptionOptions.cs new file mode 100644 index 0000000..1ac7691 --- /dev/null +++ b/src/ReC.Application/Common/Options/SqlExceptionOptions.cs @@ -0,0 +1,6 @@ +namespace ReC.Application.Common.Options; + +public class SqlExceptionOptions +{ + public HashSet SqlExceptionNumber { get; set; } = []; +} \ No newline at end of file diff --git a/src/ReC.Application/Common/Options/SqlExceptionTranslatorOptions.cs b/src/ReC.Application/Common/Options/SqlExceptionTranslatorOptions.cs deleted file mode 100644 index 0d91077..0000000 --- a/src/ReC.Application/Common/Options/SqlExceptionTranslatorOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace ReC.Application.Common.Options; - -public class SqlExceptionTranslatorOptions -{ - public HashSet ErrorNumbers { get; private set; } = []; - - private Dictionary _badRequestMessages = new() - { - [515] = "{Operation} '{Target}' failed because a required field was not provided or was null. Verify mandatory values and retry.", - [547] = "{Operation} '{Target}' failed because one or more referenced entities do not exist or violate relational rules. Please verify identifiers and constraints.", - [2601] = "{Operation} '{Target}' failed because the data conflicts with a unique constraint. Remove duplicate values and retry.", - [2627] = "{Operation} '{Target}' failed because the data conflicts with a unique constraint. Remove duplicate values and retry." - }; - - public Dictionary ErrorMessages - { - get => _badRequestMessages; - set - { - _badRequestMessages = value; - ErrorNumbers = [.. value.Keys]; - } - } -} diff --git a/src/ReC.Application/Common/Procedures/SqlExceptionTranslator.cs b/src/ReC.Application/Common/Procedures/SqlExceptionTranslator.cs deleted file mode 100644 index 921eaea..0000000 --- a/src/ReC.Application/Common/Procedures/SqlExceptionTranslator.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.Data.SqlClient; -using Microsoft.Extensions.Options; -using ReC.Application.Common.Options; - -namespace ReC.Application.Common.Procedures; - -internal interface ISqlExceptionTranslator -{ - bool IsBadRequestDataIssue(SqlException exception); - - string BuildBadRequestMessage(string operation, string? entity, SqlException exception); -} - -internal sealed class SqlExceptionTranslator : ISqlExceptionTranslator -{ - private readonly IOptionsMonitor _optionsMonitor; - - public SqlExceptionTranslator(IOptionsMonitor optionsMonitor) - { - _optionsMonitor = optionsMonitor; - } - - public bool IsBadRequestDataIssue(SqlException exception) - { - var options = _optionsMonitor.CurrentValue; - - if (options.BadRequestErrorNumbers is { Count: > 0 } && - options.BadRequestErrorNumbers.Contains(exception.Number)) - { - return true; - } - - return options.BadRequestMessages?.ContainsKey(exception.Number) ?? false; - } - - public string BuildBadRequestMessage(string operation, string? entity, SqlException exception) - { - var target = string.IsNullOrWhiteSpace(entity) ? "object" : entity; - var options = _optionsMonitor.CurrentValue; - var template = ResolveTemplate(options, exception.Number); - - return template - .Replace("{Operation}", operation) - .Replace("{Target}", target) - .Replace("{ErrorMessage}", exception.Message); - } - - private static string ResolveTemplate(SqlExceptionTranslatorOptions options, int errorNumber) - { - if (options.BadRequestMessages is not null && - options.BadRequestMessages.TryGetValue(errorNumber, out var template) && - !string.IsNullOrWhiteSpace(template)) - { - return template; - } - - return string.IsNullOrWhiteSpace(options.DefaultBadRequestMessage) - ? SqlExceptionTranslatorOptions.DefaultFallbackMessage - : options.DefaultBadRequestMessage; - } -}