< Summary

Information
Class: Elsa.Persistence.EFCore.DbExceptionClassifier
Assembly: Elsa.Persistence.EFCore.Common
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Persistence.EFCore.Common/DbExceptionClassifier.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 81
Coverable lines: 81
Total lines: 143
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 84
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
IsSqlServerTransient(...)0%620%
IsDuplicateKey(...)100%210%
IsSqlServerTransientException(...)0%2040%
IsDuplicateKeyException(...)0%1332360%
IsSqlClientException(...)0%2040%
GetErrorNumbers()0%272160%
GetIntProperty(...)0%156120%
GetStringProperty(...)0%620%
EnumerateExceptions()0%7280%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Persistence.EFCore.Common/DbExceptionClassifier.cs

#LineLine coverage
 1namespace Elsa.Persistence.EFCore;
 2
 3internal static class DbExceptionClassifier
 4{
 05    private static readonly HashSet<int> SqlServerTransientErrorNumbers =
 06    [
 07        -2,
 08        64,
 09        233,
 010        1205,
 011        4060,
 012        10928,
 013        10929,
 014        40197,
 015        40501,
 016        40613,
 017        49918,
 018        49919,
 019        49920,
 020    ];
 21
 22    public static bool IsSqlServerTransient(string providerName, Exception exception)
 23    {
 024        if (!providerName.Contains("SqlServer", StringComparison.OrdinalIgnoreCase))
 025            return false;
 26
 027        return EnumerateExceptions(exception).Any(IsSqlServerTransientException);
 28    }
 29
 30    public static bool IsDuplicateKey(Exception exception)
 31    {
 032        return EnumerateExceptions(exception).Any(IsDuplicateKeyException);
 33    }
 34
 35    private static bool IsSqlServerTransientException(Exception exception)
 36    {
 037        if (!IsSqlClientException(exception))
 038            return false;
 39
 040        return GetErrorNumbers(exception).Any(SqlServerTransientErrorNumbers.Contains)
 041               || exception.Message.Contains("deadlock", StringComparison.OrdinalIgnoreCase);
 42    }
 43
 44    private static bool IsDuplicateKeyException(Exception exception)
 45    {
 046        var type = exception.GetType();
 047        var typeName = type.Name;
 048        var typeNamespace = type.Namespace ?? string.Empty;
 049        var errorNumbers = GetErrorNumbers(exception).ToList();
 50
 051        if (IsSqlClientException(exception) && errorNumbers.Any(number => number is 2601 or 2627))
 052            return true;
 53
 054        if (typeName.Contains("MySql", StringComparison.OrdinalIgnoreCase) && errorNumbers.Contains(1062))
 055            return true;
 56
 057        if (typeName.Contains("Sqlite", StringComparison.OrdinalIgnoreCase) && errorNumbers.Any(number => number is 19 o
 058            return true;
 59
 060        if (typeName.Contains("Oracle", StringComparison.OrdinalIgnoreCase) && errorNumbers.Contains(1))
 061            return true;
 62
 063        if (GetStringProperty(exception, "SqlState") == "23505")
 064            return true;
 65
 066        return typeNamespace.Contains("Data", StringComparison.OrdinalIgnoreCase)
 067               && (exception.Message.Contains("duplicate key", StringComparison.OrdinalIgnoreCase)
 068               || exception.Message.Contains("unique constraint", StringComparison.OrdinalIgnoreCase)
 069               || exception.Message.Contains("UNIQUE constraint failed", StringComparison.OrdinalIgnoreCase)
 070               || exception.Message.Contains("ORA-00001", StringComparison.OrdinalIgnoreCase));
 71    }
 72
 73    private static bool IsSqlClientException(Exception exception)
 74    {
 075        var type = exception.GetType();
 076        return type.Name.Equals("SqlException", StringComparison.OrdinalIgnoreCase)
 077               && type.Namespace?.Contains("SqlClient", StringComparison.OrdinalIgnoreCase) == true;
 78    }
 79
 80    private static IEnumerable<int> GetErrorNumbers(object source)
 81    {
 082        if (GetIntProperty(source, "Number") is { } number)
 083            yield return number;
 84
 085        if (GetIntProperty(source, "SqliteErrorCode") is { } sqliteErrorCode)
 086            yield return sqliteErrorCode;
 87
 088        if (GetIntProperty(source, "SqliteExtendedErrorCode") is { } sqliteExtendedErrorCode)
 089            yield return sqliteExtendedErrorCode;
 90
 091        var errors = source.GetType().GetProperty("Errors")?.GetValue(source);
 092        if (errors is not System.Collections.IEnumerable errorCollection)
 093            yield break;
 94
 095        foreach (var error in errorCollection)
 96        {
 097            if (error is null)
 98                continue;
 99
 0100            if (GetIntProperty(error, "Number") is { } errorNumber)
 0101                yield return errorNumber;
 102        }
 0103    }
 104
 105    private static int? GetIntProperty(object source, string name)
 106    {
 0107        var value = source.GetType().GetProperty(name)?.GetValue(source);
 0108        return value switch
 0109        {
 0110            int number => number,
 0111            short number => number,
 0112            long number when number is >= int.MinValue and <= int.MaxValue => (int)number,
 0113            _ => null
 0114        };
 115    }
 116
 117    private static string? GetStringProperty(object source, string name)
 118    {
 0119        return source.GetType().GetProperty(name)?.GetValue(source) as string;
 120    }
 121
 122    private static IEnumerable<Exception> EnumerateExceptions(Exception exception)
 123    {
 0124        var stack = new Stack<Exception>();
 0125        stack.Push(exception);
 126
 0127        while (stack.Count > 0)
 128        {
 0129            var current = stack.Pop();
 0130            yield return current;
 131
 0132            if (current is AggregateException aggregateException)
 133            {
 0134                foreach (var inner in aggregateException.InnerExceptions)
 0135                    stack.Push(inner);
 136            }
 0137            else if (current.InnerException is not null)
 138            {
 0139                stack.Push(current.InnerException);
 140            }
 0141        }
 0142    }
 143}