Files
maraphon-app/src/Marathon.Infrastructure/Persistence/Configurations/SnapshotConfiguration.cs
T
alexei.dolgolyov f294255f10 perf: batch repository reads, index snapshots, centralize date encoding
- Add IEventRepository/IResultRepository.GetManyAsync to kill N+1 lookups at
  6 sites (backtest, outcome eval, both bet-journal paths, anomaly browsing,
  results selection); guarded by a Received(1).GetManyAsync test.
- Add EventRepository.QueryAsync to push date+sport filtering to SQL (was
  load-whole-range-then-filter); search/sort stay in-memory for Cyrillic order.
- Add AnomalyRepository.CountSinceAsync (unread badge) + ListByDateRangeAsync
  (feed date filter); add Event/Snapshot count methods for the dashboard.
- Add composite indexes IX_Snapshots_EventCode_CapturedAt and
  _EventCode_Source_CapturedAt via a new migration + model snapshot.
- Introduce SqliteDateText as the single source of the O-format date encoding
  shared by Mapping (read/write) and the repositories' range predicates.
- Fix LiveOddsPoller cadence drift (budget sleep against cycle time); make
  DetectAnomalies dedup O(1) per event; add Event.Title to dedup the title join.

Tests adapted to the batched GetManyAsync via a TestFixtures bridge.
2026-05-28 22:34:08 +03:00

38 lines
1.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Marathon.Infrastructure.Persistence.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Marathon.Infrastructure.Persistence.Configurations;
internal sealed class SnapshotConfiguration : IEntityTypeConfiguration<SnapshotEntity>
{
public void Configure(EntityTypeBuilder<SnapshotEntity> builder)
{
builder.ToTable("Snapshots");
builder.HasKey(s => s.Id);
builder.Property(s => s.Id).HasColumnType("INTEGER").ValueGeneratedOnAdd();
builder.Property(s => s.EventCode).HasColumnType("TEXT").IsRequired();
builder.Property(s => s.CapturedAt).HasColumnType("TEXT").IsRequired();
builder.Property(s => s.Source).HasColumnType("INTEGER").IsRequired();
builder.HasIndex(s => s.EventCode).HasDatabaseName("IX_Snapshots_EventCode");
// Snapshots is the largest table (live cadence 510s, 90-day retention) and
// every hot read filters EventCode + CapturedAt range, often with an ORDER BY
// CapturedAt. These composite indexes let SQLite satisfy the filter and the
// ordering from the index instead of scanning + sorting the table.
builder.HasIndex(s => new { s.EventCode, s.CapturedAt })
.HasDatabaseName("IX_Snapshots_EventCode_CapturedAt");
// Covers GetLatestPreMatchAsync: EventCode + Source filter, ORDER BY CapturedAt DESC.
builder.HasIndex(s => new { s.EventCode, s.Source, s.CapturedAt })
.HasDatabaseName("IX_Snapshots_EventCode_Source_CapturedAt");
builder.HasMany(s => s.Bets)
.WithOne(b => b.Snapshot)
.HasForeignKey(b => b.SnapshotId)
.OnDelete(DeleteBehavior.Cascade);
}
}