Files
OrpaonVision/OrpaonVision.ConfigApp/Infrastructure/Persistence/SqlProductionSessionRepository.cs
2026-04-06 22:04:05 +08:00

1468 lines
62 KiB
C#

using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OrpaonVision.ConfigApp.Infrastructure.Options;
using OrpaonVision.Core.Results;
using OrpaonVision.Core.Common;
using OrpaonVision.Model.Production;
using System.Collections.Concurrent;
using System.Data;
using System.Text.Json;
namespace OrpaonVision.ConfigApp.Infrastructure.Persistence;
/// <summary>
/// SQL产品会话仓储实现。
/// </summary>
public sealed class SqlProductionSessionRepository : IProductionSessionRepository
{
private readonly ILogger<SqlProductionSessionRepository> _logger;
private readonly SessionPersistenceOptions _options;
private readonly ConcurrentDictionary<string, SqlConnection> _connections = new();
/// <summary>
/// 构造函数。
/// </summary>
public SqlProductionSessionRepository(
ILogger<SqlProductionSessionRepository> logger,
IOptions<SessionPersistenceOptions> options)
{
_logger = logger;
_options = options.Value;
}
/// <inheritdoc />
public async Task<Result> CreateAsync(ProductionSessionModel session, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("创建会话: {SessionId}", session.SessionId);
using var connection = await GetConnectionAsync(cancellationToken);
using var command = connection.CreateCommand();
command.CommandText = @"
INSERT INTO mdl_production_session (
SessionId, ProductTypeCode, ProductTypeName, StationId, StationName,
OperatorId, OperatorName, ShiftId, ShiftName, StartedAtUtc, EndedAtUtc,
Status, Result, CurrentLayer, TotalLayers, NgReason, Remark,
CreatedAtUtc, UpdatedAtUtc, CreatedBy, UpdatedBy
) VALUES (
@SessionId, @ProductTypeCode, @ProductTypeName, @StationId, @StationName,
@OperatorId, @OperatorName, @ShiftId, @ShiftName, @StartedAtUtc, @EndedAtUtc,
@Status, @Result, @CurrentLayer, @TotalLayers, @NgReason, @Remark,
@CreatedAtUtc, @UpdatedAtUtc, @CreatedBy, @UpdatedBy
)";
AddParameter(command, "@SessionId", session.SessionId);
AddParameter(command, "@ProductTypeCode", session.ProductTypeCode);
AddParameter(command, "@ProductTypeName", session.ProductTypeName);
AddParameter(command, "@StationId", session.StationId);
AddParameter(command, "@StationName", session.StationName);
AddParameter(command, "@OperatorId", session.OperatorId);
AddParameter(command, "@OperatorName", session.OperatorName);
AddParameter(command, "@ShiftId", session.ShiftId);
AddParameter(command, "@ShiftName", session.ShiftName);
AddParameter(command, "@StartedAtUtc", session.StartedAtUtc);
AddParameter(command, "@EndedAtUtc", (object?)session.EndedAtUtc ?? DBNull.Value);
AddParameter(command, "@Status", (int)session.Status);
AddParameter(command, "@Result", (int)session.Result);
AddParameter(command, "@CurrentLayer", session.CurrentLayer);
AddParameter(command, "@TotalLayers", session.TotalLayers);
AddParameter(command, "@NgReason", (object?)session.NgReason ?? DBNull.Value);
AddParameter(command, "@Remark", (object?)session.Remark ?? DBNull.Value);
AddParameter(command, "@CreatedAtUtc", session.CreatedAtUtc);
AddParameter(command, "@UpdatedAtUtc", session.UpdatedAtUtc);
AddParameter(command, "@CreatedBy", session.CreatedBy);
AddParameter(command, "@UpdatedBy", session.UpdatedBy);
var rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken);
if (rowsAffected > 0)
{
_logger.LogDebug("会话创建成功: {SessionId}", session.SessionId);
return Result.Success();
}
else
{
return Result.Fail("CREATE_FAILED", "会话创建失败,未影响任何行");
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "创建会话失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "CREATE_SESSION_FAILED", "创建会话失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result> UpdateAsync(ProductionSessionModel session, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("更新会话: {SessionId}", session.SessionId);
using var connection = await GetConnectionAsync(cancellationToken);
using var command = connection.CreateCommand();
command.CommandText = @"
UPDATE mdl_production_session SET
ProductTypeCode = @ProductTypeCode,
ProductTypeName = @ProductTypeName,
StationId = @StationId,
StationName = @StationName,
OperatorId = @OperatorId,
OperatorName = @OperatorName,
ShiftId = @ShiftId,
ShiftName = @ShiftName,
StartedAtUtc = @StartedAtUtc,
EndedAtUtc = @EndedAtUtc,
Status = @Status,
Result = @Result,
CurrentLayer = @CurrentLayer,
TotalLayers = @TotalLayers,
NgReason = @NgReason,
Remark = @Remark,
UpdatedAtUtc = @UpdatedAtUtc,
UpdatedBy = @UpdatedBy
WHERE SessionId = @SessionId";
AddParameter(command, "@SessionId", session.SessionId);
AddParameter(command, "@ProductTypeCode", session.ProductTypeCode);
AddParameter(command, "@ProductTypeName", session.ProductTypeName);
AddParameter(command, "@StationId", session.StationId);
AddParameter(command, "@StationName", session.StationName);
AddParameter(command, "@OperatorId", session.OperatorId);
AddParameter(command, "@OperatorName", session.OperatorName);
AddParameter(command, "@ShiftId", session.ShiftId);
AddParameter(command, "@ShiftName", session.ShiftName);
AddParameter(command, "@StartedAtUtc", session.StartedAtUtc);
AddParameter(command, "@EndedAtUtc", (object?)session.EndedAtUtc ?? DBNull.Value);
AddParameter(command, "@Status", (int)session.Status);
AddParameter(command, "@Result", (int)session.Result);
AddParameter(command, "@CurrentLayer", session.CurrentLayer);
AddParameter(command, "@TotalLayers", session.TotalLayers);
AddParameter(command, "@NgReason", (object?)session.NgReason ?? DBNull.Value);
AddParameter(command, "@Remark", (object?)session.Remark ?? DBNull.Value);
AddParameter(command, "@UpdatedAtUtc", session.UpdatedAtUtc);
AddParameter(command, "@UpdatedBy", session.UpdatedBy);
var rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken);
if (rowsAffected > 0)
{
_logger.LogDebug("会话更新成功: {SessionId}", session.SessionId);
return Result.Success();
}
else
{
return Result.Fail("UPDATE_FAILED", "会话更新失败,未找到匹配的记录");
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "更新会话失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "UPDATE_SESSION_FAILED", "更新会话失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result> DeleteAsync(Guid sessionId, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("删除会话: {SessionId}", sessionId);
using var connection = await GetConnectionAsync(cancellationToken);
using var command = connection.CreateCommand();
command.CommandText = "DELETE FROM mdl_production_session WHERE SessionId = @SessionId";
AddParameter(command, "@SessionId", sessionId);
var rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken);
if (rowsAffected > 0)
{
_logger.LogDebug("会话删除成功: {SessionId}", sessionId);
return Result.Success();
}
else
{
return Result.Fail("DELETE_FAILED", "会话删除失败,未找到匹配的记录");
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "删除会话失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "DELETE_SESSION_FAILED", "删除会话失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<ProductionSessionModel?>> GetByIdAsync(Guid sessionId, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("根据ID获取会话: {SessionId}", sessionId);
using var connection = await GetConnectionAsync(cancellationToken);
using var command = connection.CreateCommand();
command.CommandText = @"
SELECT SessionId, ProductTypeCode, ProductTypeName, StationId, StationName,
OperatorId, OperatorName, ShiftId, ShiftName, StartedAtUtc, EndedAtUtc,
Status, Result, CurrentLayer, TotalLayers, NgReason, Remark,
CreatedAtUtc, UpdatedAtUtc, CreatedBy, UpdatedBy
FROM mdl_production_session
WHERE SessionId = @SessionId";
AddParameter(command, "@SessionId", sessionId);
using var reader = await command.ExecuteReaderAsync(cancellationToken);
if (await reader.ReadAsync(cancellationToken))
{
var session = MapToSession(reader);
_logger.LogDebug("会话获取成功: {SessionId}", sessionId);
return Result<ProductionSessionModel?>.Success(session);
}
else
{
_logger.LogDebug("未找到会话: {SessionId}", sessionId);
return Result<ProductionSessionModel?>.Success(null);
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "根据ID获取会话失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GET_SESSION_FAILED", "根据ID获取会话失败。", traceId);
return Result<ProductionSessionModel?>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<ProductionSessionModel?>> GetActiveByStationIdAsync(string stationId, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("根据工位ID获取活动会话: {StationId}", stationId);
using var connection = await GetConnectionAsync(cancellationToken);
using var command = connection.CreateCommand();
command.CommandText = @"
SELECT TOP 1 SessionId, ProductTypeCode, ProductTypeName, StationId, StationName,
OperatorId, OperatorName, ShiftId, ShiftName, StartedAtUtc, EndedAtUtc,
Status, Result, CurrentLayer, TotalLayers, NgReason, Remark,
CreatedAtUtc, UpdatedAtUtc, CreatedBy, UpdatedBy
FROM mdl_production_session
WHERE StationId = @StationId AND Status IN (0, 4) -- InProgress, Paused
ORDER BY StartedAtUtc DESC";
AddParameter(command, "@StationId", stationId);
using var reader = await command.ExecuteReaderAsync(cancellationToken);
if (await reader.ReadAsync(cancellationToken))
{
var session = MapToSession(reader);
_logger.LogDebug("活动会话获取成功: {SessionId}", session.SessionId);
return Result<ProductionSessionModel?>.Success(session);
}
else
{
_logger.LogDebug("工位 {StationId} 无活动会话", stationId);
return Result<ProductionSessionModel?>.Success(null);
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "根据工位ID获取活动会话失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GET_ACTIVE_SESSION_FAILED", "根据工位ID获取活动会话失败。", traceId);
return Result<ProductionSessionModel?>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<IReadOnlyList<ProductionSessionModel>>> GetByQueryAsync(SessionQuery query, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("根据查询条件获取会话列表");
var sql = BuildQuerySql(query);
var parameters = BuildQueryParameters(query);
using var connection = await GetConnectionAsync(cancellationToken);
using var command = connection.CreateCommand();
command.CommandText = sql;
foreach (var param in parameters)
{
AddParameter(command, param.Key, param.Value);
}
var sessions = new List<ProductionSessionModel>();
using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
sessions.Add(MapToSession(reader));
}
_logger.LogDebug("查询到 {Count} 个会话", sessions.Count);
return Result<IReadOnlyList<ProductionSessionModel>>.Success(sessions);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "根据查询条件获取会话列表失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "QUERY_SESSIONS_FAILED", "根据查询条件获取会话列表失败。", traceId);
return Result<IReadOnlyList<ProductionSessionModel>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<PagedSessionResult>> GetPagedByQueryAsync(SessionQuery query, int pageIndex, int pageSize, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("分页查询会话: 页码={PageIndex}, 页大小={PageSize}", pageIndex, pageSize);
var countSql = BuildCountSql(query);
var dataSql = BuildQuerySql(query, pageIndex, pageSize);
var parameters = BuildQueryParameters(query);
using var connection = await GetConnectionAsync(cancellationToken);
// 获取总数
using var countCommand = connection.CreateCommand();
countCommand.CommandText = countSql;
foreach (var param in parameters)
{
AddParameter(countCommand, param.Key, param.Value);
}
var totalCount = (int?)await countCommand.ExecuteScalarAsync(cancellationToken) ?? 0;
// 获取数据
using var dataCommand = connection.CreateCommand();
dataCommand.CommandText = dataSql;
foreach (var param in parameters)
{
AddParameter(dataCommand, param.Key, param.Value);
}
var sessions = new List<ProductionSessionModel>();
using var reader = await dataCommand.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
sessions.Add(MapToSession(reader));
}
var result = new PagedSessionResult
{
Items = sessions,
TotalCount = totalCount,
PageIndex = pageIndex,
PageSize = pageSize
};
_logger.LogDebug("分页查询完成: 总数={TotalCount}, 当前页={Count}", totalCount, sessions.Count);
return Result<PagedSessionResult>.Success(result);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "分页查询会话失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "PAGED_QUERY_FAILED", "分页查询会话失败。", traceId);
return Result<PagedSessionResult>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<BatchCreateResult>> BatchCreateAsync(IReadOnlyList<ProductionSessionModel> sessions, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("批量创建会话: 数量={Count}", sessions.Count);
var totalCount = sessions.Count;
var successCount = 0;
var failureCount = 0;
var failedIndexes = new List<int>();
var errors = new List<string>();
using var connection = await GetConnectionAsync(cancellationToken);
await connection.OpenAsync(cancellationToken);
using var transaction = connection.BeginTransaction();
try
{
for (int i = 0; i < sessions.Count; i++)
{
var session = sessions[i];
using var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = @"
INSERT INTO mdl_production_session (
SessionId, ProductTypeCode, ProductTypeName, StationId, StationName,
OperatorId, OperatorName, ShiftId, ShiftName, StartedAtUtc, EndedAtUtc,
Status, Result, CurrentLayer, TotalLayers, NgReason, Remark,
CreatedAtUtc, UpdatedAtUtc, CreatedBy, UpdatedBy
) VALUES (
@SessionId, @ProductTypeCode, @ProductTypeName, @StationId, @StationName,
@OperatorId, @OperatorName, @ShiftId, @ShiftName, @StartedAtUtc, @EndedAtUtc,
@Status, @Result, @CurrentLayer, @TotalLayers, @NgReason, @Remark,
@CreatedAtUtc, @UpdatedAtUtc, @CreatedBy, @UpdatedBy
)";
AddParameter(command, "@SessionId", session.SessionId);
AddParameter(command, "@ProductTypeCode", session.ProductTypeCode);
AddParameter(command, "@ProductTypeName", session.ProductTypeName);
AddParameter(command, "@StationId", session.StationId);
AddParameter(command, "@StationName", session.StationName);
AddParameter(command, "@OperatorId", session.OperatorId);
AddParameter(command, "@OperatorName", session.OperatorName);
AddParameter(command, "@ShiftId", session.ShiftId);
AddParameter(command, "@ShiftName", session.ShiftName);
AddParameter(command, "@StartedAtUtc", session.StartedAtUtc);
AddParameter(command, "@EndedAtUtc", (object?)session.EndedAtUtc ?? DBNull.Value);
AddParameter(command, "@Status", (int)session.Status);
AddParameter(command, "@Result", (int)session.Result);
AddParameter(command, "@CurrentLayer", session.CurrentLayer);
AddParameter(command, "@TotalLayers", session.TotalLayers);
AddParameter(command, "@NgReason", (object?)session.NgReason ?? DBNull.Value);
AddParameter(command, "@Remark", (object?)session.Remark ?? DBNull.Value);
AddParameter(command, "@CreatedAtUtc", session.CreatedAtUtc);
AddParameter(command, "@UpdatedAtUtc", session.UpdatedAtUtc);
AddParameter(command, "@CreatedBy", session.CreatedBy);
AddParameter(command, "@UpdatedBy", session.UpdatedBy);
try
{
var rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken);
if (rowsAffected > 0)
{
successCount++;
}
else
{
failureCount++;
failedIndexes.Add(i);
errors.Add($"会话 {session.SessionId} 创建失败,未影响任何行");
}
}
catch (Exception ex)
{
failureCount++;
failedIndexes.Add(i);
errors.Add($"会话 {session.SessionId} 创建异常: {ex.Message}");
}
}
await transaction.CommitAsync(cancellationToken);
_logger.LogInformation("批量创建会话完成: 成功={SuccessCount}, 失败={FailureCount}", successCount, failureCount);
var result = new BatchCreateResult
{
TotalCount = totalCount,
SuccessCount = successCount,
FailureCount = failureCount,
FailedIndexes = failedIndexes,
Errors = errors
};
return Result<BatchCreateResult>.Success(result);
}
catch (Exception ex)
{
await transaction.RollbackAsync(cancellationToken);
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "批量创建会话事务失败。TraceId: {TraceId}", traceId);
return Result<BatchCreateResult>.FailWithTrace("BATCH_CREATE_TRANSACTION_FAILED", "批量创建会话事务失败。", traceId, ex.Message);
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "批量创建会话失败。TraceId: {TraceId}", traceId);
return Result<BatchCreateResult>.FailWithTrace("BATCH_CREATE_FAILED", "批量创建会话失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<BatchUpdateResult>> BatchUpdateAsync(IReadOnlyList<ProductionSessionModel> sessions, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("批量更新会话: 数量={Count}", sessions.Count);
var successCount = 0;
var failureCount = 0;
var failedIndexes = new List<int>();
var errors = new List<string>();
using var connection = await GetConnectionAsync(cancellationToken);
await connection.OpenAsync(cancellationToken);
using var transaction = connection.BeginTransaction();
try
{
for (int i = 0; i < sessions.Count; i++)
{
var session = sessions[i];
using var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = @"
UPDATE mdl_production_session SET
ProductTypeCode = @ProductTypeCode,
ProductTypeName = @ProductTypeName,
StationId = @StationId,
StationName = @StationName,
OperatorId = @OperatorId,
OperatorName = @OperatorName,
ShiftId = @ShiftId,
ShiftName = @ShiftName,
StartedAtUtc = @StartedAtUtc,
EndedAtUtc = @EndedAtUtc,
Status = @Status,
Result = @Result,
CurrentLayer = @CurrentLayer,
TotalLayers = @TotalLayers,
NgReason = @NgReason,
Remark = @Remark,
UpdatedAtUtc = @UpdatedAtUtc,
UpdatedBy = @UpdatedBy
WHERE SessionId = @SessionId";
AddParameter(command, "@SessionId", session.SessionId);
AddParameter(command, "@ProductTypeCode", session.ProductTypeCode);
AddParameter(command, "@ProductTypeName", session.ProductTypeName);
AddParameter(command, "@StationId", session.StationId);
AddParameter(command, "@StationName", session.StationName);
AddParameter(command, "@OperatorId", session.OperatorId);
AddParameter(command, "@OperatorName", session.OperatorName);
AddParameter(command, "@ShiftId", session.ShiftId);
AddParameter(command, "@ShiftName", session.ShiftName);
AddParameter(command, "@StartedAtUtc", session.StartedAtUtc);
AddParameter(command, "@EndedAtUtc", session.EndedAtUtc);
AddParameter(command, "@Status", session.Status);
AddParameter(command, "@Result", session.Result);
AddParameter(command, "@CurrentLayer", session.CurrentLayer);
AddParameter(command, "@TotalLayers", session.TotalLayers);
AddParameter(command, "@NgReason", (object?)session.NgReason ?? DBNull.Value);
AddParameter(command, "@Remark", (object?)session.Remark ?? DBNull.Value);
AddParameter(command, "@UpdatedAtUtc", DateTime.UtcNow);
AddParameter(command, "@UpdatedBy", session.UpdatedBy);
try
{
var rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken);
if (rowsAffected > 0)
{
successCount++;
}
else
{
failureCount++;
failedIndexes.Add(i);
errors.Add($"会话 {session.SessionId} 更新失败,未找到匹配的记录");
}
}
catch (Exception ex)
{
failureCount++;
failedIndexes.Add(i);
errors.Add($"会话 {session.SessionId} 更新异常: {ex.Message}");
}
}
await transaction.CommitAsync(cancellationToken);
_logger.LogInformation("批量更新会话完成: 成功={SuccessCount}, 失败={FailureCount}", successCount, failureCount);
var result = new BatchUpdateResult
{
TotalCount = sessions.Count,
SuccessCount = successCount,
FailureCount = failureCount,
FailedIndexes = failedIndexes,
Errors = errors
};
return Result<BatchUpdateResult>.Success(result);
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "批量更新会话失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "BATCH_UPDATE_FAILED", "批量更新会话失败。", traceId);
return Result<BatchUpdateResult>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<BatchDeleteResult>> BatchDeleteAsync(IReadOnlyList<Guid> sessionIds, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("批量删除会话: 数量={Count}", sessionIds.Count);
var successCount = 0;
var failureCount = 0;
var failedSessionIds = new List<Guid>();
var errors = new List<string>();
using var connection = await GetConnectionAsync(cancellationToken);
await connection.OpenAsync(cancellationToken);
using var transaction = connection.BeginTransaction();
try
{
for (int i = 0; i < sessionIds.Count; i++)
{
var sessionId = sessionIds[i];
using var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = "DELETE FROM mdl_production_session WHERE SessionId = @SessionId";
AddParameter(command, "@SessionId", sessionId);
try
{
var rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken);
if (rowsAffected > 0)
{
successCount++;
}
else
{
failureCount++;
failedSessionIds.Add(sessionId);
errors.Add($"会话 {sessionId} 删除失败,未找到匹配的记录");
}
}
catch (Exception ex)
{
failureCount++;
failedSessionIds.Add(sessionId);
errors.Add($"会话 {sessionId} 删除异常: {ex.Message}");
}
}
await transaction.CommitAsync(cancellationToken);
_logger.LogInformation("批量删除会话完成: 成功={SuccessCount}, 失败={FailureCount}", successCount, failureCount);
var result = new BatchDeleteResult
{
TotalCount = sessionIds.Count,
SuccessCount = successCount,
FailureCount = failureCount,
FailedSessionIds = failedSessionIds,
Errors = errors
};
return Result<BatchDeleteResult>.Success(result);
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "批量删除会话失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "BATCH_DELETE_FAILED", "批量删除会话失败。", traceId);
return Result<BatchDeleteResult>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<SessionStatistics>> GetStatisticsAsync(SessionStatisticsQuery query, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("获取会话统计信息");
using var connection = await GetConnectionAsync(cancellationToken);
// 获取基础统计数据
int totalSessions = 0, okSessions = 0, ngSessions = 0, inProgressSessions = 0, cancelledSessions = 0, pausedSessions = 0;
using var basicCommand = connection.CreateCommand();
basicCommand.CommandText = BuildBasicStatisticsSql(query);
AddParameter(basicCommand, "@StartTime", query.StartTime);
AddParameter(basicCommand, "@EndTime", query.EndTime);
using var reader = await basicCommand.ExecuteReaderAsync(cancellationToken);
if (await reader.ReadAsync(cancellationToken))
{
totalSessions = reader.GetInt32(0);
okSessions = reader.GetInt32(1);
ngSessions = reader.GetInt32(2);
inProgressSessions = reader.GetInt32(3);
cancelledSessions = reader.GetInt32(4);
pausedSessions = reader.GetInt32(5);
}
// 计算合格率
var passRate = totalSessions > 0 ? (double)okSessions / totalSessions * 100 : 0;
// 获取平均处理时间和处理量
double averageProcessingTimeSeconds = 0, throughputPerHour = 0;
using var timeCommand = connection.CreateCommand();
timeCommand.CommandText = BuildTimeStatisticsSql(query);
AddParameter(timeCommand, "@StartTime", query.StartTime);
AddParameter(timeCommand, "@EndTime", query.EndTime);
using var timeReader = await timeCommand.ExecuteReaderAsync(cancellationToken);
if (await timeReader.ReadAsync(cancellationToken))
{
if (!timeReader.IsDBNull(0))
averageProcessingTimeSeconds = timeReader.GetDouble(0);
if (!timeReader.IsDBNull(1))
throughputPerHour = timeReader.GetDouble(1);
}
// 按维度分组统计
var byProductType = new List<ProductTypeStatistics>();
var byStation = new List<StationStatistics>();
var byOperator = new List<OperatorStatistics>();
var byDate = new List<DailyStatistics>();
var byShift = new List<ShiftStatistics>();
if (query.Dimensions.Contains(StatisticsDimension.ByProductType))
{
byProductType = await GetProductTypeStatisticsAsync(query, connection, cancellationToken);
}
if (query.Dimensions.Contains(StatisticsDimension.ByStation))
{
byStation = await GetStationStatisticsAsync(query, connection, cancellationToken);
}
if (query.Dimensions.Contains(StatisticsDimension.ByOperator))
{
byOperator = await GetOperatorStatisticsAsync(query, connection, cancellationToken);
}
if (query.Dimensions.Contains(StatisticsDimension.ByDate))
{
byDate = await GetDailyStatisticsAsync(query, connection, cancellationToken);
}
if (query.Dimensions.Contains(StatisticsDimension.ByShift))
{
byShift = await GetShiftStatisticsAsync(query, connection, cancellationToken);
}
// 创建统计对象
var statistics = new SessionStatistics
{
TotalSessions = totalSessions,
OkSessions = okSessions,
NgSessions = ngSessions,
InProgressSessions = inProgressSessions,
CancelledSessions = cancelledSessions,
PausedSessions = pausedSessions,
PassRate = passRate,
AverageProcessingTimeSeconds = averageProcessingTimeSeconds,
ThroughputPerHour = throughputPerHour,
ByProductType = byProductType,
ByStation = byStation,
ByOperator = byOperator,
ByDate = byDate,
ByShift = byShift
};
_logger.LogDebug("会话统计获取完成: 总数={TotalCount}, 合格率={PassRate:F2}%",
statistics.TotalSessions, statistics.PassRate);
return Result<SessionStatistics>.Success(statistics);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取会话统计信息失败。TraceId: {TraceId}", traceId);
return Result<SessionStatistics>.FailWithTrace("GET_STATISTICS_FAILED", "获取会话统计信息失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<CleanupResult>> CleanupExpiredAsync(DateTime cutoffDate, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("清理过期会话: 截止日期={CutoffDate}", cutoffDate);
var startTime = DateTime.UtcNow;
using var connection = await GetConnectionAsync(cancellationToken);
await connection.OpenAsync(cancellationToken);
using var transaction = connection.BeginTransaction();
try
{
// 获取要清理的会话数量
using var countCommand = connection.CreateCommand();
countCommand.Transaction = transaction;
countCommand.CommandText = "SELECT COUNT(*) FROM mdl_production_session WHERE EndedAtUtc < @CutoffDate";
AddParameter(countCommand, "@CutoffDate", cutoffDate);
var cleanedCount = (int?)await countCommand.ExecuteScalarAsync(cancellationToken) ?? 0;
// 删除过期会话
using var deleteCommand = connection.CreateCommand();
deleteCommand.Transaction = transaction;
deleteCommand.CommandText = "DELETE FROM mdl_production_session WHERE EndedAtUtc < @CutoffDate";
AddParameter(deleteCommand, "@CutoffDate", cutoffDate);
var rowsAffected = await deleteCommand.ExecuteNonQueryAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
var elapsed = DateTime.UtcNow - startTime;
var result = new CleanupResult
{
CleanedCount = cleanedCount,
FreedSpaceBytes = rowsAffected * 1024, // 假设每个会话占用1KB
ElapsedMilliseconds = (long)elapsed.TotalMilliseconds,
CleanedEventCount = rowsAffected // 简化实现
};
_logger.LogInformation("清理过期会话完成: 清理数量={CleanedCount}, 耗时={ElapsedMs}ms",
result.CleanedCount, result.ElapsedMilliseconds);
return Result<CleanupResult>.Success(result);
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "清理过期会话失败。TraceId: {TraceId}", traceId);
return Result<CleanupResult>.FailWithTrace("CLEANUP_FAILED", "清理过期会话失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<bool>> ExistsAsync(Guid sessionId, CancellationToken cancellationToken = default)
{
try
{
using var connection = await GetConnectionAsync(cancellationToken);
using var command = connection.CreateCommand();
command.CommandText = "SELECT COUNT(*) FROM mdl_production_session WHERE SessionId = @SessionId";
AddParameter(command, "@SessionId", sessionId);
var count = (int?)await command.ExecuteScalarAsync(cancellationToken) ?? 0;
return Result<bool>.Success(count > 0);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "检查会话是否存在失败。TraceId: {TraceId}", traceId);
return Result<bool>.FailWithTrace("EXISTS_CHECK_FAILED", "检查会话是否存在失败。", traceId, ex.Message);
}
}
/// <inheritdoc />
public async Task<Result<int>> CountAsync(SessionQuery query, CancellationToken cancellationToken = default)
{
try
{
var sql = BuildCountSql(query);
var parameters = BuildQueryParameters(query);
using var connection = await GetConnectionAsync(cancellationToken);
using var command = connection.CreateCommand();
command.CommandText = sql;
foreach (var param in parameters)
{
AddParameter(command, param.Key, param.Value);
}
var count = (int?)await command.ExecuteScalarAsync(cancellationToken) ?? 0;
return Result<int>.Success(count);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取会话数量失败。TraceId: {TraceId}", traceId);
return Result<int>.FailWithTrace("COUNT_FAILED", "获取会话数量失败。", traceId, ex.Message);
}
}
#region
/// <summary>
/// 获取数据库连接。
/// </summary>
private async Task<SqlConnection> GetConnectionAsync(CancellationToken cancellationToken = default)
{
var connectionString = _options.ConnectionString;
if (string.IsNullOrWhiteSpace(connectionString))
{
throw new InvalidOperationException("数据库连接字符串未配置");
}
var connection = new SqlConnection(connectionString);
await connection.OpenAsync(cancellationToken);
return connection;
}
/// <summary>
/// 添加参数。
/// </summary>
private static void AddParameter(SqlCommand command, string name, object value)
{
var parameter = command.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
command.Parameters.Add(parameter);
}
/// <summary>
/// 从DataReader映射到会话模型。
/// </summary>
private static ProductionSessionModel MapToSession(SqlDataReader reader)
{
return new ProductionSessionModel
{
SessionId = reader.GetGuid(0),
ProductTypeCode = reader.GetString(1),
ProductTypeName = reader.GetString(2),
StationId = reader.GetString(3),
StationName = reader.GetString(4),
OperatorId = reader.GetString(5),
OperatorName = reader.GetString(6),
ShiftId = reader.GetString(7),
ShiftName = reader.GetString(8),
StartedAtUtc = reader.GetDateTime(9),
EndedAtUtc = reader.IsDBNull(10) ? null : reader.GetDateTime(10),
Status = (ProductionSessionStatus)reader.GetInt32(11),
Result = (ProductionSessionResult)reader.GetInt32(12),
CurrentLayer = reader.GetInt32(13),
TotalLayers = reader.GetInt32(14),
NgReason = reader.IsDBNull(15) ? null : reader.GetString(15),
Remark = reader.IsDBNull(16) ? null : reader.GetString(16),
CreatedAtUtc = reader.GetDateTime(17),
UpdatedAtUtc = reader.GetDateTime(18),
CreatedBy = reader.GetString(19),
UpdatedBy = reader.GetString(20)
};
}
/// <summary>
/// 构建查询SQL。
/// </summary>
private string BuildQuerySql(SessionQuery query, int? pageIndex = null, int? pageSize = null)
{
var sql = @"
SELECT SessionId, ProductTypeCode, ProductTypeName, StationId, StationName,
OperatorId, OperatorName, ShiftId, ShiftName, StartedAtUtc, EndedAtUtc,
Status, Result, CurrentLayer, TotalLayers, NgReason, Remark,
CreatedAtUtc, UpdatedAtUtc, CreatedBy, UpdatedBy
FROM mdl_production_session
WHERE 1=1";
var conditions = new List<string>();
var parameters = new Dictionary<string, object>();
if (query.SessionIds?.Any() == true)
{
conditions.Add($"SessionId IN ({string.Join(",", query.SessionIds.Select((_, i) => $"@SessionId{i}"))})");
for (int i = 0; i < query.SessionIds.Count; i++)
{
parameters[$"@SessionId{i}"] = query.SessionIds[i];
}
}
if (!string.IsNullOrWhiteSpace(query.ProductTypeCode))
{
conditions.Add("ProductTypeCode = @ProductTypeCode");
parameters["@ProductTypeCode"] = query.ProductTypeCode;
}
if (!string.IsNullOrWhiteSpace(query.StationId))
{
conditions.Add("StationId = @StationId");
parameters["@StationId"] = query.StationId;
}
if (!string.IsNullOrWhiteSpace(query.OperatorId))
{
conditions.Add("OperatorId = @OperatorId");
parameters["@OperatorId"] = query.OperatorId;
}
if (!string.IsNullOrWhiteSpace(query.ShiftId))
{
conditions.Add("ShiftId = @ShiftId");
parameters["@ShiftId"] = query.ShiftId;
}
if (query.Statuses?.Any() == true)
{
conditions.Add($"Status IN ({string.Join(",", query.Statuses.Select(s => (int)s))})");
}
if (query.Results?.Any() == true)
{
conditions.Add($"Result IN ({string.Join(",", query.Results.Select(r => (int)r))})");
}
if (query.StartTimeRange != null)
{
if (query.StartTimeRange.IncludeStart)
conditions.Add("StartedAtUtc >= @StartTimeStart");
else
conditions.Add("StartedAtUtc > @StartTimeStart");
parameters["@StartTimeStart"] = query.StartTimeRange.Start;
if (query.StartTimeRange.IncludeEnd)
conditions.Add("StartedAtUtc <= @StartTimeEnd");
else
conditions.Add("StartedAtUtc < @StartTimeEnd");
parameters["@StartTimeEnd"] = query.StartTimeRange.End;
}
if (query.EndTimeRange != null)
{
if (query.EndTimeRange.IncludeStart)
conditions.Add("EndedAtUtc >= @EndTimeStart");
else
conditions.Add("EndedAtUtc > @EndTimeStart");
parameters["@EndTimeStart"] = query.EndTimeRange.Start;
if (query.EndTimeRange.IncludeEnd)
conditions.Add("EndedAtUtc <= @EndTimeEnd");
else
conditions.Add("EndedAtUtc < @EndTimeEnd");
parameters["@EndTimeEnd"] = query.EndTimeRange.End;
}
if (query.CreatedTimeRange != null)
{
if (query.CreatedTimeRange.IncludeStart)
conditions.Add("CreatedAtUtc >= @CreatedTimeStart");
else
conditions.Add("CreatedAtUtc > @CreatedTimeStart");
parameters["@CreatedTimeStart"] = query.CreatedTimeRange.Start;
if (query.CreatedTimeRange.IncludeEnd)
conditions.Add("CreatedAtUtc <= @CreatedTimeEnd");
else
conditions.Add("CreatedAtUtc < @CreatedTimeEnd");
parameters["@CreatedTimeEnd"] = query.CreatedTimeRange.End;
}
if (!string.IsNullOrWhiteSpace(query.Keyword))
{
conditions.Add("(ProductTypeCode LIKE @Keyword OR StationId LIKE @Keyword OR OperatorName LIKE @Keyword)");
parameters["@Keyword"] = $"%{query.Keyword}%";
}
if (query.ActiveOnly)
{
conditions.Add("Status IN (0, 4)"); // InProgress, Paused
}
if (query.CompletedOnly)
{
conditions.Add("Status IN (1, 2)"); // CompletedOk, CompletedNg
}
if (conditions.Any())
{
sql += " AND " + string.Join(" AND ", conditions);
}
// 添加排序
var sortField = query.SortField switch
{
SessionSortField.StartTimeUtc => "StartedAtUtc",
SessionSortField.EndTimeUtc => "EndedAtUtc",
SessionSortField.ProductTypeCode => "ProductTypeCode",
SessionSortField.StationId => "StationId",
SessionSortField.OperatorName => "OperatorName",
SessionSortField.CurrentLayer => "CurrentLayer",
_ => "StartedAtUtc"
};
var sortDirection = query.SortDirection == SortDirection.Ascending ? "ASC" : "DESC";
sql += $" ORDER BY {sortField} {sortDirection}";
// 添加分页
if (pageIndex.HasValue && pageSize.HasValue)
{
var offset = (pageIndex.Value - 1) * pageSize.Value;
sql += $" OFFSET {offset} ROWS FETCH NEXT {pageSize.Value} ROWS ONLY";
}
return sql;
}
/// <summary>
/// 构建计数SQL。
/// </summary>
private string BuildCountSql(SessionQuery query)
{
var querySql = BuildQuerySql(query);
return $"SELECT COUNT(*) FROM ({querySql}) AS CountQuery";
}
/// <summary>
/// 构建查询参数。
/// </summary>
private Dictionary<string, object> BuildQueryParameters(SessionQuery query)
{
var parameters = new Dictionary<string, object>();
if (query.SessionIds?.Any() == true)
{
for (int i = 0; i < query.SessionIds.Count; i++)
{
parameters[$"@SessionId{i}"] = query.SessionIds[i];
}
}
if (!string.IsNullOrWhiteSpace(query.ProductTypeCode))
parameters["@ProductTypeCode"] = query.ProductTypeCode;
if (!string.IsNullOrWhiteSpace(query.StationId))
parameters["@StationId"] = query.StationId;
if (!string.IsNullOrWhiteSpace(query.OperatorId))
parameters["@OperatorId"] = query.OperatorId;
if (!string.IsNullOrWhiteSpace(query.ShiftId))
parameters["@ShiftId"] = query.ShiftId;
if (query.StartTimeRange != null)
{
parameters["@StartTimeStart"] = query.StartTimeRange.Start;
parameters["@StartTimeEnd"] = query.StartTimeRange.End;
}
if (query.EndTimeRange != null)
{
parameters["@EndTimeStart"] = query.EndTimeRange.Start;
parameters["@EndTimeEnd"] = query.EndTimeRange.End;
}
if (query.CreatedTimeRange != null)
{
parameters["@CreatedTimeStart"] = query.CreatedTimeRange.Start;
parameters["@CreatedTimeEnd"] = query.CreatedTimeRange.End;
}
if (!string.IsNullOrWhiteSpace(query.Keyword))
parameters["@Keyword"] = $"%{query.Keyword}%";
return parameters;
}
/// <summary>
/// 构建基础统计SQL。
/// </summary>
private string BuildBasicStatisticsSql(SessionStatisticsQuery query)
{
var sql = @"
SELECT
COUNT(*) as TotalSessions,
SUM(CASE WHEN Result = 1 THEN 1 ELSE 0 END) as OkSessions,
SUM(CASE WHEN Result = 2 THEN 1 ELSE 0 END) as NgSessions,
SUM(CASE WHEN Status = 0 THEN 1 ELSE 0 END) as InProgressSessions,
SUM(CASE WHEN Status = 3 THEN 1 ELSE 0 END) as CancelledSessions,
SUM(CASE WHEN Status = 4 THEN 1 ELSE 0 END) as PausedSessions
FROM mdl_production_session
WHERE StartedAtUtc >= @StartTime AND StartedAtUtc <= @EndTime";
if (!string.IsNullOrWhiteSpace(query.ProductTypeCode))
sql += " AND ProductTypeCode = @ProductTypeCode";
if (!string.IsNullOrWhiteSpace(query.StationId))
sql += " AND StationId = @StationId";
if (!string.IsNullOrWhiteSpace(query.OperatorId))
sql += " AND OperatorId = @OperatorId";
if (!string.IsNullOrWhiteSpace(query.ShiftId))
sql += " AND ShiftId = @ShiftId";
return sql;
}
/// <summary>
/// 构建时间统计SQL。
/// </summary>
private string BuildTimeStatisticsSql(SessionStatisticsQuery query)
{
var sql = @"
SELECT
AVG(DATEDIFF(SECOND, StartedAtUtc, EndedAtUtc)) as AvgProcessingTime,
COUNT(*) / NULLIF(SUM(DATEDIFF(HOUR, StartedAtUtc, EndedAtUtc)), 0) as ThroughputPerHour
FROM mdl_production_session
WHERE StartedAtUtc >= @StartTime AND StartedAtUtc <= @EndTime
AND EndedAtUtc IS NOT NULL";
if (!string.IsNullOrWhiteSpace(query.ProductTypeCode))
sql += " AND ProductTypeCode = @ProductTypeCode";
if (!string.IsNullOrWhiteSpace(query.StationId))
sql += " AND StationId = @StationId";
if (!string.IsNullOrWhiteSpace(query.OperatorId))
sql += " AND OperatorId = @OperatorId";
if (!string.IsNullOrWhiteSpace(query.ShiftId))
sql += " AND ShiftId = @ShiftId";
return sql;
}
/// <summary>
/// 获取产品类型统计。
/// </summary>
private async Task<List<ProductTypeStatistics>> GetProductTypeStatisticsAsync(SessionStatisticsQuery query, SqlConnection connection, CancellationToken cancellationToken = default)
{
var statistics = new List<ProductTypeStatistics>();
using var command = connection.CreateCommand();
command.CommandText = @"
SELECT
ProductTypeCode, ProductTypeName,
COUNT(*) as TotalSessions,
SUM(CASE WHEN Result = 1 THEN 1 ELSE 0 END) as OkSessions,
SUM(CASE WHEN Result = 2 THEN 1 ELSE 0 END) as NgSessions
FROM mdl_production_session
WHERE StartedAtUtc >= @StartTime AND StartedAtUtc <= @EndTime
GROUP BY ProductTypeCode, ProductTypeName";
AddParameter(command, "@StartTime", query.StartTime);
AddParameter(command, "@EndTime", query.EndTime);
using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
statistics.Add(new ProductTypeStatistics
{
ProductTypeCode = reader.GetString(0),
ProductTypeName = reader.GetString(1),
TotalSessions = reader.GetInt32(2),
OkSessions = reader.GetInt32(3),
NgSessions = reader.GetInt32(4),
PassRate = reader.GetInt32(2) > 0 ? (double)reader.GetInt32(3) / reader.GetInt32(2) * 100 : 0
});
}
return statistics;
}
/// <summary>
/// 获取工位统计。
/// </summary>
private async Task<List<StationStatistics>> GetStationStatisticsAsync(SessionStatisticsQuery query, SqlConnection connection, CancellationToken cancellationToken = default)
{
var statistics = new List<StationStatistics>();
using var command = connection.CreateCommand();
command.CommandText = @"
SELECT
StationId, StationName,
COUNT(*) as TotalSessions,
SUM(CASE WHEN Result = 1 THEN 1 ELSE 0 END) as OkSessions,
SUM(CASE WHEN Result = 2 THEN 1 ELSE 0 END) as NgSessions,
AVG(DATEDIFF(SECOND, StartedAtUtc, EndedAtUtc)) as AvgProcessingTime
FROM mdl_production_session
WHERE StartedAtUtc >= @StartTime AND StartedAtUtc <= @EndTime
AND EndedAtUtc IS NOT NULL
GROUP BY StationId, StationName";
AddParameter(command, "@StartTime", query.StartTime);
AddParameter(command, "@EndTime", query.EndTime);
using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
var totalSessions = reader.GetInt32(2);
var avgProcessingTime = reader.IsDBNull(5) ? 0.0 : reader.GetDouble(5);
statistics.Add(new StationStatistics
{
StationId = reader.GetString(0),
StationName = reader.GetString(1),
TotalSessions = totalSessions,
OkSessions = reader.GetInt32(3),
NgSessions = reader.GetInt32(4),
PassRate = totalSessions > 0 ? (double)reader.GetInt32(3) / totalSessions * 100 : 0,
ThroughputPerHour = avgProcessingTime > 0 ? totalSessions / (avgProcessingTime / 3600) : 0
});
}
return statistics;
}
/// <summary>
/// 获取操作员统计。
/// </summary>
private async Task<List<OperatorStatistics>> GetOperatorStatisticsAsync(SessionStatisticsQuery query, SqlConnection connection, CancellationToken cancellationToken = default)
{
var statistics = new List<OperatorStatistics>();
using var command = connection.CreateCommand();
command.CommandText = @"
SELECT
OperatorId, OperatorName,
COUNT(*) as TotalSessions,
SUM(CASE WHEN Result = 1 THEN 1 ELSE 0 END) as OkSessions,
SUM(CASE WHEN Result = 2 THEN 1 ELSE 0 END) as NgSessions,
AVG(DATEDIFF(SECOND, StartedAtUtc, EndedAtUtc)) as AvgProcessingTime
FROM mdl_production_session
WHERE StartedAtUtc >= @StartTime AND StartedAtUtc <= @EndTime
AND EndedAtUtc IS NOT NULL
GROUP BY OperatorId, OperatorName";
AddParameter(command, "@StartTime", query.StartTime);
AddParameter(command, "@EndTime", query.EndTime);
using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
var totalSessions = reader.GetInt32(2);
statistics.Add(new OperatorStatistics
{
OperatorId = reader.GetString(0),
OperatorName = reader.GetString(1),
TotalSessions = totalSessions,
OkSessions = reader.GetInt32(3),
NgSessions = reader.GetInt32(4),
PassRate = totalSessions > 0 ? (double)reader.GetInt32(3) / totalSessions * 100 : 0,
AverageProcessingTimeSeconds = reader.IsDBNull(5) ? 0.0 : reader.GetDouble(5)
});
}
return statistics;
}
/// <summary>
/// 获取日期统计。
/// </summary>
private async Task<List<DailyStatistics>> GetDailyStatisticsAsync(SessionStatisticsQuery query, SqlConnection connection, CancellationToken cancellationToken = default)
{
var statistics = new List<DailyStatistics>();
using var command = connection.CreateCommand();
command.CommandText = @"
SELECT
CAST(StartedAtUtc AS DATE) as Date,
COUNT(*) as TotalSessions,
SUM(CASE WHEN Result = 1 THEN 1 ELSE 0 END) as OkSessions,
SUM(CASE WHEN Result = 2 THEN 1 ELSE 0 END) as NgSessions,
AVG(DATEDIFF(SECOND, StartedAtUtc, EndedAtUtc)) as AvgProcessingTime
FROM mdl_production_session
WHERE StartedAtUtc >= @StartTime AND StartedAtUtc <= @EndTime
AND EndedAtUtc IS NOT NULL
GROUP BY CAST(StartedAtUtc AS DATE)
ORDER BY Date";
AddParameter(command, "@StartTime", query.StartTime);
AddParameter(command, "@EndTime", query.EndTime);
using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
var totalSessions = reader.GetInt32(1);
var avgProcessingTime = reader.IsDBNull(4) ? 0.0 : reader.GetDouble(4);
statistics.Add(new DailyStatistics
{
DateUtc = reader.GetDateTime(0),
TotalSessions = totalSessions,
OkSessions = reader.GetInt32(2),
NgSessions = reader.GetInt32(3),
PassRate = totalSessions > 0 ? (double)reader.GetInt32(2) / totalSessions * 100 : 0,
ThroughputPerHour = avgProcessingTime > 0 ? totalSessions / (avgProcessingTime / 3600) : 0
});
}
return statistics;
}
/// <summary>
/// 获取班次统计。
/// </summary>
private async Task<List<ShiftStatistics>> GetShiftStatisticsAsync(SessionStatisticsQuery query, SqlConnection connection, CancellationToken cancellationToken = default)
{
var statistics = new List<ShiftStatistics>();
using var command = connection.CreateCommand();
command.CommandText = @"
SELECT
ShiftId, ShiftName,
COUNT(*) as TotalSessions,
SUM(CASE WHEN Result = 1 THEN 1 ELSE 0 END) as OkSessions,
SUM(CASE WHEN Result = 2 THEN 1 ELSE 0 END) as NgSessions,
AVG(DATEDIFF(SECOND, StartedAtUtc, EndedAtUtc)) as AvgProcessingTime
FROM mdl_production_session
WHERE StartedAtUtc >= @StartTime AND StartedAtUtc <= @EndTime
AND EndedAtUtc IS NOT NULL
GROUP BY ShiftId, ShiftName";
AddParameter(command, "@StartTime", query.StartTime);
AddParameter(command, "@EndTime", query.EndTime);
using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
var totalSessions = reader.GetInt32(2);
var avgProcessingTime = reader.IsDBNull(4) ? 0.0 : reader.GetDouble(4);
statistics.Add(new ShiftStatistics
{
ShiftId = reader.GetString(0),
ShiftName = reader.GetString(1),
TotalSessions = totalSessions,
OkSessions = reader.GetInt32(3),
NgSessions = reader.GetInt32(4),
PassRate = totalSessions > 0 ? (double)reader.GetInt32(3) / totalSessions * 100 : 0,
ThroughputPerHour = avgProcessingTime > 0 ? totalSessions / (avgProcessingTime / 3600) : 0
});
}
return statistics;
}
#endregion
}