fix(billing): 修复账单详情查询的数据库并发错误
问题: - Npgsql.NpgsqlOperationInProgressException: A command is already in progress - 在同一个数据库连接上,billReader 未释放就执行 paymentReader 根因: - GetBillingDetailQueryHandler 中先查询账单并打开 billReader - 读取账单数据后未释放 reader - 直接在同一连接上执行支付记录查询,触发并发异常 解决方案: - 将账单字段先读取到本地变量 - 主动 DisposeAsync 释放 billReader - 再执行支付记录查询 - 最后用本地变量组装 BillingDetailDto
This commit is contained in:
@@ -46,14 +46,35 @@ public sealed class GetBillingDetailQueryHandler(
|
|||||||
throw new BusinessException(ErrorCodes.NotFound, "账单不存在");
|
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? reminderSentAt = billReader.IsDBNull(15) ? null : billReader.GetDateTime(15);
|
||||||
DateTime? overdueNotifiedAt = billReader.IsDBNull(16) ? null : billReader.GetDateTime(16);
|
DateTime? overdueNotifiedAt = billReader.IsDBNull(16) ? null : billReader.GetDateTime(16);
|
||||||
var notes = billReader.IsDBNull(17) ? null : billReader.GetString(17);
|
var notes = billReader.IsDBNull(17) ? null : billReader.GetString(17);
|
||||||
var lineItemsJson = billReader.IsDBNull(18) ? null : billReader.GetString(18);
|
var lineItemsJson = billReader.IsDBNull(18) ? null : billReader.GetString(18);
|
||||||
|
var createdAt = billReader.GetDateTime(19);
|
||||||
long? createdBy = billReader.IsDBNull(20) ? null : billReader.GetInt64(20);
|
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);
|
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>();
|
var lineItems = new List<BillingLineItemDto>();
|
||||||
if (!string.IsNullOrWhiteSpace(lineItemsJson))
|
if (!string.IsNullOrWhiteSpace(lineItemsJson))
|
||||||
{
|
{
|
||||||
@@ -67,7 +88,7 @@ public sealed class GetBillingDetailQueryHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.3 (空行后) 查询支付记录
|
// 1.5 (空行后) 查询支付记录
|
||||||
var payments = new List<PaymentRecordDto>();
|
var payments = new List<PaymentRecordDto>();
|
||||||
await using var paymentCommand = CreateCommand(
|
await using var paymentCommand = CreateCommand(
|
||||||
connection,
|
connection,
|
||||||
@@ -100,39 +121,36 @@ public sealed class GetBillingDetailQueryHandler(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1.4 (空行后) 组装详情 DTO
|
// 1.6 (空行后) 组装详情 DTO
|
||||||
var amountDue = billReader.GetDecimal(9);
|
|
||||||
var discountAmount = billReader.GetDecimal(10);
|
|
||||||
var taxAmount = billReader.GetDecimal(11);
|
|
||||||
var totalAmount = amountDue - discountAmount + taxAmount;
|
var totalAmount = amountDue - discountAmount + taxAmount;
|
||||||
|
|
||||||
return new BillingDetailDto
|
return new BillingDetailDto
|
||||||
{
|
{
|
||||||
Id = billReader.GetInt64(0),
|
Id = billingId,
|
||||||
TenantId = billReader.GetInt64(1),
|
TenantId = tenantId,
|
||||||
TenantName = billReader.IsDBNull(2) ? string.Empty : billReader.GetString(2),
|
TenantName = tenantName,
|
||||||
SubscriptionId = billReader.IsDBNull(3) ? null : billReader.GetInt64(3),
|
SubscriptionId = subscriptionId,
|
||||||
StatementNo = billReader.GetString(4),
|
StatementNo = statementNo,
|
||||||
BillingType = (BillingType)billReader.GetInt32(5),
|
BillingType = billingType,
|
||||||
Status = (TenantBillingStatus)billReader.GetInt32(6),
|
Status = status,
|
||||||
PeriodStart = billReader.GetDateTime(7),
|
PeriodStart = periodStart,
|
||||||
PeriodEnd = billReader.GetDateTime(8),
|
PeriodEnd = periodEnd,
|
||||||
AmountDue = billReader.GetDecimal(9),
|
AmountDue = amountDue,
|
||||||
DiscountAmount = billReader.GetDecimal(10),
|
DiscountAmount = discountAmount,
|
||||||
TaxAmount = billReader.GetDecimal(11),
|
TaxAmount = taxAmount,
|
||||||
TotalAmount = totalAmount,
|
TotalAmount = totalAmount,
|
||||||
AmountPaid = billReader.GetDecimal(12),
|
AmountPaid = amountPaid,
|
||||||
Currency = billReader.IsDBNull(13) ? "CNY" : billReader.GetString(13),
|
Currency = currency,
|
||||||
DueDate = billReader.GetDateTime(14),
|
DueDate = dueDate,
|
||||||
ReminderSentAt = reminderSentAt,
|
ReminderSentAt = reminderSentAt,
|
||||||
OverdueNotifiedAt = overdueNotifiedAt,
|
OverdueNotifiedAt = overdueNotifiedAt,
|
||||||
LineItemsJson = lineItemsJson,
|
LineItemsJson = lineItemsJson,
|
||||||
LineItems = lineItems,
|
LineItems = lineItems,
|
||||||
Payments = payments,
|
Payments = payments,
|
||||||
Notes = notes,
|
Notes = notes,
|
||||||
CreatedAt = billReader.GetDateTime(19),
|
CreatedAt = createdAt,
|
||||||
CreatedBy = createdBy,
|
CreatedBy = createdBy,
|
||||||
UpdatedAt = billReader.IsDBNull(21) ? null : billReader.GetDateTime(21),
|
UpdatedAt = updatedAt,
|
||||||
UpdatedBy = updatedBy
|
UpdatedBy = updatedBy
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user