From a6bd8a0e44278fc24912cd03a34f553c14c86b55 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sat, 9 May 2026 15:13:22 +0300 Subject: [PATCH] fix(persistence): drop broken Guid lookup on ISnapshotRepository (CRITICAL) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Snapshots are append-only and identified by the composite (EventId, CapturedAt), not a surrogate Guid. The previous implementation inherited IRepository and faked the lookup with (long)key.GetHashCode() on a long auto-increment PK — collision-prone and non-portable. Nobody called GetAsync(Guid) / DeleteAsync(Guid) anyway. * ISnapshotRepository no longer extends IRepository; it exposes only the methods snapshots actually have: ListAsync, ListByEventAsync, AddAsync, SaveChangesAsync. * SnapshotRepository drops the broken Get/Update/Delete methods. --- .../Abstractions/ISnapshotRepository.cs | 14 +++++++++- .../Repositories/SnapshotRepository.cs | 27 ------------------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/Marathon.Application/Abstractions/ISnapshotRepository.cs b/src/Marathon.Application/Abstractions/ISnapshotRepository.cs index 390f99c..895ba5d 100644 --- a/src/Marathon.Application/Abstractions/ISnapshotRepository.cs +++ b/src/Marathon.Application/Abstractions/ISnapshotRepository.cs @@ -6,11 +6,23 @@ namespace Marathon.Application.Abstractions; /// /// Repository for domain entities. /// -public interface ISnapshotRepository : IRepository +/// +/// Snapshots are append-only and identified by the composite (EventId, CapturedAt) +/// rather than a surrogate key, so this contract intentionally does NOT extend +/// — point lookup by Guid would be +/// meaningless. Use for retrieval. +/// +public interface ISnapshotRepository { + Task> ListAsync(CancellationToken ct = default); + Task> ListByEventAsync( EventId eventId, DateTimeOffset from, DateTimeOffset to, CancellationToken ct = default); + + Task AddAsync(OddsSnapshot entity, CancellationToken ct = default); + + Task SaveChangesAsync(CancellationToken ct = default); } diff --git a/src/Marathon.Infrastructure/Persistence/Repositories/SnapshotRepository.cs b/src/Marathon.Infrastructure/Persistence/Repositories/SnapshotRepository.cs index f9dbb2a..f3d2f1c 100644 --- a/src/Marathon.Infrastructure/Persistence/Repositories/SnapshotRepository.cs +++ b/src/Marathon.Infrastructure/Persistence/Repositories/SnapshotRepository.cs @@ -11,18 +11,6 @@ internal sealed class SnapshotRepository : ISnapshotRepository public SnapshotRepository(MarathonDbContext db) => _db = db; - public async Task GetAsync(Guid key, CancellationToken ct = default) - { - var entity = await _db.Snapshots - .Include(s => s.Bets) - .FirstOrDefaultAsync(s => s.Id == (long)key.GetHashCode(), ct); - // Note: Guid→long mapping is lossy for GetAsync by Guid; the repo interface requires Guid key. - // Snapshots are typically retrieved by event, not directly by id. - // A proper implementation would store the Guid as a TEXT column. - // For now, this method is functionally available — callers prefer ListByEventAsync. - return entity is null ? null : Mapping.ToDomain(entity); - } - public async Task> ListAsync(CancellationToken ct = default) { var entities = await _db.Snapshots.AsNoTracking() @@ -56,21 +44,6 @@ internal sealed class SnapshotRepository : ISnapshotRepository await _db.Snapshots.AddAsync(efEntity, ct); } - public Task UpdateAsync(OddsSnapshot entity, CancellationToken ct = default) - { - // Snapshots are immutable once written — update is not a typical operation. - var efEntity = Mapping.ToEntity(entity); - _db.Snapshots.Update(efEntity); - return Task.CompletedTask; - } - - public async Task DeleteAsync(Guid key, CancellationToken ct = default) - { - var entity = await _db.Snapshots.FindAsync([(long)key.GetHashCode()], ct); - if (entity is not null) - _db.Snapshots.Remove(entity); - } - public async Task SaveChangesAsync(CancellationToken ct = default) => await _db.SaveChangesAsync(ct); }