Files
WCTDataMiner/src/WCTDataMiner.Core/Services/DimensionService.cs

138 lines
4.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using WCTDataMiner.Core.Data;
using WCTDataMiner.Core.Models;
namespace WCTDataMiner.Core.Services;
/// <summary>
/// 维度表管理服务 - 负责维度表的CRUD操作并发安全版本
/// </summary>
public class DimensionService
{
private readonly WctMinerDbContext _context;
private readonly ILogger<DimensionService> _logger;
public DimensionService(WctMinerDbContext context, ILogger<DimensionService> logger)
{
_context = context;
_logger = logger;
}
/// <summary>
/// 获取或创建TX面板维度并发安全
/// </summary>
public async Task<TxPanel> GetOrCreateTxPanelAsync(string name)
{
return await GetOrCreateDimensionAsync(
() => _context.TxPanels.AsNoTracking().FirstOrDefaultAsync(p => p.Name == name),
() => new TxPanel { Name = name },
entity => _context.TxPanels.Add(entity),
name,
"TX Panel"
);
}
/// <summary>
/// 获取或创建TX硬件版本维度并发安全
/// </summary>
public async Task<TxHardware> GetOrCreateTxHardwareAsync(string version)
{
return await GetOrCreateDimensionAsync(
() => _context.TxHardwares.AsNoTracking().FirstOrDefaultAsync(h => h.Version == version),
() => new TxHardware { Version = version },
entity => _context.TxHardwares.Add(entity),
version,
"TX Hardware"
);
}
/// <summary>
/// 获取或创建TX软件版本维度并发安全
/// </summary>
public async Task<TxSoftware> GetOrCreateTxSoftwareAsync(string version)
{
return await GetOrCreateDimensionAsync(
() => _context.TxSoftwares.AsNoTracking().FirstOrDefaultAsync(s => s.Version == version),
() => new TxSoftware { Version = version },
entity => _context.TxSoftwares.Add(entity),
version,
"TX Software"
);
}
/// <summary>
/// 获取或创建RX类型维度并发安全
/// </summary>
public async Task<RxType> GetOrCreateRxTypeAsync(string name)
{
return await GetOrCreateDimensionAsync(
() => _context.RxTypes.AsNoTracking().FirstOrDefaultAsync(r => r.Name == name),
() => new RxType { Name = name },
entity => _context.RxTypes.Add(entity),
name,
"RX Type"
);
}
/// <summary>
/// 通用的并发安全 GetOrCreate 模式
/// </summary>
private async Task<T> GetOrCreateDimensionAsync<T>(
Func<Task<T?>> findExisting,
Func<T> createNew,
Action<T> addToContext,
string identifier,
string dimensionName) where T : class
{
const int maxRetries = 3;
for (int attempt = 0; attempt < maxRetries; attempt++)
{
// 使用 AsNoTracking 避免 ChangeTracker 缓存
var existing = await findExisting();
if (existing != null) return existing;
var newEntity = createNew();
addToContext(newEntity);
try
{
await _context.SaveChangesAsync();
_logger.LogDebug("创建新 {DimensionName} 维度: {Identifier}", dimensionName, identifier);
return newEntity;
}
catch (DbUpdateException ex) when (IsUniqueConstraintViolation(ex))
{
// Detach 实体以避免 ChangeTracker 问题
_context.Entry(newEntity).State = EntityState.Detached;
_logger.LogDebug("{DimensionName} 并发创建冲突,重试: {Identifier}", dimensionName, identifier);
if (attempt < maxRetries - 1)
{
// 随机退避,减少并发冲突
await Task.Delay(Random.Shared.Next(5, 20));
}
}
}
// 最后一次尝试:必然存在(唯一约束已生效)
var finalEntity = await findExisting();
if (finalEntity == null)
{
// 极端情况:重试全部失败但数据不存在,抛出异常
throw new InvalidOperationException($"无法获取或创建 {dimensionName} 维度: {identifier}");
}
return finalEntity;
}
/// <summary>
/// 检测 SQLite 唯一约束违反错误
/// </summary>
private static bool IsUniqueConstraintViolation(DbUpdateException ex)
{
return ex.InnerException is Microsoft.Data.Sqlite.SqliteException sqliteEx
&& sqliteEx.SqliteErrorCode == 19; // SQLITE_CONSTRAINT
}
}