diff --git a/ModularMonolith/Communication/Shop.Communication.DataAccess.MsSql/CommunicationDataAccessModule.cs b/ModularMonolith/Communication/Shop.Communication.DataAccess.MsSql/CommunicationDataAccessModule.cs index 3b53335..a900ded 100644 --- a/ModularMonolith/Communication/Shop.Communication.DataAccess.MsSql/CommunicationDataAccessModule.cs +++ b/ModularMonolith/Communication/Shop.Communication.DataAccess.MsSql/CommunicationDataAccessModule.cs @@ -10,21 +10,11 @@ public class CommunicationDataAccessModule : Module { public override void Load(IServiceCollection services) { - services.AddDbContext((sp, bld) => + services.AddDbContext((sp, bld) => { var factory = sp.GetRequiredService(); bld.UseSqlServer(factory.GetConnection()); }); - - services.AddScoped(sp => - { - var context = sp.GetRequiredService(); - var connectionFactory = sp.GetRequiredService(); - - context.Database.UseTransaction(connectionFactory.GetTransaction()); - - return context; - }); } } } diff --git a/ModularMonolith/Framework/Shop.Framework.Implementation/Services/ConnectionFactory.cs b/ModularMonolith/Framework/Shop.Framework.Implementation/Services/ConnectionFactory.cs index 02feaf1..700efd6 100644 --- a/ModularMonolith/Framework/Shop.Framework.Implementation/Services/ConnectionFactory.cs +++ b/ModularMonolith/Framework/Shop.Framework.Implementation/Services/ConnectionFactory.cs @@ -12,7 +12,6 @@ public ConnectionFactory(string connectionString) } private DbConnection _connection; - private DbTransaction _transaction; private readonly string _connectionString; public DbConnection GetConnection() @@ -25,18 +24,10 @@ public DbConnection GetConnection() return _connection; } - public DbTransaction GetTransaction() - { - return _transaction ??= GetConnection().BeginTransaction(); - } - public bool IsConnectionOpened => _connection != null; - public bool IsTransactionStarted => _transaction != null; - public void Dispose() { - _transaction?.Dispose(); _connection?.Dispose(); } } diff --git a/ModularMonolith/Framework/Shop.Framework.Interfaces/Services/IConnectionFactory.cs b/ModularMonolith/Framework/Shop.Framework.Interfaces/Services/IConnectionFactory.cs index c43503f..e82b52a 100644 --- a/ModularMonolith/Framework/Shop.Framework.Interfaces/Services/IConnectionFactory.cs +++ b/ModularMonolith/Framework/Shop.Framework.Interfaces/Services/IConnectionFactory.cs @@ -6,8 +6,6 @@ namespace Shop.Framework.UseCases.Interfaces.Services public interface IConnectionFactory : IDisposable { DbConnection GetConnection(); - DbTransaction GetTransaction(); bool IsConnectionOpened { get; } - bool IsTransactionStarted { get; } } } diff --git a/ModularMonolith/Order/Shop.Order.DataAccess.MsSql/OrderDataAccessModule.cs b/ModularMonolith/Order/Shop.Order.DataAccess.MsSql/OrderDataAccessModule.cs index d44fbf4..63830d7 100644 --- a/ModularMonolith/Order/Shop.Order.DataAccess.MsSql/OrderDataAccessModule.cs +++ b/ModularMonolith/Order/Shop.Order.DataAccess.MsSql/OrderDataAccessModule.cs @@ -10,21 +10,11 @@ public class OrderDataAccessModule : Module { public override void Load(IServiceCollection services) { - services.AddDbContext((sp, bld) => + services.AddDbContext((sp, bld) => { var factory = sp.GetRequiredService(); bld.UseSqlServer(factory.GetConnection()); }); - - services.AddScoped(sp => - { - var context = sp.GetRequiredService(); - var connectionFactory = sp.GetRequiredService(); - - context.Database.UseTransaction(connectionFactory.GetTransaction()); - - return context; - }); } } } diff --git a/ModularMonolith/Shop.Tests.Unit/WorkflowTests.cs b/ModularMonolith/Shop.Tests.Unit/WorkflowTests.cs index cc0c4c4..a674147 100644 --- a/ModularMonolith/Shop.Tests.Unit/WorkflowTests.cs +++ b/ModularMonolith/Shop.Tests.Unit/WorkflowTests.cs @@ -17,6 +17,7 @@ using Shop.Communication.UseCases; using Shop.Emails.Implementation; using Shop.Framework.UseCases.Implementation; +using Shop.Framework.UseCases.Interfaces.Services; using Shop.Order.Contract.Implementation; using Shop.Order.DataAccess.MsSql; using Shop.Order.UseCases; @@ -37,7 +38,7 @@ public async Task Should_Create_Order_And_Email() var (connectionString, configuration) = CreateConfiguration(); var services = CreateServiceProvider(configuration); - + services.RegisterModule(configuration); var serviceProvider = services.BuildServiceProvider(); var (orderDbContext, communicationDbContext) = await CreateDatabase(connectionString); @@ -65,7 +66,15 @@ public async Task Should_Not_Create_Order_And_Email_On_Error() var (connectionString, configuration) = CreateConfiguration(); var services = CreateServiceProvider(configuration); - services.Decorate(); + //TODO Decorator doesn't work + //services.RegisterModule(configuration); + //services.Decorate(); + services.AddDbContext((sp, bld) => + { + var factory = sp.GetRequiredService(); + bld.UseSqlServer(factory.GetConnection()); + }); + services.AddScoped(); var serviceProvider = services.BuildServiceProvider(); @@ -91,7 +100,7 @@ class TestCommunicationDbContext : ICommunicationDbContext { private readonly ICommunicationDbContext _context; - public TestCommunicationDbContext(ICommunicationDbContext context) + public TestCommunicationDbContext(CommunicationDbContext context) { _context = context; } @@ -140,9 +149,8 @@ private ServiceCollection CreateServiceProvider(IConfiguration configuration) services.AddMediatR(assemblies); services.AddAutoMapper(assemblies); - services.AddScoped(typeof(IPipelineBehavior<,>), typeof(DbTransactionPipelineBehavior<,>)); + services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TransactionScopePipelineBehavior<,>)); - services.RegisterModule(configuration); services.RegisterModule(configuration); services.RegisterModule(configuration); diff --git a/ModularMonolith/Shop.Web/Startup.cs b/ModularMonolith/Shop.Web/Startup.cs index 40e71dd..c8e7c8f 100644 --- a/ModularMonolith/Shop.Web/Startup.cs +++ b/ModularMonolith/Shop.Web/Startup.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Reflection; using System.Runtime.Loader; +using AutoMapper; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -53,7 +54,7 @@ public void ConfigureServices(IServiceCollection services) services.RegisterModule(Configuration); services.RegisterModule(Configuration); - services.AddScoped(typeof(IPipelineBehavior<,>), typeof(DbTransactionPipelineBehavior<,>)); + services.AddScoped(typeof(IPipelineBehavior<,>), typeof(TransactionScopePipelineBehavior<,>)); var location = Assembly.GetExecutingAssembly().Location; var assemblies = Directory.EnumerateFiles(Path.GetDirectoryName(location), "Shop*UseCases.dll") diff --git a/ModularMonolith/Shop.Web/Utils/DbTransactionPipelineBehavior.cs b/ModularMonolith/Shop.Web/Utils/TransactionScopePipelineBehavior.cs similarity index 59% rename from ModularMonolith/Shop.Web/Utils/DbTransactionPipelineBehavior.cs rename to ModularMonolith/Shop.Web/Utils/TransactionScopePipelineBehavior.cs index 3f92d7d..821d032 100644 --- a/ModularMonolith/Shop.Web/Utils/DbTransactionPipelineBehavior.cs +++ b/ModularMonolith/Shop.Web/Utils/TransactionScopePipelineBehavior.cs @@ -1,34 +1,38 @@ using System.Threading; using System.Threading.Tasks; +using System.Transactions; using MediatR; using Shop.Framework.UseCases.Interfaces.Services; using Shop.Framework.UseCases.Interfaces.Transactions; namespace Shop.Web.Utils { - public class DbTransactionPipelineBehavior : IPipelineBehavior + public class TransactionScopePipelineBehavior : IPipelineBehavior where TRequest : IRequest, ITransactionalRequest { private readonly IConnectionFactory _connectionFactory; - public DbTransactionPipelineBehavior(IConnectionFactory connectionFactory) + public TransactionScopePipelineBehavior(IConnectionFactory connectionFactory) { _connectionFactory = connectionFactory; } public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) { - if (_connectionFactory.IsTransactionStarted) + if (_connectionFactory.IsConnectionOpened) { return await next(); } - await using var connection = _connectionFactory.GetConnection(); - await using var transaction = _connectionFactory.GetTransaction(); + using var scope = new TransactionScope(TransactionScopeOption.Required, + new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }, + TransactionScopeAsyncFlowOption.Enabled); + await using var connection = _connectionFactory.GetConnection(); + var result = await next(); - await transaction.CommitAsync(cancellationToken); + scope.Complete(); return result; }