fix(aihit-sync): Оптимизация GetDeltaAsync и атомарность сохранения
- GetDeltaAsync: фильтрация по relevantParentNames вместо выгрузки всех связей из БД - SyncAsync: атомарность через ChangeTracker + CommitAsync без явной транзакции
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user