Enhance query performance by applying AsSplitQuery to multiple handlers and loaders for Channels and ScheduleTemplates, improving efficiency in data retrieval and reducing unnecessary data duplication in queries.
ci / build-backend (push) Successful in 1m26s
ci / build-frontend (push) Successful in 49s
ci / tests (push) Successful in 4m18s
ci / sonar (push) Failing after 2m31s

This commit is contained in:
Leonid Pershin
2026-07-26 21:50:01 +03:00
parent b52080e026
commit c4c93cff02
14 changed files with 25 additions and 0 deletions
@@ -60,6 +60,10 @@ public sealed class GridScheduleGenerator(
var channel = await dbContext
.Channels.Include(c => c.BumperTemplates)
.ThenInclude(t => t.Variants)
// Вложенные коллекции тянем отдельными запросами: иначе колонки родителя (у шаблона —
// jsonb с правилами, у слоя — jsonb применимости) приезжают по копии на каждую строку
// листа. Тик планировщика повторяет эти два запроса на каждый канал.
.AsSplitQuery()
.FirstOrDefaultAsync(c => c.Id == channelId, cancellationToken);
if (channel is null || !channel.IsEnabled || channel.TemplateId is null)
return new GenerationReport(0, [], ChannelSkipped: true);
@@ -70,6 +74,7 @@ public sealed class GridScheduleGenerator(
var template = await dbContext
.ScheduleTemplates.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.AsSplitQuery()
.FirstOrDefaultAsync(t => t.Id == channel.TemplateId, cancellationToken);
if (template is null)
return new GenerationReport(0, [], ChannelSkipped: true);
@@ -174,6 +179,7 @@ public sealed class GridScheduleGenerator(
.Channels.AsNoTracking()
.Include(c => c.BumperTemplates)
.ThenInclude(t => t.Variants)
.AsSplitQuery()
.FirstOrDefaultAsync(c => c.Id == channelId, cancellationToken);
if (channel is null || channel.TemplateId is null)
return null;
@@ -182,6 +188,7 @@ public sealed class GridScheduleGenerator(
.ScheduleTemplates.AsNoTracking()
.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.AsSplitQuery()
.FirstOrDefaultAsync(t => t.Id == channel.TemplateId, cancellationToken);
if (template is null)
return null;
@@ -25,6 +25,7 @@ public sealed class CopyTemplateCommandHandler(IAppDbContext dbContext)
.ScheduleTemplates.AsNoTracking()
.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.AsSplitQuery()
.FirstOrDefaultAsync(t => t.ChannelId == command.SourceChannelId, cancellationToken);
if (source is null)
return Result.Failure<CopyTemplateResultDto>(ChannelErrors.TemplateNotFound);
@@ -24,6 +24,7 @@ public sealed class GetChannelTemplateQueryHandler(IAppDbContext dbContext)
.ScheduleTemplates.AsNoTracking()
.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.AsSplitQuery()
.FirstOrDefaultAsync(t => t.ChannelId == channel.Id, cancellationToken);
if (template is null)
return Result.Failure<ScheduleTemplateDto>(ChannelErrors.TemplateNotFound);
@@ -109,6 +109,7 @@ internal static class LayerLoader
dbContext
.ScheduleTemplates.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.AsSplitQuery()
.FirstOrDefaultAsync(t => t.Id == templateId, cancellationToken);
public static Task<ScheduleTemplate?> ByLayerAsync(
@@ -119,5 +120,6 @@ internal static class LayerLoader
dbContext
.ScheduleTemplates.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.AsSplitQuery()
.FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken);
}
@@ -89,6 +89,7 @@ public sealed class SlotWriter(IAppDbContext dbContext)
dbContext
.ScheduleTemplates.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.AsSplitQuery()
.FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken);
/// <summary>Загружает шаблон по слоту.</summary>
@@ -99,6 +100,7 @@ public sealed class SlotWriter(IAppDbContext dbContext)
dbContext
.ScheduleTemplates.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.AsSplitQuery()
.FirstOrDefaultAsync(
t => t.Layers.Any(l => l.Slots.Any(s => s.Id == slotId)),
cancellationToken
@@ -28,6 +28,7 @@ public sealed class ValidateTemplateQueryHandler(IAppDbContext dbContext)
.ScheduleTemplates.AsNoTracking()
.Include(t => t.Layers)
.ThenInclude(l => l.Slots)
.AsSplitQuery()
.FirstOrDefaultAsync(t => t.ChannelId == channel.Id, cancellationToken);
if (template is null)
return Result.Failure<IReadOnlyList<TemplateIssueDto>>(ChannelErrors.TemplateNotFound);