fix(persistence): drop broken Guid lookup on ISnapshotRepository (CRITICAL)
Snapshots are append-only and identified by the composite (EventId, CapturedAt), not a surrogate Guid. The previous implementation inherited IRepository<Guid, OddsSnapshot> 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<Guid, OddsSnapshot>; it exposes only the methods snapshots actually have: ListAsync, ListByEventAsync, AddAsync, SaveChangesAsync. * SnapshotRepository drops the broken Get/Update/Delete methods.
This commit is contained in:
@@ -6,11 +6,23 @@ namespace Marathon.Application.Abstractions;
|
||||
/// <summary>
|
||||
/// Repository for <see cref="OddsSnapshot"/> domain entities.
|
||||
/// </summary>
|
||||
public interface ISnapshotRepository : IRepository<Guid, OddsSnapshot>
|
||||
/// <remarks>
|
||||
/// Snapshots are append-only and identified by the composite (EventId, CapturedAt)
|
||||
/// rather than a surrogate key, so this contract intentionally does NOT extend
|
||||
/// <see cref="IRepository{TKey, TEntity}"/> — point lookup by Guid would be
|
||||
/// meaningless. Use <see cref="ListByEventAsync"/> for retrieval.
|
||||
/// </remarks>
|
||||
public interface ISnapshotRepository
|
||||
{
|
||||
Task<IReadOnlyList<OddsSnapshot>> ListAsync(CancellationToken ct = default);
|
||||
|
||||
Task<IReadOnlyList<OddsSnapshot>> ListByEventAsync(
|
||||
EventId eventId,
|
||||
DateTimeOffset from,
|
||||
DateTimeOffset to,
|
||||
CancellationToken ct = default);
|
||||
|
||||
Task AddAsync(OddsSnapshot entity, CancellationToken ct = default);
|
||||
|
||||
Task SaveChangesAsync(CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -11,18 +11,6 @@ internal sealed class SnapshotRepository : ISnapshotRepository
|
||||
|
||||
public SnapshotRepository(MarathonDbContext db) => _db = db;
|
||||
|
||||
public async Task<OddsSnapshot?> 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<IReadOnlyList<OddsSnapshot>> 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user