import_viewer: fix counter upsert race + add explicit slot field
Pre-materialize ViewerEventCounters before the mission loop so in-flight Adds are visible to subsequent iterations sharing the same (EventKey, Period); add optional ImportMission.Slot field so callers can supply weekly slot identity (2 or 3) rather than relying on the LotType-derived default of 1. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -279,6 +279,31 @@ public class AdminController : SVSimController
|
||||
.ToDictionaryAsync(c => c.Id);
|
||||
var nowUtc = DateTimeOffset.UtcNow;
|
||||
|
||||
// Resolve counter keys for all missions up-front so we can load existing counters
|
||||
// in one query and avoid duplicate-Add races when two missions share the same
|
||||
// (EventKey, Period) within the same import batch.
|
||||
var resolvedCounterKeys = new Dictionary<int, (string EventKey, string Period)>();
|
||||
foreach (var m in missions)
|
||||
{
|
||||
if (!catalogs.TryGetValue(m.MissionId, out var cat)) continue;
|
||||
var r = ResolveMissionCounter(cat, nowUtc);
|
||||
if (r is not null)
|
||||
resolvedCounterKeys[m.MissionId] = r.Value;
|
||||
}
|
||||
|
||||
// Collect all distinct (EventKey, Period) pairs we will touch.
|
||||
var allEventKeys = resolvedCounterKeys.Values.Select(v => v.EventKey).Distinct().ToList();
|
||||
var allPeriods = resolvedCounterKeys.Values.Select(v => v.Period).Distinct().ToList();
|
||||
|
||||
// Load existing counters in one query (widens slightly; filter in memory below — fine for small N).
|
||||
var existingCounters = await _dbContext.ViewerEventCounters
|
||||
.Where(c => c.ViewerId == viewer.Id
|
||||
&& allEventKeys.Contains(c.EventKey)
|
||||
&& allPeriods.Contains(c.Period))
|
||||
.ToListAsync();
|
||||
var counterCache = existingCounters
|
||||
.ToDictionary(c => (c.EventKey, c.Period));
|
||||
|
||||
foreach (var m in missions)
|
||||
{
|
||||
if (!catalogs.TryGetValue(m.MissionId, out var cat))
|
||||
@@ -289,35 +314,30 @@ public class AdminController : SVSimController
|
||||
viewer.Missions.Add(new ViewerMission
|
||||
{
|
||||
MissionCatalogId = cat.Id,
|
||||
Slot = cat.LotType == 6 ? 0 : 1,
|
||||
Slot = m.Slot ?? (cat.LotType == 6 ? 0 : 1),
|
||||
MissionStatus = m.MissionStatus,
|
||||
AssignedAt = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
ClaimedAt = null
|
||||
});
|
||||
|
||||
var resolved = ResolveMissionCounter(cat, nowUtc);
|
||||
if (resolved is null)
|
||||
if (!resolvedCounterKeys.TryGetValue(m.MissionId, out var resolved))
|
||||
{
|
||||
skippedMissionCounterIds.Add(m.MissionId);
|
||||
continue;
|
||||
}
|
||||
var (eventKey, period) = resolved.Value;
|
||||
var existingCounter = await _dbContext.ViewerEventCounters
|
||||
.FirstOrDefaultAsync(c => c.ViewerId == viewer.Id && c.EventKey == eventKey && c.Period == period);
|
||||
if (existingCounter is null)
|
||||
var (eventKey, period) = resolved;
|
||||
if (!counterCache.TryGetValue((eventKey, period), out var counter))
|
||||
{
|
||||
_dbContext.ViewerEventCounters.Add(new ViewerEventCounter
|
||||
counter = new ViewerEventCounter
|
||||
{
|
||||
ViewerId = viewer.Id,
|
||||
EventKey = eventKey,
|
||||
Period = period,
|
||||
Count = m.TotalCount
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
existingCounter.Count = m.TotalCount;
|
||||
};
|
||||
_dbContext.ViewerEventCounters.Add(counter);
|
||||
counterCache[(eventKey, period)] = counter;
|
||||
}
|
||||
counter.Count = m.TotalCount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user