fix(aihit-sync): Оптимизация GetDeltaAsync и атомарность сохранения

- GetDeltaAsync: фильтрация по relevantParentNames вместо выгрузки всех связей из БД
- SyncAsync: атомарность через ChangeTracker + CommitAsync без явной транзакции
This commit is contained in:
Mikhail Kuznetsov
2026-07-15 14:40:10 +10:00
parent 4ee2880a8f
commit a087958fd5

View File

@@ -39,21 +39,11 @@ internal class RelationshipsSyncService : IRelationshipsSyncService
var (toRemove, toAdd) = await GetDeltaAsync(validPairs); var (toRemove, toAdd) = await GetDeltaAsync(validPairs);
// Транзакция гарантирует атомарность зеркалирования. // Удаление и добавление накапливаются в одном ChangeTracker.
// Без неё частичный сбой оставляет БД в состоянии, не совпадающем со снимком. // CommitAsync сохраняет всё атомарно через SaveChangesAsync.
await using var transaction = await _unitRepository.BeginTransactionAsync();
try
{
await RemoveRelationshipsAsync(toRemove); await RemoveRelationshipsAsync(toRemove);
await AddRelationshipsAsync(toAdd, unitsByName); await AddRelationshipsAsync(toAdd, unitsByName);
await CommitChangesAsync(toRemove.Count, toAdd.Count); await CommitChangesAsync(toRemove.Count, toAdd.Count);
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
} }
private List<(string ParentName, string ChildName)> ValidateInput(List<AihitData> data) private List<(string ParentName, string ChildName)> ValidateInput(List<AihitData> data)
@@ -128,32 +118,38 @@ internal class RelationshipsSyncService : IRelationshipsSyncService
_logger.LogInformation("Создание отсутствующих юнитов: {Count}", missingNames.Count); _logger.LogInformation("Создание отсутствующих юнитов: {Count}", missingNames.Count);
var newUnits = missingNames.Select(name => new Unit foreach (var name in missingNames)
{
var unit = new Unit
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Name = name, Name = name,
DateCreated = DateTimeOffset.UtcNow DateCreated = DateTimeOffset.UtcNow
}).ToList(); };
_unitRepository.CreateRange(newUnits); if (!await _unitRepository.CreateAsync(unit))
await _unitRepository.CommitAsync();
foreach (var unit in newUnits)
{ {
existingUnits[unit.Name] = unit; _logger.LogError("Не удалось создать юнит: {Name}", name);
_logger.LogDebug("Создан юнит: {Name}", unit.Name); continue;
} }
existingUnits[name] = unit;
_logger.LogDebug("Создан юнит: {Name}", name);
}
await _unitRepository.CommitAsync();
return existingUnits; return existingUnits;
} }
private async Task<(List<ExistingRelationship> ToRemove, List<(string ParentName, string ChildName)> ToAdd)> GetDeltaAsync( private async Task<(List<ExistingRelationship> ToRemove, List<(string ParentName, string ChildName)> ToAdd)> GetDeltaAsync(
List<(string ParentName, string ChildName)> sourcePairs) List<(string ParentName, string ChildName)> sourcePairs)
{ {
var allExisting = await _unitRepository.Get() var relevantParentNames = sourcePairs.Select(p => p.ParentName).Distinct().ToList();
var existingForParents = await _unitRepository.Get()
.AsNoTracking() .AsNoTracking()
.Include(u => u.ChildUnits).ThenInclude(r => r.ChildUnit) .Include(u => u.ChildUnits).ThenInclude(r => r.ChildUnit)
.Where(u => u.ChildUnits.Any()) .Where(u => relevantParentNames.Contains(u.Name))
.SelectMany(u => u.ChildUnits, (parent, relationship) => new ExistingRelationship .SelectMany(u => u.ChildUnits, (parent, relationship) => new ExistingRelationship
{ {
ParentName = parent.Name, ParentName = parent.Name,
@@ -163,10 +159,16 @@ internal class RelationshipsSyncService : IRelationshipsSyncService
.ToListAsync(); .ToListAsync();
var sourceSet = new HashSet<(string, string)>(sourcePairs); var sourceSet = new HashSet<(string, string)>(sourcePairs);
var existingSet = new HashSet<(string, string)>(allExisting.Select(x => (x.ParentName, x.ChildName))); var existingSet = new HashSet<(string, string)>(
existingForParents.Select(x => (x.ParentName, x.ChildName)));
var toRemove = allExisting.Where(r => !sourceSet.Contains((r.ParentName, r.ChildName))).ToList(); var toRemove = existingForParents
var toAdd = sourcePairs.Where(p => !existingSet.Contains(p)).ToList(); .Where(r => !sourceSet.Contains((r.ParentName, r.ChildName)))
.ToList();
var toAdd = sourcePairs
.Where(p => !existingSet.Contains(p))
.ToList();
_logger.LogInformation("Дельта: удаление {ToRemove}, добавление {ToAdd}", toRemove.Count, toAdd.Count); _logger.LogInformation("Дельта: удаление {ToRemove}, добавление {ToAdd}", toRemove.Count, toAdd.Count);
return (toRemove, toAdd); return (toRemove, toAdd);