From f12c0ccf6c63b7eac3b53fc0c1d5c48717ef254a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 25 Sep 2017 15:42:54 +0300 Subject: [PATCH] Improvements on UOW. --- .../AspNetCore/Mvc/Uow/AbpUowActionFilter.cs | 16 ++++-- .../DbContextOptionsFactory.cs | 25 +++++++--- .../DbContextOptionsFactoryContext.cs | 27 ++++++++++ .../UnitOfWorkDbContextProvider.cs | 22 +++++--- src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs | 12 +++-- src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs | 4 ++ .../Volo/Abp/Uow/IUnitOfWorkManager.cs | 4 +- src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs | 7 +++ .../Volo/Abp/Uow/UnitOfWorkAttribute.cs | 43 ++++++++++++++++ src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs | 18 +++++++ .../Volo/Abp/Uow/UnitOfWorkManager.cs | 8 +-- .../Abp/Uow/UnitOfWorkManagerExtensions.cs | 16 ++++++ .../Volo/Abp/Uow/UnitOfWorkStartOptions.cs | 50 ++++++++++++++++++- 13 files changed, 225 insertions(+), 27 deletions(-) create mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs 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 index 84d85ba08a..5996724a20 100644 --- 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 @@ -24,15 +24,25 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow return; } - if (_unitOfWorkManager.TryBeginReserved(UnitOfWorkReservationName)) + var unitOfWorkAttr = UnitOfWorkHelper.GetUnitOfWorkAttributeOrNull(context.ActionDescriptor.GetMethodInfo()); + + if (unitOfWorkAttr?.IsDisabled == true) { await next(); return; } - //TODO: Check if disabled. Get and apply attributes to control UOW. + var options = new UnitOfWorkStartOptions(); + + unitOfWorkAttr?.SetOptions(options); + + if (_unitOfWorkManager.TryBeginReserved(UnitOfWorkReservationName, options)) + { + await next(); + return; + } - using (var uow = _unitOfWorkManager.Begin()) + using (var uow = _unitOfWorkManager.Begin(options)) { var result = await next(); if (result.Exception == null || result.ExceptionHandled) diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs index 29a9572977..187c56bc21 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs @@ -12,13 +12,13 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection public static DbContextOptions Create(IServiceProvider serviceProvider) where TDbContext : AbpDbContext { - var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(); - using (var scope = serviceProvider.CreateScope()) { + var creationContext = GetCreationContext(scope.ServiceProvider); + var context = new AbpDbContextConfigurationContext( - GetConnectionString(scope, connectionStringName), - connectionStringName, + creationContext.ConnectionString, + creationContext.ConnectionStringName, scope.ServiceProvider ); @@ -47,9 +47,22 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection return scope.ServiceProvider.GetRequiredService>().Value; } - private static string GetConnectionString(IServiceScope scope, string connectionStringName) + private static DbContextOptionsFactoryContext GetCreationContext(IServiceProvider serviceProvider) + where TDbContext : AbpDbContext { - return scope.ServiceProvider.GetRequiredService().Resolve(connectionStringName); + var context = DbContextOptionsFactoryContext.Current; + if (context != null) + { + return context; + } + + var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(); + var connectionString = serviceProvider.GetRequiredService().Resolve(connectionStringName); + + return new DbContextOptionsFactoryContext( + connectionStringName, + connectionString + ); } } } \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs new file mode 100644 index 0000000000..936d685bc1 --- /dev/null +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading; + +namespace Volo.Abp.EntityFrameworkCore.DependencyInjection +{ + internal class DbContextOptionsFactoryContext + { + public static DbContextOptionsFactoryContext Current => _current.Value; + private static readonly AsyncLocal _current = new AsyncLocal(); + + public string ConnectionStringName { get; } + + public string ConnectionString { get; } + + public DbContextOptionsFactoryContext(string connectionStringName, string connectionString) + { + ConnectionStringName = connectionStringName; + ConnectionString = connectionString; + } + + public static IDisposable Use(DbContextOptionsFactoryContext context) + { + _current.Value = context; + return new DisposeAction(() => _current.Value = null); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs index 2aae39d871..bfa43617b5 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.DependencyInjection; namespace Volo.Abp.Uow.EntityFrameworkCore { @@ -26,16 +27,21 @@ namespace Volo.Abp.Uow.EntityFrameworkCore throw new AbpException("A DbContext can only be created inside a unit of work!"); } - var connectionString = _connectionStringResolver.Resolve(); + var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(); + var connectionString = _connectionStringResolver.Resolve(connectionStringName); + var dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}"; - var databaseApi = unitOfWork.GetOrAddDatabaseApi( - dbContextKey, - () => new DbContextDatabaseApi( - unitOfWork.ServiceProvider.GetRequiredService() - )); - - return ((DbContextDatabaseApi)databaseApi).DbContext; + using (DbContextOptionsFactoryContext.Use(new DbContextOptionsFactoryContext(connectionStringName, connectionString))) + { + var databaseApi = unitOfWork.GetOrAddDatabaseApi( + dbContextKey, + () => new DbContextDatabaseApi( + unitOfWork.ServiceProvider.GetRequiredService() + )); + + return ((DbContextDatabaseApi)databaseApi).DbContext; + } } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index 508017f39c..15e03332f6 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -9,10 +9,9 @@ namespace Volo.Abp.Uow { public Guid Id => _parent.Id; - public IUnitOfWork Outer - { - get => _parent.Outer; - } + public UnitOfWorkStartOptions Options => _parent.Options; + + public IUnitOfWork Outer => _parent.Outer; public bool IsReserved { @@ -31,6 +30,11 @@ namespace Volo.Abp.Uow _parent.SetOuter(outer); } + public void SetOptions(UnitOfWorkStartOptions options) + { + _parent.SetOptions(options); + } + 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 52451ae55d..442c03c075 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs @@ -4,6 +4,8 @@ namespace Volo.Abp.Uow { public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer { + UnitOfWorkStartOptions Options { get; } + IUnitOfWork Outer { get; } bool IsReserved { get; set; } @@ -11,5 +13,7 @@ namespace Volo.Abp.Uow string ReservationName { get; set; } void SetOuter([CanBeNull] IUnitOfWork outer); + + void SetOptions(UnitOfWorkStartOptions options); } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs index 1cd71e557e..493a5b1c78 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs @@ -10,8 +10,8 @@ namespace Volo.Abp.Uow [NotNull] IBasicUnitOfWork Begin([NotNull] UnitOfWorkStartOptions options); - void BeginReserved([NotNull] string reservationName); + void BeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkStartOptions options); - bool TryBeginReserved([NotNull] string reservationName); + bool TryBeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkStartOptions options); } } \ 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 5ed1014293..2409fbd4ba 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -10,6 +10,8 @@ namespace Volo.Abp.Uow { public Guid Id { get; } = Guid.NewGuid(); + public UnitOfWorkStartOptions Options { get; private set; } + public IUnitOfWork Outer { get; private set; } public bool IsReserved { get; set; } @@ -21,6 +23,11 @@ namespace Volo.Abp.Uow Outer = outer; } + public void SetOptions(UnitOfWorkStartOptions options) + { + Options = options; + } + public event EventHandler Completed; public event EventHandler Failed; public event EventHandler Disposed; diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs index 1425d7c263..3b3f68bbba 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Data; namespace Volo.Abp.Uow { @@ -11,5 +12,47 @@ namespace Volo.Abp.Uow [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)] public class UnitOfWorkAttribute : Attribute { + /// + /// Is this UOW transactional? + /// Uses default value if not supplied. + /// + public bool? IsTransactional { get; set; } + + /// + /// Timeout of UOW As milliseconds. + /// Uses default value if not supplied. + /// + public TimeSpan? Timeout { get; set; } + + /// + /// If this UOW is transactional, this option indicated the isolation level of the transaction. + /// Uses default value if not supplied. + /// + public IsolationLevel? IsolationLevel { get; set; } + + /// + /// Used to prevent starting a unit of work for the method. + /// If there is already a started unit of work, this property is ignored. + /// Default: false. + /// + public bool IsDisabled { get; set; } + + public virtual void SetOptions(UnitOfWorkStartOptions options) + { + if (IsTransactional.HasValue) + { + options.IsTransactional = IsTransactional; + } + + if (Timeout.HasValue) + { + options.Timeout = Timeout; + } + + if (IsolationLevel.HasValue) + { + options.IsolationLevel = IsolationLevel; + } + } } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs index fe8d91faef..4f1a50f429 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs @@ -54,6 +54,24 @@ namespace Volo.Abp.Uow return false; } + public static UnitOfWorkAttribute GetUnitOfWorkAttributeOrNull(MethodInfo methodInfo) + { + var attrs = methodInfo.GetCustomAttributes(true).OfType().ToArray(); + if (attrs.Length > 0) + { + return attrs[0]; + } + + attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType().ToArray(); + if (attrs.Length > 0) + { + return attrs[0]; + } + + return null; + } + + private static bool AnyMethodHasUnitOfWorkAttribute(TypeInfo implementationType) { return implementationType diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index 9d0e1d153a..334c07a369 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -35,15 +35,15 @@ namespace Volo.Abp.Uow return CreateUnitOfWork(options); } - public void BeginReserved(string reservationName) + public void BeginReserved(string reservationName, UnitOfWorkStartOptions options) { - if (!TryBeginReserved(reservationName)) + if (!TryBeginReserved(reservationName, options)) { throw new AbpException($"Could not find a reserved unit of work with reservation name: {reservationName}"); } } - public bool TryBeginReserved(string reservationName) + public bool TryBeginReserved(string reservationName, UnitOfWorkStartOptions options) { Check.NotNull(reservationName, nameof(reservationName)); @@ -61,6 +61,7 @@ namespace Volo.Abp.Uow } uow.IsReserved = false; + uow.SetOptions(options); return true; } @@ -90,6 +91,7 @@ namespace Volo.Abp.Uow unitOfWork.SetOuter(outerUow); unitOfWork.IsReserved = options.ReservationName != null; unitOfWork.ReservationName = options.ReservationName; + unitOfWork.SetOptions(options); _ambientUnitOfWork.SetUnitOfWork(unitOfWork); diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs index 0a6897e6ee..5be61ae8ae 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs @@ -28,5 +28,21 @@ namespace Volo.Abp.Uow return unitOfWorkManager.Begin(new UnitOfWorkStartOptions { ReservationName = reservationName }); } + + public static void BeginReserved([NotNull] this IUnitOfWorkManager unitOfWorkManager, [NotNull] string reservationName) + { + Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); + Check.NotNull(reservationName, nameof(reservationName)); + + unitOfWorkManager.BeginReserved(reservationName, new UnitOfWorkStartOptions()); + } + + public static void TryBeginReserved([NotNull] this IUnitOfWorkManager unitOfWorkManager, [NotNull] string reservationName) + { + Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); + Check.NotNull(reservationName, nameof(reservationName)); + + unitOfWorkManager.TryBeginReserved(reservationName, new UnitOfWorkStartOptions()); + } } } \ 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 7331902190..ba398250e9 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs @@ -1,9 +1,57 @@ -namespace Volo.Abp.Uow +using System; +using System.Data; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Uow { public class UnitOfWorkStartOptions { + //Revise this since Begin/BeginReserved accepts different options and that can make confusion! + public bool RequiresNew { get; set; } public string ReservationName { get; set; } + + public bool? IsTransactional { get; set; } + + public IsolationLevel? IsolationLevel { get; set; } + + public TimeSpan? Timeout { get; set; } + } + + /// + /// Global (default) unit of work options + /// + public class UnitOfWorkOptions + { + public IUnitOfWorkTransactionBehavior IsTransactional { get; set; } + + public IsolationLevel? IsolationLevel { get; set; } + } + + public interface IUnitOfWorkTransactionBehavior + { + UnitOfWorkStartOptions Set(UnitOfWorkTransactionBehaviorContext context); + } + + public class ConventionalUnitOfWorkTransactionBehavior : IUnitOfWorkTransactionBehavior + { + public UnitOfWorkStartOptions Set(UnitOfWorkTransactionBehaviorContext context) + { + throw new NotImplementedException(); + } + } + + public class UnitOfWorkTransactionBehaviorContext : IServiceProviderAccessor + { + public UnitOfWorkStartOptions Options { get; } + + public IServiceProvider ServiceProvider { get; } + + public UnitOfWorkTransactionBehaviorContext(UnitOfWorkStartOptions options, IServiceProvider serviceProvider) + { + Options = options; + ServiceProvider = serviceProvider; + } } } \ No newline at end of file