From a5abd6ef9037dc7479e4c6eb535b65113d24fe7f Mon Sep 17 00:00:00 2001 From: MSuMshk <2039814060@qq.com> Date: Thu, 18 Dec 2025 11:45:44 +0800 Subject: [PATCH] =?UTF-8?q?fix(billing):=20=E4=BF=AE=E5=A4=8D=E8=B4=A6?= =?UTF-8?q?=E5=8D=95=E8=AF=A6=E6=83=85=E6=9F=A5=E8=AF=A2=E7=9A=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=B9=B6=E5=8F=91=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: - Npgsql.NpgsqlOperationInProgressException: A command is already in progress - 在同一个数据库连接上,billReader 未释放就执行 paymentReader 根因: - GetBillingDetailQueryHandler 中先查询账单并打开 billReader - 读取账单数据后未释放 reader - 直接在同一连接上执行支付记录查询,触发并发异常 解决方案: - 将账单字段先读取到本地变量 - 主动 DisposeAsync 释放 billReader - 再执行支付记录查询 - 最后用本地变量组装 BillingDetailDto --- .../Handlers/GetBillingDetailQueryHandler.cs | 64 ++++++++++++------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingDetailQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingDetailQueryHandler.cs index 50a90d4..23d7e21 100644 --- a/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingDetailQueryHandler.cs +++ b/src/Application/TakeoutSaaS.Application/App/Billings/Handlers/GetBillingDetailQueryHandler.cs @@ -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(); if (!string.IsNullOrWhiteSpace(lineItemsJson)) { @@ -67,7 +88,7 @@ public sealed class GetBillingDetailQueryHandler( } } - // 1.3 (空行后) 查询支付记录 + // 1.5 (空行后) 查询支付记录 var payments = new List(); 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 }; },