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);
// Транзакция гарантирует атомарность зеркалирования.
// Без неё частичный сбой оставляет БД в состоянии, не совпадающем со снимком.
await using var transaction = await _unitRepository.BeginTransactionAsync();
try
{
// Удаление и добавление накапливаются в одном ChangeTracker.
// CommitAsync сохраняет всё атомарно через SaveChangesAsync.
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)
@@ -128,32 +118,38 @@ internal class RelationshipsSyncService : IRelationshipsSyncService
_logger.LogInformation("Создание отсутствующих юнитов: {Count}", missingNames.Count);
var newUnits = missingNames.Select(name => new Unit
foreach (var name in missingNames)
{
var unit = new Unit
{
Id = Guid.NewGuid(),
Name = name,
DateCreated = DateTimeOffset.UtcNow
}).ToList();
};
_unitRepository.CreateRange(newUnits);
await _unitRepository.CommitAsync();
foreach (var unit in newUnits)
if (!await _unitRepository.CreateAsync(unit))
{
existingUnits[unit.Name] = unit;
_logger.LogDebug("Создан юнит: {Name}", unit.Name);
_logger.LogError("Не удалось создать юнит: {Name}", name);
continue;
}
existingUnits[name] = unit;
_logger.LogDebug("Создан юнит: {Name}", name);
}
await _unitRepository.CommitAsync();
return existingUnits;
}
private async Task<(List<ExistingRelationship> ToRemove, List<(string ParentName, string ChildName)> ToAdd)> GetDeltaAsync(
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()
.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
{
ParentName = parent.Name,
@@ -163,10 +159,16 @@ internal class RelationshipsSyncService : IRelationshipsSyncService
.ToListAsync();
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);
return (toRemove, toAdd);