91b516e197
- SearchRepository: Search table operations with Dapper - SearchExecutionService: Search pipeline with proper cancellation handling - WorkProcessor: Unified BackgroundService for syncs and searches - SearchNotificationService: SignalR notifications in Api layer All 45 new tests pass. Proper shutdown vs timeout distinction prevents marking searches as error on host shutdown.
137 lines
4.4 KiB
C#
137 lines
4.4 KiB
C#
using Dapper;
|
|
using JdeScoping.Core.Models.Enums;
|
|
using JdeScoping.Core.Models.Search;
|
|
using JdeScoping.DataAccess.Interfaces;
|
|
using JdeScoping.DataSync.Contracts;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace JdeScoping.DataSync.Services;
|
|
|
|
/// <summary>
|
|
/// Repository for Search table operations.
|
|
/// </summary>
|
|
public class SearchRepository : ISearchRepository
|
|
{
|
|
private readonly IDbConnectionFactory _connectionFactory;
|
|
private readonly ILogger<SearchRepository> _logger;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SearchRepository"/> class.
|
|
/// </summary>
|
|
/// <param name="connectionFactory">The database connection factory.</param>
|
|
/// <param name="logger">The logger instance.</param>
|
|
public SearchRepository(
|
|
IDbConnectionFactory connectionFactory,
|
|
ILogger<SearchRepository> logger)
|
|
{
|
|
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<Search?> GetNextQueuedSearchAsync(CancellationToken ct = default)
|
|
{
|
|
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
|
|
|
|
const string sql = """
|
|
SELECT TOP 1
|
|
Id, UserName, Name, Status, SubmitDT as SubmitDt,
|
|
StartDT as StartDt, EndDT as EndDt, CriteriaJSON as CriteriaJson
|
|
FROM dbo.Search
|
|
WHERE Status = @Status
|
|
ORDER BY SubmitDT ASC
|
|
""";
|
|
|
|
var search = await connection.QueryFirstOrDefaultAsync<Search>(
|
|
sql,
|
|
new { Status = (int)SearchStatus.Queued },
|
|
commandTimeout: 30);
|
|
|
|
if (search != null)
|
|
{
|
|
_logger.LogDebug("Found queued search {SearchId}", search.Id);
|
|
}
|
|
|
|
return search;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task<int> ResetPartialSearchesAsync(CancellationToken ct = default)
|
|
{
|
|
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
|
|
|
|
const string sql = """
|
|
UPDATE dbo.Search
|
|
SET Status = @QueuedStatus, StartDT = NULL
|
|
WHERE Status = @RunningStatus AND EndDT IS NULL
|
|
""";
|
|
|
|
var count = await connection.ExecuteAsync(
|
|
sql,
|
|
new
|
|
{
|
|
QueuedStatus = (int)SearchStatus.Queued,
|
|
RunningStatus = (int)SearchStatus.Running
|
|
},
|
|
commandTimeout: 30);
|
|
|
|
if (count > 0)
|
|
{
|
|
_logger.LogWarning("Reset {Count} partial searches to Queued status", count);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task StartSearchAsync(int searchId, CancellationToken ct = default)
|
|
{
|
|
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
|
|
|
|
const string sql = """
|
|
UPDATE dbo.Search
|
|
SET Status = @Status, StartDT = @StartDt
|
|
WHERE Id = @SearchId
|
|
""";
|
|
|
|
await connection.ExecuteAsync(
|
|
sql,
|
|
new
|
|
{
|
|
SearchId = searchId,
|
|
Status = (int)SearchStatus.Running,
|
|
StartDt = DateTime.UtcNow
|
|
},
|
|
commandTimeout: 30);
|
|
|
|
_logger.LogDebug("Started search {SearchId}", searchId);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task CompleteSearchAsync(int searchId, bool success, byte[]? results, CancellationToken ct = default)
|
|
{
|
|
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
|
|
|
|
const string sql = """
|
|
UPDATE dbo.Search
|
|
SET Status = @Status, EndDT = @EndDt, Results = @Results
|
|
WHERE Id = @SearchId
|
|
""";
|
|
|
|
var status = success ? SearchStatus.Ended : SearchStatus.Error;
|
|
|
|
await connection.ExecuteAsync(
|
|
sql,
|
|
new
|
|
{
|
|
SearchId = searchId,
|
|
Status = (int)status,
|
|
EndDt = DateTime.UtcNow,
|
|
Results = results
|
|
},
|
|
commandTimeout: 30);
|
|
|
|
_logger.LogDebug("Completed search {SearchId} with status {Status}", searchId, status);
|
|
}
|
|
}
|