feat(shortcodesService): добавлен шорткод %РОД_ЭК_ПЕРВЫЙ:ОУ-*:КРАТКОЕ_НАИМЕНОВАНИЕ%. - Позволяет получать значения атрибутов родительских ЭК.
- Родитель определяется по маске, выбирается первый найденный, отсторитрованный по алфавиту - Имя атрибута определяется по полю AihitName
This commit is contained in:
@@ -109,6 +109,7 @@ namespace PARR.Core
|
||||
services.AddScoped<IShortcodeHandler, FieldShortcodeHandler>();
|
||||
services.AddScoped<IShortcodeHandler, TagShortcodeHandler>();
|
||||
services.AddScoped<IShortcodeHandler, RelatedUnitTagShortcodeHandler>();
|
||||
services.AddScoped<IShortcodeHandler, ParentUnitFirstShortcodeHandler>();
|
||||
|
||||
services.AddScoped<IShortcodesService, ShortcodesService>();
|
||||
#endregion
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
UnitName = 16,
|
||||
UnitsInTemplate = 32,
|
||||
UnitTags = 64,
|
||||
UnitsInTemplateTags = 128
|
||||
UnitsInTemplateTags = 128,
|
||||
ParentUnits = 256
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Core.Services.Shortcodes.Enums;
|
||||
using PARR.Core.Services.Shortcodes.Models;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PARR.Core.Services.Shortcodes.Handlers;
|
||||
|
||||
internal class ParentUnitFirstShortcodeHandler : IShortcodeHandler
|
||||
{
|
||||
private readonly IUnitInValueRepository unitInValueRepo;
|
||||
private readonly ILogger<ParentUnitFirstShortcodeHandler> logger;
|
||||
|
||||
public int Order => 55; // После RelationshipUnitShortcodeHandler
|
||||
|
||||
// Паттерн: %РОД_ЭК_ПЕРВЫЙ:МАСКА:ИМЯ_АТРИБУТА%
|
||||
private static readonly Regex _pattern = new(
|
||||
@"%РОД_ЭК_ПЕРВЫЙ:([^:]+):([^%]+)%",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public Regex Pattern => _pattern;
|
||||
|
||||
public ShortcodeDataRequirementsEnum Requirements => ShortcodeDataRequirementsEnum.ParentUnits;
|
||||
|
||||
public ParentUnitFirstShortcodeHandler(
|
||||
IUnitInValueRepository unitInValueRepo,
|
||||
ILogger<ParentUnitFirstShortcodeHandler> logger)
|
||||
{
|
||||
this.unitInValueRepo = unitInValueRepo;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public async Task<string> ResolveAsync(string input, TemplateForShortcode template, string caller, CancellationToken ct = default)
|
||||
{
|
||||
var matches = Pattern.Matches(input);
|
||||
if (matches.Count == 0) return input;
|
||||
|
||||
// Группируем уникальные запросы, чтобы не делать повторные БД-запросы для одинаковых шорткодов в одной строке
|
||||
var uniqueRequests = matches.Cast<Match>()
|
||||
.Select(m => new
|
||||
{
|
||||
FullMatch = m.Value,
|
||||
Mask = m.Groups[1].Value.Trim(),
|
||||
FieldName = m.Groups[2].Value.Trim()
|
||||
})
|
||||
.GroupBy(x => new { x.Mask, x.FieldName })
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
|
||||
var resolved = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var request in uniqueRequests)
|
||||
{
|
||||
var value = await GetParentUnitFieldValueAsync(template, request.Mask, request.FieldName, caller, ct);
|
||||
resolved[request.FullMatch] = value;
|
||||
}
|
||||
|
||||
var result = input;
|
||||
foreach (Match m in matches)
|
||||
{
|
||||
if (resolved.TryGetValue(m.Value, out var val))
|
||||
result = result.Replace(m.Value, val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<string> GetParentUnitFieldValueAsync(
|
||||
TemplateForShortcode template,
|
||||
string mask,
|
||||
string fieldName,
|
||||
string caller,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// 1. Проверяем наличие родителей в контексте
|
||||
if (template.ParentUnits == null || template.ParentUnits.Count == 0)
|
||||
{
|
||||
logger.LogDebug("[{Caller}] Родителей нет. Возвращаю 'Нет данных'.", caller);
|
||||
return "Нет данных";
|
||||
}
|
||||
|
||||
// 2. Ищем ПЕРВОГО родителя, подходящего под маску
|
||||
var matchingParent = template.ParentUnits
|
||||
.FirstOrDefault(p => MatchesMask(p.UnitName, mask));
|
||||
|
||||
if (matchingParent == null)
|
||||
{
|
||||
logger.LogDebug("[{Caller}] Не найден родитель по маске {Mask}. Возвращаю 'Нет данных'.", caller, mask);
|
||||
return "Нет данных";
|
||||
}
|
||||
|
||||
// 3. Запрашиваем значение атрибута у найденного родителя
|
||||
var fieldValue = await unitInValueRepo.Get()
|
||||
.AsNoTracking()
|
||||
.Include(uv => uv.Field)
|
||||
.Include(uv => uv.Value)
|
||||
.Where(uv => uv.UnitId == matchingParent.UnitId &&
|
||||
uv.Field!.AihitName == fieldName &&
|
||||
uv.Value != null &&
|
||||
!string.IsNullOrEmpty(uv.Value.Value))
|
||||
.Select(uv => uv.Value!.Value)
|
||||
.FirstOrDefaultAsync(ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return fieldValue ?? "Нет данных";
|
||||
}
|
||||
|
||||
private bool MatchesMask(string value, string mask)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value) || string.IsNullOrEmpty(mask))
|
||||
return false;
|
||||
|
||||
// Поддержка wildcard *
|
||||
if (mask.Contains('*'))
|
||||
{
|
||||
// Экранируем спецсимволы regex, кроме *, который заменяем на .*
|
||||
var regexPattern = "^" + Regex.Escape(mask).Replace("\\*", ".*") + "$";
|
||||
return Regex.IsMatch(value, regexPattern, RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
// Точное совпадение
|
||||
return string.Equals(value, mask, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace PARR.Core.Services.Shortcodes.Models
|
||||
{
|
||||
public record ParentUnitForShortcode(
|
||||
Guid UnitId,
|
||||
string UnitName
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
JobForShortcode? Job,
|
||||
List<UnitInTemplateForShortcode> UnitsInTemplate,
|
||||
List<string> UnitTags,
|
||||
Dictionary<Guid, List<string>> RelatedUnitTags
|
||||
Dictionary<Guid, List<string>> RelatedUnitTags,
|
||||
List<ParentUnitForShortcode> ParentUnits
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,34 +25,37 @@ internal class ShortcodesService : IShortcodesService
|
||||
|
||||
private const int MaxIterations = 3;
|
||||
|
||||
private readonly ILogger<ShortcodesService> logger;
|
||||
private readonly IEnumerable<IShortcodeHandler> handlers;
|
||||
private readonly IRedisCacheService cacheService;
|
||||
private readonly SettingsFromDb settingsFromDb;
|
||||
private readonly IJobRepository jobService;
|
||||
private readonly IUnitRepository unitService;
|
||||
private readonly IUnitFieldRepository unitFieldRepo;
|
||||
private readonly ITemplateRepository templateRepo;
|
||||
private readonly ILogger<ShortcodesService> _logger;
|
||||
private readonly IEnumerable<IShortcodeHandler> _handlers;
|
||||
private readonly IRedisCacheService _cacheService;
|
||||
private readonly SettingsFromDb _settingsFromDb;
|
||||
private readonly IJobRepository _jobService;
|
||||
private readonly IUnitRepository _unitRepo;
|
||||
private readonly IUnitFieldRepository _unitFieldRepo;
|
||||
private readonly IUnitInUnitRepository _unitInUnitRepo;
|
||||
private readonly ITemplateRepository _templateRepo;
|
||||
|
||||
public ShortcodesService(
|
||||
ILogger<ShortcodesService> logger,
|
||||
IEnumerable<IShortcodeHandler> handlers,
|
||||
IRedisCacheService cacheService,
|
||||
SettingsFromDb settingsFromDb,
|
||||
IJobRepository jobService,
|
||||
IUnitRepository unitService,
|
||||
IUnitFieldRepository unitFieldService,
|
||||
IJobRepository jobRepo,
|
||||
IUnitRepository unitRepo,
|
||||
IUnitFieldRepository unitFieldRepo,
|
||||
IUnitInUnitRepository unitInUnitRepo,
|
||||
ITemplateRepository templateRepo
|
||||
)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.handlers = handlers.OrderBy(h => h.Order).ToList();
|
||||
this.cacheService = cacheService;
|
||||
this.settingsFromDb = settingsFromDb;
|
||||
this.jobService = jobService;
|
||||
this.unitService = unitService;
|
||||
this.unitFieldRepo = unitFieldService;
|
||||
this.templateRepo = templateRepo;
|
||||
this._logger = logger;
|
||||
this._handlers = handlers.OrderBy(h => h.Order).ToList();
|
||||
this._cacheService = cacheService;
|
||||
this._settingsFromDb = settingsFromDb;
|
||||
this._jobService = jobRepo;
|
||||
this._unitRepo = unitRepo;
|
||||
this._unitFieldRepo = unitFieldRepo;
|
||||
this._unitInUnitRepo = unitInUnitRepo;
|
||||
this._templateRepo = templateRepo;
|
||||
}
|
||||
|
||||
public async Task<string> ApplyShortcodesAsync(string str, Template template, [CallerMemberName] string? caller = null)
|
||||
@@ -61,8 +64,8 @@ internal class ShortcodesService : IShortcodesService
|
||||
var callerName = caller ?? "Unknown";
|
||||
|
||||
// Кэш результата (проверяем один раз на входе)
|
||||
var cacheKey = cacheService.GetKey(new[] { "shortcodes", "result", template.Id.ToString("N") }, new[] { str });
|
||||
var cachedResult = await cacheService.GetCachedDataAsync<ShortcodesResult>(cacheKey).ConfigureAwait(false);
|
||||
var cacheKey = _cacheService.GetKey(new[] { "shortcodes", "result", template.Id.ToString("N") }, new[] { str });
|
||||
var cachedResult = await _cacheService.GetCachedDataAsync<ShortcodesResult>(cacheKey).ConfigureAwait(false);
|
||||
if (cachedResult != null) return cachedResult.Data.Result;
|
||||
|
||||
// Начальный контекст
|
||||
@@ -83,7 +86,8 @@ internal class ShortcodesService : IShortcodesService
|
||||
Job: initialJob,
|
||||
UnitsInTemplate: initialUnitsInTemplate,
|
||||
UnitTags: new List<string>(),
|
||||
RelatedUnitTags: new Dictionary<Guid, List<string>>()
|
||||
RelatedUnitTags: new Dictionary<Guid, List<string>>(),
|
||||
ParentUnits: new List<ParentUnitForShortcode>()
|
||||
);
|
||||
|
||||
var result = str;
|
||||
@@ -92,7 +96,7 @@ internal class ShortcodesService : IShortcodesService
|
||||
while (iteration < MaxIterations)
|
||||
{
|
||||
// 1. Находим хендлеры, которые сработают для ТЕКУЩЕЙ версии строки
|
||||
var matchingHandlers = handlers
|
||||
var matchingHandlers = _handlers
|
||||
.Where(h => h.Pattern.IsMatch(result))
|
||||
.ToList();
|
||||
|
||||
@@ -121,13 +125,13 @@ internal class ShortcodesService : IShortcodesService
|
||||
|
||||
if (iteration == MaxIterations && result != str)
|
||||
{
|
||||
logger.LogWarning("[{Caller}] Достигнут лимит итераций для шаблона {TemplateId}. Возможна циклическая зависимость.", callerName, template.Id);
|
||||
_logger.LogWarning("[{Caller}] Достигнут лимит итераций для шаблона {TemplateId}. Возможна циклическая зависимость.", callerName, template.Id);
|
||||
}
|
||||
|
||||
// Сохранение в кэш
|
||||
if (!string.IsNullOrEmpty(result) && result != str && template.Id != Guid.Empty)
|
||||
{
|
||||
await cacheService.SetCachedDataAsync(cacheKey, new ShortcodesResult
|
||||
await _cacheService.SetCachedDataAsync(cacheKey, new ShortcodesResult
|
||||
{
|
||||
Data = new ShortcodesResultDto { Result = result },
|
||||
Timestamp = DateTimeOffset.UtcNow,
|
||||
@@ -149,8 +153,8 @@ internal class ShortcodesService : IShortcodesService
|
||||
if (string.IsNullOrEmpty(unitName) && requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitName))
|
||||
{
|
||||
// Логирование и загрузка только если нужно и еще не загружено
|
||||
logger.LogWarning(MissingUnitNameWarningMsg, caller, template.Id);
|
||||
var unit = await unitService.Get().AsNoTracking().FirstOrDefaultAsync(u => u.Id == template.UnitId).ConfigureAwait(false);
|
||||
_logger.LogWarning(MissingUnitNameWarningMsg, caller, template.Id);
|
||||
var unit = await _unitRepo.Get().AsNoTracking().FirstOrDefaultAsync(u => u.Id == template.UnitId).ConfigureAwait(false);
|
||||
unitName = unit?.Name ?? throw new InvalidOperationException($"Unit {template.UnitId} не найден.");
|
||||
}
|
||||
|
||||
@@ -181,7 +185,7 @@ internal class ShortcodesService : IShortcodesService
|
||||
if (requirements.HasFlag(ShortcodeDataRequirementsEnum.JobTnk)) missing.Add("Tnk");
|
||||
|
||||
// Логируем только если реально пошли в БД
|
||||
logger.LogWarning(
|
||||
_logger.LogWarning(
|
||||
"[{Caller}] Шаблон {TemplateId} требует догрузки данных ({Missing}). Данные догружены.",
|
||||
caller, template.Id, string.Join(", ", missing));
|
||||
|
||||
@@ -193,11 +197,11 @@ internal class ShortcodesService : IShortcodesService
|
||||
if ((unitsInTemplate == null || unitsInTemplate.Count == 0) &&
|
||||
requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitsInTemplate))
|
||||
{
|
||||
logger.LogWarning(
|
||||
_logger.LogWarning(
|
||||
"[{Caller}] Шаблон {TemplateId} передан без UnitsInTemplate. Данные догружены автоматически.",
|
||||
caller, template.Id);
|
||||
|
||||
var fullTemplate = await templateRepo.Get()
|
||||
var fullTemplate = await _templateRepo.Get()
|
||||
.AsNoTracking()
|
||||
.Include(t => t.UnitsInTemplate)
|
||||
.FirstOrDefaultAsync(t => t.Id == template.Id)
|
||||
@@ -214,11 +218,11 @@ internal class ShortcodesService : IShortcodesService
|
||||
if ((unitTags == null || unitTags.Count == 0) &&
|
||||
requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitTags))
|
||||
{
|
||||
logger.LogWarning(
|
||||
_logger.LogWarning(
|
||||
"[{Caller}] Шаблон {TemplateId} требует теги (UnitTags). Данные догружены автоматически.",
|
||||
caller, template.Id);
|
||||
|
||||
unitTags = await unitService.Get()
|
||||
unitTags = await _unitRepo.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Id == template.UnitId)
|
||||
.SelectMany(u => u.UnitValues
|
||||
@@ -252,11 +256,11 @@ internal class ShortcodesService : IShortcodesService
|
||||
requirements.HasFlag(ShortcodeDataRequirementsEnum.UnitsInTemplateTags) &&
|
||||
relatedUnitIds.Count > 0)
|
||||
{
|
||||
logger.LogWarning(
|
||||
_logger.LogWarning(
|
||||
"[{Caller}] Шаблон {TemplateId} требует теги связанных ЭК (UnitsInTemplateTags). Данные догружены автоматически.",
|
||||
caller, template.Id);
|
||||
|
||||
var rawTags = await unitService.Get()
|
||||
var rawTags = await _unitRepo.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => relatedUnitIds.Contains(u.Id))
|
||||
.SelectMany(u => u.UnitValues
|
||||
@@ -273,21 +277,56 @@ internal class ShortcodesService : IShortcodesService
|
||||
.ToDictionary(g => g.Key, g => g.Select(x => x.Tag).ToList());
|
||||
}
|
||||
|
||||
// 6. Возвращаем обновленный рекорд
|
||||
// 6. Проверка ParentUnits (Только непосредственные родители)
|
||||
var parentUnits = currentData.ParentUnits;
|
||||
if ((parentUnits == null || parentUnits.Count == 0) &&
|
||||
requirements.HasFlag(ShortcodeDataRequirementsEnum.ParentUnits))
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"[{Caller}] Шаблон {TemplateId} требует данные о родителях (ParentUnits). Данные догружены автоматически.",
|
||||
caller, template.Id);
|
||||
|
||||
// Получаем ID всех непосредственных родителей текущего ЭК
|
||||
var parentIds = await _unitInUnitRepo.Get()
|
||||
.AsNoTracking()
|
||||
.Where(link => link.ChildUnitId == template.UnitId)
|
||||
.Select(link => link.ParentUnitId)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (parentIds.Count > 0)
|
||||
{
|
||||
// Получаем имена этих родителей одним запросом
|
||||
parentUnits = await _unitRepo.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => parentIds.Contains(u.Id))
|
||||
.Select(u => new ParentUnitForShortcode(u.Id, u.Name))
|
||||
.OrderBy(u => u.UnitName)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
parentUnits = new List<ParentUnitForShortcode>();
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Возвращаем обновленный рекорд
|
||||
return currentData with
|
||||
{
|
||||
UnitName = unitName ?? string.Empty,
|
||||
Job = jobData,
|
||||
UnitsInTemplate = unitsInTemplate ?? currentData.UnitsInTemplate ?? new List<UnitInTemplateForShortcode>(),
|
||||
UnitTags = unitTags ?? currentData.UnitTags,
|
||||
RelatedUnitTags = relatedUnitTags ?? currentData.RelatedUnitTags
|
||||
UnitTags = unitTags ?? currentData.UnitTags ?? new List<string>(),
|
||||
RelatedUnitTags = relatedUnitTags ?? currentData.RelatedUnitTags ?? new Dictionary<Guid, List<string>>(),
|
||||
ParentUnits = parentUnits ?? currentData.ParentUnits ?? new List<ParentUnitForShortcode>()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private async Task<JobForShortcode> LoadFullJobAsync(Guid jobId, string caller)
|
||||
{
|
||||
var job = await jobService.Get().AsNoTracking().AsSingleQuery()
|
||||
var job = await _jobService.Get().AsNoTracking().AsSingleQuery()
|
||||
.Include(j => j.Group).ThenInclude(g => g!.GroupType)
|
||||
.Include(j => j.Tnk)
|
||||
.FirstOrDefaultAsync(j => j.Id == jobId)
|
||||
@@ -323,7 +362,7 @@ internal class ShortcodesService : IShortcodesService
|
||||
{
|
||||
var result = new List<ShortcodeInfoDto>();
|
||||
|
||||
foreach (var constant in settingsFromDb.TemplateNameConstantPartsList)
|
||||
foreach (var constant in _settingsFromDb.TemplateNameConstantPartsList)
|
||||
{
|
||||
result.Add(new ShortcodeInfoDto
|
||||
{
|
||||
@@ -367,7 +406,14 @@ internal class ShortcodesService : IShortcodesService
|
||||
new ShortcodeInfoDto { Shortcode = "%СВЯЗИ-ПН%", Description = "Связанные ЭК с нумерацией", Type = ShortcodeTypeEnum.Relationship }
|
||||
});
|
||||
|
||||
var fieldNames = await unitFieldRepo.Get().AsNoTracking().Select(t => new { t.AihitName, t.DisplayName }).ToListAsync().ConfigureAwait(false);
|
||||
result.Add(new ShortcodeInfoDto
|
||||
{
|
||||
Shortcode = "%РОД_ЭК_ПЕРВЫЙ:МАСКА:ИМЯ_АТРИБУТА%",
|
||||
Description = "Значение атрибута первого родительского ЭК, подходящего под маску (например, %РОД_ЭК_ПЕРВЫЙ:ОУ-*:КРАТКОЕ_НАИМЕНОВАНИЕ%)",
|
||||
Type = ShortcodeTypeEnum.Relationship
|
||||
});
|
||||
|
||||
var fieldNames = await _unitFieldRepo.Get().AsNoTracking().Select(t => new { t.AihitName, t.DisplayName }).ToListAsync().ConfigureAwait(false);
|
||||
foreach (var fieldName in fieldNames.OrderBy(n => n.AihitName))
|
||||
{
|
||||
result.Add(new ShortcodeInfoDto
|
||||
@@ -380,4 +426,62 @@ internal class ShortcodesService : IShortcodesService
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<List<ParentUnitForShortcode>> LoadParentUnitsHierarchyAsync(Guid unitId, string caller)
|
||||
{
|
||||
var result = new List<ParentUnitForShortcode>();
|
||||
var visited = new HashSet<Guid>();
|
||||
var currentUnitId = unitId;
|
||||
var maxDepth = 100; // Защита от циклических зависимостей
|
||||
var depth = 0;
|
||||
|
||||
while (depth < maxDepth)
|
||||
{
|
||||
// Получаем всех родителей текущего юнита
|
||||
var parentIds = await _unitInUnitRepo.Get()
|
||||
.AsNoTracking()
|
||||
.Where(link => link.ChildUnitId == currentUnitId)
|
||||
.Select(link => link.ParentUnitId)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (parentIds.Count == 0)
|
||||
break; // Достигли корня
|
||||
|
||||
// Берем первого родителя (если их несколько)
|
||||
var parentId = parentIds.First();
|
||||
|
||||
// Защита от циклов
|
||||
if (visited.Contains(parentId))
|
||||
{
|
||||
_logger.LogWarning("[{Caller}] Обнаружена циклическая зависимость в иерархии юнитов для UnitId {UnitId}",
|
||||
caller, unitId);
|
||||
break;
|
||||
}
|
||||
|
||||
visited.Add(parentId);
|
||||
|
||||
// Получаем информацию о родителе
|
||||
var parentUnit = await _unitRepo.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => u.Id == parentId)
|
||||
.Select(u => new ParentUnitForShortcode(u.Id, u.Name))
|
||||
.FirstOrDefaultAsync()
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (parentUnit != null)
|
||||
{
|
||||
result.Add(parentUnit);
|
||||
currentUnitId = parentId;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
depth++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user