diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 53ae0da753..93517776b6 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; +using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Validation; namespace Volo.Abp.AspNetCore.Mvc @@ -24,7 +25,7 @@ namespace Volo.Abp.AspNetCore.Mvc //options.Filters.AddService(typeof(AbpAuthorizationFilter)); //options.Filters.AddService(typeof(AbpAuditActionFilter)); options.Filters.AddService(typeof(AbpValidationActionFilter)); - //options.Filters.AddService(typeof(AbpUowActionFilter)); + options.Filters.AddService(typeof(AbpUowActionFilter)); options.Filters.AddService(typeof(AbpExceptionFilter)); //options.Filters.AddService(typeof(AbpResultFilter)); } diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs index a915df25ae..d2aa6f9e53 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs @@ -15,7 +15,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow public async Task Invoke(HttpContext httpContext, IUnitOfWorkManager unitOfWorkManager) { - using (var uow = unitOfWorkManager.Begin()) + using (var uow = unitOfWorkManager.Reserve(AbpUowActionFilter.UnitOfWorkReservationName)) { await _next(httpContext); await uow.CompleteAsync(); diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs new file mode 100644 index 0000000000..dcf7d3caa8 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Filters; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Uow; + +namespace Volo.Abp.AspNetCore.Mvc.Uow +{ + public class AbpUowActionFilter : IAsyncActionFilter, ITransientDependency + { + public const string UnitOfWorkReservationName = "_AbpActionUnitOfWork"; + + private readonly IUnitOfWorkManager _unitOfWorkManager; + + public AbpUowActionFilter(IUnitOfWorkManager unitOfWorkManager) + { + _unitOfWorkManager = unitOfWorkManager; + } + + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + _unitOfWorkManager.BeginReserved(UnitOfWorkReservationName); + await next(); + } + } +} diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index c20d106e4e..db4ae98b9c 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -9,6 +9,28 @@ namespace Volo.Abp.Uow { public Guid Id => _parent.Id; + public IUnitOfWork Outer + { + get => _parent.Outer; + } + + public bool IsReserved + { + get => _parent.IsReserved; + set => _parent.IsReserved = value; + } + + public string ReservationName + { + get => _parent.ReservationName; + set => _parent.ReservationName = value; + } + + public void SetOuter(IUnitOfWork outer) + { + _parent.SetOuter(outer); + } + public event EventHandler Completed; public event EventHandler Failed; public event EventHandler Disposed; diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs index 01d6baeb2f..52451ae55d 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs @@ -1,7 +1,15 @@ -namespace Volo.Abp.Uow +using JetBrains.Annotations; + +namespace Volo.Abp.Uow { public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer { - + IUnitOfWork Outer { get; } + + bool IsReserved { get; set; } + + string ReservationName { get; set; } + + void SetOuter([CanBeNull] IUnitOfWork outer); } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs index 338b51750f..6d26cccb4e 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs @@ -9,20 +9,8 @@ namespace Volo.Abp.Uow [NotNull] IBasicUnitOfWork Begin([NotNull] UnitOfWorkStartOptions options); - } - - public static class UnitOfWorkManagerExtensions - { - [NotNull] - public static IBasicUnitOfWork Begin(this IUnitOfWorkManager unitOfWorkManager) - { - return unitOfWorkManager.Begin(new UnitOfWorkStartOptions()); - } [NotNull] - public static IBasicUnitOfWork BeginNew(this IUnitOfWorkManager unitOfWorkManager) - { - return unitOfWorkManager.Begin(new UnitOfWorkStartOptions {RequiresNew = true}); - } + IBasicUnitOfWork BeginReserved([NotNull] string reservationName); } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index 2207f94fcc..961129528e 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -10,6 +10,17 @@ namespace Volo.Abp.Uow { public Guid Id { get; } = Guid.NewGuid(); + public IUnitOfWork Outer { get; private set; } + + public bool IsReserved { get; set; } + + public string ReservationName { get; set; } + + public void SetOuter(IUnitOfWork outer) + { + Outer = outer; + } + public event EventHandler Completed; public event EventHandler Failed; public event EventHandler Disposed; diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkExtensions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkExtensions.cs new file mode 100644 index 0000000000..5c01f54a99 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkExtensions.cs @@ -0,0 +1,14 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Uow +{ + public static class UnitOfWorkExtensions + { + public static bool IsReservedFor([NotNull] this IUnitOfWork unitOfWork, string reservationName) + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + return unitOfWork.IsReserved && unitOfWork.ReservationName == reservationName; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index 73c1a0ca8f..a5a0d18880 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.Uow { public class UnitOfWorkManager : IUnitOfWorkManager, ISingletonDependency { - public IUnitOfWork Current => _ambientUnitOfWork.UnitOfWork; + public IUnitOfWork Current => GetCurrentUnitOfWork(); private readonly IServiceProvider _serviceProvider; private readonly IAmbientUnitOfWork _ambientUnitOfWork; @@ -21,18 +21,28 @@ namespace Volo.Abp.Uow { Check.NotNull(options, nameof(options)); - if (!options.RequiresNew && _ambientUnitOfWork.UnitOfWork != null) + if (!options.RequiresNew && _ambientUnitOfWork.UnitOfWork != null && !_ambientUnitOfWork.UnitOfWork.IsReserved) { return new ChildUnitOfWork(_ambientUnitOfWork.UnitOfWork); } - var parentUow = _ambientUnitOfWork.UnitOfWork; + if (_ambientUnitOfWork.UnitOfWork != null) + { + //Requires new because there is already a current UOW but it's reserved + options.RequiresNew = true; + } + + var outerUow = _ambientUnitOfWork.UnitOfWork; var scope = _serviceProvider.CreateScope(); IUnitOfWork unitOfWork; try { unitOfWork = scope.ServiceProvider.GetRequiredService(); + + unitOfWork.SetOuter(outerUow); + unitOfWork.IsReserved = options.ReservationName != null; + unitOfWork.ReservationName = options.ReservationName; } catch { @@ -44,11 +54,45 @@ namespace Volo.Abp.Uow unitOfWork.Disposed += (sender, args) => { - _ambientUnitOfWork.SetUnitOfWork(parentUow); + _ambientUnitOfWork.SetUnitOfWork(outerUow); scope.Dispose(); }; return unitOfWork; } + + public IBasicUnitOfWork BeginReserved(string reservationName) + { + Check.NotNull(reservationName, nameof(reservationName)); + + var uow = _ambientUnitOfWork.UnitOfWork; + + //Find reserved unit of work starting from current and going to outers + while (uow != null && !uow.IsReservedFor(reservationName)) + { + uow = uow.Outer; + } + + if (uow == null) + { + throw new AbpException($"Could not find a reserved unit of work with reservation name: {reservationName}"); + } + + uow.IsReserved = false; + return uow; + } + + private IUnitOfWork GetCurrentUnitOfWork() + { + var uow = _ambientUnitOfWork.UnitOfWork; + + //Skip reserved unit of work + while (uow != null && uow.IsReserved) + { + uow = uow.Outer; + } + + return uow; + } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs new file mode 100644 index 0000000000..0a6897e6ee --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs @@ -0,0 +1,32 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Uow +{ + public static class UnitOfWorkManagerExtensions + { + [NotNull] + public static IBasicUnitOfWork Begin([NotNull] this IUnitOfWorkManager unitOfWorkManager) + { + Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); + + return unitOfWorkManager.Begin(new UnitOfWorkStartOptions()); + } + + [NotNull] + public static IBasicUnitOfWork BeginNew([NotNull] this IUnitOfWorkManager unitOfWorkManager) + { + Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); + + return unitOfWorkManager.Begin(new UnitOfWorkStartOptions {RequiresNew = true}); + } + + [NotNull] + public static IBasicUnitOfWork Reserve([NotNull] this IUnitOfWorkManager unitOfWorkManager, [NotNull] string reservationName) + { + Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); + Check.NotNull(reservationName, nameof(reservationName)); + + return unitOfWorkManager.Begin(new UnitOfWorkStartOptions { ReservationName = reservationName }); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs index daf0742861..7331902190 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs @@ -3,5 +3,7 @@ public class UnitOfWorkStartOptions { public bool RequiresNew { get; set; } + + public string ReservationName { get; set; } } } \ No newline at end of file diff --git a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs index c0452c67e8..d26080d03d 100644 --- a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs +++ b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs @@ -42,5 +42,36 @@ namespace Volo.Abp.Uow _unitOfWorkManager.Current.ShouldBeNull(); } + + [Fact] + public async Task UnitOfWorkManager_Reservation_Test() + { + _unitOfWorkManager.Current.ShouldBeNull(); + + using (var uow1 = _unitOfWorkManager.Reserve("Reservation1")) + { + _unitOfWorkManager.Current.ShouldBeNull(); + + using (var uow2 = _unitOfWorkManager.Begin()) + { + _unitOfWorkManager.Current.ShouldNotBeNull(); + _unitOfWorkManager.Current.Id.ShouldNotBe(uow1.Id); + + await uow2.CompleteAsync(); + } + + _unitOfWorkManager.Current.ShouldBeNull(); + + var reserverUow = _unitOfWorkManager.BeginReserved("Reservation1"); + + _unitOfWorkManager.Current.ShouldNotBeNull(); + _unitOfWorkManager.Current.Id.ShouldBe(reserverUow.Id); + _unitOfWorkManager.Current.Id.ShouldBe(uow1.Id); + + await uow1.CompleteAsync(); + } + + _unitOfWorkManager.Current.ShouldBeNull(); + } } }