fix: 修复SKU软删除冲突导致唯一索引报错
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 50s

This commit is contained in:
2026-02-25 09:49:20 +08:00
parent 21d7be5a4f
commit b970cd4ba7

View File

@@ -1,6 +1,7 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text.Json; using System.Text.Json;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Npgsql;
using TakeoutSaaS.Domain.Products.Entities; using TakeoutSaaS.Domain.Products.Entities;
using TakeoutSaaS.Domain.Products.Enums; using TakeoutSaaS.Domain.Products.Enums;
using TakeoutSaaS.Infrastructure.App.Persistence; using TakeoutSaaS.Infrastructure.App.Persistence;
@@ -53,21 +54,30 @@ public sealed class ProductSkuSaveService(TakeoutAppDbContext dbContext)
if (explicitSkuCodes.Count > 0) if (explicitSkuCodes.Count > 0)
{ {
var codeConflict = await dbContext.ProductSkus string? codeConflict;
.AsNoTracking() using (dbContext.DisableSoftDeleteFilter())
.Where(item => item.ProductId != productId && explicitSkuCodes.Contains(item.SkuCode)) {
.Select(item => item.SkuCode) codeConflict = await dbContext.ProductSkus
.FirstOrDefaultAsync(cancellationToken); .AsNoTracking()
.Where(item => item.ProductId != productId && explicitSkuCodes.Contains(item.SkuCode))
.Select(item => item.SkuCode)
.FirstOrDefaultAsync(cancellationToken);
}
if (!string.IsNullOrWhiteSpace(codeConflict)) if (!string.IsNullOrWhiteSpace(codeConflict))
{ {
throw new BusinessException(ErrorCodes.BadRequest, $"SKU 编码已存在: {codeConflict}"); throw new BusinessException(ErrorCodes.BadRequest, $"SKU 编码已存在: {codeConflict}");
} }
} }
var existingSkus = await dbContext.ProductSkus List<ProductSku> existingSkus;
.Where(item => item.ProductId == productId) using (dbContext.DisableSoftDeleteFilter())
.OrderBy(item => item.Id) {
.ToListAsync(cancellationToken); existingSkus = await dbContext.ProductSkus
.Where(item => item.ProductId == productId)
.OrderBy(item => item.Id)
.ToListAsync(cancellationToken);
}
var existingBySkuCode = existingSkus var existingBySkuCode = existingSkus
.Where(item => !string.IsNullOrWhiteSpace(item.SkuCode)) .Where(item => !string.IsNullOrWhiteSpace(item.SkuCode))
@@ -156,7 +166,14 @@ public sealed class ProductSkuSaveService(TakeoutAppDbContext dbContext)
await dbContext.ProductSkus.AddRangeAsync(createdSkus, cancellationToken); await dbContext.ProductSkus.AddRangeAsync(createdSkus, cancellationToken);
} }
await dbContext.SaveChangesAsync(cancellationToken); try
{
await dbContext.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateException ex) when (IsSkuCodeUniqueViolation(ex))
{
throw new BusinessException(ErrorCodes.BadRequest, "SKU 编码冲突,请刷新后重试");
}
} }
private async Task ValidateSkuTemplateRefsAsync( private async Task ValidateSkuTemplateRefsAsync(
@@ -344,6 +361,20 @@ public sealed class ProductSkuSaveService(TakeoutAppDbContext dbContext)
return new string(buffer[pos..]); return new string(buffer[pos..]);
} }
private static bool IsSkuCodeUniqueViolation(DbUpdateException exception)
{
if (exception.InnerException is not PostgresException postgresException)
{
return false;
}
return postgresException.SqlState == PostgresErrorCodes.UniqueViolation &&
string.Equals(
postgresException.ConstraintName,
"IX_product_skus_TenantId_SkuCode",
StringComparison.Ordinal);
}
private sealed record SkuAttributePayload(long TemplateId, long OptionId); private sealed record SkuAttributePayload(long TemplateId, long OptionId);
} }