Browse Source

Merge pull request #112 from aspnetzero/uow-enhancements

Uow enhancements
pull/113/head
Halil İbrahim Kalkan 9 years ago
committed by GitHub
parent
commit
1835c37d63
  1. 6
      Volo.Abp.sln
  2. BIN
      src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll
  3. 12
      src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Builder/AbpAspNetCoreMvcApplicationBuilderExtensions.cs
  4. 19
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs
  5. 3
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs
  6. 25
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs
  7. 72
      src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs
  8. 1
      src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj
  9. 14
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs
  10. 14
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DbContextExtensions.cs
  11. 31
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs
  12. 69
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs
  13. 4
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreDatabaseApi.cs
  14. 60
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs
  15. 87
      src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs
  16. 28
      src/Volo.Abp/Volo/Abp/AbpApplicationBase.cs
  17. 8
      src/Volo.Abp/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs
  18. 8
      src/Volo.Abp/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs
  19. 8
      src/Volo.Abp/Volo/Abp/AbpKernelModule.cs
  20. 4
      src/Volo.Abp/Volo/Abp/AbpServiceBase.cs
  21. 19
      src/Volo.Abp/Volo/Abp/Modularity/AbpModule.cs
  22. 21
      src/Volo.Abp/Volo/Abp/Modularity/DefaultModuleLifecycleContributer.cs
  23. 9
      src/Volo.Abp/Volo/Abp/Modularity/IOnPostApplicationInitialization.cs
  24. 9
      src/Volo.Abp/Volo/Abp/Modularity/IOnPreApplicationInitialization.cs
  25. 14
      src/Volo.Abp/Volo/Abp/Modularity/ModuleLifecycleOptions.cs
  26. 18
      src/Volo.Abp/Volo/Abp/Modularity/ModuleManager.cs
  27. 8
      src/Volo.Abp/Volo/Abp/Uow/AmbientUnitOfWork.cs
  28. 80
      src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs
  29. 24
      src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs
  30. 6
      src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs
  31. 12
      src/Volo.Abp/Volo/Abp/Uow/ISupportsRollback.cs
  32. 13
      src/Volo.Abp/Volo/Abp/Uow/ITransactionApi.cs
  33. 16
      src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs
  34. 43
      src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs
  35. 9
      src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs
  36. 14
      src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkOptions.cs
  37. 205
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs
  38. 43
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs
  39. 50
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs
  40. 20
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkEventArgs.cs
  41. 14
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkExtensions.cs
  42. 18
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs
  43. 33
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs
  44. 33
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkInterceptor.cs
  45. 114
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs
  46. 31
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs
  47. 27
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs
  48. 11
      src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkTransactionBehavior.cs
  49. 2
      test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs
  50. 30
      test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/UnitOfWorkTestController.cs
  51. 22
      test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs
  52. 30
      test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj
  53. 42
      test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs
  54. 12
      test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EntityFrameworkCoreTestBase.cs
  55. 38
      test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Basic_Repository_Tests.cs
  56. 62
      test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Transaction_Tests.cs
  57. 17
      test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  58. 6
      test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestModule.cs
  59. 76
      test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs
  60. 31
      test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Events_Tests.cs
  61. 46
      test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs

6
Volo.Abp.sln

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

BIN
src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll

Binary file not shown.

12
src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Builder/AbpAspNetCoreMvcApplicationBuilderExtensions.cs

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

19
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs

@ -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.DependencyInjection;
using Volo.Abp.Guids;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Uow;
namespace Volo.Abp.AspNetCore.Mvc namespace Volo.Abp.AspNetCore.Mvc
{ {
public abstract class AbpController : Controller, ITransientDependency 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);
} }
} }

3
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc.Uow;
using Volo.Abp.AspNetCore.Mvc.Validation; using Volo.Abp.AspNetCore.Mvc.Validation;
namespace Volo.Abp.AspNetCore.Mvc namespace Volo.Abp.AspNetCore.Mvc
@ -24,7 +25,7 @@ namespace Volo.Abp.AspNetCore.Mvc
//options.Filters.AddService(typeof(AbpAuthorizationFilter)); //options.Filters.AddService(typeof(AbpAuthorizationFilter));
//options.Filters.AddService(typeof(AbpAuditActionFilter)); //options.Filters.AddService(typeof(AbpAuditActionFilter));
options.Filters.AddService(typeof(AbpValidationActionFilter)); options.Filters.AddService(typeof(AbpValidationActionFilter));
//options.Filters.AddService(typeof(AbpUowActionFilter)); options.Filters.AddService(typeof(AbpUowActionFilter));
options.Filters.AddService(typeof(AbpExceptionFilter)); options.Filters.AddService(typeof(AbpExceptionFilter));
//options.Filters.AddService(typeof(AbpResultFilter)); //options.Filters.AddService(typeof(AbpResultFilter));
} }

25
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs

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

72
src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs

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

1
src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj

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

14
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs

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

14
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DbContextExtensions.cs

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

31
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs

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

69
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs

@ -12,44 +12,55 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection
public static DbContextOptions<TDbContext> Create<TDbContext>(IServiceProvider serviceProvider) public static DbContextOptions<TDbContext> Create<TDbContext>(IServiceProvider serviceProvider)
where TDbContext : AbpDbContext<TDbContext> 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>( throw new AbpException($"No configuration found for {typeof(DbContext).AssemblyQualifiedName}! Use services.Configure<AbpDbContextOptions>(...) to configure it.");
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;
} }
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
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextDatabaseApi.cs → src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreDatabaseApi.cs

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

60
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs

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

87
src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs

@ -1,9 +1,15 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.DependencyInjection;
namespace Volo.Abp.Uow.EntityFrameworkCore namespace Volo.Abp.Uow.EntityFrameworkCore
{ {
//TODO: Implement logic in DefaultDbContextResolver.Resolve in old ABP.
public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbContext> public class UnitOfWorkDbContextProvider<TDbContext> : IDbContextProvider<TDbContext>
where TDbContext : AbpDbContext<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!"); 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 dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}";
var databaseApi = unitOfWork.GetOrAddDatabaseApi( var databaseApi = unitOfWork.GetOrAddDatabaseApi(
dbContextKey, dbContextKey,
() => new DbContextDatabaseApi<TDbContext>( () => new EfCoreDatabaseApi<TDbContext>(
unitOfWork.ServiceProvider.GetRequiredService<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;
}
} }
} }
} }

28
src/Volo.Abp/Volo/Abp/AbpApplicationBase.cs

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

8
src/Volo.Abp/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs

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

8
src/Volo.Abp/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs

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

8
src/Volo.Abp/Volo/Abp/AbpKernelModule.cs

@ -37,6 +37,14 @@ namespace Volo.Abp
services.AddAssemblyOf<AbpKernelModule>(); services.AddAssemblyOf<AbpKernelModule>();
services.TryAddObjectAccessor<IServiceProvider>(); 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) public override void OnApplicationInitialization(ApplicationInitializationContext context)

4
src/Volo.Abp/Volo/Abp/AbpServiceBase.cs

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

19
src/Volo.Abp/Volo/Abp/Modularity/AbpModule.cs

@ -4,7 +4,14 @@ using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.Modularity 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) 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 OnApplicationInitialization(ApplicationInitializationContext context)
{ {
} }
public virtual void OnPostApplicationInitialization(ApplicationInitializationContext context)
{
}
public virtual void OnApplicationShutdown(ApplicationShutdownContext context) public virtual void OnApplicationShutdown(ApplicationShutdownContext context)
{ {

21
src/Volo.Abp/Volo/Abp/Modularity/DefaultModuleLifecycleContributer.cs

@ -1,15 +1,34 @@
namespace Volo.Abp.Modularity namespace Volo.Abp.Modularity
{ {
public class DefaultModuleLifecycleContributer : ModuleLifecycleContributerBase public class OnApplicationInitializationModuleLifecycleContributer : ModuleLifecycleContributerBase
{ {
public override void Initialize(ApplicationInitializationContext context, IAbpModule module) public override void Initialize(ApplicationInitializationContext context, IAbpModule module)
{ {
(module as IOnApplicationInitialization)?.OnApplicationInitialization(context); (module as IOnApplicationInitialization)?.OnApplicationInitialization(context);
} }
}
public class OnApplicationShutdownModuleLifecycleContributer : ModuleLifecycleContributerBase
{
public override void Shutdown(ApplicationShutdownContext context, IAbpModule module) public override void Shutdown(ApplicationShutdownContext context, IAbpModule module)
{ {
(module as IOnApplicationShutdown)?.OnApplicationShutdown(context); (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);
}
}
} }

9
src/Volo.Abp/Volo/Abp/Modularity/IOnPostApplicationInitialization.cs

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

9
src/Volo.Abp/Volo/Abp/Modularity/IOnPreApplicationInitialization.cs

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

14
src/Volo.Abp/Volo/Abp/Modularity/ModuleLifecycleOptions.cs

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

18
src/Volo.Abp/Volo/Abp/Modularity/ModuleManager.cs

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

8
src/Volo.Abp/Volo/Abp/Uow/AmbientUnitOfWork.cs

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

80
src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs

@ -7,9 +7,19 @@ namespace Volo.Abp.Uow
{ {
internal class ChildUnitOfWork : IUnitOfWork 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<UnitOfWorkFailedEventArgs> Failed;
public event EventHandler Disposed; public event EventHandler<UnitOfWorkEventArgs> Disposed;
public IServiceProvider ServiceProvider => _parent.ServiceProvider; public IServiceProvider ServiceProvider => _parent.ServiceProvider;
@ -26,6 +36,21 @@ namespace Volo.Abp.Uow
_parent.Disposed += (sender, args) => { Disposed.InvokeSafely(sender, args); }; _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() public void SaveChanges()
{ {
_parent.SaveChanges(); _parent.SaveChanges();
@ -35,30 +60,65 @@ namespace Volo.Abp.Uow
{ {
return _parent.SaveChangesAsync(cancellationToken); return _parent.SaveChangesAsync(cancellationToken);
} }
public void Complete() public void Complete()
{ {
} }
public Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken)) public Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken))
{ {
return Task.CompletedTask; 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 void Dispose()
{ {
} }
public override string ToString()
{
return $"[UnitOfWork {Id}]";
}
} }
} }

24
src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs

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

6
src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs

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

12
src/Volo.Abp/Volo/Abp/Uow/ISupportsRollback.cs

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

13
src/Volo.Abp/Volo/Abp/Uow/ITransactionApi.cs

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

16
src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs

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

43
src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs

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

9
src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs

@ -8,6 +8,13 @@ namespace Volo.Abp.Uow
IUnitOfWork Current { get; } IUnitOfWork Current { get; }
[NotNull] [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);
} }
} }

14
src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkOptions.cs

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

205
src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs

@ -2,46 +2,71 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace Volo.Abp.Uow namespace Volo.Abp.Uow
{ {
public class UnitOfWork : IUnitOfWork, ITransientDependency 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<UnitOfWorkFailedEventArgs> Failed;
public event EventHandler Disposed; public event EventHandler<UnitOfWorkEventArgs> Disposed;
public IServiceProvider ServiceProvider { get; } public IServiceProvider ServiceProvider { get; }
private readonly Dictionary<string, IDatabaseApi> _databaseApis; private readonly Dictionary<string, IDatabaseApi> _databaseApis;
private readonly Dictionary<string, ITransactionApi> _transactionApis;
private readonly UnitOfWorkDefaultOptions _defaultOptions;
private Exception _exception; private Exception _exception;
private bool _isCompleted; private bool _isCompleted;
private bool _isDisposed; private bool _isDisposed;
private bool _isRolledback;
public UnitOfWork(IServiceProvider serviceProvider) public UnitOfWork(IServiceProvider serviceProvider, IOptions<UnitOfWorkDefaultOptions> options)
{ {
ServiceProvider = serviceProvider; ServiceProvider = serviceProvider;
_defaultOptions = options.Value;
_databaseApis = new Dictionary<string, IDatabaseApi>(); _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) public void Reserve(string reservationName)
{ {
OnFailed(_exception); Check.NotNull(reservationName, nameof(reservationName));
}
OnDisposed(); ReservationName = reservationName;
IsReserved = true;
}
public void SetOuter(IUnitOfWork outer)
{
Outer = outer;
} }
public void SaveChanges() public void SaveChanges()
@ -65,10 +90,17 @@ namespace Volo.Abp.Uow
public void Complete() public void Complete()
{ {
if (_isRolledback)
{
return;
}
PreventMultipleComplete(); PreventMultipleComplete();
try try
{ {
SaveChanges(); SaveChanges();
CommitTransactions();
OnCompleted(); OnCompleted();
} }
catch (Exception ex) catch (Exception ex)
@ -80,11 +112,17 @@ namespace Volo.Abp.Uow
public async Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken)) public async Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken))
{ {
if (_isRolledback)
{
return;
}
PreventMultipleComplete(); PreventMultipleComplete();
try try
{ {
await SaveChangesAsync(cancellationToken); await SaveChangesAsync(cancellationToken);
await CommitTransactionsAsync();
OnCompleted(); OnCompleted();
} }
catch (Exception ex) 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)); Check.NotNull(factory, nameof(factory));
return _databaseApis.GetOrAdd(id, factory); return _transactionApis.GetOrAdd(key, factory);
} }
protected virtual void OnCompleted() 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() 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() private void PreventMultipleComplete()
@ -131,5 +273,26 @@ namespace Volo.Abp.Uow
_isCompleted = true; _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();
}
}
} }
} }

43
src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Data;
namespace Volo.Abp.Uow namespace Volo.Abp.Uow
{ {
@ -11,5 +12,47 @@ namespace Volo.Abp.Uow
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)] [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)]
public class UnitOfWorkAttribute : Attribute 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;
}
}
} }
} }

50
src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs

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

20
src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkEventArgs.cs

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

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

18
src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs

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

33
src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs

@ -1,5 +1,6 @@
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using JetBrains.Annotations;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
@ -25,13 +26,16 @@ namespace Volo.Abp.Uow
return false; 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 //Method declaration
var attrs = methodInfo.GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray(); var attrs = methodInfo.GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
if (attrs.Any()) if (attrs.Any())
{ {
return true; unitOfWorkAttribute = attrs.First();
return !unitOfWorkAttribute.IsDisabled;
} }
if (methodInfo.DeclaringType != null) if (methodInfo.DeclaringType != null)
@ -40,20 +44,41 @@ namespace Volo.Abp.Uow
attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray(); attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType<UnitOfWorkAttribute>().ToArray();
if (attrs.Any()) 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) || if (typeof(IApplicationService).GetTypeInfo().IsAssignableFrom(methodInfo.DeclaringType) ||
typeof(IRepository).GetTypeInfo().IsAssignableFrom(methodInfo.DeclaringType)) typeof(IRepository).GetTypeInfo().IsAssignableFrom(methodInfo.DeclaringType))
{ {
unitOfWorkAttribute = null;
return true; return true;
} }
} }
unitOfWorkAttribute = null;
return false; 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) private static bool AnyMethodHasUnitOfWorkAttribute(TypeInfo implementationType)
{ {
return implementationType return implementationType

33
src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkInterceptor.cs

@ -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.DependencyInjection;
using Volo.Abp.DynamicProxy; using Volo.Abp.DynamicProxy;
@ -7,40 +11,55 @@ namespace Volo.Abp.Uow
public class UnitOfWorkInterceptor : AbpInterceptor, ITransientDependency public class UnitOfWorkInterceptor : AbpInterceptor, ITransientDependency
{ {
private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly UnitOfWorkDefaultOptions _defaultOptions;
public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager) public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager, IOptions<UnitOfWorkDefaultOptions> options)
{ {
_unitOfWorkManager = unitOfWorkManager; _unitOfWorkManager = unitOfWorkManager;
_defaultOptions = options.Value;
} }
public override void Intercept(IAbpMethodInvocation invocation) public override void Intercept(IAbpMethodInvocation invocation)
{ {
if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method)) if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute))
{ {
invocation.Proceed(); invocation.Proceed();
return; return;
} }
using (var uow = _unitOfWorkManager.Begin()) using (var uow = _unitOfWorkManager.Begin(CreateOptions(invocation, unitOfWorkAttribute)))
{ {
invocation.Proceed(); invocation.Proceed();
uow.Complete(); 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(); await invocation.ProceedAsync();
return; return;
} }
using (var uow = _unitOfWorkManager.Begin()) using (var uow = _unitOfWorkManager.Begin(CreateOptions(invocation, unitOfWorkAttribute)))
{ {
await invocation.ProceedAsync(); await invocation.ProceedAsync();
await uow.CompleteAsync(); 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;
}
} }
} }

114
src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs

@ -1,5 +1,4 @@
using System; using System;
using System.Diagnostics;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
@ -7,61 +6,120 @@ namespace Volo.Abp.Uow
{ {
public class UnitOfWorkManager : IUnitOfWorkManager, ISingletonDependency public class UnitOfWorkManager : IUnitOfWorkManager, ISingletonDependency
{ {
public IUnitOfWork Current => _ambientUnitOfWork.UnitOfWork; public IUnitOfWork Current => GetCurrentUnitOfWork();
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IAmbientUnitOfWork _ambientUnitOfWork; private readonly IAmbientUnitOfWork _ambientUnitOfWork;
public UnitOfWorkManager(IServiceProvider serviceProvider, IAmbientUnitOfWork ambientUnitOfWork) public UnitOfWorkManager(
IServiceProvider serviceProvider,
IAmbientUnitOfWork ambientUnitOfWork)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_ambientUnitOfWork = ambientUnitOfWork; _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); return new ChildUnitOfWork(_ambientUnitOfWork.UnitOfWork);
} }
var parentUow = _ambientUnitOfWork.UnitOfWork; var unitOfWork = CreateNewUnitOfWork();
unitOfWork.Initialize(options);
var scope = _serviceProvider.CreateScope(); return unitOfWork;
IUnitOfWork unitOfWork; }
try
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 new AbpException($"Could not find a reserved unit of work with reservation name: {reservationName}");
throw; }
}
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( return true;
_ambientUnitOfWork.UnitOfWork != null, }
"_ambientUnitOfWork.UnitOfWork can not be null since it's set by _ambientUnitOfWork.SetUnitOfWork method!"
);
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(); scope.Dispose();
}; throw;
}
return _ambientUnitOfWork.UnitOfWork;
} }
} }
} }

31
src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs

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

27
src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs

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

11
src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkTransactionBehavior.cs

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

2
test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs

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

30
test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/UnitOfWorkTestController.cs

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

22
test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs

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

30
test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj

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

42
test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs

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

12
test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EntityFrameworkCoreTestBase.cs

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

38
test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Basic_Repository_Tests.cs

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

62
test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Transaction_Tests.cs

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

17
test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs

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

6
test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestModule.cs

@ -5,6 +5,7 @@ using Volo.Abp.Autofac;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.Uow;
namespace Volo.Abp.Identity namespace Volo.Abp.Identity
{ {
@ -26,6 +27,11 @@ namespace Volo.Abp.Identity
context.DbContextOptions.UseInMemoryDatabase(databaseName); 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) public override void OnApplicationInitialization(ApplicationInitializationContext context)

76
test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs

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

31
test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Events_Tests.cs

@ -108,5 +108,36 @@ namespace Volo.Abp.Uow
failed.ShouldBeTrue(); failed.ShouldBeTrue();
disposed.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();
}
} }
} }

46
test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs

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