Harden owner and suggestion invariants for concurrent writes
This commit is contained in:
@@ -2,6 +2,7 @@ using GameList.Contracts;
|
||||
using GameList.Data;
|
||||
using GameList.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
|
||||
namespace GameList.Endpoints;
|
||||
|
||||
@@ -71,26 +72,46 @@ internal sealed class VoteWorkflowService(AppDbContext db)
|
||||
.Where(v => v.PlayerId == playerId && linkedIds.Contains(v.SuggestionId))
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var linkedSuggestionId in linkedIds)
|
||||
for (var attempt = 0; attempt < 2; attempt++)
|
||||
{
|
||||
var vote = existingVotes.FirstOrDefault(v => v.SuggestionId == linkedSuggestionId);
|
||||
if (vote == null)
|
||||
foreach (var linkedSuggestionId in linkedIds)
|
||||
{
|
||||
db.Votes.Add(new Vote
|
||||
var vote = existingVotes.FirstOrDefault(v => v.SuggestionId == linkedSuggestionId);
|
||||
if (vote == null)
|
||||
{
|
||||
PlayerId = playerId,
|
||||
SuggestionId = linkedSuggestionId,
|
||||
Score = score
|
||||
});
|
||||
db.Votes.Add(new Vote
|
||||
{
|
||||
PlayerId = playerId,
|
||||
SuggestionId = linkedSuggestionId,
|
||||
Score = score
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
vote.Score = score;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
try
|
||||
{
|
||||
vote.Score = score;
|
||||
await db.SaveChangesAsync();
|
||||
return Results.Ok(new VoteUpsertResponse(linkedIds, score));
|
||||
}
|
||||
catch (DbUpdateException ex) when (attempt == 0 && EndpointHelpers.IsSqliteConstraintViolation(ex))
|
||||
{
|
||||
DetachAddedVotes(db.ChangeTracker.Entries<Vote>());
|
||||
|
||||
await db.Votes
|
||||
.Where(v => v.PlayerId == playerId && linkedIds.Contains(v.SuggestionId))
|
||||
.ExecuteUpdateAsync(v => v.SetProperty(x => x.Score, score));
|
||||
|
||||
existingVotes = await db.Votes
|
||||
.Where(v => v.PlayerId == playerId && linkedIds.Contains(v.SuggestionId))
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return Results.Ok(new VoteUpsertResponse(linkedIds, score));
|
||||
return EndpointHelpers.ConflictError("Vote update conflict. Please retry.");
|
||||
}
|
||||
|
||||
public async Task<IResult> SetFinalizeAsync(Guid playerId, bool final)
|
||||
@@ -105,4 +126,13 @@ internal sealed class VoteWorkflowService(AppDbContext db)
|
||||
await db.SaveChangesAsync();
|
||||
return Results.Ok(new VoteFinalizeResponse(player.VotesFinal));
|
||||
}
|
||||
|
||||
private static void DetachAddedVotes(IEnumerable<EntityEntry<Vote>> voteEntries)
|
||||
{
|
||||
foreach (var entry in voteEntries)
|
||||
{
|
||||
if (entry.State == EntityState.Added)
|
||||
entry.State = EntityState.Detached;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user