fix(billing): 修复账单详情查询的数据库并发错误

问题:
- Npgsql.NpgsqlOperationInProgressException: A command is already in progress
- 在同一个数据库连接上,billReader 未释放就执行 paymentReader

根因:
- GetBillingDetailQueryHandler 中先查询账单并打开 billReader
- 读取账单数据后未释放 reader
- 直接在同一连接上执行支付记录查询,触发并发异常

解决方案:
- 将账单字段先读取到本地变量
- 主动 DisposeAsync 释放 billReader
- 再执行支付记录查询
- 最后用本地变量组装 BillingDetailDto
This commit is contained in:
2025-12-18 11:45:44 +08:00
parent 4b53862ded
commit a5abd6ef90

View File

@@ -46,14 +46,35 @@ public sealed class GetBillingDetailQueryHandler(
throw new BusinessException(ErrorCodes.NotFound, "账单不存在");
}
// 1.2 (空行后) 读取账单行数据到内存(释放 Reader避免同连接并发执行命令
var billingId = billReader.GetInt64(0);
var tenantId = billReader.GetInt64(1);
var tenantName = billReader.IsDBNull(2) ? string.Empty : billReader.GetString(2);
long? subscriptionId = billReader.IsDBNull(3) ? null : billReader.GetInt64(3);
var statementNo = billReader.GetString(4);
var billingType = (BillingType)billReader.GetInt32(5);
var status = (TenantBillingStatus)billReader.GetInt32(6);
var periodStart = billReader.GetDateTime(7);
var periodEnd = billReader.GetDateTime(8);
var amountDue = billReader.GetDecimal(9);
var discountAmount = billReader.GetDecimal(10);
var taxAmount = billReader.GetDecimal(11);
var amountPaid = billReader.GetDecimal(12);
var currency = billReader.IsDBNull(13) ? "CNY" : billReader.GetString(13);
var dueDate = billReader.GetDateTime(14);
DateTime? reminderSentAt = billReader.IsDBNull(15) ? null : billReader.GetDateTime(15);
DateTime? overdueNotifiedAt = billReader.IsDBNull(16) ? null : billReader.GetDateTime(16);
var notes = billReader.IsDBNull(17) ? null : billReader.GetString(17);
var lineItemsJson = billReader.IsDBNull(18) ? null : billReader.GetString(18);
var createdAt = billReader.GetDateTime(19);
long? createdBy = billReader.IsDBNull(20) ? null : billReader.GetInt64(20);
DateTime? updatedAt = billReader.IsDBNull(21) ? null : billReader.GetDateTime(21);
long? updatedBy = billReader.IsDBNull(22) ? null : billReader.GetInt64(22);
// 1.2 (空行后) 反序列化账单明细
// 1.3 (空行后) 主动释放账单 Reader确保后续查询不会触发 Npgsql 并发命令异常
await billReader.DisposeAsync();
// 1.4 (空行后) 反序列化账单明细
var lineItems = new List<BillingLineItemDto>();
if (!string.IsNullOrWhiteSpace(lineItemsJson))
{
@@ -67,7 +88,7 @@ public sealed class GetBillingDetailQueryHandler(
}
}
// 1.3 (空行后) 查询支付记录
// 1.5 (空行后) 查询支付记录
var payments = new List<PaymentRecordDto>();
await using var paymentCommand = CreateCommand(
connection,
@@ -100,39 +121,36 @@ public sealed class GetBillingDetailQueryHandler(
});
}
// 1.4 (空行后) 组装详情 DTO
var amountDue = billReader.GetDecimal(9);
var discountAmount = billReader.GetDecimal(10);
var taxAmount = billReader.GetDecimal(11);
// 1.6 (空行后) 组装详情 DTO
var totalAmount = amountDue - discountAmount + taxAmount;
return new BillingDetailDto
{
Id = billReader.GetInt64(0),
TenantId = billReader.GetInt64(1),
TenantName = billReader.IsDBNull(2) ? string.Empty : billReader.GetString(2),
SubscriptionId = billReader.IsDBNull(3) ? null : billReader.GetInt64(3),
StatementNo = billReader.GetString(4),
BillingType = (BillingType)billReader.GetInt32(5),
Status = (TenantBillingStatus)billReader.GetInt32(6),
PeriodStart = billReader.GetDateTime(7),
PeriodEnd = billReader.GetDateTime(8),
AmountDue = billReader.GetDecimal(9),
DiscountAmount = billReader.GetDecimal(10),
TaxAmount = billReader.GetDecimal(11),
Id = billingId,
TenantId = tenantId,
TenantName = tenantName,
SubscriptionId = subscriptionId,
StatementNo = statementNo,
BillingType = billingType,
Status = status,
PeriodStart = periodStart,
PeriodEnd = periodEnd,
AmountDue = amountDue,
DiscountAmount = discountAmount,
TaxAmount = taxAmount,
TotalAmount = totalAmount,
AmountPaid = billReader.GetDecimal(12),
Currency = billReader.IsDBNull(13) ? "CNY" : billReader.GetString(13),
DueDate = billReader.GetDateTime(14),
AmountPaid = amountPaid,
Currency = currency,
DueDate = dueDate,
ReminderSentAt = reminderSentAt,
OverdueNotifiedAt = overdueNotifiedAt,
LineItemsJson = lineItemsJson,
LineItems = lineItems,
Payments = payments,
Notes = notes,
CreatedAt = billReader.GetDateTime(19),
CreatedAt = createdAt,
CreatedBy = createdBy,
UpdatedAt = billReader.IsDBNull(21) ? null : billReader.GetDateTime(21),
UpdatedAt = updatedAt,
UpdatedBy = updatedBy
};
},