fix: 修复SKU软删除冲突导致唯一索引报错
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 50s
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 50s
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user