From 7ecf069efd9dd646b270dfb51f437c6ee57118ca Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Thu, 19 Feb 2026 17:57:52 +0800 Subject: [PATCH] fix(pickup): prevent null rowversion on settings and slots save --- .../Controllers/StorePickupController.cs | 22 +++++++++++++++---- .../App/Persistence/TakeoutAppDbContext.cs | 8 +++++-- .../Persistence/TakeoutTenantAppDbContext.cs | 10 +++++++-- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/Api/TakeoutSaaS.TenantApi/Controllers/StorePickupController.cs b/src/Api/TakeoutSaaS.TenantApi/Controllers/StorePickupController.cs index 8ffccc9..98e518a 100644 --- a/src/Api/TakeoutSaaS.TenantApi/Controllers/StorePickupController.cs +++ b/src/Api/TakeoutSaaS.TenantApi/Controllers/StorePickupController.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using System.Security.Cryptography; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -92,6 +93,7 @@ public sealed class StorePickupController( setting.AllowDaysAhead = Math.Clamp(request.BasicSettings.BookingDays, 1, 30); setting.MaxQuantityPerOrder = request.BasicSettings.MaxItemsPerOrder; setting.Mode = request.Mode is null ? setting.Mode : StoreApiHelpers.ToPickupMode(request.Mode); + setting.RowVersion = CreateRowVersion(); await dbContext.SaveChangesAsync(cancellationToken); return ApiResponse.Ok(null); @@ -110,6 +112,7 @@ public sealed class StorePickupController( var setting = await EnsurePickupSettingAsync(tenantId, parsedStoreId, cancellationToken); setting.Mode = request.Mode is null ? setting.Mode : StoreApiHelpers.ToPickupMode(request.Mode); + setting.RowVersion = CreateRowVersion(); var existingSlots = await dbContext.StorePickupSlots .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId) @@ -137,7 +140,8 @@ public sealed class StorePickupController( Capacity = capacity, ReservedCount = Math.Clamp(slot.ReservedCount, 0, capacity), Weekdays = StoreApiHelpers.SerializeWeekdays(slot.DayOfWeeks), - IsEnabled = slot.Enabled + IsEnabled = slot.Enabled, + RowVersion = CreateRowVersion() }); } @@ -163,6 +167,7 @@ public sealed class StorePickupController( var setting = await EnsurePickupSettingAsync(tenantId, parsedStoreId, cancellationToken); setting.Mode = request.Mode is null ? setting.Mode : StoreApiHelpers.ToPickupMode(request.Mode); + setting.RowVersion = CreateRowVersion(); var normalizedRule = NormalizeFineRule(request.FineRule); setting.FineRuleJson = JsonSerializer.Serialize(normalizedRule, StoreApiHelpers.JsonOptions); @@ -218,7 +223,8 @@ public sealed class StorePickupController( { targetSetting = new StorePickupSetting { - StoreId = targetStoreId + StoreId = targetStoreId, + RowVersion = CreateRowVersion() }; await dbContext.StorePickupSettings.AddAsync(targetSetting, cancellationToken); } @@ -229,6 +235,7 @@ public sealed class StorePickupController( targetSetting.MaxQuantityPerOrder = sourceSetting?.MaxQuantityPerOrder ?? 20; targetSetting.Mode = sourceSetting?.Mode ?? StorePickupMode.Big; targetSetting.FineRuleJson = sourceSetting?.FineRuleJson; + targetSetting.RowVersion = CreateRowVersion(); } var targetSlots = await dbContext.StorePickupSlots @@ -247,7 +254,8 @@ public sealed class StorePickupController( Capacity = slot.Capacity, ReservedCount = slot.ReservedCount, Weekdays = slot.Weekdays, - IsEnabled = slot.IsEnabled + IsEnabled = slot.IsEnabled, + RowVersion = CreateRowVersion() })) .ToList(); @@ -275,12 +283,18 @@ public sealed class StorePickupController( setting = new StorePickupSetting { - StoreId = storeId + StoreId = storeId, + RowVersion = CreateRowVersion() }; await dbContext.StorePickupSettings.AddAsync(setting, cancellationToken); return setting; } + private static byte[] CreateRowVersion() + { + return RandomNumberGenerator.GetBytes(16); + } + private static PickupFineRuleDto ParseFineRule(string? raw) { if (!string.IsNullOrWhiteSpace(raw)) diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs index c982879..0e2f984 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs @@ -1093,7 +1093,9 @@ public sealed class TakeoutAppDbContext( builder.Property(x => x.Mode).HasConversion(); builder.Property(x => x.FineRuleJson).HasColumnType("text"); builder.Property(x => x.RowVersion) - .IsConcurrencyToken(); + .IsRequired() + .IsConcurrencyToken() + .ValueGeneratedNever(); builder.HasIndex(x => new { x.TenantId, x.StoreId }).IsUnique(); } @@ -1106,7 +1108,9 @@ public sealed class TakeoutAppDbContext( builder.Property(x => x.Weekdays).HasMaxLength(32).IsRequired(); builder.Property(x => x.CutoffMinutes).HasDefaultValue(30); builder.Property(x => x.RowVersion) - .IsConcurrencyToken(); + .IsRequired() + .IsConcurrencyToken() + .ValueGeneratedNever(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.Name }); } diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutTenantAppDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutTenantAppDbContext.cs index fe2e4c2..9f49b0b 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutTenantAppDbContext.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutTenantAppDbContext.cs @@ -283,7 +283,10 @@ public sealed class TakeoutTenantAppDbContext( builder.Property(x => x.AllowDaysAhead).HasDefaultValue(3); builder.Property(x => x.DefaultCutoffMinutes).HasDefaultValue(30); builder.Property(x => x.MaxQuantityPerOrder); - builder.Property(x => x.RowVersion).IsRowVersion(); + builder.Property(x => x.RowVersion) + .IsRequired() + .IsConcurrencyToken() + .ValueGeneratedNever(); builder.HasIndex(x => new { x.TenantId, x.StoreId }).IsUnique(); } @@ -300,7 +303,10 @@ public sealed class TakeoutTenantAppDbContext( builder.Property(x => x.ReservedCount).HasDefaultValue(0); builder.Property(x => x.Weekdays).HasMaxLength(32).HasDefaultValue("1,2,3,4,5,6,7"); builder.Property(x => x.IsEnabled).HasDefaultValue(true); - builder.Property(x => x.RowVersion).IsRowVersion(); + builder.Property(x => x.RowVersion) + .IsRequired() + .IsConcurrencyToken() + .ValueGeneratedNever(); builder.HasIndex(x => new { x.TenantId, x.StoreId, x.StartTime, x.EndTime }).IsUnique(); } }