4 Commits

Author SHA1 Message Date
761fd208e5 Bump version to 2.2.1-beta
Updated Version, AssemblyVersion, and FileVersion in ReC.API.csproj from 2.2.0-beta/2.2.0.0 to 2.2.1-beta/2.2.1.0. No other changes made.
2026-04-17 00:34:40 +02:00
d149cbea3a Improve error handling in query behaviors for SQL execution
Wrap ExecuteScalarAsync in try-catch blocks in BodyQueryBehavior and HeaderQueryBehavior. Throw DataIntegrityException with detailed context if SQL execution fails, aiding in diagnosing malformed or problematic stored SQL queries.
2026-04-17 00:34:01 +02:00
bb2dd4d63b Stricter error handling in BodyQuery and HeaderQuery behaviors
Throw DataIntegrityException when body or header queries return
null, no result, or invalid JSON. Remove ILogger and related
logging from HeaderQueryBehavior for simplification. This change
ensures data integrity issues are surfaced immediately.
2026-04-17 00:23:36 +02:00
4bde1d090f Refactor query behaviors to process action collections
Refactored BodyQueryBehavior and HeaderQueryBehavior to operate on collections of RecActionViewDto instead of single instances. Moved SQL execution and property assignment logic into private helper methods using ADO.NET commands. Improved null checks and logging, and updated type constraints to reflect the new usage. Behaviors now return the modified collection after processing.
2026-04-17 00:15:52 +02:00
3 changed files with 88 additions and 39 deletions

View File

@@ -10,9 +10,9 @@
<Product>ReC.API</Product> <Product>ReC.API</Product>
<PackageIcon>Assets\icon.ico</PackageIcon> <PackageIcon>Assets\icon.ico</PackageIcon>
<PackageTags>digital data rest-caller rec api</PackageTags> <PackageTags>digital data rest-caller rec api</PackageTags>
<Version>2.2.0-beta</Version> <Version>2.2.1-beta</Version>
<AssemblyVersion>2.2.0.0</AssemblyVersion> <AssemblyVersion>2.2.1.0</AssemblyVersion>
<FileVersion>2.2.0.0</FileVersion> <FileVersion>2.2.1.0</FileVersion>
<InformationalVersion>2.2.0-beta</InformationalVersion> <InformationalVersion>2.2.0-beta</InformationalVersion>
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright> <Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>

View File

@@ -1,23 +1,54 @@
using MediatR; using MediatR;
using ReC.Application.Common.Dto;
using ReC.Application.Common.Interfaces;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using ReC.Application.Common.Dto;
using ReC.Application.Common.Exceptions;
using ReC.Application.Common.Interfaces;
namespace ReC.Application.Common.Behaviors.Action; namespace ReC.Application.Common.Behaviors.Action;
public class BodyQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse> public class BodyQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse>
where TRequest : RecActionViewDto where TRequest : notnull
where TResponse : notnull where TResponse : IEnumerable<RecActionViewDto>
{ {
public async Task<TResponse> Handle(TRequest action, RequestHandlerDelegate<TResponse> next, CancellationToken cancel) public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
{ {
if (action.BodyQuery is null) var actions = await next(cancel);
return await next(cancel);
var result = await dbContext.BodyQueryResults.FromSqlRaw(action.BodyQuery).SingleOrDefaultAsync(cancel); foreach (var action in actions)
await SetBody(action, cancel);
action.Body = result?.RawBody; return actions;
}
return await next(cancel); private async Task SetBody(RecActionViewDto action, CancellationToken cancel)
{
if (action.BodyQuery is not string bodyQuery)
return;
await using var command = dbContext.Database.GetDbConnection().CreateCommand();
command.CommandText = bodyQuery;
await dbContext.Database.OpenConnectionAsync(cancel);
try
{
object? scalar;
try
{
scalar = await command.ExecuteScalarAsync(cancel);
}
catch (Exception ex)
{
throw new DataIntegrityException(
$"Body query execution failed. The stored SQL may be malformed. ActionId: {action.Id}, ProfileId: {action.ProfileId}, Error: {ex.Message}");
}
action.Body = scalar as string
?? throw new DataIntegrityException(
$"Body query returned no result or a null value. ActionId: {action.Id}, ProfileId: {action.ProfileId}");
}
finally
{
await dbContext.Database.CloseConnectionAsync();
}
} }
} }

View File

@@ -1,43 +1,61 @@
using MediatR; using MediatR;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ReC.Application.Common.Dto; using ReC.Application.Common.Dto;
using ReC.Application.Common.Exceptions;
using ReC.Application.Common.Interfaces; using ReC.Application.Common.Interfaces;
using System.Text.Json; using System.Text.Json;
namespace ReC.Application.Common.Behaviors.Action; namespace ReC.Application.Common.Behaviors.Action;
public class HeaderQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext, ILogger<HeaderQueryBehavior<TRequest, TResponse>>? logger = null) : IPipelineBehavior<TRequest, TResponse> public class HeaderQueryBehavior<TRequest, TResponse>(IRecDbContext dbContext) : IPipelineBehavior<TRequest, TResponse>
where TRequest : RecActionViewDto where TRequest : notnull
where TResponse : notnull where TResponse : IEnumerable<RecActionViewDto>
{ {
public async Task<TResponse> Handle(TRequest action, RequestHandlerDelegate<TResponse> next, CancellationToken cancel) public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancel)
{ {
if (action.HeaderQuery is null) var actions = await next(cancel);
return await next(cancel);
var result = await dbContext.HeaderQueryResults.FromSqlRaw(action.HeaderQuery).SingleOrDefaultAsync(cancel); foreach (var action in actions)
await SetHeader(action, cancel);
if (result?.RawHeader is null) return actions;
{
logger?.LogWarning("Header query did not return a result or returned a null REQUEST_HEADER. Profile ID: {ProfileId}, Action ID: {Id}", action.ProfileId, action.Id);
return await next(cancel);
} }
var headerDict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(result.RawHeader); private async Task SetHeader(RecActionViewDto action, CancellationToken cancel)
if(headerDict is null)
{ {
logger?.LogWarning( if (action.HeaderQuery is not string headerQuery)
"Header JSON deserialization returned null. RawHeader: {RawHeader}, ProfileId: {ProfileId}, Id: {Id}", return;
result.RawHeader, action.ProfileId, action.Id);
return await next(cancel); await using var command = dbContext.Database.GetDbConnection().CreateCommand();
command.CommandText = headerQuery;
await dbContext.Database.OpenConnectionAsync(cancel);
try
{
object? scalar;
try
{
scalar = await command.ExecuteScalarAsync(cancel);
} }
catch (Exception ex)
{
throw new DataIntegrityException(
$"Header query execution failed. The stored SQL may be malformed. ActionId: {action.Id}, ProfileId: {action.ProfileId}, Error: {ex.Message}");
}
if (scalar is not string rawHeader)
throw new DataIntegrityException(
$"Header query returned no result or a null value. ActionId: {action.Id}, ProfileId: {action.ProfileId}");
var headerDict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(rawHeader)
?? throw new DataIntegrityException(
$"Header query returned invalid JSON. ActionId: {action.Id}, ProfileId: {action.ProfileId}");
action.Headers = headerDict.ToDictionary(header => header.Key, kvp => kvp.Value.ToString()); action.Headers = headerDict.ToDictionary(header => header.Key, kvp => kvp.Value.ToString());
}
return await next(cancel); finally
{
await dbContext.Database.CloseConnectionAsync();
}
} }
} }