sozsoft-platform/api/modules/Sozsoft.SqlQueryManager/Sozsoft.SqlQueryManager.Domain/Services/SqlExecutorService.cs
2026-03-02 21:31:49 +03:00

227 lines
7.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Sozsoft.Platform.DynamicData;
using Sozsoft.Platform.Queries;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.Domain.Services;
namespace Sozsoft.SqlQueryManager.Domain.Services;
public class SqlExecutorService : DomainService, ISqlExecutorService
{
private readonly IDataSourceManager _dataSourceManager;
private readonly IServiceProvider _serviceProvider;
public SqlExecutorService(
IDataSourceManager dataSourceManager,
IServiceProvider serviceProvider)
{
_dataSourceManager = dataSourceManager;
_serviceProvider = serviceProvider;
}
private async Task<IDynamicDataRepository> GetRepositoryAsync(string dataSourceCode)
{
// Get DataSource to determine database type
var dataSource = await _dataSourceManager.GetDataSourceAsync(
CurrentTenant.IsAvailable,
dataSourceCode);
if (dataSource == null)
{
throw new BusinessException("SqlQueryManager:DataSourceNotFound")
.WithData("DataSourceCode", dataSourceCode);
}
// Get appropriate repository based on database type
// For now, using MS SQL Server repository
var repository = _serviceProvider.GetKeyedService<IDynamicDataRepository>("Ms");
if (repository == null)
{
throw new BusinessException("SqlQueryManager:RepositoryNotFound")
.WithData("DatabaseType", "Ms");
}
return repository;
}
public async Task<SqlExecutionResult> ExecuteQueryAsync(
string sql,
string dataSourceCode,
Dictionary<string, object> parameters = null)
{
var stopwatch = Stopwatch.StartNew();
var result = new SqlExecutionResult();
try
{
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
CurrentTenant.IsAvailable,
dataSourceCode);
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new BusinessException("SqlQueryManager:InvalidConnectionString")
.WithData("DataSourceCode", dataSourceCode);
}
var repository = await GetRepositoryAsync(dataSourceCode);
var data = await repository.QueryAsync(sql, connectionString, parameters);
stopwatch.Stop();
result.Success = true;
result.Data = data;
result.RowsAffected = data?.Count() ?? 0;
result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;
result.Message = $"Query executed successfully.";
}
catch (Exception ex)
{
stopwatch.Stop();
result.Success = false;
result.Message = $"Query execution failed: {ex.Message}";
result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;
result.Metadata["ErrorDetail"] = ex.ToString();
}
return result;
}
public async Task<SqlExecutionResult> ExecuteNonQueryAsync(
string sql,
string dataSourceCode,
Dictionary<string, object> parameters = null)
{
var stopwatch = Stopwatch.StartNew();
var result = new SqlExecutionResult();
try
{
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
CurrentTenant.IsAvailable,
dataSourceCode);
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new BusinessException("SqlQueryManager:InvalidConnectionString")
.WithData("DataSourceCode", dataSourceCode);
}
var repository = await GetRepositoryAsync(dataSourceCode);
var rowsAffected = await repository.ExecuteAsync(sql, connectionString, parameters);
stopwatch.Stop();
result.Success = true;
result.RowsAffected = rowsAffected;
result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;
result.Message = $"Command executed successfully. Rows affected: {rowsAffected}";
}
catch (Exception ex)
{
stopwatch.Stop();
result.Success = false;
result.Message = $"Command execution failed: {ex.Message}";
result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;
result.Metadata["ErrorDetail"] = ex.ToString();
}
return result;
}
public async Task<SqlExecutionResult> ExecuteScalarAsync<T>(
string sql,
string dataSourceCode,
Dictionary<string, object> parameters = null)
{
var stopwatch = Stopwatch.StartNew();
var result = new SqlExecutionResult();
try
{
var connectionString = await _dataSourceManager.GetConnectionStringAsync(
CurrentTenant.IsAvailable,
dataSourceCode);
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new BusinessException("SqlQueryManager:InvalidConnectionString")
.WithData("DataSourceCode", dataSourceCode);
}
var repository = await GetRepositoryAsync(dataSourceCode);
var scalarValue = await repository.ExecuteScalarAsync<T>(sql, connectionString, parameters);
stopwatch.Stop();
result.Success = true;
result.Data = new[] { new { Value = scalarValue } };
result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;
result.Message = "Scalar query executed successfully";
result.Metadata["ScalarValue"] = scalarValue;
}
catch (Exception ex)
{
stopwatch.Stop();
result.Success = false;
result.Message = $"Scalar query execution failed: {ex.Message}";
result.ExecutionTimeMs = stopwatch.ElapsedMilliseconds;
result.Metadata["ErrorDetail"] = ex.ToString();
}
return result;
}
public Task<(bool IsValid, string ErrorMessage)> ValidateSqlAsync(string sql)
{
try
{
if (string.IsNullOrWhiteSpace(sql))
{
return Task.FromResult((false, "SQL query is empty"));
}
// Basic validation - check for dangerous keywords
var dangerousPatterns = new[]
{
@"\bDROP\s+DATABASE\b",
@"\bDROP\s+SCHEMA\b",
@"\bTRUNCATE\s+TABLE\b",
@"\bALTER\s+DATABASE\b",
@"\bSHUTDOWN\b",
@"\bxp_cmdshell\b"
};
foreach (var pattern in dangerousPatterns)
{
if (Regex.IsMatch(sql, pattern, RegexOptions.IgnoreCase))
{
return Task.FromResult((false, $"SQL contains potentially dangerous command: {pattern}"));
}
}
// Check for balanced parentheses
var openCount = sql.Count(c => c == '(');
var closeCount = sql.Count(c => c == ')');
if (openCount != closeCount)
{
return Task.FromResult((false, "Unbalanced parentheses in SQL"));
}
return Task.FromResult((true, string.Empty));
}
catch (Exception ex)
{
return Task.FromResult((false, $"Validation error: {ex.Message}"));
}
}
}