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
+ }
+ }
+}