Merge pull request #112 from aspnetzero/uow-enhancements

Uow enhancements
pull/113/head
Halil İbrahim Kalkan 7 years ago committed by GitHub
commit 1835c37d63

@ -138,6 +138,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Domain",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Domain.Shared", "src\Volo.Abp.Identity.Domain.Shared\Volo.Abp.Identity.Domain.Shared.csproj", "{DF676F73-3FC9-46CE-909A-2D75E19982AD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCore.Tests", "test\Volo.Abp.EntityFrameworkCore.Tests\Volo.Abp.EntityFrameworkCore.Tests.csproj", "{3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleConsoleDemo", "test\SimpleConsoleDemo\SimpleConsoleDemo.csproj", "{2B48CF90-DBDB-469F-941C-5B5AECEEACE0}"
EndProject
Global
@ -350,6 +351,10 @@ Global
{DF676F73-3FC9-46CE-909A-2D75E19982AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF676F73-3FC9-46CE-909A-2D75E19982AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF676F73-3FC9-46CE-909A-2D75E19982AD}.Release|Any CPU.Build.0 = Release|Any CPU
{3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8}.Release|Any CPU.Build.0 = Release|Any CPU
{2B48CF90-DBDB-469F-941C-5B5AECEEACE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B48CF90-DBDB-469F-941C-5B5AECEEACE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B48CF90-DBDB-469F-941C-5B5AECEEACE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -419,6 +424,7 @@ Global
{40E21A35-1C66-4E89-A16E-0475011F7EFD} = {146F561E-C7B8-4166-9383-47E1BC1A2E62}
{43D4005C-4F04-4128-937B-52BEAC5A113B} = {1895A5C9-50D4-4568-9A3A-14657E615A5E}
{DF676F73-3FC9-46CE-909A-2D75E19982AD} = {1895A5C9-50D4-4568-9A3A-14657E615A5E}
{3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8} = {37087D1B-3693-4E96-983D-A69F210BDE53}
{2B48CF90-DBDB-469F-941C-5B5AECEEACE0} = {37087D1B-3693-4E96-983D-A69F210BDE53}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution

@ -0,0 +1,12 @@
using Volo.Abp.AspNetCore.Mvc.Uow;
namespace Microsoft.AspNetCore.Builder
{
public static class AbpAspNetCoreMvcApplicationBuilderExtensions
{
public static IApplicationBuilder UseUnitOfWork(this IApplicationBuilder app)
{
return app.UseMiddleware<AbpUnitOfWorkMiddleware>();
}
}
}

@ -1,10 +1,27 @@
using Microsoft.AspNetCore.Mvc;
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Uow;
namespace Volo.Abp.AspNetCore.Mvc
{
public abstract class AbpController : Controller, ITransientDependency
{
public IUnitOfWorkManager UnitOfWorkManager { get; set; }
public IObjectMapper ObjectMapper { get; set; }
public IGuidGenerator GuidGenerator { get; set; }
public ILoggerFactory LoggerFactory { get; set; }
protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current;
protected ILogger Logger => _lazyLogger.Value;
private Lazy<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true);
}
}

@ -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));
}

@ -0,0 +1,25 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Volo.Abp.Uow;
namespace Volo.Abp.AspNetCore.Mvc.Uow
{
public class AbpUnitOfWorkMiddleware
{
private readonly RequestDelegate _next;
public AbpUnitOfWorkMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, IUnitOfWorkManager unitOfWorkManager)
{
using (var uow = unitOfWorkManager.Reserve(AbpUowActionFilter.UnitOfWorkReservationName))
{
await _next(httpContext);
await uow.CompleteAsync(httpContext.RequestAborted);
}
}
}
}

@ -0,0 +1,72 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;
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;
private readonly UnitOfWorkDefaultOptions _defaultOptions;
public AbpUowActionFilter(IUnitOfWorkManager unitOfWorkManager, IOptions<UnitOfWorkDefaultOptions> options)
{
_unitOfWorkManager = unitOfWorkManager;
_defaultOptions = options.Value;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.ActionDescriptor.IsControllerAction())
{
await next();
return;
}
var methodInfo = context.ActionDescriptor.GetMethodInfo();
var unitOfWorkAttr = UnitOfWorkHelper.GetUnitOfWorkAttributeOrNull(methodInfo);
if (unitOfWorkAttr?.IsDisabled == true)
{
await next();
return;
}
var options = CreateOptions(context, unitOfWorkAttr);
if (_unitOfWorkManager.TryBeginReserved(UnitOfWorkReservationName, options))
{
await next();
return;
}
using (var uow = _unitOfWorkManager.Begin(options))
{
var result = await next();
if (result.Exception == null || result.ExceptionHandled)
{
await uow.CompleteAsync(context.HttpContext.RequestAborted);
}
}
}
private UnitOfWorkOptions CreateOptions(ActionExecutingContext context, UnitOfWorkAttribute unitOfWorkAttr)
{
var options = new UnitOfWorkOptions();
unitOfWorkAttr?.SetOptions(options);
options.IsTransactional = _defaultOptions.CalculateIsTransactional(
autoValue: !string.Equals(context.HttpContext.Request.Method, HttpMethod.Get.Method, StringComparison.OrdinalIgnoreCase)
);
return options;
}
}
}

@ -17,6 +17,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.0.0" />
</ItemGroup>
</Project>

@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.EntityFrameworkCore
{
public static class DatabaseFacadeExtensions
{
public static bool IsRelational(this DatabaseFacade database)
{
return database.GetInfrastructure().GetService<IRelationalConnection>() != null;
}
}
}

@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
namespace Volo.Abp.EntityFrameworkCore
{
internal static class DbContextExtensions
{
public static bool HasRelationalTransactionManager(this DbContext dbContext)
{
return dbContext.Database.GetService<IDbContextTransactionManager>() is IRelationalTransactionManager;
}
}
}

@ -0,0 +1,31 @@
using System;
using System.Data.Common;
using System.Threading;
namespace Volo.Abp.EntityFrameworkCore.DependencyInjection
{
public class DbContextCreationContext
{
public static DbContextCreationContext Current => _current.Value;
private static readonly AsyncLocal<DbContextCreationContext> _current = new AsyncLocal<DbContextCreationContext>();
public string ConnectionStringName { get; }
public string ConnectionString { get; }
public DbConnection ExistingConnection { get; set; }
public DbContextCreationContext(string connectionStringName, string connectionString)
{
ConnectionStringName = connectionStringName;
ConnectionString = connectionString;
}
public static IDisposable Use(DbContextCreationContext context)
{
var previousValue = Current;
_current.Value = context;
return new DisposeAction(() => _current.Value = previousValue);
}
}
}

@ -12,44 +12,55 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection
public static DbContextOptions<TDbContext> Create<TDbContext>(IServiceProvider serviceProvider)
where TDbContext : AbpDbContext<TDbContext>
{
var connectionStringName = ConnectionStringNameAttribute.GetConnStringName<TDbContext>();
var creationContext = GetCreationContext<TDbContext>(serviceProvider);
var context = new AbpDbContextConfigurationContext<TDbContext>(
creationContext.ConnectionString,
creationContext.ConnectionStringName,
serviceProvider
);
using (var scope = serviceProvider.CreateScope())
var dbContextOptions = GetDbContextOptions<TDbContext>(serviceProvider);
var configureAction = dbContextOptions.ConfigureActions.GetOrDefault(typeof(TDbContext));
if (configureAction != null)
{
((Action<AbpDbContextConfigurationContext<TDbContext>>)configureAction).Invoke(context);
}
else if (dbContextOptions.DefaultConfigureAction != null)
{
dbContextOptions.DefaultConfigureAction.Invoke(context);
}
else
{
var context = new AbpDbContextConfigurationContext<TDbContext>(
GetConnectionString(scope, connectionStringName),
connectionStringName,
scope.ServiceProvider
);
var dbContextOptions = GetDbContextOptions<TDbContext>(scope);
var configureAction = dbContextOptions.ConfigureActions.GetOrDefault(typeof(TDbContext));
if (configureAction != null)
{
((Action<AbpDbContextConfigurationContext<TDbContext>>)configureAction).Invoke(context);
}
else if (dbContextOptions.DefaultConfigureAction != null)
{
dbContextOptions.DefaultConfigureAction.Invoke(context);
}
else
{
throw new AbpException($"No configuration found for {typeof(DbContext).AssemblyQualifiedName}! Use services.Configure<AbpDbContextOptions>(...) to configure it.");
}
return context.DbContextOptions.Options;
throw new AbpException($"No configuration found for {typeof(DbContext).AssemblyQualifiedName}! Use services.Configure<AbpDbContextOptions>(...) to configure it.");
}
return context.DbContextOptions.Options;
}
private static AbpDbContextOptions GetDbContextOptions<TDbContext>(IServiceScope scope) where TDbContext : AbpDbContext<TDbContext>
private static AbpDbContextOptions GetDbContextOptions<TDbContext>(IServiceProvider serviceProvider)
where TDbContext : AbpDbContext<TDbContext>
{
return scope.ServiceProvider.GetRequiredService<IOptions<AbpDbContextOptions>>().Value;
return serviceProvider.GetRequiredService<IOptions<AbpDbContextOptions>>().Value;
}
private static string GetConnectionString(IServiceScope scope, string connectionStringName)
private static DbContextCreationContext GetCreationContext<TDbContext>(IServiceProvider serviceProvider)
where TDbContext : AbpDbContext<TDbContext>
{
return scope.ServiceProvider.GetRequiredService<IConnectionStringResolver>().Resolve(connectionStringName);
var context = DbContextCreationContext.Current;
if (context != null)
{
return context;
}
var connectionStringName = ConnectionStringNameAttribute.GetConnStringName<TDbContext>();
var connectionString = serviceProvider.GetRequiredService<IConnectionStringResolver>().Resolve(connectionStringName);
return new DbContextCreationContext(
connectionStringName,
connectionString
);
}
}
}

@ -4,12 +4,12 @@ using Volo.Abp.EntityFrameworkCore;
namespace Volo.Abp.Uow.EntityFrameworkCore
{
public class DbContextDatabaseApi<TDbContext> : IDatabaseApi, ISupportsSavingChanges
public class EfCoreDatabaseApi<TDbContext> : IDatabaseApi, ISupportsSavingChanges
where TDbContext : AbpDbContext<TDbContext>
{
public TDbContext DbContext { get; }
public DbContextDatabaseApi(TDbContext dbContext)
public EfCoreDatabaseApi(TDbContext dbContext)
{
DbContext = dbContext;
}

@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Volo.Abp.EntityFrameworkCore;
namespace Volo.Abp.Uow.EntityFrameworkCore
{
public class EfCoreTransactionApi : ITransactionApi, ISupportsRollback
{
public IDbContextTransaction DbContextTransaction { get; }
public DbContext StarterDbContext { get; }
public List<DbContext> AttendedDbContexts { get; }
public EfCoreTransactionApi(IDbContextTransaction dbContextTransaction, DbContext starterDbContext)
{
DbContextTransaction = dbContextTransaction;
StarterDbContext = starterDbContext;
AttendedDbContexts = new List<DbContext>();
}
public void Commit()
{
DbContextTransaction.Commit();
foreach (var dbContext in AttendedDbContexts)
{
if (dbContext.HasRelationalTransactionManager())
{
continue; //Relational databases use the shared transaction
}
dbContext.Database.CommitTransaction();
}
}
public Task CommitAsync()
{
Commit();
return Task.CompletedTask;
}
public void Dispose()
{
DbContextTransaction.Dispose();
}
public void Rollback()
{
DbContextTransaction.Rollback();
}
public Task RollbackAsync(CancellationToken cancellationToken)
{
DbContextTransaction.Rollback();
return Task.CompletedTask;
}
}
}

@ -1,9 +1,15 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
namespace Volo.Abp.Uow.EntityFrameworkCore
{
//TODO: Implement logic in DefaultDbContextResolver.Resolve in old ABP.
public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbContext>
where TDbContext : AbpDbContext<TDbContext>
{
@ -26,16 +32,87 @@ namespace Volo.Abp.Uow.EntityFrameworkCore
throw new AbpException("A DbContext can only be created inside a unit of work!");
}
var connectionString = _connectionStringResolver.Resolve<TDbContext>();
var connectionStringName = ConnectionStringNameAttribute.GetConnStringName<TDbContext>();
var connectionString = _connectionStringResolver.Resolve(connectionStringName);
var dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}";
var databaseApi = unitOfWork.GetOrAddDatabaseApi(
dbContextKey,
() => new DbContextDatabaseApi<TDbContext>(
unitOfWork.ServiceProvider.GetRequiredService<TDbContext>()
() => new EfCoreDatabaseApi<TDbContext>(
CreateDbContext(unitOfWork, connectionStringName, connectionString)
));
return ((DbContextDatabaseApi<TDbContext>)databaseApi).DbContext;
return ((EfCoreDatabaseApi<TDbContext>)databaseApi).DbContext;
}
private TDbContext CreateDbContext(IUnitOfWork unitOfWork, string connectionStringName, string connectionString)
{
var creationContext = new DbContextCreationContext(connectionStringName, connectionString);
using (DbContextCreationContext.Use(creationContext))
{
var dbContext = CreateDbContext(unitOfWork);
if (unitOfWork.Options.Timeout.HasValue &&
dbContext.Database.IsRelational() &&
!dbContext.Database.GetCommandTimeout().HasValue)
{
dbContext.Database.SetCommandTimeout(unitOfWork.Options.Timeout.Value.TotalSeconds.To<int>());
}
return dbContext;
}
}
private TDbContext CreateDbContext(IUnitOfWork unitOfWork)
{
return unitOfWork.Options.IsTransactional
? CreateDbContextWithTransaction(unitOfWork)
: unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
}
public TDbContext CreateDbContextWithTransaction(IUnitOfWork unitOfWork)
{
var transactionApiKey = $"EntityFrameworkCore_{DbContextCreationContext.Current.ConnectionString}";
var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as EfCoreTransactionApi;
if (activeTransaction == null)
{
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
var dbtransaction = unitOfWork.Options.IsolationLevel.HasValue
? dbContext.Database.BeginTransaction(unitOfWork.Options.IsolationLevel.Value)
: dbContext.Database.BeginTransaction();
unitOfWork.AddTransactionApi(
transactionApiKey,
new EfCoreTransactionApi(
dbtransaction,
dbContext
)
);
return dbContext;
}
else
{
DbContextCreationContext.Current.ExistingConnection = activeTransaction.DbContextTransaction.GetDbTransaction().Connection;
var dbContext = unitOfWork.ServiceProvider.GetRequiredService<TDbContext>();
if (dbContext.HasRelationalTransactionManager())
{
dbContext.Database.UseTransaction(activeTransaction.DbContextTransaction.GetDbTransaction());
}
else
{
dbContext.Database.BeginTransaction(); //TODO: Why not using the new created transaction?
}
activeTransaction.AttendedDbContexts.Add(dbContext);
return dbContext;
}
}
}
}

@ -39,6 +39,18 @@ namespace Volo.Abp
Modules = LoadModules(services, options);
}
public virtual void Shutdown()
{
ServiceProvider
.GetRequiredService<IModuleManager>()
.ShutdownModules(new ApplicationShutdownContext());
}
public virtual void Dispose()
{
}
private IReadOnlyList<IAbpModuleDescriptor> LoadModules(IServiceCollection services, AbpApplicationCreationOptions options)
{
return services
@ -50,16 +62,14 @@ namespace Volo.Abp
);
}
public virtual void Shutdown()
{
ServiceProvider
.GetRequiredService<IModuleManager>()
.ShutdownModules(new ApplicationShutdownContext());
}
public virtual void Dispose()
protected virtual void InitializeModules()
{
using (var scope = ServiceProvider.CreateScope())
{
ServiceProvider
.GetRequiredService<IModuleManager>()
.InitializeModules(new ApplicationInitializationContext(scope.ServiceProvider));
}
}
}
}

@ -1,7 +1,6 @@
using System;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace Volo.Abp
{
@ -25,12 +24,7 @@ namespace Volo.Abp
ServiceProvider = serviceProvider;
using (var scope = ServiceProvider.CreateScope())
{
ServiceProvider
.GetRequiredService<IModuleManager>()
.InitializeModules(new ApplicationInitializationContext(scope.ServiceProvider));
}
InitializeModules();
}
}
}

@ -1,7 +1,6 @@
using System;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
namespace Volo.Abp
{
@ -37,12 +36,7 @@ namespace Volo.Abp
ServiceScope = Services.BuildServiceProviderFromFactory().CreateScope();
ServiceProvider = ServiceScope.ServiceProvider;
using (var scope = ServiceProvider.CreateScope())
{
ServiceProvider
.GetRequiredService<IModuleManager>()
.InitializeModules(new ApplicationInitializationContext(scope.ServiceProvider));
}
InitializeModules();
}
public override void Dispose()

@ -37,6 +37,14 @@ namespace Volo.Abp
services.AddAssemblyOf<AbpKernelModule>();
services.TryAddObjectAccessor<IServiceProvider>();
services.Configure<ModuleLifecycleOptions>(options =>
{
options.Contributers.Add<OnPreApplicationInitializationModuleLifecycleContributer>();
options.Contributers.Add<OnApplicationInitializationModuleLifecycleContributer>();
options.Contributers.Add<OnPostApplicationInitializationModuleLifecycleContributer>();
options.Contributers.Add<OnApplicationShutdownModuleLifecycleContributer>();
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)

@ -15,10 +15,10 @@ namespace Volo.Abp
public IGuidGenerator GuidGenerator { get; set; }
protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current;
public ILoggerFactory LoggerFactory { get; set; }
protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current;
protected ILogger Logger => _lazyLogger.Value;
private Lazy<ILogger> _lazyLogger => new Lazy<ILogger>(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true);

@ -4,7 +4,14 @@ using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.Modularity
{
public abstract class AbpModule : IAbpModule, IOnApplicationInitialization, IOnApplicationShutdown, IPreConfigureServices, IPostConfigureServices
public abstract class AbpModule :
IAbpModule,
IOnPreApplicationInitialization,
IOnApplicationInitialization,
IOnPostApplicationInitialization,
IOnApplicationShutdown,
IPreConfigureServices,
IPostConfigureServices
{
public virtual void PreConfigureServices(IServiceCollection services)
{
@ -21,11 +28,21 @@ namespace Volo.Abp.Modularity
}
public virtual void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
}
public virtual void OnApplicationInitialization(ApplicationInitializationContext context)
{
}
public virtual void OnPostApplicationInitialization(ApplicationInitializationContext context)
{
}
public virtual void OnApplicationShutdown(ApplicationShutdownContext context)
{

@ -1,15 +1,34 @@
namespace Volo.Abp.Modularity
{
public class DefaultModuleLifecycleContributer : ModuleLifecycleContributerBase
public class OnApplicationInitializationModuleLifecycleContributer : ModuleLifecycleContributerBase
{
public override void Initialize(ApplicationInitializationContext context, IAbpModule module)
{
(module as IOnApplicationInitialization)?.OnApplicationInitialization(context);
}
}
public class OnApplicationShutdownModuleLifecycleContributer : ModuleLifecycleContributerBase
{
public override void Shutdown(ApplicationShutdownContext context, IAbpModule module)
{
(module as IOnApplicationShutdown)?.OnApplicationShutdown(context);
}
}
public class OnPreApplicationInitializationModuleLifecycleContributer : ModuleLifecycleContributerBase
{
public override void Initialize(ApplicationInitializationContext context, IAbpModule module)
{
(module as IOnPreApplicationInitialization)?.OnPreApplicationInitialization(context);
}
}
public class OnPostApplicationInitializationModuleLifecycleContributer : ModuleLifecycleContributerBase
{
public override void Initialize(ApplicationInitializationContext context, IAbpModule module)
{
(module as IOnPostApplicationInitialization)?.OnPostApplicationInitialization(context);
}
}
}

@ -0,0 +1,9 @@
using JetBrains.Annotations;
namespace Volo.Abp.Modularity
{
public interface IOnPostApplicationInitialization
{
void OnPostApplicationInitialization([NotNull] ApplicationInitializationContext context);
}
}

@ -0,0 +1,9 @@
using JetBrains.Annotations;
namespace Volo.Abp.Modularity
{
public interface IOnPreApplicationInitialization
{
void OnPreApplicationInitialization([NotNull] ApplicationInitializationContext context);
}
}

@ -0,0 +1,14 @@
using Volo.Abp.Collections;
namespace Volo.Abp.Modularity
{
public class ModuleLifecycleOptions
{
public ITypeList<IModuleLifecycleContributer> Contributers { get; }
public ModuleLifecycleOptions()
{
Contributers = new TypeList<IModuleLifecycleContributer>();
}
}
}

@ -1,5 +1,9 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Modularity
@ -12,12 +16,18 @@ namespace Volo.Abp.Modularity
public ModuleManager(
IModuleContainer moduleContainer,
IEnumerable<IModuleLifecycleContributer> lifecycleContributers,
ILogger<ModuleManager> logger)
ILogger<ModuleManager> logger,
IOptions<ModuleLifecycleOptions> options,
IServiceProvider serviceProvider)
{
_moduleContainer = moduleContainer;
_lifecycleContributers = lifecycleContributers;
_logger = logger;
_lifecycleContributers = options.Value
.Contributers
.Select(serviceProvider.GetRequiredService)
.Cast<IModuleLifecycleContributer>()
.ToArray();
}
public void InitializeModules(ApplicationInitializationContext context)

@ -6,18 +6,18 @@ namespace Volo.Abp.Uow
[ExposeServices(typeof(IAmbientUnitOfWork), typeof(IUnitOfWorkAccessor))]
public class AmbientUnitOfWork : IAmbientUnitOfWork, ISingletonDependency
{
public IUnitOfWork UnitOfWork => _currentUowInfo.Value;
public IUnitOfWork UnitOfWork => _currentUow.Value;
private readonly AsyncLocal<IUnitOfWork> _currentUowInfo;
private readonly AsyncLocal<IUnitOfWork> _currentUow;
public AmbientUnitOfWork()
{
_currentUowInfo = new AsyncLocal<IUnitOfWork>();
_currentUow = new AsyncLocal<IUnitOfWork>();
}
public void SetUnitOfWork(IUnitOfWork unitOfWork)
{
_currentUowInfo.Value = unitOfWork;
_currentUow.Value = unitOfWork;
}
}
}

@ -7,9 +7,19 @@ namespace Volo.Abp.Uow
{
internal class ChildUnitOfWork : IUnitOfWork
{
public event EventHandler Completed;
public Guid Id => _parent.Id;
public IUnitOfWorkOptions Options => _parent.Options;
public IUnitOfWork Outer => _parent.Outer;
public bool IsReserved => _parent.IsReserved;
public string ReservationName => _parent.ReservationName;
public event EventHandler<UnitOfWorkEventArgs> Completed;
public event EventHandler<UnitOfWorkFailedEventArgs> Failed;
public event EventHandler Disposed;
public event EventHandler<UnitOfWorkEventArgs> Disposed;
public IServiceProvider ServiceProvider => _parent.ServiceProvider;
@ -26,6 +36,21 @@ namespace Volo.Abp.Uow
_parent.Disposed += (sender, args) => { Disposed.InvokeSafely(sender, args); };
}
public void SetOuter(IUnitOfWork outer)
{
_parent.SetOuter(outer);
}
public void Initialize(UnitOfWorkOptions options)
{
_parent.Initialize(options);
}
public void Reserve(string reservationName)
{
_parent.Reserve(reservationName);
}
public void SaveChanges()
{
_parent.SaveChanges();
@ -35,30 +60,65 @@ namespace Volo.Abp.Uow
{
return _parent.SaveChangesAsync(cancellationToken);
}
public void Complete()
{
}
public Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return Task.CompletedTask;
}
public IDatabaseApi FindDatabaseApi(string id)
public void Rollback()
{
_parent.Rollback();
}
public Task RollbackAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return _parent.FindDatabaseApi(id);
return _parent.RollbackAsync(cancellationToken);
}
public IDatabaseApi GetOrAddDatabaseApi(string id, Func<IDatabaseApi> factory)
public IDatabaseApi FindDatabaseApi(string key)
{
return _parent.GetOrAddDatabaseApi(id, factory);
return _parent.FindDatabaseApi(key);
}
public void AddDatabaseApi(string key, IDatabaseApi api)
{
_parent.AddDatabaseApi(key, api);
}
public IDatabaseApi GetOrAddDatabaseApi(string key, Func<IDatabaseApi> factory)
{
return _parent.GetOrAddDatabaseApi(key, factory);
}
public ITransactionApi FindTransactionApi(string key)
{
return _parent.FindTransactionApi(key);
}
public void AddTransactionApi(string key, ITransactionApi api)
{
_parent.AddTransactionApi(key, api);
}
public ITransactionApi GetOrAddTransactionApi(string key, Func<ITransactionApi> factory)
{
return _parent.GetOrAddTransactionApi(key, factory);
}
public void Dispose()
{
}
public override string ToString()
{
return $"[UnitOfWork {Id}]";
}
}
}

@ -1,24 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Abp.Uow
{
//TODO: Find a better naming :(
public interface IBasicUnitOfWork : IDisposable
{
event EventHandler Completed;
event EventHandler<UnitOfWorkFailedEventArgs> Failed;
event EventHandler Disposed;
void SaveChanges();
Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
void Complete();
Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken));
}
}

@ -7,9 +7,11 @@ namespace Volo.Abp.Uow
public interface IDatabaseApiContainer : IServiceProviderAccessor
{
[CanBeNull]
IDatabaseApi FindDatabaseApi([NotNull] string id);
IDatabaseApi FindDatabaseApi([NotNull] string key);
void AddDatabaseApi([NotNull] string key, [NotNull] IDatabaseApi api);
[NotNull]
IDatabaseApi GetOrAddDatabaseApi(string id, Func<IDatabaseApi> factory);
IDatabaseApi GetOrAddDatabaseApi([NotNull] string key, [NotNull] Func<IDatabaseApi> factory);
}
}

@ -0,0 +1,12 @@
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Abp.Uow
{
public interface ISupportsRollback
{
void Rollback();
Task RollbackAsync(CancellationToken cancellationToken);
}
}

@ -0,0 +1,13 @@
using System.Threading.Tasks;
namespace Volo.Abp.Uow
{
public interface ITransactionApi
{
void Commit();
Task CommitAsync();
void Dispose();
}
}

@ -0,0 +1,16 @@
using System;
using JetBrains.Annotations;
namespace Volo.Abp.Uow
{
public interface ITransactionApiContainer
{
[CanBeNull]
ITransactionApi FindTransactionApi([NotNull] string key);
void AddTransactionApi([NotNull] string key, [NotNull] ITransactionApi api);
[NotNull]
ITransactionApi GetOrAddTransactionApi([NotNull] string key, [NotNull] Func<ITransactionApi> factory);
}
}

@ -1,7 +1,44 @@
namespace Volo.Abp.Uow
using System;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Volo.Abp.Uow
{
public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer
public interface IUnitOfWork : IDatabaseApiContainer, ITransactionApiContainer, IDisposable
{
Guid Id { get; }
event EventHandler<UnitOfWorkEventArgs> Completed;
event EventHandler<UnitOfWorkFailedEventArgs> Failed;
event EventHandler<UnitOfWorkEventArgs> Disposed;
IUnitOfWorkOptions Options { get; }
IUnitOfWork Outer { get; }
bool IsReserved { get; }
string ReservationName { get; }
void SetOuter([CanBeNull] IUnitOfWork outer);
void Initialize([NotNull] UnitOfWorkOptions options);
void Reserve([NotNull] string reservationName);
void SaveChanges();
Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
void Complete();
Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken));
void Rollback();
Task RollbackAsync(CancellationToken cancellationToken = default(CancellationToken));
}
}

@ -8,6 +8,13 @@ namespace Volo.Abp.Uow
IUnitOfWork Current { get; }
[NotNull]
IBasicUnitOfWork Begin();
IUnitOfWork Begin([NotNull] UnitOfWorkOptions options, bool requiresNew = false);
[NotNull]
IUnitOfWork Reserve([NotNull] string reservationName, bool requiresNew = false);
void BeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkOptions options);
bool TryBeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkOptions options);
}
}

@ -0,0 +1,14 @@
using System;
using System.Data;
namespace Volo.Abp.Uow
{
public interface IUnitOfWorkOptions
{
bool IsTransactional { get; }
IsolationLevel? IsolationLevel { get; }
TimeSpan? Timeout { get; }
}
}

@ -2,46 +2,71 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Uow
{
public class UnitOfWork : IUnitOfWork, ITransientDependency
{
public event EventHandler Completed;
public Guid Id { get; } = Guid.NewGuid();
public IUnitOfWorkOptions Options { get; private set; }
public IUnitOfWork Outer { get; private set; }
public bool IsReserved { get; set; }
public string ReservationName { get; set; }
public event EventHandler<UnitOfWorkEventArgs> Completed;
public event EventHandler<UnitOfWorkFailedEventArgs> Failed;
public event EventHandler Disposed;
public event EventHandler<UnitOfWorkEventArgs> Disposed;
public IServiceProvider ServiceProvider { get; }
private readonly Dictionary<string, IDatabaseApi> _databaseApis;
private readonly Dictionary<string, ITransactionApi> _transactionApis;
private readonly UnitOfWorkDefaultOptions _defaultOptions;
private Exception _exception;
private bool _isCompleted;
private bool _isDisposed;
private bool _isRolledback;
public UnitOfWork(IServiceProvider serviceProvider)
public UnitOfWork(IServiceProvider serviceProvider, IOptions<UnitOfWorkDefaultOptions> options)
{
ServiceProvider = serviceProvider;
_defaultOptions = options.Value;
_databaseApis = new Dictionary<string, IDatabaseApi>();
_transactionApis = new Dictionary<string, ITransactionApi>();
}
public void Dispose()
public void Initialize(UnitOfWorkOptions options)
{
if (_isDisposed)
Check.NotNull(options, nameof(options));
if (Options != null)
{
return;
throw new AbpException("This unit of work is already initialized before!");
}
_isDisposed = true;
Options = _defaultOptions.Normalize(options.Clone());
IsReserved = false;
}
if (!_isCompleted || _exception != null)
{
OnFailed(_exception);
}
public void Reserve(string reservationName)
{
Check.NotNull(reservationName, nameof(reservationName));
OnDisposed();
ReservationName = reservationName;
IsReserved = true;
}
public void SetOuter(IUnitOfWork outer)
{
Outer = outer;
}
public void SaveChanges()
@ -65,10 +90,17 @@ namespace Volo.Abp.Uow
public void Complete()
{
if (_isRolledback)
{
return;
}
PreventMultipleComplete();
try
{
SaveChanges();
CommitTransactions();
OnCompleted();
}
catch (Exception ex)
@ -80,11 +112,17 @@ namespace Volo.Abp.Uow
public async Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken))
{
if (_isRolledback)
{
return;
}
PreventMultipleComplete();
try
{
await SaveChangesAsync(cancellationToken);
await CommitTransactionsAsync();
OnCompleted();
}
catch (Exception ex)
@ -94,32 +132,136 @@ namespace Volo.Abp.Uow
}
}
public IDatabaseApi FindDatabaseApi(string id)
public void Rollback()
{
if (_isRolledback)
{
return;
}
_isRolledback = true;
foreach (var databaseApi in _databaseApis.Values)
{
(databaseApi as ISupportsRollback)?.Rollback();
}
foreach (var transactionApi in _transactionApis.Values)
{
(transactionApi as ISupportsRollback)?.Rollback();
}
}
public async Task RollbackAsync(CancellationToken cancellationToken = default(CancellationToken))
{
if (_isRolledback)
{
return;
}
_isRolledback = true;
foreach (var databaseApi in _databaseApis.Values)
{
if (databaseApi is ISupportsRollback)
{
await (databaseApi as ISupportsRollback).RollbackAsync(cancellationToken);
}
}
foreach (var transactionApi in _transactionApis.Values)
{
if (transactionApi is ISupportsRollback)
{
await (transactionApi as ISupportsRollback).RollbackAsync(cancellationToken);
}
}
}
public IDatabaseApi FindDatabaseApi(string key)
{
return _databaseApis.GetOrDefault(key);
}
public void AddDatabaseApi(string key, IDatabaseApi api)
{
Check.NotNull(key, nameof(key));
Check.NotNull(api, nameof(api));
if (_databaseApis.ContainsKey(key))
{
throw new AbpException("There is already a database API in this unit of work with given key: " + key);
}
_databaseApis.Add(key, api);
}
public IDatabaseApi GetOrAddDatabaseApi(string key, Func<IDatabaseApi> factory)
{
return _databaseApis.GetOrDefault(id);
Check.NotNull(key, nameof(key));
Check.NotNull(factory, nameof(factory));
return _databaseApis.GetOrAdd(key, factory);
}
public ITransactionApi FindTransactionApi(string key)
{
Check.NotNull(key, nameof(key));
return _transactionApis.GetOrDefault(key);
}
public void AddTransactionApi(string key, ITransactionApi api)
{
Check.NotNull(key, nameof(key));
Check.NotNull(api, nameof(api));
if (_transactionApis.ContainsKey(key))
{
throw new AbpException("There is already a transaction API in this unit of work with given key: " + key);
}
_transactionApis.Add(key, api);
}
public IDatabaseApi GetOrAddDatabaseApi(string id, Func<IDatabaseApi> factory)
public ITransactionApi GetOrAddTransactionApi(string key, Func<ITransactionApi> factory)
{
Check.NotNull(id, nameof(id));
Check.NotNull(key, nameof(key));
Check.NotNull(factory, nameof(factory));
return _databaseApis.GetOrAdd(id, factory);
return _transactionApis.GetOrAdd(key, factory);
}
protected virtual void OnCompleted()
{
Completed.InvokeSafely(this);
Completed.InvokeSafely(this, new UnitOfWorkEventArgs(this));
}
protected virtual void OnFailed(Exception exception)
protected virtual void OnFailed()
{
Failed.InvokeSafely(this, new UnitOfWorkFailedEventArgs(exception));
Failed.InvokeSafely(this, new UnitOfWorkFailedEventArgs(this, _exception, _isRolledback));
}
protected virtual void OnDisposed()
{
Disposed.InvokeSafely(this);
Disposed.InvokeSafely(this, new UnitOfWorkEventArgs(this));
}
public void Dispose()
{
if (_isDisposed)
{
return;
}
_isDisposed = true;
if (!_isCompleted || _exception != null)
{
OnFailed();
}
OnDisposed();
}
private void PreventMultipleComplete()
@ -131,5 +273,26 @@ namespace Volo.Abp.Uow
_isCompleted = true;
}
public override string ToString()
{
return $"[UnitOfWork {Id}]";
}
protected virtual void CommitTransactions()
{
foreach (var transaction in _transactionApis.Values)
{
transaction.Commit();
}
}
protected virtual async Task CommitTransactionsAsync()
{
foreach (var transaction in _transactionApis.Values)
{
await transaction.CommitAsync();
}
}
}
}

@ -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
{
/// <summary>
/// Is this UOW transactional?
/// Uses default value if not supplied.
/// </summary>
public bool? IsTransactional { get; set; }
/// <summary>
/// Timeout of UOW As milliseconds.
/// Uses default value if not supplied.
/// </summary>
public TimeSpan? Timeout { get; set; }
/// <summary>
/// If this UOW is transactional, this option indicated the isolation level of the transaction.
/// Uses default value if not supplied.
/// </summary>
public IsolationLevel? IsolationLevel { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool IsDisabled { get; set; }
public virtual void SetOptions(UnitOfWorkOptions options)
{
if (IsTransactional.HasValue)
{
options.IsTransactional = IsTransactional.Value;
}
if (Timeout.HasValue)
{
options.Timeout = Timeout;
}
if (IsolationLevel.HasValue)
{
options.IsolationLevel = IsolationLevel;
}
}
}
}

@ -0,0 +1,50 @@
using System;
using System.Data;
using System.Reflection;
namespace Volo.Abp.Uow
{
//TODO: Implement default options!
/// <summary>
/// Global (default) unit of work options
/// </summary>
public class UnitOfWorkDefaultOptions
{
public UnitOfWorkTransactionBehavior TransactionBehavior { get; set; }
public IsolationLevel? IsolationLevel { get; set; }
public TimeSpan? Timeout { get; set; }
internal UnitOfWorkOptions Normalize(UnitOfWorkOptions options)
{
if (options.IsolationLevel == null)
{
options.IsolationLevel = IsolationLevel;
}
if (options.Timeout == null)
{
options.Timeout = Timeout;
}
return options;
}
internal bool CalculateIsTransactional(bool autoValue)
{
switch (TransactionBehavior)
{
case UnitOfWorkTransactionBehavior.Enabled:
return true;
case UnitOfWorkTransactionBehavior.Disabled:
return false;
case UnitOfWorkTransactionBehavior.Auto:
return autoValue;
default:
throw new AbpException("Not implemented TransactionBehavior value: " + TransactionBehavior);
}
}
}
}

@ -0,0 +1,20 @@
using System;
using JetBrains.Annotations;
namespace Volo.Abp.Uow
{
public class UnitOfWorkEventArgs : EventArgs
{
/// <summary>
/// Reference to the unit of work related to this event.
/// </summary>
public IUnitOfWork UnitOfWork { get; }
public UnitOfWorkEventArgs([NotNull] IUnitOfWork unitOfWork)
{
Check.NotNull(unitOfWork, nameof(unitOfWork));
UnitOfWork = unitOfWork;
}
}
}

@ -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;
}
}
}

@ -6,23 +6,29 @@ namespace Volo.Abp.Uow
/// <summary>
/// Used as event arguments on <see cref="IUnitOfWork.Failed"/> event.
/// </summary>
public class UnitOfWorkFailedEventArgs : EventArgs
public class UnitOfWorkFailedEventArgs : UnitOfWorkEventArgs
{
/// <summary>
/// Exception that caused failure. This is set only if an error occured during <see cref="IBasicUnitOfWork.Complete"/>.
/// Can be null if there is no exception, but <see cref="IBasicUnitOfWork.Complete"/> is not called.
/// Exception that caused failure. This is set only if an error occured during <see cref="IUnitOfWork.Complete"/>.
/// Can be null if there is no exception, but <see cref="IUnitOfWork.Complete"/> is not called.
/// Can be null if another exception occurred during the UOW.
/// </summary>
[CanBeNull]
public Exception Exception { get; private set; }
public Exception Exception { get; }
/// <summary>
/// True, if the unit of work is manually rolled back.
/// </summary>
public bool IsRolledback { get; }
/// <summary>
/// Creates a new <see cref="UnitOfWorkFailedEventArgs"/> object.
/// </summary>
/// <param name="exception">Exception that caused failure</param>
public UnitOfWorkFailedEventArgs([CanBeNull] Exception exception)
public UnitOfWorkFailedEventArgs([NotNull] IUnitOfWork unitOfWork, [CanBeNull] Exception exception, bool isRolledback)
: base(unitOfWork)
{
Exception = exception;
IsRolledback = isRolledback;
}
}
}

@ -1,5 +1,6 @@
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
@ -25,13 +26,16 @@ namespace Volo.Abp.Uow
return false;
}
public static bool IsUnitOfWorkMethod(MethodInfo methodInfo)
public static bool IsUnitOfWorkMethod([NotNull] MethodInfo methodInfo, [CanBeNull] out UnitOfWorkAttribute unitOfWorkAttribute)
{
Check.NotNull(methodInfo, nameof(methodInfo));
//Method declaration
var attrs = methodInfo.GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
if (attrs.Any())
{
return true;
unitOfWorkAttribute = attrs.First();
return !unitOfWorkAttribute.IsDisabled;
}
if (methodInfo.DeclaringType != null)
@ -40,20 +44,41 @@ namespace Volo.Abp.Uow
attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
if (attrs.Any())
{
return true;
unitOfWorkAttribute = attrs.First();
return !unitOfWorkAttribute.IsDisabled;
}
//Conventional classes
//Conventional classes //TODO: Make this extendible to add new conventions!
if (typeof(IApplicationService).GetTypeInfo().IsAssignableFrom(methodInfo.DeclaringType) ||
typeof(IRepository).GetTypeInfo().IsAssignableFrom(methodInfo.DeclaringType))
{
unitOfWorkAttribute = null;
return true;
}
}
unitOfWorkAttribute = null;
return false;
}
public static UnitOfWorkAttribute GetUnitOfWorkAttributeOrNull(MethodInfo methodInfo)
{
var attrs = methodInfo.GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
if (attrs.Length > 0)
{
return attrs[0];
}
attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
if (attrs.Length > 0)
{
return attrs[0];
}
return null;
}
private static bool AnyMethodHasUnitOfWorkAttribute(TypeInfo implementationType)
{
return implementationType

@ -1,4 +1,8 @@
using System.Threading.Tasks;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
@ -7,40 +11,55 @@ namespace Volo.Abp.Uow
public class UnitOfWorkInterceptor : AbpInterceptor, ITransientDependency
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly UnitOfWorkDefaultOptions _defaultOptions;
public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager)
public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager, IOptions<UnitOfWorkDefaultOptions> options)
{
_unitOfWorkManager = unitOfWorkManager;
_defaultOptions = options.Value;
}
public override void Intercept(IAbpMethodInvocation invocation)
{
if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method))
if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute))
{
invocation.Proceed();
return;
}
using (var uow = _unitOfWorkManager.Begin())
using (var uow = _unitOfWorkManager.Begin(CreateOptions(invocation, unitOfWorkAttribute)))
{
invocation.Proceed();
uow.Complete();
}
}
public override async Task InterceptAsync(IAbpMethodInvocation invocation)
public override async Task InterceptAsync(IAbpMethodInvocation invocation)
{
if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method))
if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute))
{
await invocation.ProceedAsync();
return;
}
using (var uow = _unitOfWorkManager.Begin())
using (var uow = _unitOfWorkManager.Begin(CreateOptions(invocation, unitOfWorkAttribute)))
{
await invocation.ProceedAsync();
await uow.CompleteAsync();
}
}
private UnitOfWorkOptions CreateOptions(IAbpMethodInvocation invocation, [CanBeNull] UnitOfWorkAttribute unitOfWorkAttribute)
{
var options = new UnitOfWorkOptions();
unitOfWorkAttribute?.SetOptions(options);
options.IsTransactional = _defaultOptions.CalculateIsTransactional(
autoValue: !invocation.Method.Name.StartsWith("Get", StringComparison.InvariantCultureIgnoreCase)
);
return options;
}
}
}

@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
@ -7,61 +6,120 @@ 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;
public UnitOfWorkManager(IServiceProvider serviceProvider, IAmbientUnitOfWork ambientUnitOfWork)
public UnitOfWorkManager(
IServiceProvider serviceProvider,
IAmbientUnitOfWork ambientUnitOfWork)
{
_serviceProvider = serviceProvider;
_ambientUnitOfWork = ambientUnitOfWork;
}
public IBasicUnitOfWork Begin()
public IUnitOfWork Begin(UnitOfWorkOptions options, bool requiresNew = false)
{
if (_ambientUnitOfWork.UnitOfWork != null)
Check.NotNull(options, nameof(options));
if (!requiresNew && _ambientUnitOfWork.UnitOfWork != null && !_ambientUnitOfWork.UnitOfWork.IsReserved)
{
return new ChildUnitOfWork(_ambientUnitOfWork.UnitOfWork);
}
var parentUow = _ambientUnitOfWork.UnitOfWork;
var unitOfWork = CreateNewUnitOfWork();
unitOfWork.Initialize(options);
var scope = _serviceProvider.CreateScope();
IUnitOfWork unitOfWork;
try
return unitOfWork;
}
public IUnitOfWork Reserve(string reservationName, bool requiresNew = false)
{
Check.NotNull(reservationName, nameof(reservationName));
if (!requiresNew &&
_ambientUnitOfWork.UnitOfWork != null &&
_ambientUnitOfWork.UnitOfWork.IsReservedFor(reservationName))
{
unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
return new ChildUnitOfWork(_ambientUnitOfWork.UnitOfWork);
}
catch
var unitOfWork = CreateNewUnitOfWork();
unitOfWork.Reserve(reservationName);
return unitOfWork;
}
public void BeginReserved(string reservationName, UnitOfWorkOptions options)
{
if (!TryBeginReserved(reservationName, options))
{
scope.Dispose();
throw;
throw new AbpException($"Could not find a reserved unit of work with reservation name: {reservationName}");
}
}
public bool TryBeginReserved(string reservationName, UnitOfWorkOptions options)
{
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)
{
return false;
}
_ambientUnitOfWork.SetUnitOfWork(unitOfWork);
uow.Initialize(options);
Debug.Assert(
_ambientUnitOfWork.UnitOfWork != null,
"_ambientUnitOfWork.UnitOfWork can not be null since it's set by _ambientUnitOfWork.SetUnitOfWork method!"
);
return true;
}
unitOfWork.Completed += (sender, args) =>
private IUnitOfWork GetCurrentUnitOfWork()
{
var uow = _ambientUnitOfWork.UnitOfWork;
//Skip reserved unit of work
while (uow != null && uow.IsReserved)
{
_ambientUnitOfWork.SetUnitOfWork(parentUow);
};
uow = uow.Outer;
}
unitOfWork.Failed += (sender, args) =>
return uow;
}
private IUnitOfWork CreateNewUnitOfWork()
{
var scope = _serviceProvider.CreateScope();
try
{
_ambientUnitOfWork.SetUnitOfWork(parentUow);
};
var outerUow = _ambientUnitOfWork.UnitOfWork;
var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
unitOfWork.SetOuter(outerUow);
_ambientUnitOfWork.SetUnitOfWork(unitOfWork);
unitOfWork.Disposed += (sender, args) =>
{
_ambientUnitOfWork.SetUnitOfWork(outerUow);
scope.Dispose();
};
unitOfWork.Disposed += (sender, args) =>
return unitOfWork;
}
catch
{
scope.Dispose();
};
return _ambientUnitOfWork.UnitOfWork;
throw;
}
}
}
}

@ -0,0 +1,31 @@
using JetBrains.Annotations;
namespace Volo.Abp.Uow
{
public static class UnitOfWorkManagerExtensions
{
[NotNull]
public static IUnitOfWork Begin([NotNull] this IUnitOfWorkManager unitOfWorkManager, bool requiresNew = false)
{
Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager));
return unitOfWorkManager.Begin(new UnitOfWorkOptions(), requiresNew);
}
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 UnitOfWorkOptions());
}
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 UnitOfWorkOptions());
}
}
}

@ -0,0 +1,27 @@
using System;
using System.Data;
namespace Volo.Abp.Uow
{
public class UnitOfWorkOptions : IUnitOfWorkOptions
{
/// <summary>
/// Default: false.
/// </summary>
public bool IsTransactional { get; set; }
public IsolationLevel? IsolationLevel { get; set; }
public TimeSpan? Timeout { get; set; }
public UnitOfWorkOptions Clone()
{
return new UnitOfWorkOptions
{
IsTransactional = IsTransactional,
IsolationLevel = IsolationLevel,
Timeout = Timeout
};
}
}
}

@ -0,0 +1,11 @@
namespace Volo.Abp.Uow
{
public enum UnitOfWorkTransactionBehavior
{
Auto,
Enabled,
Disabled
}
}

@ -34,6 +34,8 @@ namespace Volo.Abp.AspNetCore.App
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
var app = context.GetApplicationBuilder();
app.UseUnitOfWork();
app.UseMvcWithDefaultRoute();
}
}

@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Mvc;
using Shouldly;
using Volo.Abp.AspNetCore.Mvc;
namespace Volo.Abp.AspNetCore.App
{
[Route("api/unitofwork-test")]
public class UnitOfWorkTestController : AbpController
{
[HttpGet]
[Route("ActionRequiresUow")]
public ActionResult ActionRequiresUow()
{
CurrentUnitOfWork.ShouldNotBeNull();
CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse();
return Content("OK");
}
[HttpPost]
[Route("ActionRequiresUowPost")]
public ActionResult ActionRequiresUowPost()
{
CurrentUnitOfWork.ShouldNotBeNull();
CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue();
return Content("OK");
}
}
}

@ -0,0 +1,22 @@
using System.Threading.Tasks;
using Shouldly;
using Xunit;
namespace Volo.Abp.AspNetCore.Mvc.Uow
{
public class UnitOfWorkMiddleware_Tests : AspNetCoreMvcTestBase
{
[Fact]
public async Task Get_Actions_Should_Not_Be_Transactional()
{
await GetResponseAsStringAsync("/api/unitofwork-test/ActionRequiresUow");
}
[Fact]
public async Task Non_Get_Actions_Should_Be_Transactional()
{
var result = await Client.PostAsync("/api/unitofwork-test/ActionRequiresUowPost", null);
result.IsSuccessStatusCode.ShouldBeTrue();
}
}
}

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyName>Volo.Abp.EntityFrameworkCore.Tests</AssemblyName>
<PackageId>Volo.Abp.EntityFrameworkCore.Tests</PackageId>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.EntityFrameworkCore\Volo.Abp.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<ProjectReference Include="..\Volo.Abp.TestApp\Volo.Abp.TestApp.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Volo\Abp\TestApp\EntityFrameworkCore\" />
</ItemGroup>
</Project>

@ -0,0 +1,42 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
using Volo.Abp.TestApp;
using Volo.Abp.TestApp.EntityFrameworkCore;
namespace Volo.Abp.EntityFrameworkCore
{
[DependsOn(typeof(AbpEntityFrameworkCoreModule))]
[DependsOn(typeof(TestAppModule))]
[DependsOn(typeof(AbpAutofacModule))]
public class AbpEntityFrameworkCoreTestModule : AbpModule
{
public override void ConfigureServices(IServiceCollection services)
{
services.AddAssemblyOf<AbpEntityFrameworkCoreTestModule>();
services.AddAbpDbContext<TestAppDbContext>(options =>
{
options.WithDefaultRepositories();
});
var inMemorySqlite = new SqliteConnection("Data Source=:memory:");
inMemorySqlite.Open();
services.Configure<AbpDbContextOptions>(options =>
{
options.Configure(context =>
{
context.DbContextOptions.UseSqlite(inMemorySqlite);
});
});
}
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
context.ServiceProvider.GetRequiredService<TestAppDbContext>().Database.EnsureCreated();
}
}
}

@ -0,0 +1,12 @@
using Volo.Abp.TestBase;
namespace Volo.Abp.EntityFrameworkCore
{
public abstract class EntityFrameworkCoreTestBase : AbpIntegratedTest<AbpEntityFrameworkCoreTestModule>
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}
}

@ -0,0 +1,38 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Domain;
using Xunit;
namespace Volo.Abp.EntityFrameworkCore.Repositories
{
public class Basic_Repository_Tests : EntityFrameworkCoreTestBase
{
private readonly IRepository<Person> _personRepository;
public Basic_Repository_Tests()
{
_personRepository = ServiceProvider.GetRequiredService<IRepository<Person>>();
}
[Fact]
public void GetList()
{
_personRepository.GetList().Any().ShouldBeTrue();
}
[Fact]
public async Task InsertAsync()
{
var personId = Guid.NewGuid();
await _personRepository.InsertAsync(new Person(personId, "Adam", 42));
var person = await _personRepository.FindAsync(personId);
person.ShouldNotBeNull();
}
}
}

@ -0,0 +1,62 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.Uow;
using Xunit;
namespace Volo.Abp.EntityFrameworkCore
{
public class Transaction_Tests : EntityFrameworkCoreTestBase
{
private readonly IRepository<Person> _personRepository;
private readonly IUnitOfWorkManager _unitOfWorkManager;
public Transaction_Tests()
{
_personRepository = ServiceProvider.GetRequiredService<IRepository<Person>>();
_unitOfWorkManager = ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
}
[Fact]
public async Task Should_Rollback_Transaction_When_An_Exception_Is_Thrown()
{
var personId = Guid.NewGuid();
const string exceptionMessage = "thrown to rollback the transaction!";
try
{
await WithUnitOfWorkAsync(async () =>
{
await _personRepository.InsertAsync(new Person(personId, "Adam", 42));
throw new Exception(exceptionMessage);
});
}
catch (Exception e) when (e.Message == exceptionMessage)
{
}
var person = await _personRepository.FindAsync(personId);
person.ShouldBeNull();
}
[Fact]
public async Task Should_Rollback_Transaction_Manually()
{
var personId = Guid.NewGuid();
await WithUnitOfWorkAsync(async () =>
{
_unitOfWorkManager.Current.ShouldNotBeNull();
await _personRepository.InsertAsync(new Person(personId, "Adam", 42));
await _unitOfWorkManager.Current.RollbackAsync();
});
var person = await _personRepository.FindAsync(personId);
person.ShouldBeNull();
}
}
}

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.TestApp.Domain;
namespace Volo.Abp.TestApp.EntityFrameworkCore
{
public class TestAppDbContext : AbpDbContext<TestAppDbContext>
{
public DbSet<Person> People { get; set; }
public TestAppDbContext(DbContextOptions<TestAppDbContext> options)
: base(options)
{
}
}
}

@ -5,6 +5,7 @@ using Volo.Abp.Autofac;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.Modularity;
using Volo.Abp.Uow;
namespace Volo.Abp.Identity
{
@ -26,6 +27,11 @@ namespace Volo.Abp.Identity
context.DbContextOptions.UseInMemoryDatabase(databaseName);
});
});
services.Configure<UnitOfWorkDefaultOptions>(options =>
{
options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; //EF in-memory database does not support transactions
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)

@ -0,0 +1,76 @@
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.TestBase;
using Xunit;
namespace Volo.Abp.Uow
{
public class UnitOfWork_Ambient_Scope_Tests : AbpIntegratedTest<IndependentEmptyModule>
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
public UnitOfWork_Ambient_Scope_Tests()
{
_unitOfWorkManager = ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
}
[Fact]
public async Task UnitOfWorkManager_Current_Should_Set_Correctly()
{
_unitOfWorkManager.Current.ShouldBeNull();
using (var uow1 = _unitOfWorkManager.Begin())
{
_unitOfWorkManager.Current.ShouldNotBeNull();
_unitOfWorkManager.Current.ShouldBe(uow1);
using (var uow2 = _unitOfWorkManager.Begin())
{
_unitOfWorkManager.Current.ShouldNotBeNull();
_unitOfWorkManager.Current.Id.ShouldBe(uow1.Id);
await uow2.CompleteAsync();
}
_unitOfWorkManager.Current.ShouldNotBeNull();
_unitOfWorkManager.Current.ShouldBe(uow1);
await uow1.CompleteAsync();
}
_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();
_unitOfWorkManager.BeginReserved("Reservation1");
_unitOfWorkManager.Current.ShouldNotBeNull();
_unitOfWorkManager.Current.Id.ShouldBe(uow1.Id);
await uow1.CompleteAsync();
}
_unitOfWorkManager.Current.ShouldBeNull();
}
}
}

@ -108,5 +108,36 @@ namespace Volo.Abp.Uow
failed.ShouldBeTrue();
disposed.ShouldBeTrue();
}
[InlineData(true)]
[InlineData(false)]
[Theory]
public void Should_Trigger_Failed_If_Rolled_Back(bool callComplete)
{
var completed = false;
var failed = false;
var disposed = false;
Assert.Throws<Exception>(() =>
{
using (var uow = _unitOfWorkManager.Begin())
{
uow.Completed += (sender, args) => completed = true;
uow.Failed += (sender, args) => { failed = true; args.IsRolledback.ShouldBeTrue(); };
uow.Disposed += (sender, args) => disposed = true;
uow.Rollback();
if (callComplete)
{
uow.Complete();
}
}
}).Message.ShouldBe("test exception");
completed.ShouldBeFalse();
failed.ShouldBeTrue();
disposed.ShouldBeTrue();
}
}
}

@ -0,0 +1,46 @@
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.TestBase;
using Xunit;
namespace Volo.Abp.Uow
{
public class UnitOfWork_Nested_Tests : AbpIntegratedTest<IndependentEmptyModule>
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
public UnitOfWork_Nested_Tests()
{
_unitOfWorkManager = ServiceProvider.GetRequiredService<IUnitOfWorkManager>();
}
[Fact]
public async Task Should_Create_Nested_UnitOfWorks()
{
_unitOfWorkManager.Current.ShouldBeNull();
using (var uow1 = _unitOfWorkManager.Begin())
{
_unitOfWorkManager.Current.ShouldNotBeNull();
_unitOfWorkManager.Current.ShouldBe(uow1);
using (var uow2 = _unitOfWorkManager.Begin(requiresNew: true))
{
_unitOfWorkManager.Current.ShouldNotBeNull();
_unitOfWorkManager.Current.Id.ShouldNotBe(uow1.Id);
await uow2.CompleteAsync();
}
_unitOfWorkManager.Current.ShouldNotBeNull();
_unitOfWorkManager.Current.ShouldBe(uow1);
await uow1.CompleteAsync();
}
_unitOfWorkManager.Current.ShouldBeNull();
}
}
}
Loading…
Cancel
Save