From 4bd160565d7d754b322e09f1bfc8d66d1fcddc82 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 20 Aug 2026 15:08:51 +0800 Subject: [PATCH] Complete only the reserved unit of work on response start --- .../AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs | 7 +++--- .../Mvc/Uow/UnitOfWorkMiddleware_Tests.cs | 7 ++++++ .../Mvc/Uow/UnitOfWorkTestController.cs | 24 +++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs index 4c1b11fc1d..534d8f9828 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/Uow/AbpUnitOfWorkMiddleware.cs @@ -39,12 +39,13 @@ public class AbpUnitOfWorkMiddleware : AbpMiddlewareBase, ITransientDependency { // Commit the ambient unit of work before the response starts, so data written // during the request is committed before the response is flushed to the client. + // Only when this reserved unit of work is the current one: if an explicit nested + // unit of work is in progress, it is the current one and must be left to its owner. context.Response.OnStarting(async () => { - var currentUow = _unitOfWorkManager.Current; - if (currentUow != null && !currentUow.IsCompleted) + if (_unitOfWorkManager.Current == uow) { - await currentUow.CompleteAsync(_cancellationTokenProvider.Token); + await uow.CompleteAsync(_cancellationTokenProvider.Token); } }); diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs index b829df769d..7953162f40 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs @@ -68,4 +68,11 @@ public class UnitOfWorkMiddleware_Tests : AspNetCoreMvcTestBase var body = await GetResponseAsStringAsync("/api/unitofwork-test/RawDatabaseProviderAfterResponseFlush"); body.ShouldBe("first:threw-AbpException"); } + + [Fact] + public async Task Response_Flush_Inside_Nested_Uow_Should_Not_Complete_The_Nested_Uow() + { + var body = await GetResponseAsStringAsync("/api/unitofwork-test/NestedUowDuringResponseFlush"); + body.ShouldBe("first:nested-completed-by-owner"); + } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestController.cs index 106946a238..cdb7c80917 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestController.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkTestController.cs @@ -151,4 +151,28 @@ public class UnitOfWorkTestController : AbpController await Response.WriteAsync(outcome); } + + [HttpGet] + [Route("NestedUowDuringResponseFlush")] + public async Task NestedUowDuringResponseFlush() + { + using (var nested = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false)) + { + await Response.WriteAsync("first"); + await Response.Body.FlushAsync(); + + string outcome; + try + { + await nested.CompleteAsync(); + outcome = ":nested-completed-by-owner"; + } + catch (AbpException) + { + outcome = ":nested-already-completed"; + } + + await Response.WriteAsync(outcome); + } + } }