diff --git a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs index 9d943441..70c5e6e8 100644 --- a/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs +++ b/src/abstractions/Backend.Fx/Patterns/DependencyInjection/BackendFxApplication.cs @@ -72,7 +72,7 @@ public IDisposable BeginScope(IIdentity identity = null, TenantId tenantId = nul return new MultipleDisposable(scope, scopeDurationLogger); } - + public void Run() where TJob : class, IJob { var tenantIds = TenantIdService.GetActiveTenantIds(); @@ -91,23 +91,21 @@ public void Invoke(Action action, IIdentity identity, TenantId tenantId) { using (BeginScope(new SystemIdentity(), tenantId)) { - using (var unitOfWork = CompositionRoot.GetInstance()) + var unitOfWork = CompositionRoot.GetInstance(); + try + { + unitOfWork.Begin(); + action.Invoke(); + unitOfWork.Complete(); + } + catch (TargetInvocationException ex) + { + ExceptionLogger.LogException(ex.InnerException ?? ex); + } + catch (Exception ex) { - try - { - unitOfWork.Begin(); - action.Invoke(); - unitOfWork.Complete(); - } - catch (TargetInvocationException ex) - { - ExceptionLogger.LogException(ex.InnerException ?? ex); - } - catch (Exception ex) - { - Logger.Info(ex); - ExceptionLogger.LogException(ex); - } + Logger.Info(ex); + ExceptionLogger.LogException(ex); } } } @@ -116,23 +114,21 @@ public async Task InvokeAsync(Func awaitableAsyncAction, IIdentity identit { using (BeginScope(new SystemIdentity(), tenantId)) { - using (var unitOfWork = CompositionRoot.GetInstance()) + var unitOfWork = CompositionRoot.GetInstance(); + try + { + unitOfWork.Begin(); + await awaitableAsyncAction.Invoke(); + unitOfWork.Complete(); + } + catch (TargetInvocationException ex) + { + ExceptionLogger.LogException(ex.InnerException ?? ex); + } + catch (Exception ex) { - try - { - unitOfWork.Begin(); - await awaitableAsyncAction.Invoke(); - unitOfWork.Complete(); - } - catch (TargetInvocationException ex) - { - ExceptionLogger.LogException(ex.InnerException ?? ex); - } - catch (Exception ex) - { - Logger.Info(ex); - ExceptionLogger.LogException(ex); - } + Logger.Info(ex); + ExceptionLogger.LogException(ex); } } } diff --git a/src/abstractions/Backend.Fx/Patterns/Transactions/ReadonlyDecorator.cs b/src/abstractions/Backend.Fx/Patterns/Transactions/ReadonlyDecorator.cs new file mode 100644 index 00000000..a7ab2bee --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/Transactions/ReadonlyDecorator.cs @@ -0,0 +1,26 @@ +using System.Data; + +namespace Backend.Fx.Patterns.Transactions +{ + public class ReadonlyDecorator : ITransactionContext + { + private readonly ITransactionContext _transactionContext; + + public ReadonlyDecorator(ITransactionContext transactionContext) + { + _transactionContext = transactionContext; + } + + public void BeginTransaction() => _transactionContext.BeginTransaction(); + + public void CommitTransaction() + { + RollbackTransaction(); + } + + public void RollbackTransaction() => _transactionContext.RollbackTransaction(); + + + public IDbTransaction CurrentTransaction => _transactionContext.CurrentTransaction; + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/TransactionContext.cs b/src/abstractions/Backend.Fx/Patterns/Transactions/TransactionContext.cs similarity index 70% rename from src/abstractions/Backend.Fx/Patterns/UnitOfWork/TransactionContext.cs rename to src/abstractions/Backend.Fx/Patterns/Transactions/TransactionContext.cs index 0742a421..49899c67 100644 --- a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/TransactionContext.cs +++ b/src/abstractions/Backend.Fx/Patterns/Transactions/TransactionContext.cs @@ -2,45 +2,64 @@ using System.Data; using Backend.Fx.Logging; -namespace Backend.Fx.Patterns.UnitOfWork +namespace Backend.Fx.Patterns.Transactions { - public class TransactionContext : IDisposable + /// + /// wraps an underlying database transaction. In combination with a injection container, access to + /// the current transaction can be gained by means of this interface. + /// + public interface ITransactionContext + { + IDbTransaction CurrentTransaction { get; } + void BeginTransaction(); + void CommitTransaction(); + void RollbackTransaction(); + } + + public class TransactionContext : ITransactionContext, IDisposable { private static readonly ILogger Logger = LogManager.Create(); - private IDisposable _transactionLifetimeLogger; private readonly bool _shouldHandleConnectionState; - - public IDbConnection Connection { get; } - - public IDbTransaction CurrentTransaction { get; private set; } - + private IDisposable _transactionLifetimeLogger; + public TransactionContext(IDbConnection connection) { Connection = connection; - ConnectionState connectionState = Connection.State; - switch (connectionState) + ConnectionState state = Connection.State; + switch (state) { - case ConnectionState.Open: - _shouldHandleConnectionState = false; - break; case ConnectionState.Closed: _shouldHandleConnectionState = true; break; + case ConnectionState.Open: + _shouldHandleConnectionState = false; + break; default: - throw new InvalidOperationException($"A connection provided to the TransactionContext must either be closed or open, but must not be {connectionState}"); + throw new InvalidOperationException($"A connection provided to the TransactionContext must either be closed or open, but must not be {state}"); } } + public IDbConnection Connection { get; } + + public IDbTransaction CurrentTransaction { get; private set; } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + public void BeginTransaction() { if (_shouldHandleConnectionState) { Connection.Open(); } + CurrentTransaction = Connection.BeginTransaction(); _transactionLifetimeLogger = Logger.DebugDuration("Transaction open"); } - + public void CommitTransaction() { CurrentTransaction.Commit(); @@ -54,6 +73,19 @@ public void CommitTransaction() } } + public void RollbackTransaction() + { + CurrentTransaction.Rollback(); + CurrentTransaction.Dispose(); + CurrentTransaction = null; + _transactionLifetimeLogger?.Dispose(); + _transactionLifetimeLogger = null; + if (_shouldHandleConnectionState) + { + Connection.Close(); + } + } + private void Dispose(bool disposing) { if (disposing) @@ -71,16 +103,10 @@ private void Dispose(bool disposing) { Logger.Error(ex, "Dispose failed"); } - + _transactionLifetimeLogger?.Dispose(); CurrentTransaction?.Dispose(); } } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } } } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/DbConnectionDecorator.cs b/src/abstractions/Backend.Fx/Patterns/UnitOfWork/DbConnectionDecorator.cs new file mode 100644 index 00000000..a07eb772 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/UnitOfWork/DbConnectionDecorator.cs @@ -0,0 +1,36 @@ +using System.Data; +using System.Security.Principal; +using Backend.Fx.Patterns.DependencyInjection; + +namespace Backend.Fx.Patterns.UnitOfWork +{ + /// + /// Enriches the unit of work to open and close a database connection during lifetime + /// + public class DbConnectionDecorator : IUnitOfWork + { + public DbConnectionDecorator(IDbConnection dbConnection, IUnitOfWork unitOfWork) + { + DbConnection = dbConnection; + UnitOfWork = unitOfWork; + } + + public IUnitOfWork UnitOfWork { get; } + + public IDbConnection DbConnection { get; } + + public ICurrentTHolder IdentityHolder => UnitOfWork.IdentityHolder; + + public void Begin() + { + DbConnection.Open(); + UnitOfWork.Begin(); + } + + public void Complete() + { + UnitOfWork.Complete(); + DbConnection.Close(); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/DbTransactionDecorator.cs b/src/abstractions/Backend.Fx/Patterns/UnitOfWork/DbTransactionDecorator.cs new file mode 100644 index 00000000..fdbc3200 --- /dev/null +++ b/src/abstractions/Backend.Fx/Patterns/UnitOfWork/DbTransactionDecorator.cs @@ -0,0 +1,36 @@ +using System.Security.Principal; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.Transactions; + +namespace Backend.Fx.Patterns.UnitOfWork +{ + /// + /// Enriches the unit of work to use a database transaction during lifetime. + /// + public class DbTransactionDecorator : IUnitOfWork + { + public DbTransactionDecorator(ITransactionContext transactionContext, IUnitOfWork unitOfWork) + { + TransactionContext = transactionContext; + UnitOfWork = unitOfWork; + } + + public IUnitOfWork UnitOfWork { get; } + + public ITransactionContext TransactionContext { get; } + + public ICurrentTHolder IdentityHolder => UnitOfWork.IdentityHolder; + + public void Begin() + { + TransactionContext.BeginTransaction(); + UnitOfWork.Begin(); + } + + public void Complete() + { + UnitOfWork.Complete(); + TransactionContext.CommitTransaction(); + } + } +} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/DbUnitOfWork.cs b/src/abstractions/Backend.Fx/Patterns/UnitOfWork/DbUnitOfWork.cs deleted file mode 100644 index 78496e33..00000000 --- a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/DbUnitOfWork.cs +++ /dev/null @@ -1,83 +0,0 @@ -// using System; -// using System.Data; -// using System.Data.Common; -// using System.Security.Principal; -// using Backend.Fx.Environment.DateAndTime; -// using Backend.Fx.Logging; -// using Backend.Fx.Patterns.DependencyInjection; -// using Backend.Fx.Patterns.EventAggregation.Domain; -// using Backend.Fx.Patterns.EventAggregation.Integration; -// -// namespace Backend.Fx.Patterns.UnitOfWork -// { -// public abstract class DbUnitOfWork : UnitOfWork -// { -// private static readonly ILogger Logger = LogManager.Create(); -// private IDisposable _transactionLifetimeLogger; -// private readonly bool _shouldHandleConnectionState; -// -// protected DbUnitOfWork(IClock clock, ICurrentTHolder identityHolder, IDomainEventAggregator eventAggregator, -// IEventBusScope eventBusScope, DbConnection dbConnection) -// : base(clock, identityHolder, eventAggregator, eventBusScope) -// { -// Connection = dbConnection; -// if (Connection.State == ConnectionState.Open) -// { -// _shouldHandleConnectionState = false; -// } -// } -// -// public DbConnection Connection { get; } -// -// public DbTransaction CurrentTransaction { get; private set; } -// -// public override void Begin() -// { -// base.Begin(); -// if (_shouldHandleConnectionState) Connection.Open(); -// CurrentTransaction = Connection.BeginTransaction(); -// _transactionLifetimeLogger = Logger.DebugDuration("Transaction open"); -// } -// -// protected override void Commit() -// { -// CurrentTransaction.Commit(); -// CurrentTransaction.Dispose(); -// CurrentTransaction = null; -// _transactionLifetimeLogger?.Dispose(); -// _transactionLifetimeLogger = null; -// if (_shouldHandleConnectionState) Connection.Close(); -// } -// -// protected override void Rollback() -// { -// Logger.Info("Rolling back transaction"); -// try -// { -// CurrentTransaction?.Rollback(); -// CurrentTransaction?.Dispose(); -// CurrentTransaction = null; -// if (_shouldHandleConnectionState && Connection.State == ConnectionState.Open) -// { -// Connection.Close(); -// } -// } -// catch (Exception ex) -// { -// Logger.Error(ex, "Rollback failed"); -// } -// _transactionLifetimeLogger?.Dispose(); -// _transactionLifetimeLogger = null; -// } -// -// protected override void Dispose(bool disposing) -// { -// base.Dispose(disposing); -// if (disposing) -// { -// _transactionLifetimeLogger?.Dispose(); -// CurrentTransaction?.Dispose(); -// } -// } -// } -// } \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/IUnitOfWork.cs b/src/abstractions/Backend.Fx/Patterns/UnitOfWork/IUnitOfWork.cs index 8a81f722..962422f6 100644 --- a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/IUnitOfWork.cs +++ b/src/abstractions/Backend.Fx/Patterns/UnitOfWork/IUnitOfWork.cs @@ -10,9 +10,9 @@ using Logging; /// - /// All-or-nothing operation wrapper, typically implemented by a surrounding database transaction + /// Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. /// - public interface IUnitOfWork : IDisposable + public interface IUnitOfWork { void Begin(); void Complete(); @@ -52,7 +52,7 @@ public virtual void Begin() _lifetimeLogger = Logger.DebugDuration($"Beginning unit of work #{_instanceId}", $"Disposing unit of work #{_instanceId}"); _isCompleted = false; } - + public virtual void Complete() { Logger.Debug("Completing unit of work #" + _instanceId); diff --git a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/ReadonlyDecorator.cs b/src/abstractions/Backend.Fx/Patterns/UnitOfWork/ReadonlyDecorator.cs deleted file mode 100644 index 824d42dd..00000000 --- a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/ReadonlyDecorator.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Security.Principal; -using Backend.Fx.Patterns.DependencyInjection; - -namespace Backend.Fx.Patterns.UnitOfWork -{ - public class ReadonlyDecorator : IUnitOfWork - { - private readonly IUnitOfWork _unitOfWorkImplementation; - - public ReadonlyDecorator(IUnitOfWork unitOfWorkImplementation) - { - _unitOfWorkImplementation = unitOfWorkImplementation; - } - - public void Dispose() - { - _unitOfWorkImplementation.Dispose(); - } - - public void Begin() - { - _unitOfWorkImplementation.Begin(); - } - - public void Complete() - { - // prevent completion, results in rollback on disposal - } - - public ICurrentTHolder IdentityHolder => _unitOfWorkImplementation.IdentityHolder; - } -} \ No newline at end of file diff --git a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/UnitOfWorkTransactionDecorator.cs b/src/abstractions/Backend.Fx/Patterns/UnitOfWork/UnitOfWorkTransactionDecorator.cs deleted file mode 100644 index a1fb6ac0..00000000 --- a/src/abstractions/Backend.Fx/Patterns/UnitOfWork/UnitOfWorkTransactionDecorator.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Data; -using System.Security.Principal; -using Backend.Fx.Patterns.DependencyInjection; - -namespace Backend.Fx.Patterns.UnitOfWork -{ - public class UnitOfWorkTransactionDecorator : IUnitOfWork - { - public UnitOfWorkTransactionDecorator(IDbConnection dbConnection, IUnitOfWork unitOfWork) - { - UnitOfWork = unitOfWork; - TransactionContext = new TransactionContext(dbConnection); - } - - public IUnitOfWork UnitOfWork { get; } - - public TransactionContext TransactionContext { get; } - - public ICurrentTHolder IdentityHolder => UnitOfWork.IdentityHolder; - - public virtual void Begin() - { - TransactionContext.BeginTransaction(); - UnitOfWork.Begin(); - } - - public virtual void Complete() - { - UnitOfWork.Complete(); - TransactionContext.CommitTransaction(); - } - - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - UnitOfWork?.Dispose(); - TransactionContext?.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} \ No newline at end of file diff --git a/src/environments/Backend.Fx.AspNetCore.Mvc/UnitOfWork/UnitOfWorkFilter.cs b/src/environments/Backend.Fx.AspNetCore.Mvc/UnitOfWork/UnitOfWorkFilter.cs index e62864b5..d1f16f6c 100644 --- a/src/environments/Backend.Fx.AspNetCore.Mvc/UnitOfWork/UnitOfWorkFilter.cs +++ b/src/environments/Backend.Fx.AspNetCore.Mvc/UnitOfWork/UnitOfWorkFilter.cs @@ -18,10 +18,6 @@ public UnitOfWorkFilter(IBackendFxApplication application) public void OnActionExecuting(ActionExecutingContext context) { var unitOfWork = _application.CompositionRoot.GetInstance(); - if (context.HttpContext.Request.IsSafe()) - { - unitOfWork = new ReadonlyDecorator(unitOfWork); - } unitOfWork.Begin(); } @@ -29,20 +25,13 @@ public void OnActionExecuted(ActionExecutedContext context) { var unitOfWork = _application.CompositionRoot.GetInstance(); - try + if (context.Exception == null) { - if (context.Exception == null) - { - unitOfWork.Complete(); - } - else - { - Logger.Warn($"Preventing unit of work completion due to {context.Exception.GetType().Name}: {context.Exception.Message}"); - } + unitOfWork.Complete(); } - finally + else { - unitOfWork.Dispose(); + Logger.Warn($"Preventing unit of work completion due to {context.Exception.GetType().Name}: {context.Exception.Message}"); } } } diff --git a/src/environments/Backend.Fx.AspNetCore/UnitOfWork/UnitOfWorkMiddleware.cs b/src/environments/Backend.Fx.AspNetCore/UnitOfWork/UnitOfWorkMiddleware.cs index d4f82d90..581952ff 100644 --- a/src/environments/Backend.Fx.AspNetCore/UnitOfWork/UnitOfWorkMiddleware.cs +++ b/src/environments/Backend.Fx.AspNetCore/UnitOfWork/UnitOfWorkMiddleware.cs @@ -30,23 +30,10 @@ public UnitOfWorkMiddleware(RequestDelegate next, IBackendFxApplication applicat [UsedImplicitly] public async Task Invoke(HttpContext context) { - IUnitOfWork unitOfWork = _application.CompositionRoot.GetInstance(); - try - { - // Safe requests (GET, HEAD, OPTIONS) are handled using a unit of work that gets never completed - if (context.Request.IsSafe()) - { - unitOfWork = new ReadonlyDecorator(unitOfWork); - } - - unitOfWork.Begin(); - await _next.Invoke(context); - unitOfWork.Complete(); - } - finally - { - unitOfWork.Dispose(); - } + var unitOfWork = _application.CompositionRoot.GetInstance(); + unitOfWork.Begin(); + await _next.Invoke(context); + unitOfWork.Complete(); } } } diff --git a/src/implementations/Backend.Fx.EfCorePersistence/DbContextTransactionDecorator.cs b/src/implementations/Backend.Fx.EfCorePersistence/DbContextTransactionDecorator.cs new file mode 100644 index 00000000..c73ce3dd --- /dev/null +++ b/src/implementations/Backend.Fx.EfCorePersistence/DbContextTransactionDecorator.cs @@ -0,0 +1,52 @@ +using System; +using System.Data.Common; +using System.Security.Principal; +using Backend.Fx.Patterns.DependencyInjection; +using Backend.Fx.Patterns.Transactions; +using Backend.Fx.Patterns.UnitOfWork; +using Microsoft.EntityFrameworkCore; + +namespace Backend.Fx.EfCorePersistence +{ + /// + /// Makes sure the DbContext gets enlisted in a transaction. The transaction gets started, before IUnitOfWork.Begin() is being called + /// and gets committed after IUnitOfWork.Complete() is being called + /// + public class DbContextTransactionDecorator : IUnitOfWork + { + public DbContextTransactionDecorator(ITransactionContext transactionContext, DbContext dbContext, IUnitOfWork unitOfWork) + { + TransactionContext = transactionContext; + DbContext = dbContext; + UnitOfWork = unitOfWork; + } + + public IUnitOfWork UnitOfWork { get; } + + public ITransactionContext TransactionContext { get; } + + public DbContext DbContext { get; } + + public ICurrentTHolder IdentityHolder => UnitOfWork.IdentityHolder; + + public void Begin() + { + TransactionContext.BeginTransaction(); + if (DbContext.Database.GetDbConnection() != TransactionContext.CurrentTransaction.Connection) + { + throw new InvalidOperationException("The connection used by the DbContext instance does not equal the connection of the transaction context"); + } + DbContext.Database.UseTransaction((DbTransaction) TransactionContext.CurrentTransaction); + UnitOfWork.Begin(); + } + + public void Complete() + { + UnitOfWork.Complete(); + TransactionContext.CommitTransaction(); + } + + public void Dispose() + { } + } +} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.EfCorePersistence/EfUnitOfWork.cs b/src/implementations/Backend.Fx.EfCorePersistence/EfUnitOfWork.cs index d35f1145..e57d6c10 100644 --- a/src/implementations/Backend.Fx.EfCorePersistence/EfUnitOfWork.cs +++ b/src/implementations/Backend.Fx.EfCorePersistence/EfUnitOfWork.cs @@ -9,28 +9,36 @@ namespace Backend.Fx.EfCorePersistence { - public class EfUnitOfWork : UnitOfWork where TDbContext : DbContext + public class EfUnitOfWork : UnitOfWork { - // private static readonly ILogger Logger = LogManager.Create>(); public EfUnitOfWork(IClock clock, ICurrentTHolder identityHolder, IDomainEventAggregator eventAggregator, - IEventBusScope eventBusScope, TDbContext dbContext) + IEventBusScope eventBusScope, DbContext dbContext) : base(clock, identityHolder, eventAggregator, eventBusScope) { DbContext = dbContext; } - public TDbContext DbContext { get; } + public DbContext DbContext { get; } public override void Flush() { base.Flush(); DbContext.SaveChanges(); } - + protected override void UpdateTrackingProperties(string userId, DateTime utcNow) { DbContext.UpdateTrackingProperties(userId, utcNow); } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + DbContext?.Dispose(); + } + base.Dispose(disposing); + } } } diff --git a/src/implementations/Backend.Fx.EfCorePersistence/EfUnitOfWorkTransactionDecorator.cs b/src/implementations/Backend.Fx.EfCorePersistence/EfUnitOfWorkTransactionDecorator.cs deleted file mode 100644 index c4d47cff..00000000 --- a/src/implementations/Backend.Fx.EfCorePersistence/EfUnitOfWorkTransactionDecorator.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Data; -using System.Data.Common; -using Backend.Fx.Patterns.UnitOfWork; -using Microsoft.EntityFrameworkCore; - -namespace Backend.Fx.EfCorePersistence -{ - public class EfUnitOfWorkTransactionDecorator : UnitOfWorkTransactionDecorator where TDbContext : DbContext - { - private readonly EfUnitOfWork _unitOfWork; - - public EfUnitOfWorkTransactionDecorator(IDbConnection dbConnection, EfUnitOfWork unitOfWork) : base(dbConnection, unitOfWork) - { - _unitOfWork = unitOfWork; - } - - public override void Begin() - { - base.Begin(); - _unitOfWork.DbContext.Database.UseTransaction((DbTransaction) TransactionContext.CurrentTransaction); - } - } -} \ No newline at end of file diff --git a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryUnitOfWork.cs b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryUnitOfWork.cs index 900b337a..8c1b21a9 100644 --- a/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryUnitOfWork.cs +++ b/src/implementations/Backend.Fx.InMemoryPersistence/InMemoryUnitOfWork.cs @@ -25,7 +25,5 @@ public override void Complete() { CommitCalls++; } - - } } diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DbSession.cs b/tests/Backend.Fx.EfCorePersistence.Tests/DbSession.cs index a9191d70..8a466999 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DbSession.cs +++ b/tests/Backend.Fx.EfCorePersistence.Tests/DbSession.cs @@ -7,6 +7,7 @@ using Backend.Fx.Patterns.DependencyInjection; using Backend.Fx.Patterns.EventAggregation.Domain; using Backend.Fx.Patterns.EventAggregation.Integration; +using Backend.Fx.Patterns.Transactions; using Backend.Fx.Patterns.UnitOfWork; using FakeItEasy; using Microsoft.EntityFrameworkCore; @@ -25,18 +26,29 @@ public DbSession(DbConnection connection, DbContextOptionsBuilder public DbConnection Connection { get; } public DbContextOptionsBuilder OptionsBuilder { get; } - public IUnitOfWork BeginUnitOfWork(IClock clock = null, IIdentity identity = null) + public IUnitOfWork BeginUnitOfWork(bool asReadonly = false, IClock clock = null, IIdentity identity = null) { ICurrentTHolder currentIdentityHolder = new CurrentIdentityHolder(); currentIdentityHolder.ReplaceCurrent(identity ?? new SystemIdentity()); - var efuow = new EfUnitOfWork(clock ?? new FrozenClock(), + + var dbContext = new TestDbContext(OptionsBuilder.Options); + IUnitOfWork unitOfWork = new EfUnitOfWork(clock ?? new FrozenClock(), currentIdentityHolder, A.Fake(), A.Fake(), - new TestDbContext(OptionsBuilder.Options)); - var uow = new EfUnitOfWorkTransactionDecorator(Connection, efuow); - uow.Begin(); - return uow; + dbContext); + + ITransactionContext transactionContext = new TransactionContext(Connection); + if (asReadonly) + { + transactionContext = new ReadonlyDecorator(transactionContext); + } + + unitOfWork = new DbContextTransactionDecorator(transactionContext,dbContext, unitOfWork); + // unitOfWork = new DbConnectionDecorator(Connection, unitOfWork); + + unitOfWork.Begin(); + return unitOfWork; } public void Dispose() @@ -49,25 +61,45 @@ public static class TestEx { public static DbContext GetDbContext(this IUnitOfWork unitOfWork) { - if (unitOfWork is EfUnitOfWork efUnitOfWork) + if (unitOfWork is EfUnitOfWork efUnitOfWork) { return efUnitOfWork.DbContext; } - if (unitOfWork is UnitOfWorkTransactionDecorator transactionDecorator) + if (unitOfWork is DbContextTransactionDecorator dbcTransactionDecorator) + { + return GetDbContext(dbcTransactionDecorator.UnitOfWork); + } + + if (unitOfWork is DbTransactionDecorator transactionDecorator) { return GetDbContext(transactionDecorator.UnitOfWork); } + + if (unitOfWork is DbConnectionDecorator connectionDecorator) + { + return GetDbContext(connectionDecorator.UnitOfWork); + } throw new InvalidOperationException(); } public static DbTransaction GetDbTransaction(this IUnitOfWork unitOfWork) { - if (unitOfWork is UnitOfWorkTransactionDecorator transactionDecorator) + if (unitOfWork is DbContextTransactionDecorator dbcTransactionDecorator) + { + return (DbTransaction) dbcTransactionDecorator.TransactionContext.CurrentTransaction; + } + + if (unitOfWork is DbTransactionDecorator transactionDecorator) { return (DbTransaction) transactionDecorator.TransactionContext.CurrentTransaction; } + + if (unitOfWork is DbConnectionDecorator connectionDecorator) + { + return GetDbTransaction(connectionDecorator.UnitOfWork); + } throw new InvalidOperationException(); } diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs b/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs index 96aea536..9679a81e 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs +++ b/tests/Backend.Fx.EfCorePersistence.Tests/DummyImpl/Persistence/TestDbContext.cs @@ -5,14 +5,10 @@ namespace Backend.Fx.EfCorePersistence.Tests.DummyImpl.Persistence { - public class TestDbContext : DbContext + public sealed class TestDbContext : DbContext { public TestDbContext([NotNull] DbContextOptions options) : base(options) - {} - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - base.OnConfiguring(optionsBuilder); Database.AutoTransactionsEnabled = false; } diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheEfUnitOfWork.cs b/tests/Backend.Fx.EfCorePersistence.Tests/TheEfUnitOfWork.cs index f41e42b5..7f0bf112 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheEfUnitOfWork.cs +++ b/tests/Backend.Fx.EfCorePersistence.Tests/TheEfUnitOfWork.cs @@ -35,22 +35,19 @@ public void OpensTransaction() { using (DbSession dbs = _fixture.UseDbSession()) { - using (IUnitOfWork sut = dbs.BeginUnitOfWork()) - { - Assert.NotNull(sut.GetDbContext().Database.CurrentTransaction); - Assert.Equal(dbs.Connection, sut.GetDbContext().Database.CurrentTransaction.GetDbTransaction().Connection); - - sut.GetDbContext().Add(new Blogger(333, "Metulsky", "Bratislav")); - sut.Complete(); - - Assert.Null(sut.GetDbTransaction()); - Assert.Throws(() => sut.GetDbContext().Database.CurrentTransaction.Commit()); // because sut.Complete() did it already - } - - using (IUnitOfWork sut = dbs.BeginUnitOfWork()) - { - Assert.NotNull(sut.GetDbContext().Set().SingleOrDefault(b => b.Id == 333 && b.FirstName == "Bratislav" && b.LastName == "Metulsky")); - } + IUnitOfWork sut = dbs.BeginUnitOfWork(); + Assert.NotNull(sut.GetDbContext().Database.CurrentTransaction); + Assert.Equal(dbs.Connection, sut.GetDbContext().Database.CurrentTransaction.GetDbTransaction().Connection); + + sut.GetDbContext().Add(new Blogger(333, "Metulsky", "Bratislav")); + sut.Complete(); + + Assert.Null(sut.GetDbTransaction()); + Assert.Throws(() => sut.GetDbContext().Database.CurrentTransaction.Commit()); // because sut.Complete() did it already + + + sut = dbs.BeginUnitOfWork(); + Assert.NotNull(sut.GetDbContext().Set().SingleOrDefault(b => b.Id == 333 && b.FirstName == "Bratislav" && b.LastName == "Metulsky")); } } @@ -59,21 +56,18 @@ public void RollsBackTransactionOnDisposal() { using (DbSession dbs = _fixture.UseDbSession()) { - { - IUnitOfWork sut = dbs.BeginUnitOfWork(); - sut.GetDbContext().Add(new Blogger(333, "Metulsky", "Bratislav")); - sut.GetDbContext().SaveChanges(); - // no sut.Complete() - sut.Dispose(); - - Assert.Null(sut.GetDbTransaction()); - Assert.Throws(() => sut.GetDbContext().Database.CurrentTransaction.Commit()); // because sut.Dispose() has already rolled back the open tx - } + + IUnitOfWork sut = dbs.BeginUnitOfWork(); + sut.GetDbContext().Add(new Blogger(333, "Metulsky", "Bratislav")); + sut.GetDbContext().SaveChanges(); + // no sut.Complete() + sut.GetDbTransaction().Dispose(); + Assert.Throws(() => + sut.GetDbContext().Database.CurrentTransaction.Commit()); // because sut.Dispose() has already rolled back the open tx - using (IUnitOfWork sut = dbs.BeginUnitOfWork()) - { - Assert.Null(sut.GetDbContext().Set().SingleOrDefault(b => b.Id == 333 && b.FirstName == "Bratislav" && b.LastName == "Metulsky")); - } + sut = dbs.BeginUnitOfWork(); + + Assert.Null(sut.GetDbContext().Set().SingleOrDefault(b => b.Id == 333 && b.FirstName == "Bratislav" && b.LastName == "Metulsky")); } } @@ -104,37 +98,34 @@ public void FlushesAfterDomainEventHandling() using (DbSession dbs = _fixture.UseDbSession()) { - using (EfUnitOfWork sut = new EfUnitOfWork(new FrozenClock(), + var sut = new EfUnitOfWork(new FrozenClock(), CurrentIdentityHolder.CreateSystem(), domainEventAggregator, A.Fake(), // ReSharper disable once AccessToDisposedClosure - new TestDbContext(dbs.OptionsBuilder.Options))) - { - sut.Begin(); - A.CallTo(() => fakeEventHandlerProvider.GetAllEventHandlers()) - .ReturnsLazily(() => - { - // ReSharper disable once AccessToDisposedClosure - var repo = new EfRepository(sut.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); - return new[] {new AnEventHandler(repo)}; - }); - - domainEventAggregator.PublishDomainEvent(new AnEvent()); - Blog createdViaEvent = sut.DbContext.Set().Find(99999); - Assert.Null(createdViaEvent); - sut.Complete(); - } + new TestDbContext(dbs.OptionsBuilder.Options)); + + sut.Begin(); + A.CallTo(() => fakeEventHandlerProvider.GetAllEventHandlers()) + .ReturnsLazily(() => + { + // ReSharper disable once AccessToDisposedClosure + var repo = new EfRepository(sut.DbContext, new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); + return new[] {new AnEventHandler(repo)}; + }); + + domainEventAggregator.PublishDomainEvent(new AnEvent()); + Blog createdViaEvent = sut.DbContext.Set().Find(99999); + Assert.Null(createdViaEvent); + sut.Complete(); } using (DbSession dbs = _fixture.UseDbSession()) { - using (IUnitOfWork sut = dbs.BeginUnitOfWork()) - { - Blog createdViaEvent = sut.GetDbContext().Set().Find(99999); - Assert.NotNull(createdViaEvent); - Assert.Equal("Created via Event Handling", createdViaEvent.Name); - } + IUnitOfWork sut = dbs.BeginUnitOfWork(); + Blog createdViaEvent = sut.GetDbContext().Set().Find(99999); + Assert.NotNull(createdViaEvent); + Assert.Equal("Created via Event Handling", createdViaEvent.Name); } } } diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheReadonlyEfUnitOfWork.cs b/tests/Backend.Fx.EfCorePersistence.Tests/TheReadonlyEfUnitOfWork.cs deleted file mode 100644 index 5d98dfd5..00000000 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheReadonlyEfUnitOfWork.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; -using Backend.Fx.EfCorePersistence.Tests.Fixtures; -using Backend.Fx.Patterns.UnitOfWork; -using Xunit; - -namespace Backend.Fx.EfCorePersistence.Tests -{ - public class TheReadonlyEfUnitOfWork - { - private readonly DatabaseFixture _fixture; - - public TheReadonlyEfUnitOfWork() - { - _fixture = new SqliteDatabaseFixture(); - //_fixture = new SqlServerDatabaseFixture(); - _fixture.CreateDatabase(); - } - - [Fact] - public void OpensTransaction() - { - using(DbSession dbs = _fixture.UseDbSession()) - { - IUnitOfWork unitOfWork = dbs.BeginUnitOfWork(); - var sut = new ReadonlyDecorator(unitOfWork); - Assert.NotNull(unitOfWork.GetDbContext()); - Assert.NotNull(unitOfWork.GetDbContext().Database.CurrentTransaction); - sut.Complete(); - sut.Dispose(); - Assert.Throws(() => unitOfWork.GetDbContext().Database.CurrentTransaction.Commit()); - } - } - - [Fact] - public void RollbacksTransactionOnComplete() - { - using (DbSession dbs = _fixture.UseDbSession()) - { - IUnitOfWork unitOfWork = dbs.BeginUnitOfWork(); - var sut = new ReadonlyDecorator(unitOfWork); - unitOfWork.GetDbContext().Add(new Blogger(334, "Bratislav", "Metulsky")); - sut.Complete(); - sut.Dispose(); - Assert.Throws(() => unitOfWork.GetDbContext().Database.CurrentTransaction.Commit()); - } - - using (DbSession dbs = _fixture.UseDbSession()) - { - using (IUnitOfWork sut = dbs.BeginUnitOfWork()) - { - Assert.Empty(sut.GetDbContext().Set()); - } - } - } - - [Fact] - public void RollbacksTransactionOnDisposal() - { - using (DbSession dbs = _fixture.UseDbSession()) - { - IUnitOfWork unitOfWork = dbs.BeginUnitOfWork(); - var sut = new ReadonlyDecorator(unitOfWork); - unitOfWork.GetDbContext().Add(new Blogger(335, "Bratislav", "Metulsky")); - sut.Dispose(); - Assert.Throws(() => unitOfWork.GetDbContext().Database.CurrentTransaction.Commit()); - } - - using (DbSession dbs = _fixture.UseDbSession()) - { - using (IUnitOfWork sut = dbs.BeginUnitOfWork()) - { - Assert.Empty(sut.GetDbContext().Set()); - } - } - } - } -} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheReadonlyTransactionContext.cs b/tests/Backend.Fx.EfCorePersistence.Tests/TheReadonlyTransactionContext.cs new file mode 100644 index 00000000..06865dfd --- /dev/null +++ b/tests/Backend.Fx.EfCorePersistence.Tests/TheReadonlyTransactionContext.cs @@ -0,0 +1,69 @@ +using System; +using Backend.Fx.EfCorePersistence.Tests.DummyImpl.Domain; +using Backend.Fx.EfCorePersistence.Tests.Fixtures; +using Backend.Fx.Patterns.UnitOfWork; +using Xunit; + +namespace Backend.Fx.EfCorePersistence.Tests +{ + public class TheReadonlyTransactionContext + { + private readonly DatabaseFixture _fixture; + + public TheReadonlyTransactionContext() + { + _fixture = new SqliteDatabaseFixture(); + //_fixture = new SqlServerDatabaseFixture(); + _fixture.CreateDatabase(); + } + + [Fact] + public void OpensTransaction() + { + using(DbSession dbs = _fixture.UseDbSession()) + { + IUnitOfWork sut = dbs.BeginUnitOfWork(true); + Assert.NotNull(sut.GetDbContext()); + Assert.NotNull(sut.GetDbContext().Database.CurrentTransaction); + sut.Complete(); + Assert.Throws(() => sut.GetDbContext().Database.CurrentTransaction.Commit()); + } + } + + [Fact] + public void RollbacksTransactionOnComplete() + { + using (DbSession dbs = _fixture.UseDbSession()) + { + IUnitOfWork sut = dbs.BeginUnitOfWork(true); + sut.GetDbContext().Add(new Blogger(334, "Bratislav", "Metulsky")); + sut.Complete(); + Assert.Throws(() => sut.GetDbContext().Database.CurrentTransaction.Commit()); + } + + using (DbSession dbs = _fixture.UseDbSession()) + { + IUnitOfWork sut = dbs.BeginUnitOfWork(); + Assert.Empty(sut.GetDbContext().Set()); + } + } + + [Fact] + public void RollbacksTransactionOnDisposal() + { + using (DbSession dbs = _fixture.UseDbSession()) + { + IUnitOfWork sut = dbs.BeginUnitOfWork(true); + sut.GetDbContext().Add(new Blogger(335, "Bratislav", "Metulsky")); + sut.GetDbTransaction().Dispose(); + Assert.Throws(() => sut.GetDbContext().Database.CurrentTransaction.Commit()); + } + + using (DbSession dbs = _fixture.UseDbSession()) + { + IUnitOfWork sut = dbs.BeginUnitOfWork(); + Assert.Empty(sut.GetDbContext().Set()); + } + } + } +} diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs b/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs index 24989eb3..bd8840a2 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs +++ b/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfComposedAggregate.cs @@ -9,6 +9,7 @@ using Backend.Fx.Extensions; using Backend.Fx.Patterns.Authorization; using Backend.Fx.Patterns.IdGeneration; +using Backend.Fx.Patterns.UnitOfWork; using FakeItEasy; using Xunit; @@ -46,8 +47,8 @@ public void CanCreate() Assert.Equal(0, count); } - using (var uow = dbs.BeginUnitOfWork(_clock)) { + IUnitOfWork uow = dbs.BeginUnitOfWork(clock:_clock); var sut = new EfRepository(uow.GetDbContext(), new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); var blog = new Blog(_idGenerator.NextId(), "my blog"); blog.AddPost(_idGenerator, "my post"); @@ -74,8 +75,8 @@ public void CanRead() var id = CreateBlogWithPost(dbs); Blog blog; - using (var uow = dbs.BeginUnitOfWork(_clock)) { + IUnitOfWork uow = dbs.BeginUnitOfWork(clock:_clock); var sut = new EfRepository(uow.GetDbContext(), new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); blog = sut.Single(id); @@ -99,8 +100,8 @@ public void CanUpdate() { var id = CreateBlogWithPost(dbs); - using (var uow = dbs.BeginUnitOfWork(_clock)) { + IUnitOfWork uow = dbs.BeginUnitOfWork(clock:_clock); var sut = new EfRepository(uow.GetDbContext(), new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); Blog blog = sut.Single(id); @@ -122,8 +123,8 @@ public void CanDelete() { var id = CreateBlogWithPost(dbs); - using (var uow = dbs.BeginUnitOfWork(_clock)) { + IUnitOfWork uow = dbs.BeginUnitOfWork(clock:_clock); var sut = new EfRepository(uow.GetDbContext(), new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); Blog blog = sut.Single(id); sut.Delete(blog); @@ -150,8 +151,8 @@ public void CanDeleteDependent() Assert.Equal(10, count); } - using (var uow = dbs.BeginUnitOfWork(_clock)) { + IUnitOfWork uow = dbs.BeginUnitOfWork(clock:_clock); var sut = new EfRepository(uow.GetDbContext(), new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); var blog = sut.Single(id); @@ -176,8 +177,8 @@ public void CanUpdateDependant() int id = CreateBlogWithPost(dbs, 10); Post post; - using (var uow = dbs.BeginUnitOfWork(_clock)) { + IUnitOfWork uow = dbs.BeginUnitOfWork(clock:_clock); var sut = new EfRepository(uow.GetDbContext(), new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); var blog = sut.Single(id); @@ -237,16 +238,13 @@ public void CanAddDependent() { int id = CreateBlogWithPost(dbs, 10); - using (var uow = dbs.BeginUnitOfWork(_clock)) { + IUnitOfWork uow = dbs.BeginUnitOfWork(clock:_clock); var sut = new EfRepository(uow.GetDbContext(), new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); var blog = sut.Single(id); blog.Posts.Add(new Post(_idGenerator.NextId(), blog, "added")); uow.Complete(); - } - - { int count = dbs.Connection.ExecuteScalar("SELECT count(*) FROM Posts"); Assert.Equal(11, count); } @@ -261,8 +259,8 @@ public void CanReplaceDependentCollection() { var id = CreateBlogWithPost(dbs, 10); - using (var uow = dbs.BeginUnitOfWork(_clock)) { + IUnitOfWork uow = dbs.BeginUnitOfWork(clock:_clock); var sut = new EfRepository(uow.GetDbContext(), new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); var blog = sut.Single(id); blog.Posts.Clear(); @@ -292,16 +290,16 @@ public void UpdatesAggregateTrackingPropertiesOnDeleteOfDependant() var expectedModifiedOn = _clock.UtcNow.AddHours(1); _clock.OverrideUtcNow(expectedModifiedOn); - using (var uow = dbs.BeginUnitOfWork(_clock)) { + IUnitOfWork uow = dbs.BeginUnitOfWork(clock:_clock); var sut = new EfRepository(uow.GetDbContext(), new BlogMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); var b = sut.Single(id); b.Posts.Remove(b.Posts.First()); uow.Complete(); } - using (var uow = dbs.BeginUnitOfWork(_clock)) { + IUnitOfWork uow = dbs.BeginUnitOfWork(clock:_clock); var blog = uow.GetDbContext().Set().Find(id); Assert.NotNull(blog.ChangedOn); Assert.Equal(expectedModifiedOn, blog.ChangedOn.Value, _tolerantDateTimeComparer); diff --git a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs b/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs index 80a51f9e..0d591e4c 100644 --- a/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs +++ b/tests/Backend.Fx.EfCorePersistence.Tests/TheRepositoryOfPlainAggregate.cs @@ -6,6 +6,7 @@ using Backend.Fx.Environment.MultiTenancy; using Backend.Fx.Extensions; using Backend.Fx.Patterns.Authorization; +using Backend.Fx.Patterns.UnitOfWork; using Xunit; namespace Backend.Fx.EfCorePersistence.Tests @@ -28,8 +29,8 @@ public void CanCreate() { using (var dbs = _fixture.UseDbSession()) { - using (var uow = dbs.BeginUnitOfWork()) { + IUnitOfWork uow = dbs.BeginUnitOfWork(); var repo = new EfRepository(uow.GetDbContext(), new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); repo.Add(new Blogger(345, "Metulsky", "Bratislav")); uow.Complete(); @@ -58,8 +59,8 @@ public void CanRead() $"VALUES (444, {_tenantId}, '2012-05-12 23:12:09', 'the test', 'Bratislav', 'Metulsky', 'whatever')"); } - using (var uow = dbs.BeginUnitOfWork()) { + IUnitOfWork uow = dbs.BeginUnitOfWork(); var repo = new EfRepository(uow.GetDbContext(), new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); Blogger bratislavMetulsky = repo.Single(444); Assert.Equal(_tenantId, bratislavMetulsky.TenantId); @@ -86,8 +87,8 @@ public void CanDelete() $"VALUES (555, {_tenantId}, '2012-05-12 23:12:09', 'the test', 'Bratislav', 'Metulsky', 'whatever')"); } - using (var uow = dbs.BeginUnitOfWork()) { + IUnitOfWork uow = dbs.BeginUnitOfWork(); var repo = new EfRepository(uow.GetDbContext(), new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); Blogger bratislavMetulsky = repo.Single(555); repo.Delete(bratislavMetulsky); @@ -114,8 +115,8 @@ public void CanUpdate() $"VALUES (456, {_tenantId}, '2012-05-12 23:12:09', 'the test', 'Bratislav', 'Metulsky', 'whatever')"); } - using (var uow = dbs.BeginUnitOfWork()) { + IUnitOfWork uow = dbs.BeginUnitOfWork(); var repo = new EfRepository(uow.GetDbContext(), new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); Blogger bratislavMetulsky = repo.Single(456); @@ -135,8 +136,8 @@ public void CanUpdate() Assert.Equal(1L, count); } - using (var uow = dbs.BeginUnitOfWork()) { + IUnitOfWork uow = dbs.BeginUnitOfWork(); var repo = new EfRepository(uow.GetDbContext(), new BloggerMapping(), CurrentTenantIdHolder.Create(_tenantId), new AllowAll()); Blogger johnnyFlash = repo.Single(456); Assert.Equal(DateTime.UtcNow, johnnyFlash.ChangedOn, new TolerantDateTimeComparer(TimeSpan.FromMilliseconds(5000))); diff --git a/tests/Backend.Fx.Tests/Patterns/UnitOfWork/TheReadonlyUnitOfWork.cs b/tests/Backend.Fx.Tests/Patterns/UnitOfWork/TheReadonlyUnitOfWork.cs index b8b1085a..601a48b2 100644 --- a/tests/Backend.Fx.Tests/Patterns/UnitOfWork/TheReadonlyUnitOfWork.cs +++ b/tests/Backend.Fx.Tests/Patterns/UnitOfWork/TheReadonlyUnitOfWork.cs @@ -1,4 +1,4 @@ -using Backend.Fx.Patterns.UnitOfWork; +using Backend.Fx.Patterns.Transactions; using FakeItEasy; using Xunit; @@ -9,24 +9,11 @@ public class TheReadonlyDecorator [Fact] public void RollsBackOnComplete() { - IUnitOfWork uow = A.Fake(); - IUnitOfWork sut = new ReadonlyDecorator(uow); - sut.Begin(); - sut.Complete(); - sut.Dispose(); - A.CallTo(() => uow.Complete()).MustNotHaveHappened(); - A.CallTo(() => uow.Dispose()).MustHaveHappenedOnceExactly(); - } - - [Fact] - public void RollsBackOnDispose() - { - IUnitOfWork uow = A.Fake(); - IUnitOfWork sut = new ReadonlyDecorator(uow); - sut.Begin(); - sut.Dispose(); - A.CallTo(() => uow.Complete()).MustNotHaveHappened(); - A.CallTo(() => uow.Dispose()).MustHaveHappenedOnceExactly(); + var transactionContext = A.Fake(); + ITransactionContext sut = new ReadonlyDecorator(transactionContext); + sut.BeginTransaction(); + sut.CommitTransaction(); + A.CallTo(() => transactionContext.CommitTransaction()).MustNotHaveHappened(); } } }