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(); await RemoveRelationshipsAsync(toRemove);
try await AddRelationshipsAsync(toAdd, unitsByName);
{ await CommitChangesAsync(toRemove.Count, toAdd.Count);
await RemoveRelationshipsAsync(toRemove);
await AddRelationshipsAsync(toAdd, unitsByName);
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)
{ {
Id = Guid.NewGuid(), var unit = new Unit
Name = name, {
DateCreated = DateTimeOffset.UtcNow Id = Guid.NewGuid(),
}).ToList(); Name = name,
DateCreated = DateTimeOffset.UtcNow
};
_unitRepository.CreateRange(newUnits); if (!await _unitRepository.CreateAsync(unit))
await _unitRepository.CommitAsync(); {
_logger.LogError("Не удалось создать юнит: {Name}", name);
continue;
}
foreach (var unit in newUnits) existingUnits[name] = unit;
{ _logger.LogDebug("Создан юнит: {Name}", name);
existingUnits[unit.Name] = unit;
_logger.LogDebug("Создан юнит: {Name}", unit.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,
@@ -161,13 +157,19 @@ internal class RelationshipsSyncService : IRelationshipsSyncService
Relationship = relationship Relationship = relationship
}) })
.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 toAdd = sourcePairs.Where(p => !existingSet.Contains(p)).ToList(); var toRemove = existingForParents
.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);
} }