feat(aihitSyncer): RemoveFromCacheAsync после обновления атрибутов ЭК
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using PARR.AIHITMainSyncer.Settings;
|
||||
using PARR.Core.Common.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Core.Services.UnitService.Interfaces;
|
||||
using PARR.Domain.Common.Rabbit.Messages;
|
||||
using PARR.Domain.Entities.Unit;
|
||||
|
||||
@@ -13,26 +13,29 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
{
|
||||
private readonly ILogger<SyncerService> logger;
|
||||
private readonly ITransformService transformService;
|
||||
private readonly IUnitRepository unitService;
|
||||
private readonly IUnitFieldRepository unitFieldService;
|
||||
private readonly IUnitFieldValueRepository unitFieldValueService;
|
||||
private readonly IUnitRepository unitRepository;
|
||||
private readonly IUnitFieldRepository unitFieldRepository;
|
||||
private readonly IUnitFieldValueRepository unitFieldValueRepository;
|
||||
private readonly FieldValueReplacementsSettings fieldValueReplacementsSettings;
|
||||
private readonly IUnitService unitService;
|
||||
|
||||
public SyncerService(
|
||||
ILogger<SyncerService> logger,
|
||||
ITransformService transformService,
|
||||
IUnitRepository unitService,
|
||||
IUnitFieldRepository unitFieldService,
|
||||
IUnitFieldValueRepository unitFieldValueService,
|
||||
FieldValueReplacementsSettings fieldValueReplacementsSettings
|
||||
IUnitRepository unitRepository,
|
||||
IUnitFieldRepository unitFieldRepository,
|
||||
IUnitFieldValueRepository unitFieldValueRepository,
|
||||
FieldValueReplacementsSettings fieldValueReplacementsSettings,
|
||||
IUnitService unitService
|
||||
)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.transformService = transformService;
|
||||
this.unitService = unitService;
|
||||
this.unitFieldService = unitFieldService;
|
||||
this.unitFieldValueService = unitFieldValueService;
|
||||
this.unitRepository = unitRepository;
|
||||
this.unitFieldRepository = unitFieldRepository;
|
||||
this.unitFieldValueRepository = unitFieldValueRepository;
|
||||
this.fieldValueReplacementsSettings = fieldValueReplacementsSettings;
|
||||
this.unitService = unitService;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +48,6 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
logger.LogWarning("Не удалось десериализовать сообщение в объект AihitMainDataMq. Сообщение: {Message}", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
await SyncUnitAsync(objFromQuery);
|
||||
}
|
||||
|
||||
@@ -55,7 +57,7 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
logger.LogDebug("Начинаем синхронизацию Unit для объекта с именем: {UnitName}", objFromQuery.Name);
|
||||
|
||||
//Получаем сразу словарь атрибутов, чтобы не ходить каждый раз в базу
|
||||
var fieldsFromDB = await unitFieldService.Get().AsNoTracking().ToListAsync();
|
||||
var fieldsFromDB = await unitFieldRepository.Get().AsNoTracking().ToListAsync();
|
||||
|
||||
if (!objFromQuery.Properties.Any())
|
||||
{
|
||||
@@ -63,24 +65,24 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Создаем копию и нормализуем свойства (удаляем лишние пробелы)
|
||||
// 1. Создаем копию и нормализуем свойства
|
||||
var normalizedProperties = new Dictionary<string, string?>();
|
||||
foreach (var kvp in objFromQuery.Properties)
|
||||
normalizedProperties[kvp.Key.Trim()] = kvp.Value?.Trim();
|
||||
|
||||
// 2. Применяем замены значений к нормализованным свойствам
|
||||
// 2. Применяем замены значений
|
||||
ApplyFieldValueReplacements(normalizedProperties);
|
||||
|
||||
var listAihitData = new List<KeyValuePair<string, string?>>();
|
||||
|
||||
// 3. Обрабатываем простые значения из нормализованных свойств
|
||||
// 3. Обрабатываем простые значения
|
||||
var propertisWithSimpleValues = normalizedProperties
|
||||
.Where(t => fieldsFromDB.Any(a => IsStringEqual(t.Key.Trim(), a.AihitName) && a.IsMultipleValue != true))
|
||||
.Select(s => new KeyValuePair<string, string?>(s.Key.Trim(), s.Value?.Trim())).ToList();
|
||||
|
||||
listAihitData.AddRange(propertisWithSimpleValues);
|
||||
|
||||
// 4. Обрабатываем множественные значения из нормализованных свойств
|
||||
// 4. Обрабатываем множественные значения
|
||||
var propertiesWithMultipleValues = objFromQuery.Properties.Where(t => !propertisWithSimpleValues.Any(a => t.Key.Trim() == a.Key)).ToList();
|
||||
|
||||
propertiesWithMultipleValues.ForEach(p => listAihitData.AddRange(
|
||||
@@ -110,10 +112,13 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
)
|
||||
.ToList();
|
||||
|
||||
bool hasChanges = false;
|
||||
|
||||
if (unitValuesToRemove.Any())
|
||||
{
|
||||
logger.LogDebug("Удаляем {Count} устаревших значений для Unit {UnitName}", unitValuesToRemove.Count, unit.Name);
|
||||
unitValuesToRemove.ForEach(uv => unit.UnitValues.Remove(uv));
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
var unitValueMissing = listAihitData.Where(t =>
|
||||
@@ -130,7 +135,6 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
var valuesInChanges = unitValueMissing.Select(s => s.Value).Distinct().ToList();
|
||||
|
||||
await SyncFieldsAsync(fieldsInChanges!, fieldsFromDB);
|
||||
|
||||
await SyncValuesAsync(valuesInChanges);
|
||||
|
||||
var normalizedFieldNames = fieldsInChanges
|
||||
@@ -138,7 +142,7 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
.Where(n => n != null)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var fieldsNameWithId = await unitFieldService.Get().AsNoTracking()
|
||||
var fieldsNameWithId = await unitFieldRepository.Get().AsNoTracking()
|
||||
.Where(f => normalizedFieldNames.Contains(f.AihitName))
|
||||
.ToListAsync();
|
||||
|
||||
@@ -147,7 +151,7 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
.Select(v => v!.Trim())
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var valuesWithId = await unitFieldValueService.Get().AsNoTracking()
|
||||
var valuesWithId = await unitFieldValueRepository.Get().AsNoTracking()
|
||||
.Where(v =>
|
||||
(v.Value == null && valuesInChanges.Contains(null)) ||
|
||||
(v.Value != null && normalizedValues.Contains(v.Value)))
|
||||
@@ -156,17 +160,23 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
foreach (var change in unitValueMissing)
|
||||
ApplyChange(unit, fieldsNameWithId, valuesWithId, change);
|
||||
|
||||
if (!await unitService.CommitAsync())
|
||||
hasChanges = true;
|
||||
}
|
||||
else
|
||||
logger.LogDebug("Нет новых значений для синхронизации для Unit {UnitName}", unit.Name);
|
||||
|
||||
// Сохраняем изменения и инвалидируем кэш только если были реальные изменения
|
||||
if (hasChanges)
|
||||
{
|
||||
if (!await unitRepository.CommitAsync())
|
||||
{
|
||||
logger.LogError("Не удалось применить изменения по актуализиции аттрибуто Unit {UnitName} в базе данных", unit.Name);
|
||||
logger.LogError("Не удалось применить изменения по актуализации атрибутов Unit {UnitName} в базе данных", unit.Name);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogInformation("Успешно обновлены атрибуты для Unit {UnitName}. Добавлено {AddedCount} значений", unit.Name, unitValueMissing.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Нет новых значений для синхронизации для Unit {UnitName}", unit.Name);
|
||||
// Инвалидация кэша после успешного сохранения
|
||||
await unitService.RemoveFromCacheAsync(unit.Id);
|
||||
logger.LogInformation("Успешно обновлены атрибуты для Unit {UnitName}. Кэш инвалидирован.", unit.Name);
|
||||
}
|
||||
|
||||
logger.LogInformation("Завершена синхронизация Unit {UnitName}", unit.Name);
|
||||
@@ -191,7 +201,6 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
bool isTagProperty = tagUnitField != null && multiValueProperty.Key == tagUnitField.AihitName;
|
||||
|
||||
string[] splitValues;
|
||||
|
||||
if (isTagProperty)
|
||||
{
|
||||
// Для тегов нормализуем разделители до пробела
|
||||
@@ -249,10 +258,9 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
}
|
||||
|
||||
|
||||
private async Task SyncValuesAsync(List<string?> values)
|
||||
private async Task SyncValuesAsync(List<string?> values)
|
||||
{
|
||||
bool hasNull = values.Any(v => v is null);
|
||||
// все поля из АИХИТ
|
||||
var normalizedNoneNullValues = values
|
||||
.Where(v => v != null)
|
||||
.Select(v => v!.Trim())
|
||||
@@ -261,8 +269,7 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
|
||||
logger.LogDebug("Синхронизируем {Count} значений (включая null: {HasNull})", normalizedNoneNullValues.Count, hasNull);
|
||||
|
||||
// Получаем существующие значения из БД
|
||||
var existingValuesInDb = await unitFieldValueService.Get()
|
||||
var existingValuesInDb = await unitFieldValueRepository.Get()
|
||||
.Where(v => normalizedNoneNullValues.Contains(v.Value!) || v.Value == null)
|
||||
.Select(v => v.Value)
|
||||
.ToListAsync();
|
||||
@@ -287,35 +294,22 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
return;
|
||||
}
|
||||
|
||||
// Создаём сущности — сохраняем оригинальный регистр из nonNullValues!
|
||||
// (для null — просто null)
|
||||
var newFieldValues = newValuesToInsert.Select(v =>
|
||||
new UnitFieldValue
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Value = v // ← v — уже trim()-нутый non-null, или null
|
||||
Value = v
|
||||
})
|
||||
.ToList();
|
||||
|
||||
if (!await unitFieldValueService.AddRangeAsync(newFieldValues) || !await unitFieldValueService.CommitAsync())
|
||||
// Только добавляем в контекст, НЕ коммитим
|
||||
if (!await unitFieldValueRepository.AddRangeAsync(newFieldValues))
|
||||
{
|
||||
logger.LogError(
|
||||
"Не удалось сохранить {Count} новых значений FieldValues",
|
||||
newValuesToInsert.Count);
|
||||
logger.LogError("Не удалось добавить {Count} новых значений FieldValues в контекст", newValuesToInsert.Count);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (newValuesToInsert.Count <= 10)
|
||||
{
|
||||
var preview = string.Join(", ", newValuesToInsert.Select(v => v ?? "<null>"));
|
||||
logger.LogInformation("Добавлено {Count} значений FieldValues: [{Values}]", newValuesToInsert.Count, preview);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogInformation("Добавлено {Count} значений FieldValues (первые 5: {Preview})",
|
||||
newValuesToInsert.Count,
|
||||
string.Join(", ", newValuesToInsert.Take(5).Select(v => v ?? "<null>")));
|
||||
}
|
||||
logger.LogDebug("Подготовлено {Count} новых значений FieldValues для сохранения", newValuesToInsert.Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,24 +335,16 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
EsppName = null,
|
||||
};
|
||||
|
||||
if (!await unitFieldService.CreateAsync(field))
|
||||
// Только добавляем в контекст, НЕ коммитим
|
||||
if (!await unitFieldRepository.CreateAsync(field))
|
||||
{
|
||||
logger.LogError("Не удалось создать запись в таблице Fields: {FieldName}", item);
|
||||
logger.LogError("Не удалось добавить поле в контекст: {FieldName}", item);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogInformation("Создана запись в таблице Fields: {FieldName}", item);
|
||||
logger.LogDebug("Подготовлено поле для сохранения: {FieldName}", item);
|
||||
}
|
||||
}
|
||||
|
||||
if (!await unitFieldService.CommitAsync())
|
||||
{
|
||||
logger.LogError("Не удалось применить изменения по добавлению новых Fields в базе данных");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Успешно зафиксированы изменения по добавлению новых Fields");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -393,7 +379,7 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
{
|
||||
var unit = new Unit { Name = name.Trim().ToUpperInvariant() };
|
||||
|
||||
if (!await unitService.CreateAsync(unit) || !await unitService.CommitAsync())
|
||||
if (!await unitRepository.CreateAsync(unit) || !await unitRepository.CommitAsync())
|
||||
{
|
||||
logger.LogError("Не удалось создать Unit {UnitName}", unit.Name);
|
||||
throw new InvalidOperationException($"Не удалось создать Unit {unit.Name}");
|
||||
@@ -402,7 +388,6 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
{
|
||||
logger.LogInformation("Создан Unit: {UnitName}", unit.Name);
|
||||
}
|
||||
|
||||
return unit;
|
||||
}
|
||||
|
||||
@@ -417,7 +402,7 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
|
||||
private IQueryable<Unit> GetUnitWithFieldsAndValues()
|
||||
{
|
||||
return unitService.Get()
|
||||
return unitRepository.Get()
|
||||
.Include(t => t.UnitValues)
|
||||
.ThenInclude(uv => uv.Field)
|
||||
.Include(t => t.UnitValues)
|
||||
@@ -442,7 +427,6 @@ namespace PARR.AIHITMainSyncer.Services
|
||||
replacement.FieldName, currentValue, replacement.NewValue);
|
||||
properties[replacement.FieldName] = replacement.NewValue;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace PARR.Core.Services.MatchingStatusService
|
||||
case SyncTaskEntityTypeEnum.Template:
|
||||
throw new Exception("Нет реализации получения кэша статуса matching для template");
|
||||
//todo: У нас вообще нет matching для Template, позже надо удалить это вообще из Enum SyncTaskEntityTypeEnum
|
||||
break;
|
||||
//break;
|
||||
}
|
||||
|
||||
// Если списки пустые, мало ли, говорим что нет matching`a
|
||||
|
||||
@@ -1,29 +1,12 @@
|
||||
using Elastic.CommonSchema.Serilog;
|
||||
using PARR.Core;
|
||||
using PARR.DAL;
|
||||
using PARR.EsppApi;
|
||||
using PARR.Infrastructure;
|
||||
using PARR.TemplateMatcher;
|
||||
using PARR.Test;
|
||||
using PARR.Test.NextRun;
|
||||
using Serilog;
|
||||
|
||||
/*IHost host = Host.CreateDefaultBuilder(args)
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
//services.InstallEsppApiServices(hostContext.Configuration);
|
||||
//services.InstallBllServices(hostContext.Configuration);
|
||||
services.InstallDalServices(hostContext.Configuration);
|
||||
//services.InstallTemplateDistributorSerivces(hostContext.Configuration);
|
||||
|
||||
services.AddHostedService<Worker>();
|
||||
})
|
||||
.UseSerilog((hostContext, services, config) =>
|
||||
{
|
||||
config
|
||||
.WriteTo.Console()
|
||||
.ReadFrom.Configuration(hostContext.Configuration);
|
||||
})
|
||||
.Build();*/
|
||||
var builder = Host.CreateApplicationBuilder();
|
||||
|
||||
builder.Services.AddLogging(config =>
|
||||
@@ -42,12 +25,17 @@ builder.Services.AddLogging(config =>
|
||||
config.AddSerilog(logger.CreateLogger());
|
||||
});
|
||||
|
||||
builder.Services.AddDalServices(builder.Configuration);
|
||||
builder.Services.AddCoreServices(builder.Configuration);
|
||||
builder.Services.AddInfrastructureServices(builder.Configuration);
|
||||
|
||||
builder.Services.InstallTemplateMatcherSerivces(builder.Configuration);
|
||||
// builder.Services.AddDalServices(builder.Configuration);
|
||||
// builder.Services.AddCoreServices(builder.Configuration);
|
||||
// builder.Services.AddInfrastructureServices(builder.Configuration);
|
||||
builder.Services.InstallEsppApiServices(builder.Configuration);
|
||||
|
||||
builder.Configuration.AddTemplateMatcherConfigurations(builder.Services);
|
||||
builder.Configuration.AddDalConfigurations(builder.Services);
|
||||
|
||||
builder.Services.AddTemplateMatcherSettings(builder.Configuration);
|
||||
builder.Services.AddDallSettings(builder.Configuration);
|
||||
|
||||
builder.Services.AddTransient<NextRunTest>();
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PARR.Core.Common.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Domain.Cache;
|
||||
using PARR.Domain.Entities.Base.History;
|
||||
using PARR.Domain.Enums;
|
||||
using PARR.EsppApi;
|
||||
using PARR.EsppApi.Constants;
|
||||
using PARR.EsppApi.Models.Query;
|
||||
using PARR.TemplateMatcher;
|
||||
using PARR.Test.NextRun;
|
||||
|
||||
namespace PARR.Test
|
||||
@@ -37,16 +41,11 @@ namespace PARR.Test
|
||||
//var bbb = aaa.ToOffset(new TimeSpan(3, 0, 0));
|
||||
|
||||
|
||||
#region NextRunTest
|
||||
await TemplateMatcherTest();
|
||||
|
||||
using (var scope = serviceProvider.CreateScope())
|
||||
{
|
||||
var service = scope.ServiceProvider.GetService<NextRunTest>();
|
||||
|
||||
await service!.Test();
|
||||
}
|
||||
|
||||
#endregion
|
||||
//await NextRunTest();
|
||||
|
||||
|
||||
#region test
|
||||
@@ -161,21 +160,46 @@ namespace PARR.Test
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
////test interval
|
||||
//await intervalService.IntervalInitAsync(async () =>
|
||||
//{
|
||||
// _logger.LogInformation("---Выполняю метод---");
|
||||
// await Task.Delay(29*1000);
|
||||
// _logger.LogInformation("===Выполнил метод===");
|
||||
//}, new TimeSpan(0, 0, 30));
|
||||
|
||||
|
||||
|
||||
//await TestEsppApi();
|
||||
//await StopDiscontinuedOrdersInEspp();
|
||||
}
|
||||
|
||||
#region TemplateMatcher
|
||||
private async Task TemplateMatcherTest()
|
||||
{
|
||||
await using var scope = serviceProvider.CreateAsyncScope();
|
||||
|
||||
var templateMatcher = scope.ServiceProvider.GetRequiredService<ITemplateMatcher>();
|
||||
var groupRepository = scope.ServiceProvider.GetRequiredService<IJobGroupRepository>();
|
||||
|
||||
var groups = await groupRepository.Get().AsNoTracking()
|
||||
.Where(t => t.GroupType != null && t.GroupType.Code == JobGroupTypesEnum.Group)
|
||||
.Select(t => t.Id).ToHashSetAsync();
|
||||
|
||||
foreach (var groupId in groups)
|
||||
{
|
||||
await templateMatcher.SyncTemplatesForJobGroup(groupId,
|
||||
new HistoryInitiator
|
||||
{
|
||||
InitiatorIp = "10.99.246.156",
|
||||
InitiatorParrComponentId = ParrComponentsEnum.Master,
|
||||
InitiatorComment = "Тестовый проект. Синхронизация Группированых работ"
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private async Task NextRunTest()
|
||||
{
|
||||
using (var scope = serviceProvider.CreateScope())
|
||||
{
|
||||
var service = scope.ServiceProvider.GetService<NextRunTest>();
|
||||
|
||||
await service!.Test();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TestEsppApi()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user