From a087958fd55b1b059ce4a66b3c16fc3f35f87d0e Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Wed, 15 Jul 2026 14:40:10 +1000 Subject: [PATCH] =?UTF-8?q?fix(aihit-sync):=20=D0=9E=D0=BF=D1=82=D0=B8?= =?UTF-8?q?=D0=BC=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20GetDeltaAsync=20?= =?UTF-8?q?=D0=B8=20=D0=B0=D1=82=D0=BE=D0=BC=D0=B0=D1=80=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GetDeltaAsync: фильтрация по relevantParentNames вместо выгрузки всех связей из БД - SyncAsync: атомарность через ChangeTracker + CommitAsync без явной транзакции --- .../RelationshipsSyncService.cs | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/PARR.AIHITRelationshipsSyncer/Services/Implementations/RelationshipsSyncService.cs b/PARR.AIHITRelationshipsSyncer/Services/Implementations/RelationshipsSyncService.cs index 8a1585da..eed168fe 100644 --- a/PARR.AIHITRelationshipsSyncer/Services/Implementations/RelationshipsSyncService.cs +++ b/PARR.AIHITRelationshipsSyncer/Services/Implementations/RelationshipsSyncService.cs @@ -39,21 +39,11 @@ internal class RelationshipsSyncService : IRelationshipsSyncService var (toRemove, toAdd) = await GetDeltaAsync(validPairs); - // Транзакция гарантирует атомарность зеркалирования. - // Без неё частичный сбой оставляет БД в состоянии, не совпадающем со снимком. - await using var transaction = await _unitRepository.BeginTransactionAsync(); - try - { - await RemoveRelationshipsAsync(toRemove); - await AddRelationshipsAsync(toAdd, unitsByName); - await CommitChangesAsync(toRemove.Count, toAdd.Count); - await transaction.CommitAsync(); - } - catch - { - await transaction.RollbackAsync(); - throw; - } + // Удаление и добавление накапливаются в одном ChangeTracker. + // CommitAsync сохраняет всё атомарно через SaveChangesAsync. + await RemoveRelationshipsAsync(toRemove); + await AddRelationshipsAsync(toAdd, unitsByName); + await CommitChangesAsync(toRemove.Count, toAdd.Count); } private List<(string ParentName, string ChildName)> ValidateInput(List 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) { - Id = Guid.NewGuid(), - Name = name, - DateCreated = DateTimeOffset.UtcNow - }).ToList(); + var unit = new Unit + { + Id = Guid.NewGuid(), + Name = name, + DateCreated = DateTimeOffset.UtcNow + }; - _unitRepository.CreateRange(newUnits); - await _unitRepository.CommitAsync(); + if (!await _unitRepository.CreateAsync(unit)) + { + _logger.LogError("Не удалось создать юнит: {Name}", name); + continue; + } - foreach (var unit in newUnits) - { - existingUnits[unit.Name] = unit; - _logger.LogDebug("Создан юнит: {Name}", unit.Name); + existingUnits[name] = unit; + _logger.LogDebug("Создан юнит: {Name}", name); } + await _unitRepository.CommitAsync(); return existingUnits; } private async Task<(List 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, @@ -161,13 +157,19 @@ internal class RelationshipsSyncService : IRelationshipsSyncService Relationship = relationship }) .ToListAsync(); - + var sourceSet = new HashSet<(string, string)>(sourcePairs); - var existingSet = new HashSet<(string, string)>(allExisting.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 existingSet = new HashSet<(string, string)>( + existingForParents.Select(x => (x.ParentName, x.ChildName))); + + 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); }