diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/UpdateMerchantCommand.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/UpdateMerchantCommand.cs index 034a6f8..a140562 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/UpdateMerchantCommand.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Commands/UpdateMerchantCommand.cs @@ -44,7 +44,7 @@ public sealed record UpdateMerchantCommand : IRequest public string? ContactEmail { get; init; } /// - /// 并发控制版本。 + /// 并发控制版本(兼容字段,当前由数据库 xmin 托管)。 /// - public byte[] RowVersion { get; init; } = Array.Empty(); + public byte[]? RowVersion { get; init; } } diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs index 1a0eb65..3c76c78 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Handlers/UpdateMerchantCommandHandler.cs @@ -29,16 +29,11 @@ public sealed class UpdateMerchantCommandHandler( /// public async Task Handle(UpdateMerchantCommand request, CancellationToken cancellationToken) { - if (request.RowVersion == null || request.RowVersion.Length == 0) - { - throw new BusinessException(ErrorCodes.ValidationFailed, "RowVersion 不能为空"); - } - // 1. 获取操作者权限 var currentTenantId = tenantProvider.GetCurrentTenantId(); // 2. 读取商户信息 - var merchant = await merchantRepository.FindByIdAsync(request.MerchantId, currentTenantId, cancellationToken); + var merchant = await merchantRepository.GetForUpdateAsync(request.MerchantId, currentTenantId, cancellationToken); if (merchant == null) { @@ -73,7 +68,6 @@ public sealed class UpdateMerchantCommandHandler( merchant.Address = registeredAddress; merchant.ContactPhone = contactPhone; merchant.ContactEmail = contactEmail; - merchant.RowVersion = request.RowVersion; var requiresReview = merchant.Status == MerchantStatus.Approved && criticalChanged; if (requiresReview) diff --git a/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/UpdateMerchantCommandValidator.cs b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/UpdateMerchantCommandValidator.cs index 248f60c..db43914 100644 --- a/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/UpdateMerchantCommandValidator.cs +++ b/src/Application/TakeoutSaaS.Application/App/Merchants/Validators/UpdateMerchantCommandValidator.cs @@ -21,6 +21,5 @@ public sealed class UpdateMerchantCommandValidator : AbstractValidator x.ContactPhone).NotEmpty().MaximumLength(32); RuleFor(x => x.ContactEmail).EmailAddress().MaximumLength(128) .When(x => !string.IsNullOrWhiteSpace(x.ContactEmail)); - RuleFor(x => x.RowVersion).NotEmpty(); } } diff --git a/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs b/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs index c1b972c..00b769d 100644 --- a/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs +++ b/src/Domain/TakeoutSaaS.Domain/Merchants/Repositories/IMerchantRepository.cs @@ -18,6 +18,15 @@ public interface IMerchantRepository /// 商户实体或 null。 Task FindByIdAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default); + /// + /// 依据标识获取商户(用于更新,返回可跟踪实体)。 + /// + /// 商户 ID。 + /// 租户 ID。 + /// 取消标记。 + /// 商户实体或 null。 + Task GetForUpdateAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default); + /// /// 按状态筛选商户列表。 /// diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs index c0f05da..d4c44b6 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Persistence/TakeoutAppDbContext.cs @@ -536,10 +536,12 @@ public sealed class TakeoutAppDbContext( builder.Property(x => x.IsFrozen).HasDefaultValue(false); builder.Property(x => x.FrozenReason).HasMaxLength(500); builder.Property(x => x.ClaimedByName).HasMaxLength(100); - builder.Property(x => x.RowVersion) - .IsRowVersion() - .IsConcurrencyToken() - .HasColumnType("bytea"); + builder.Ignore(x => x.RowVersion); + builder.Property("xmin") + .HasColumnName("xmin") + .HasColumnType("xid") + .ValueGeneratedOnAddOrUpdate() + .IsConcurrencyToken(); builder.HasIndex(x => x.TenantId); builder.HasIndex(x => new { x.TenantId, x.Status }); builder.HasIndex(x => x.ClaimedBy); diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs index 17e9fc4..03e14da 100644 --- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs +++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfMerchantRepository.cs @@ -25,6 +25,14 @@ public sealed class EfMerchantRepository(TakeoutAppDbContext context, TakeoutLog .FirstOrDefaultAsync(cancellationToken); } + /// + public Task GetForUpdateAsync(long merchantId, long tenantId, CancellationToken cancellationToken = default) + { + return context.Merchants + .Where(x => x.TenantId == tenantId && x.Id == merchantId) + .FirstOrDefaultAsync(cancellationToken); + } + /// public async Task> SearchAsync(long tenantId, MerchantStatus? status, CancellationToken cancellationToken = default) {