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.
This commit is contained in:
+1
@@ -16,6 +16,7 @@ public sealed class AddBumperTextVariantCommandHandler(IAppDbContext dbContext)
|
|||||||
var channel = await dbContext
|
var channel = await dbContext
|
||||||
.Channels.Include(c => c.BumperTemplates)
|
.Channels.Include(c => c.BumperTemplates)
|
||||||
.ThenInclude(t => t.Variants)
|
.ThenInclude(t => t.Variants)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(c => c.Id == command.ChannelId, cancellationToken);
|
.FirstOrDefaultAsync(c => c.Id == command.ChannelId, cancellationToken);
|
||||||
if (channel is null)
|
if (channel is null)
|
||||||
return Result.Failure<Guid>(ChannelErrors.NotFound);
|
return Result.Failure<Guid>(ChannelErrors.NotFound);
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ public sealed class BumperSpecLoader(
|
|||||||
.Channels.AsNoTracking()
|
.Channels.AsNoTracking()
|
||||||
.Include(c => c.BumperTemplates)
|
.Include(c => c.BumperTemplates)
|
||||||
.ThenInclude(t => t.Variants)
|
.ThenInclude(t => t.Variants)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(c => c.Id == cache.ChannelId, cancellationToken);
|
.FirstOrDefaultAsync(c => c.Id == cache.ChannelId, cancellationToken);
|
||||||
var template = channel?.BumperTemplates.FirstOrDefault(t => t.Id == cache.TemplateId);
|
var template = channel?.BumperTemplates.FirstOrDefault(t => t.Id == cache.TemplateId);
|
||||||
var variant = template?.Variants.FirstOrDefault(v => v.Id == cache.VariantId);
|
var variant = template?.Variants.FirstOrDefault(v => v.Id == cache.VariantId);
|
||||||
|
|||||||
+1
@@ -16,6 +16,7 @@ public sealed class RemoveBumperTextVariantCommandHandler(IAppDbContext dbContex
|
|||||||
var channel = await dbContext
|
var channel = await dbContext
|
||||||
.Channels.Include(c => c.BumperTemplates)
|
.Channels.Include(c => c.BumperTemplates)
|
||||||
.ThenInclude(t => t.Variants)
|
.ThenInclude(t => t.Variants)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(c => c.Id == command.ChannelId, cancellationToken);
|
.FirstOrDefaultAsync(c => c.Id == command.ChannelId, cancellationToken);
|
||||||
if (channel is null)
|
if (channel is null)
|
||||||
return Result.Failure(ChannelErrors.NotFound);
|
return Result.Failure(ChannelErrors.NotFound);
|
||||||
|
|||||||
+1
@@ -33,6 +33,7 @@ public sealed class RenderBumperPreviewCommandHandler(
|
|||||||
.Channels.AsNoTracking()
|
.Channels.AsNoTracking()
|
||||||
.Include(c => c.BumperTemplates)
|
.Include(c => c.BumperTemplates)
|
||||||
.ThenInclude(t => t.Variants)
|
.ThenInclude(t => t.Variants)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(c => c.Id == query.ChannelId, cancellationToken);
|
.FirstOrDefaultAsync(c => c.Id == query.ChannelId, cancellationToken);
|
||||||
if (channel is null)
|
if (channel is null)
|
||||||
return Result.Failure(ChannelErrors.NotFound);
|
return Result.Failure(ChannelErrors.NotFound);
|
||||||
|
|||||||
+1
@@ -16,6 +16,7 @@ public sealed class UpdateBumperTextVariantCommandHandler(IAppDbContext dbContex
|
|||||||
var channel = await dbContext
|
var channel = await dbContext
|
||||||
.Channels.Include(c => c.BumperTemplates)
|
.Channels.Include(c => c.BumperTemplates)
|
||||||
.ThenInclude(t => t.Variants)
|
.ThenInclude(t => t.Variants)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(c => c.Id == command.ChannelId, cancellationToken);
|
.FirstOrDefaultAsync(c => c.Id == command.ChannelId, cancellationToken);
|
||||||
if (channel is null)
|
if (channel is null)
|
||||||
return Result.Failure(ChannelErrors.NotFound);
|
return Result.Failure(ChannelErrors.NotFound);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public sealed class GetChannelQueryHandler(IAppDbContext dbContext)
|
|||||||
.Channels.AsNoTracking()
|
.Channels.AsNoTracking()
|
||||||
.Include(c => c.BumperTemplates)
|
.Include(c => c.BumperTemplates)
|
||||||
.ThenInclude(t => t.Variants)
|
.ThenInclude(t => t.Variants)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(c => c.Id == query.Id, cancellationToken);
|
.FirstOrDefaultAsync(c => c.Id == query.Id, cancellationToken);
|
||||||
if (channel is null)
|
if (channel is null)
|
||||||
return Result.Failure<ChannelDto>(ChannelErrors.NotFound);
|
return Result.Failure<ChannelDto>(ChannelErrors.NotFound);
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ public sealed class GetShowQueryHandler(IAppDbContext dbContext)
|
|||||||
.Shows.AsNoTracking()
|
.Shows.AsNoTracking()
|
||||||
.Include(s => s.Episodes)
|
.Include(s => s.Episodes)
|
||||||
.Include(s => s.Genres)
|
.Include(s => s.Genres)
|
||||||
|
// Сиблинги: серии × жанры перемножились бы в одном запросе (см. ListShowsQueryHandler).
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(s => s.Id == query.Id, cancellationToken);
|
.FirstOrDefaultAsync(s => s.Id == query.Id, cancellationToken);
|
||||||
if (show is null)
|
if (show is null)
|
||||||
return Result.Failure<ShowDto>(ShowErrors.NotFound);
|
return Result.Failure<ShowDto>(ShowErrors.NotFound);
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ public sealed class ListShowsQueryHandler(IAppDbContext dbContext)
|
|||||||
.Shows.AsNoTracking()
|
.Shows.AsNoTracking()
|
||||||
.Include(s => s.Episodes)
|
.Include(s => s.Episodes)
|
||||||
.Include(s => s.Genres)
|
.Include(s => s.Genres)
|
||||||
|
// Серии и жанры — сиблинги: одним запросом это честное перемножение (сериал на 200 серий
|
||||||
|
// с тремя жанрами даёт 600 строк вместо 203). Разделяем.
|
||||||
|
.AsSplitQuery()
|
||||||
.Where(s =>
|
.Where(s =>
|
||||||
query.Interstitials
|
query.Interstitials
|
||||||
? s.Kind == ShowKind.Interstitial
|
? s.Kind == ShowKind.Interstitial
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ public sealed class GridScheduleGenerator(
|
|||||||
var channel = await dbContext
|
var channel = await dbContext
|
||||||
.Channels.Include(c => c.BumperTemplates)
|
.Channels.Include(c => c.BumperTemplates)
|
||||||
.ThenInclude(t => t.Variants)
|
.ThenInclude(t => t.Variants)
|
||||||
|
// Вложенные коллекции тянем отдельными запросами: иначе колонки родителя (у шаблона —
|
||||||
|
// jsonb с правилами, у слоя — jsonb применимости) приезжают по копии на каждую строку
|
||||||
|
// листа. Тик планировщика повторяет эти два запроса на каждый канал.
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(c => c.Id == channelId, cancellationToken);
|
.FirstOrDefaultAsync(c => c.Id == channelId, cancellationToken);
|
||||||
if (channel is null || !channel.IsEnabled || channel.TemplateId is null)
|
if (channel is null || !channel.IsEnabled || channel.TemplateId is null)
|
||||||
return new GenerationReport(0, [], ChannelSkipped: true);
|
return new GenerationReport(0, [], ChannelSkipped: true);
|
||||||
@@ -70,6 +74,7 @@ public sealed class GridScheduleGenerator(
|
|||||||
var template = await dbContext
|
var template = await dbContext
|
||||||
.ScheduleTemplates.Include(t => t.Layers)
|
.ScheduleTemplates.Include(t => t.Layers)
|
||||||
.ThenInclude(l => l.Slots)
|
.ThenInclude(l => l.Slots)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(t => t.Id == channel.TemplateId, cancellationToken);
|
.FirstOrDefaultAsync(t => t.Id == channel.TemplateId, cancellationToken);
|
||||||
if (template is null)
|
if (template is null)
|
||||||
return new GenerationReport(0, [], ChannelSkipped: true);
|
return new GenerationReport(0, [], ChannelSkipped: true);
|
||||||
@@ -174,6 +179,7 @@ public sealed class GridScheduleGenerator(
|
|||||||
.Channels.AsNoTracking()
|
.Channels.AsNoTracking()
|
||||||
.Include(c => c.BumperTemplates)
|
.Include(c => c.BumperTemplates)
|
||||||
.ThenInclude(t => t.Variants)
|
.ThenInclude(t => t.Variants)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(c => c.Id == channelId, cancellationToken);
|
.FirstOrDefaultAsync(c => c.Id == channelId, cancellationToken);
|
||||||
if (channel is null || channel.TemplateId is null)
|
if (channel is null || channel.TemplateId is null)
|
||||||
return null;
|
return null;
|
||||||
@@ -182,6 +188,7 @@ public sealed class GridScheduleGenerator(
|
|||||||
.ScheduleTemplates.AsNoTracking()
|
.ScheduleTemplates.AsNoTracking()
|
||||||
.Include(t => t.Layers)
|
.Include(t => t.Layers)
|
||||||
.ThenInclude(l => l.Slots)
|
.ThenInclude(l => l.Slots)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(t => t.Id == channel.TemplateId, cancellationToken);
|
.FirstOrDefaultAsync(t => t.Id == channel.TemplateId, cancellationToken);
|
||||||
if (template is null)
|
if (template is null)
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
+1
@@ -25,6 +25,7 @@ public sealed class CopyTemplateCommandHandler(IAppDbContext dbContext)
|
|||||||
.ScheduleTemplates.AsNoTracking()
|
.ScheduleTemplates.AsNoTracking()
|
||||||
.Include(t => t.Layers)
|
.Include(t => t.Layers)
|
||||||
.ThenInclude(l => l.Slots)
|
.ThenInclude(l => l.Slots)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(t => t.ChannelId == command.SourceChannelId, cancellationToken);
|
.FirstOrDefaultAsync(t => t.ChannelId == command.SourceChannelId, cancellationToken);
|
||||||
if (source is null)
|
if (source is null)
|
||||||
return Result.Failure<CopyTemplateResultDto>(ChannelErrors.TemplateNotFound);
|
return Result.Failure<CopyTemplateResultDto>(ChannelErrors.TemplateNotFound);
|
||||||
|
|||||||
+1
@@ -24,6 +24,7 @@ public sealed class GetChannelTemplateQueryHandler(IAppDbContext dbContext)
|
|||||||
.ScheduleTemplates.AsNoTracking()
|
.ScheduleTemplates.AsNoTracking()
|
||||||
.Include(t => t.Layers)
|
.Include(t => t.Layers)
|
||||||
.ThenInclude(l => l.Slots)
|
.ThenInclude(l => l.Slots)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(t => t.ChannelId == channel.Id, cancellationToken);
|
.FirstOrDefaultAsync(t => t.ChannelId == channel.Id, cancellationToken);
|
||||||
if (template is null)
|
if (template is null)
|
||||||
return Result.Failure<ScheduleTemplateDto>(ChannelErrors.TemplateNotFound);
|
return Result.Failure<ScheduleTemplateDto>(ChannelErrors.TemplateNotFound);
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ internal static class LayerLoader
|
|||||||
dbContext
|
dbContext
|
||||||
.ScheduleTemplates.Include(t => t.Layers)
|
.ScheduleTemplates.Include(t => t.Layers)
|
||||||
.ThenInclude(l => l.Slots)
|
.ThenInclude(l => l.Slots)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(t => t.Id == templateId, cancellationToken);
|
.FirstOrDefaultAsync(t => t.Id == templateId, cancellationToken);
|
||||||
|
|
||||||
public static Task<ScheduleTemplate?> ByLayerAsync(
|
public static Task<ScheduleTemplate?> ByLayerAsync(
|
||||||
@@ -119,5 +120,6 @@ internal static class LayerLoader
|
|||||||
dbContext
|
dbContext
|
||||||
.ScheduleTemplates.Include(t => t.Layers)
|
.ScheduleTemplates.Include(t => t.Layers)
|
||||||
.ThenInclude(l => l.Slots)
|
.ThenInclude(l => l.Slots)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken);
|
.FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ public sealed class SlotWriter(IAppDbContext dbContext)
|
|||||||
dbContext
|
dbContext
|
||||||
.ScheduleTemplates.Include(t => t.Layers)
|
.ScheduleTemplates.Include(t => t.Layers)
|
||||||
.ThenInclude(l => l.Slots)
|
.ThenInclude(l => l.Slots)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken);
|
.FirstOrDefaultAsync(t => t.Layers.Any(l => l.Id == layerId), cancellationToken);
|
||||||
|
|
||||||
/// <summary>Загружает шаблон по слоту.</summary>
|
/// <summary>Загружает шаблон по слоту.</summary>
|
||||||
@@ -99,6 +100,7 @@ public sealed class SlotWriter(IAppDbContext dbContext)
|
|||||||
dbContext
|
dbContext
|
||||||
.ScheduleTemplates.Include(t => t.Layers)
|
.ScheduleTemplates.Include(t => t.Layers)
|
||||||
.ThenInclude(l => l.Slots)
|
.ThenInclude(l => l.Slots)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(
|
.FirstOrDefaultAsync(
|
||||||
t => t.Layers.Any(l => l.Slots.Any(s => s.Id == slotId)),
|
t => t.Layers.Any(l => l.Slots.Any(s => s.Id == slotId)),
|
||||||
cancellationToken
|
cancellationToken
|
||||||
|
|||||||
+1
@@ -28,6 +28,7 @@ public sealed class ValidateTemplateQueryHandler(IAppDbContext dbContext)
|
|||||||
.ScheduleTemplates.AsNoTracking()
|
.ScheduleTemplates.AsNoTracking()
|
||||||
.Include(t => t.Layers)
|
.Include(t => t.Layers)
|
||||||
.ThenInclude(l => l.Slots)
|
.ThenInclude(l => l.Slots)
|
||||||
|
.AsSplitQuery()
|
||||||
.FirstOrDefaultAsync(t => t.ChannelId == channel.Id, cancellationToken);
|
.FirstOrDefaultAsync(t => t.ChannelId == channel.Id, cancellationToken);
|
||||||
if (template is null)
|
if (template is null)
|
||||||
return Result.Failure<IReadOnlyList<TemplateIssueDto>>(ChannelErrors.TemplateNotFound);
|
return Result.Failure<IReadOnlyList<TemplateIssueDto>>(ChannelErrors.TemplateNotFound);
|
||||||
|
|||||||
Reference in New Issue
Block a user