mirror of https://github.com/abpframework/abp.git
committed by
GitHub
61 changed files with 1622 additions and 179 deletions
Binary file not shown.
@ -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.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); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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,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); |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
@ -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,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)); |
|
||||
} |
|
||||
} |
|
||||
@ -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)); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -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) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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…
Reference in new issue