From 26ea1db09fb69be0793a80766f063c59110a78a9 Mon Sep 17 00:00:00 2001 From: TekH Date: Sat, 11 Jul 2026 16:43:57 +0200 Subject: [PATCH] feat(infrastructure): implement ISQLExecutor with EF6/EF Core support - Move ISQLExecutor interface from Application to Domain layer - Follow Clean Architecture: Infrastructure references Domain, not Application - Namespace: ECMJobRunner.Domain.Interfaces - Implement SQLExecutor service with conditional compilation - EF6 (net480): Database.SqlQuery() - EF Core (net8.0): Database.SqlQueryRaw() - Register ISQLExecutor in DI (AddInfrastructure, AddInfrastructureInMemory) - Scoped lifetime (aligns with DbContext) --- .../Interfaces/ISQLExecutor.cs | 20 +++++++ .../DependencyExtension.cs | 10 ++++ .../Services/SQLExecutor.cs | 57 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 ECMJobRunner.Domain/Interfaces/ISQLExecutor.cs create mode 100644 ECMJobRunner.Infrastructure/Services/SQLExecutor.cs diff --git a/ECMJobRunner.Domain/Interfaces/ISQLExecutor.cs b/ECMJobRunner.Domain/Interfaces/ISQLExecutor.cs new file mode 100644 index 0000000..9059f65 --- /dev/null +++ b/ECMJobRunner.Domain/Interfaces/ISQLExecutor.cs @@ -0,0 +1,20 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace ECMJobRunner.Domain.Interfaces +{ + /// + /// Interface for executing SQL queries and mapping results to DTOs + /// + public interface ISQLExecutor + { + /// + /// Executes a SQL query and maps the result to the specified type + /// + /// The type to map the query result to + /// The SQL query to execute + /// Cancellation token + /// The mapped result or null if no result + Task ExecuteQueryAsync(string sql, CancellationToken cancellationToken = default); + } +} diff --git a/ECMJobRunner.Infrastructure/DependencyExtension.cs b/ECMJobRunner.Infrastructure/DependencyExtension.cs index 7eed1f6..5fdb885 100644 --- a/ECMJobRunner.Infrastructure/DependencyExtension.cs +++ b/ECMJobRunner.Infrastructure/DependencyExtension.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using ECMJobRunner.Domain.Interfaces; using ECMJobRunner.Infrastructure.Data; using ECMJobRunner.Infrastructure.Repositories; +using ECMJobRunner.Infrastructure.Services; namespace ECMJobRunner.Infrastructure { @@ -43,6 +44,9 @@ namespace ECMJobRunner.Infrastructure services.AddScoped(); services.AddScoped(); + // Register SQL executor as scoped + services.AddScoped(); + return services; } @@ -70,6 +74,9 @@ namespace ECMJobRunner.Infrastructure services.AddScoped(); services.AddScoped(); + // Register SQL executor as scoped + services.AddScoped(); + return services; } #else @@ -96,6 +103,9 @@ namespace ECMJobRunner.Infrastructure services.AddScoped(); services.AddScoped(); + // Register SQL executor as scoped + services.AddScoped(); + return services; } #endif diff --git a/ECMJobRunner.Infrastructure/Services/SQLExecutor.cs b/ECMJobRunner.Infrastructure/Services/SQLExecutor.cs new file mode 100644 index 0000000..0b4d942 --- /dev/null +++ b/ECMJobRunner.Infrastructure/Services/SQLExecutor.cs @@ -0,0 +1,57 @@ +#if NET48 +using System.Data.Entity; +using System.Linq; +#else +using Microsoft.EntityFrameworkCore; +#endif +using ECMJobRunner.Domain.Interfaces; +using ECMJobRunner.Infrastructure.Data; +using System.Threading; +using System.Threading.Tasks; + +namespace ECMJobRunner.Infrastructure.Services +{ + /// + /// Entity Framework implementation of ISQLExecutor + /// Executes raw SQL queries and maps results to DTOs + /// + public class SQLExecutor : ISQLExecutor + { + private readonly JobRunnerDbContext _context; + + /// + /// Initializes a new instance of SQLExecutor + /// + /// The database context + public SQLExecutor(JobRunnerDbContext context) + { + _context = context; + } + + /// + /// Executes a SQL query and maps the result to the specified type + /// + /// The type to map the query result to + /// The SQL query to execute + /// Cancellation token + /// The mapped result or null if no result + public async Task ExecuteQueryAsync(string sql, CancellationToken cancellationToken = default) + { +#if NET48 + // Entity Framework 6 implementation + var result = await _context.Database + .SqlQuery(sql) + .FirstOrDefaultAsync(cancellationToken); + + return result; +#else + // Entity Framework Core implementation + var result = await _context.Database + .SqlQueryRaw(sql) + .FirstOrDefaultAsync(cancellationToken); + + return result; +#endif + } + } +}