From 302443098055f026819f7f6015a2d461df2e012c Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Tue, 29 Mar 2022 19:57:33 +0800 Subject: [PATCH 01/10] feat(uoW): add IUnitOfWorkManager, Support for creating new DbContext, IUnitOfWork Add IServiceProvider refactor(IntegrationEvents.Dapr): Refactoring background tasks --- .../DataConnectionStringProvider.cs | 19 +++++++++ .../DispatcherOptionsExtensions.cs | 3 +- .../Masa.Contrib.Data.UoW.EF/UnitOfWork.cs | 9 ++--- .../UnitOfWorkManager.cs | 18 +++++++++ src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs | 5 ++- .../Repository.cs | 2 +- .../DeleteLocalQueueExpiresProcessor.cs | 8 ++-- .../DeletePublishedExpireEventProcessor.cs | 24 +++++------ .../Processor/InfiniteLoopProcessor.cs | 5 ++- .../Processor/ProcessorBase.cs | 40 +++++++++++++++---- .../Processor/RetryByDataProcessor.cs | 20 +++++----- .../Processor/RetryByLocalQueueProcessor.cs | 17 ++++---- .../Servers/DefaultHostedService.cs | 13 +++--- 13 files changed, 119 insertions(+), 64 deletions(-) create mode 100644 src/Data/Masa.Contrib.Data.UoW.EF/DataConnectionStringProvider.cs create mode 100644 src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/DataConnectionStringProvider.cs b/src/Data/Masa.Contrib.Data.UoW.EF/DataConnectionStringProvider.cs new file mode 100644 index 000000000..554ef368c --- /dev/null +++ b/src/Data/Masa.Contrib.Data.UoW.EF/DataConnectionStringProvider.cs @@ -0,0 +1,19 @@ +namespace Masa.Contrib.Data.UoW.EF; + +public class DataConnectionStringProvider : BaseDataConnectionStringProvider +{ + private readonly IOptionsMonitor _options; + + public DataConnectionStringProvider(IOptionsMonitor options) + { + _options = options; + } + + protected override List GetDbContextOptionsList() + { + return new () + { + new(_options.CurrentValue.DefaultConnection) + }; + } +} diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs index 3aeee4c68..de2b6f68b 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs @@ -16,6 +16,8 @@ public static IDispatcherOptions UseUoW( return options; options.Services.AddSingleton(); + options.Services.TryAddSingleton(); + options.Services.TryAddSingleton(); options.Services.AddScoped(serviceProvider => new UnitOfWork(serviceProvider) { @@ -35,4 +37,3 @@ private class UoWProvider } } - diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs index 1e97ee7b7..e7008c2f3 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs @@ -1,13 +1,12 @@ namespace Masa.Contrib.Data.UoW.EF; -public class UnitOfWork : IUnitOfWork - where TDbContext : MasaDbContext +public class UnitOfWork : IUnitOfWork where TDbContext : MasaDbContext { - private readonly IServiceProvider _serviceProvider; + public IServiceProvider ServiceProvider { get; } private DbContext? _context; - protected DbContext Context => _context ??= _serviceProvider.GetRequiredService(); + protected DbContext Context => _context ??= ServiceProvider.GetRequiredService(); public DbTransaction Transaction { @@ -35,7 +34,7 @@ public DbTransaction Transaction public UnitOfWork(IServiceProvider serviceProvider) { - _serviceProvider = serviceProvider; + ServiceProvider = serviceProvider; } public async Task SaveChangesAsync(CancellationToken cancellationToken = default) diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs new file mode 100644 index 000000000..a4c8e7678 --- /dev/null +++ b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs @@ -0,0 +1,18 @@ +namespace Masa.Contrib.Data.UoW.EF; + +public class UnitOfWorkManager : IUnitOfWorkManager +{ + private readonly IServiceProvider _serviceProvider; + + public UnitOfWorkManager(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; + + public Task CreateDbContextAsync(Masa.BuildingBlocks.Data.UoW.Options.MasaDbContextOptions dbContextOptions) + { + ArgumentNullException.ThrowIfNull(dbContextOptions, nameof(dbContextOptions)); + if (dbContextOptions.Connection == null && string.IsNullOrEmpty(dbContextOptions.ConnectionString)) + throw new ArgumentException($"Invalid {nameof(dbContextOptions)}"); + + var scope = _serviceProvider.CreateAsyncScope(); + return Task.FromResult(scope.ServiceProvider.GetRequiredService()); + } +} diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs b/src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs index cad5525d3..d65fe8488 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs @@ -4,8 +4,9 @@ global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.Storage; global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Logging; global using System.Data.Common; global using System.Text.Json.Serialization; global using EntityState = Masa.BuildingBlocks.Data.UoW.EntityState; - +global using Microsoft.Extensions.Options; +global using DbContextOptions = Masa.BuildingBlocks.Data.UoW.Options.MasaDbContextOptions; +global using Microsoft.Extensions.DependencyInjection.Extensions; diff --git a/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Repository.cs b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Repository.cs index 239910865..195c39ed2 100644 --- a/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Repository.cs +++ b/src/Ddd/Masa.Contrib.Ddd.Domain.Repository.EF/Repository.cs @@ -7,7 +7,7 @@ public class Repository : { protected readonly TDbContext Context; - public Repository(TDbContext context, IUnitOfWork unitOfWork) + public Repository(TDbContext context, IUnitOfWork unitOfWork) : base(unitOfWork.ServiceProvider) { Context = context; UnitOfWork = unitOfWork; diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs index ad10e902a..3210aab44 100644 --- a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs @@ -4,7 +4,9 @@ public class DeleteLocalQueueExpiresProcessor : ProcessorBase { private readonly IOptions _options; - public DeleteLocalQueueExpiresProcessor(IOptions options) + public override int Delay => _options.Value.CleaningLocalQueueExpireInterval; + + public DeleteLocalQueueExpiresProcessor(IOptions options) : base(null) { _options = options; } @@ -14,11 +16,9 @@ public DeleteLocalQueueExpiresProcessor(IOptions options) /// /// /// - public override Task ExecuteAsync(CancellationToken stoppingToken) + protected override Task ExecutingAsync(CancellationToken stoppingToken) { LocalQueueProcessor.Default.Delete(_options.Value.LocalRetryTimes); return Task.CompletedTask; } - - public override int Delay => _options.Value.CleaningLocalQueueExpireInterval; } diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs index 35bcf2745..c8d3e8ac8 100644 --- a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeletePublishedExpireEventProcessor.cs @@ -2,31 +2,25 @@ public class DeletePublishedExpireEventProcessor : ProcessorBase { - private readonly IServiceProvider _serviceProvider; private readonly IOptions _options; - public DeletePublishedExpireEventProcessor( - IServiceProvider serviceProvider, - IOptions options) + public override int Delay => _options.Value.CleaningExpireInterval; + + public DeletePublishedExpireEventProcessor(IServiceProvider serviceProvider, IOptions options) + : base(serviceProvider) { - _serviceProvider = serviceProvider; _options = options; } /// /// Delete expired events /// + /// /// - /// - public override async Task ExecuteAsync(CancellationToken stoppingToken) + protected override async Task ExecuteAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken) { - using (var scope = _serviceProvider.CreateScope()) - { - var logService = scope.ServiceProvider.GetRequiredService(); - var expireDate = (_options.Value.GetCurrentTime?.Invoke() ?? DateTime.UtcNow).AddSeconds(-_options.Value.PublishedExpireTime); - await logService.DeleteExpiresAsync(expireDate, _options.Value.DeleteBatchCount, stoppingToken); - } + var logService = serviceProvider.GetRequiredService(); + var expireDate = (_options.Value.GetCurrentTime?.Invoke() ?? DateTime.UtcNow).AddSeconds(-_options.Value.PublishedExpireTime); + await logService.DeleteExpiresAsync(expireDate, _options.Value.DeleteBatchCount, stoppingToken); } - - public override int Delay => _options.Value.CleaningExpireInterval; } diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs index 9386930a1..be2bd0884 100644 --- a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/InfiniteLoopProcessor.cs @@ -5,10 +5,11 @@ public class InfiniteLoopProcessor : ProcessorBase private readonly IProcessor _processor; private readonly ILogger? _logger; - public InfiniteLoopProcessor(IProcessor processor, ILogger? logger = null) + public InfiniteLoopProcessor(IServiceProvider serviceProvider, IProcessor processor) + : base(serviceProvider) { _processor = processor; - _logger = logger; + _logger = serviceProvider.GetService>(); } public override async Task ExecuteAsync(CancellationToken stoppingToken) diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs index c95c8d123..44ee4926f 100644 --- a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs @@ -1,19 +1,43 @@ -namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Processor; public abstract class ProcessorBase : IProcessor { - public abstract Task ExecuteAsync(CancellationToken stoppingToken); + protected readonly IServiceProvider? ServiceProvider; + + /// + /// Task delay time, unit: seconds + /// + public virtual int Delay { get; } + + protected ProcessorBase(IServiceProvider? serviceProvider) => ServiceProvider = serviceProvider; + + public virtual async Task ExecuteAsync(CancellationToken stoppingToken) + { + if (ServiceProvider != null) + { + var unitOfWorkManager = ServiceProvider.GetRequiredService(); + var dataConnectionStringProvider = ServiceProvider.GetRequiredService(); + var optionsList = dataConnectionStringProvider.DbContextOptionsList; + foreach (var option in optionsList) + { + await using var unitOfWork = await unitOfWorkManager.CreateDbContextAsync(option); + await ExecuteAsync(unitOfWork.ServiceProvider, stoppingToken); + } + } + else + { + await ExecutingAsync(stoppingToken); + } + } // /// // /// Easy to switch between background tasks // /// /// unit: seconds // /// - public Task DelayAsync(int delay) - => Task.Delay(TimeSpan.FromSeconds(delay)); + public Task DelayAsync(int delay) => Task.Delay(TimeSpan.FromSeconds(delay)); - /// - /// Task delay time, unit: seconds - /// - public virtual int Delay { get; } + protected virtual Task ExecuteAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken) => Task.CompletedTask; + + protected virtual Task ExecutingAsync(CancellationToken stoppingToken) => Task.CompletedTask; } diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs index 1275f7a06..09065a0d3 100644 --- a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByDataProcessor.cs @@ -7,11 +7,13 @@ public class RetryByDataProcessor : ProcessorBase private readonly IOptionsMonitor? _appConfig; private readonly ILogger? _logger; + public override int Delay => _options.Value.FailedRetryInterval; + public RetryByDataProcessor( IServiceProvider serviceProvider, IOptions options, IOptionsMonitor? appConfig = null, - ILogger? logger = null) + ILogger? logger = null) : base(serviceProvider) { _serviceProvider = serviceProvider; _appConfig = appConfig; @@ -19,19 +21,18 @@ public RetryByDataProcessor( _logger = logger; } - public override async Task ExecuteAsync(CancellationToken stoppingToken) + protected override async Task ExecuteAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken) { - using (var scope = _serviceProvider.CreateScope()) - { - var unitOfWork = scope.ServiceProvider.GetService(); + var unitOfWork = serviceProvider.GetService(); if (unitOfWork != null) unitOfWork.UseTransaction = false; - var dapr = scope.ServiceProvider.GetRequiredService(); - var eventLogService = scope.ServiceProvider.GetRequiredService(); + var dapr = serviceProvider.GetRequiredService(); + var eventLogService = serviceProvider.GetRequiredService(); var retrieveEventLogs = - await eventLogService.RetrieveEventLogsFailedToPublishAsync(_options.Value.RetryBatchSize, _options.Value.MaxRetryTimes, _options.Value.MinimumRetryInterval); + await eventLogService.RetrieveEventLogsFailedToPublishAsync(_options.Value.RetryBatchSize, _options.Value.MaxRetryTimes, + _options.Value.MinimumRetryInterval); foreach (var eventLog in retrieveEventLogs) { @@ -64,8 +65,5 @@ public override async Task ExecuteAsync(CancellationToken stoppingToken) await eventLogService.MarkEventAsFailedAsync(eventLog.EventId); } } - } } - - public override int Delay => _options.Value.FailedRetryInterval; } diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs index 2bc3b59bc..9c0a21baf 100644 --- a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/RetryByLocalQueueProcessor.cs @@ -7,11 +7,13 @@ public class RetryByLocalQueueProcessor : ProcessorBase private readonly IOptions _options; private readonly ILogger? _logger; + public override int Delay => _options.Value.LocalFailedRetryInterval; + public RetryByLocalQueueProcessor( IServiceProvider serviceProvider, IOptions options, IOptionsMonitor? appConfig = null, - ILogger? logger = null) + ILogger? logger = null) : base(serviceProvider) { _serviceProvider = serviceProvider; _appConfig = appConfig; @@ -19,16 +21,14 @@ public RetryByLocalQueueProcessor( _logger = logger; } - public override async Task ExecuteAsync(CancellationToken stoppingToken) + protected override async Task ExecuteAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken) { - using (var scope = _serviceProvider.CreateScope()) - { - var unitOfWork = scope.ServiceProvider.GetService(); + var unitOfWork = serviceProvider.GetService(); if (unitOfWork != null) unitOfWork.UseTransaction = false; - var dapr = scope.ServiceProvider.GetRequiredService(); - var eventLogService = scope.ServiceProvider.GetRequiredService(); + var dapr = serviceProvider.GetRequiredService(); + var eventLogService = serviceProvider.GetRequiredService(); var retrieveEventLogs = LocalQueueProcessor.Default.RetrieveEventLogsFailedToPublishAsync(_options.Value.LocalRetryTimes, @@ -67,8 +67,5 @@ public override async Task ExecuteAsync(CancellationToken stoppingToken) await eventLogService.MarkEventAsFailedAsync(eventLog.EventId); } } - } } - - public override int Delay => _options.Value.LocalFailedRetryInterval; } diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs index 31a53c037..8071d489e 100644 --- a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Servers/DefaultHostedService.cs @@ -1,19 +1,22 @@ -namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Servers; +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Servers; public class DefaultHostedService : IProcessingServer { + private readonly IServiceProvider _serviceProvider; private readonly IEnumerable _processors; - private readonly ILogger? _logger; - public DefaultHostedService(IEnumerable processors, ILogger? logger = null) + public DefaultHostedService(IServiceProvider serviceProvider, IEnumerable processors) { + _serviceProvider = serviceProvider; _processors = processors; - _logger = logger; } public Task ExecuteAsync(CancellationToken stoppingToken) { - var processorTasks = _processors.Select(processor => new InfiniteLoopProcessor(processor, _logger)) + if (_serviceProvider.GetService() == null) + return Task.CompletedTask; + + var processorTasks = _processors.Select(processor => new InfiniteLoopProcessor(_serviceProvider, processor)) .Select(process => process.ExecuteAsync(stoppingToken)); return Task.WhenAll(processorTasks); } From c45bc0dd3128b6d63b86563b896f18f60f286101 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Tue, 29 Mar 2022 20:00:40 +0800 Subject: [PATCH 02/10] test: Adapt unit tests to new background tasks --- .../RepositoryTest.cs | 2 +- .../Internal/CustomizeProcessor.cs | 10 +- .../ProcessorTest.cs | 109 +++++++++++++----- 3 files changed, 86 insertions(+), 35 deletions(-) diff --git a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/RepositoryTest.cs b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/RepositoryTest.cs index 9d7d59fce..b94289f1f 100644 --- a/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/RepositoryTest.cs +++ b/test/Masa.Contrib.Ddd.Domain.Repository.EF.Tests/RepositoryTest.cs @@ -31,7 +31,7 @@ public async Task InitializeAsync(Action? action) action.Invoke(_services); var serviceProvider = _services.BuildServiceProvider(); - _dbContext = _services.BuildServiceProvider().GetRequiredService(); + _dbContext = serviceProvider.GetRequiredService(); await _dbContext.Database.EnsureCreatedAsync(); _uoW = new UnitOfWork(serviceProvider); _dispatcherOptions.Object.UseUoW(); diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Internal/CustomizeProcessor.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Internal/CustomizeProcessor.cs index 3b9e19b71..aaedebcf9 100644 --- a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Internal/CustomizeProcessor.cs +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/Internal/CustomizeProcessor.cs @@ -3,12 +3,16 @@ public class CustomizeProcessor : ProcessorBase { internal static int Times = 0; - + + public override int Delay => 2; + + public CustomizeProcessor(IServiceProvider? serviceProvider) : base(serviceProvider) + { + } + public override Task ExecuteAsync(CancellationToken stoppingToken) { Times++; return Task.CompletedTask; } - - public override int Delay => 2; } diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs index 1192ce1af..e9933b68b 100644 --- a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs @@ -1,4 +1,4 @@ -namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests; +namespace Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests; [TestClass] public class ProcessorTest @@ -50,10 +50,6 @@ public async Task RetryByDataProcessorExecuteTestAsync() => client.PublishEventAsync(It.IsAny(), It.IsAny(), It.IsAny(), cancellationTokenSource.Token)); services.AddScoped(_ => daprClient.Object); - Mock uoW = new(); - uoW.Setup(u => u.CommitAsync(cancellationTokenSource.Token)).Verifiable(); - services.AddScoped(_ => uoW.Object); - Mock integrationEventLogService = new(); RegisterUserIntegrationEvent @event = new RegisterUserIntegrationEvent(); @@ -74,6 +70,22 @@ public async Task RetryByDataProcessorExecuteTestAsync() .Verifiable(); services.AddScoped(_ => integrationEventLogService.Object); + Mock uoW = new(); + uoW.Setup(u => u.CommitAsync(cancellationTokenSource.Token)).Verifiable(); + uoW.Setup(u => u.ServiceProvider).Returns(services.BuildServiceProvider()).Verifiable(); + services.AddScoped(_ => uoW.Object); + + Mock unitOfWorkManager = new(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + services.AddSingleton(_ => unitOfWorkManager.Object); + + Mock dataConnectionStringProvider = new(); + dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List + { + new(string.Empty) + }).Verifiable(); + services.AddSingleton(_ => dataConnectionStringProvider.Object); + Mock> options = new(); options.Setup(opt => opt.Value).Returns(new DispatcherOptions(services, AppDomain.CurrentDomain.GetAssemblies())); AppConfig appConfig = new() @@ -102,10 +114,6 @@ public async Task RetryByDataProcessorExecute2TestAsync() CancellationTokenSource cancellationTokenSource = new(); cancellationTokenSource.CancelAfter(1000); - Mock uoW = new(); - uoW.Setup(u => u.CommitAsync(cancellationTokenSource.Token)).Verifiable(); - services.AddScoped(_ => uoW.Object); - Mock integrationEventLogService = new(); integrationEventLogService.Setup(service => service.MarkEventAsInProgressAsync(It.IsAny())).Verifiable(); integrationEventLogService.Setup(service => service.MarkEventAsPublishedAsync(It.IsAny())).Verifiable(); @@ -140,6 +148,22 @@ public async Task RetryByDataProcessorExecute2TestAsync() .Verifiable(); services.AddScoped(_ => integrationEventLogService.Object); + Mock uoW = new(); + uoW.Setup(u => u.CommitAsync(cancellationTokenSource.Token)).Verifiable(); + uoW.Setup(u => u.ServiceProvider).Returns(services.BuildServiceProvider()).Verifiable(); + services.AddScoped(_ => uoW.Object); + + Mock unitOfWorkManager = new(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + services.AddSingleton(_ => unitOfWorkManager.Object); + + Mock dataConnectionStringProvider = new(); + dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List + { + new(string.Empty) + }).Verifiable(); + services.AddSingleton(_ => dataConnectionStringProvider.Object); + Mock> options = new(); options.Setup(opt => opt.Value).Returns(new DispatcherOptions(services, AppDomain.CurrentDomain.GetAssemblies())); AppConfig appConfig = new() @@ -168,10 +192,6 @@ public async Task RetryByDataProcessorExecute2AndNotUseLoggerTestAsync() CancellationTokenSource cancellationTokenSource = new(); cancellationTokenSource.CancelAfter(1000); - Mock uoW = new(); - uoW.Setup(u => u.CommitAsync(cancellationTokenSource.Token)).Verifiable(); - services.AddScoped(_ => uoW.Object); - Mock integrationEventLogService = new(); integrationEventLogService.Setup(service => service.MarkEventAsInProgressAsync(It.IsAny())).Verifiable(); integrationEventLogService.Setup(service => service.MarkEventAsPublishedAsync(It.IsAny())).Verifiable(); @@ -213,6 +233,22 @@ public async Task RetryByDataProcessorExecute2AndNotUseLoggerTestAsync() AppId = "test" }; + Mock uoW = new(); + uoW.Setup(u => u.CommitAsync(cancellationTokenSource.Token)).Verifiable(); + uoW.Setup(u => u.ServiceProvider).Returns(services.BuildServiceProvider()).Verifiable(); + services.AddScoped(_ => uoW.Object); + + Mock unitOfWorkManager = new(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + services.AddSingleton(_ => unitOfWorkManager.Object); + + Mock dataConnectionStringProvider = new(); + dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List() + { + new(string.Empty) + }).Verifiable(); + services.AddSingleton(_ => dataConnectionStringProvider.Object); + var serviceProvider = services.BuildServiceProvider(); RetryByDataProcessor retryByDataProcessor = new( serviceProvider, @@ -237,35 +273,42 @@ public void RetryByLocalQueueProcessorDelayTestAsync() public async Task DeletePublishedExpireEventProcessorExecuteTestAsync() { Mock integrationEventLogService = new(); - integrationEventLogService.Setup(service => - service.DeleteExpiresAsync(It.IsAny(), It.IsAny(), default)).Verifiable(); + integrationEventLogService.Setup(service => service.DeleteExpiresAsync(It.IsAny(), It.IsAny(), default)).Verifiable(); _options.Value.Services.AddScoped(_ => integrationEventLogService.Object); + + Mock uoW = new(); + uoW.Setup(uow => uow.ServiceProvider).Returns(_options.Value.Services.BuildServiceProvider()).Verifiable(); + + Mock unitOfWorkManager = new(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + _options.Value.Services.AddSingleton(_ => unitOfWorkManager.Object); + + Mock dataConnectionStringProvider = new(); + dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List() + { + new(string.Empty) + }).Verifiable(); + _options.Value.Services.AddSingleton(_ => dataConnectionStringProvider.Object); + var processor = new DeletePublishedExpireEventProcessor(_options.Value.Services.BuildServiceProvider(), _options); await processor.ExecuteAsync(default); - integrationEventLogService.Verify(service => service.DeleteExpiresAsync(It.IsAny(), It.IsAny(), default), - Times.Once); + integrationEventLogService.Verify(service => service.DeleteExpiresAsync(It.IsAny(), It.IsAny(), default), Times.Once); } [TestMethod] - public async Task SetGetCurrentTimeAsync() + public void SetGetCurrentTime() { var services = new ServiceCollection(); + DateTime dateNow = DateTime.Now; services.AddDaprEventBus(opt => { - opt.GetCurrentTime = () => DateTime.Now; + opt.GetCurrentTime = () => dateNow; }); - Mock integrationEventLogService = new(); - integrationEventLogService.Setup(service => - service.DeleteExpiresAsync(It.IsAny(), It.IsAny(), default)).Verifiable(); - services.AddScoped(_ => integrationEventLogService.Object); - var serviceProvider = services.BuildServiceProvider(); - var options = serviceProvider.GetRequiredService>(); - var processor = new DeletePublishedExpireEventProcessor(serviceProvider, options); - await processor.ExecuteAsync(default); - integrationEventLogService.Verify(service => service.DeleteExpiresAsync(It.IsAny(), It.IsAny(), default), - Times.Once); + var dispatcherOption = services.BuildServiceProvider().GetRequiredService>(); + Assert.IsNotNull(dispatcherOption); + Assert.IsTrue((dispatcherOption!.Value.GetCurrentTime!.Invoke() - DateTime.Now).TotalMinutes < 1); } [TestMethod] @@ -276,7 +319,7 @@ public async Task InfiniteLoopProcessorExecuteTestAsync() cancellationTokenSource.CancelAfter(3000); processor.Setup(pro => pro.ExecuteAsync(cancellationTokenSource.Token)).Verifiable(); - InfiniteLoopProcessor infiniteLoopProcessor = new InfiniteLoopProcessor(processor.Object); + InfiniteLoopProcessor infiniteLoopProcessor = new InfiniteLoopProcessor(_serviceProvider, processor.Object); await infiniteLoopProcessor.ExecuteAsync(cancellationTokenSource.Token); processor.Verify(pro => pro.ExecuteAsync(cancellationTokenSource.Token), Times.AtLeastOnce); @@ -291,7 +334,7 @@ public async Task InfiniteLoopProcessorExecuteAndUseLoggerTestAsync() processor.Setup(pro => pro.ExecuteAsync(cancellationTokenSource.Token)).Verifiable(); InfiniteLoopProcessor infiniteLoopProcessor = - new InfiniteLoopProcessor(processor.Object, new NullLoggerFactory().CreateLogger()); + new InfiniteLoopProcessor(_serviceProvider, processor.Object); await infiniteLoopProcessor.ExecuteAsync(cancellationTokenSource.Token); processor.Verify(pro => pro.ExecuteAsync(cancellationTokenSource.Token), Times.AtLeastOnce); @@ -301,6 +344,8 @@ public async Task InfiniteLoopProcessorExecuteAndUseLoggerTestAsync() public async Task DefaultHostedServiceTestAsync() { var services = new ServiceCollection(); + Mock unitOfWorkManager = new(); + services.AddSingleton(_ => unitOfWorkManager.Object); services.AddScoped(); services.AddDaprEventBus(opt => { @@ -323,6 +368,8 @@ public async Task DefaultHostedServiceTestAsync() public async Task DefaultHostedServiceAndUseLoggerTestAsync() { var services = new ServiceCollection(); + Mock unitOfWorkManager = new(); + services.AddSingleton(_ => unitOfWorkManager.Object); services.AddLogging(); services.AddScoped(); services.AddDaprEventBus(opt => From 6555898f2837c8602c6080d71f76bdb072de50ef Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Tue, 29 Mar 2022 20:02:43 +0800 Subject: [PATCH 03/10] test(uoW): Added UnitOfWorkManager unit test --- ...ustomerDbContext.cs => CustomDbContext.cs} | 9 +- .../Masa.Contrib.Data.UoW.EF.Tests.csproj | 1 + .../TestUnitOfWork.cs | 82 ++++++++++++------- 3 files changed, 55 insertions(+), 37 deletions(-) rename test/Masa.Contrib.Data.UoW.EF.Tests/{CustomerDbContext.cs => CustomDbContext.cs} (71%) diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/CustomDbContext.cs similarity index 71% rename from test/Masa.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs rename to test/Masa.Contrib.Data.UoW.EF.Tests/CustomDbContext.cs index 3577a84ea..db209d90e 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/CustomerDbContext.cs +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/CustomDbContext.cs @@ -1,13 +1,8 @@ namespace Masa.Contrib.Data.UoW.EF.Tests; -public class CustomerDbContext : MasaDbContext +public class CustomDbContext : MasaDbContext { - private static readonly Mock masaDbContextOptions = new(); - public CustomerDbContext() : this(masaDbContextOptions.Object) - { - } - - public CustomerDbContext(MasaDbContextOptions options) : base(options) { } + public CustomDbContext(MasaDbContextOptions options) : base(options) { } public DbSet User { get; set; } diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj b/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj index be1d242e1..277153b9f 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj @@ -18,6 +18,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs index 7fb9624e5..f2697a9c7 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.Configuration; + namespace Masa.Contrib.Data.UoW.EF.Tests; [TestClass] @@ -16,33 +18,23 @@ public void Initialize() public void TestAddUoWAndNullServices() { var options = new Mock(); - Assert.ThrowsException(() => options.Object.UseUoW()); - } - - [TestMethod] - public void TestAddUoW() - { - _options.Object.UseUoW(); - var serviceProvider = _options.Object.Services.BuildServiceProvider(); - Assert.ThrowsException(() - => serviceProvider.GetRequiredService() - ); + Assert.ThrowsException(() => options.Object.UseUoW()); } [TestMethod] public void TestAddUoWAndUseSqlLite() { - _options.Object.UseUoW(options => options.UseSqlite(_connectionString)); + _options.Object.UseUoW(options => options.UseSqlite(_connectionString)); var serviceProvider = _options.Object.Services.BuildServiceProvider(); - Assert.IsNotNull(serviceProvider.GetRequiredService()); + Assert.IsNotNull(serviceProvider.GetRequiredService()); } [TestMethod] public void TestAddMultUoW() { _options.Object - .UseUoW(options => options.UseSqlite(_connectionString)) - .UseUoW(options => options.UseSqlite(_connectionString)); + .UseUoW(options => options.UseSqlite(_connectionString)) + .UseUoW(options => options.UseSqlite(_connectionString)); var serviceProvider = _options.Object.Services.BuildServiceProvider(); Assert.IsTrue(serviceProvider.GetServices().Count() == 1); @@ -58,9 +50,9 @@ public void TestTransaction() [TestMethod] public async Task TestUseTranscationAsync() { - _options.Object.UseUoW(options => options.UseSqlite(Connection)); + _options.Object.UseUoW(options => options.UseSqlite(Connection)); var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); + var dbContext = serviceProvider.GetRequiredService(); await dbContext.Database.EnsureCreatedAsync(); var uoW = serviceProvider.GetRequiredService(); @@ -79,11 +71,11 @@ public async Task TestUseTranscationAsync() [TestMethod] public async Task TestNotUseTranscationAsync() { - _options.Object.UseUoW(options => options.UseSqlite(Connection)); + _options.Object.UseUoW(options => options.UseSqlite(Connection)); var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); + var dbContext = serviceProvider.GetRequiredService(); await dbContext.Database.EnsureCreatedAsync(); - var uoW = new UnitOfWork(serviceProvider); + var uoW = new UnitOfWork(serviceProvider); Users user = new Users() { @@ -97,22 +89,22 @@ public async Task TestNotUseTranscationAsync() [TestMethod] public async Task TestNotTransactionCommitAsync() { - _options.Object.UseUoW(options => options.UseSqlite(_connectionString)); + _options.Object.UseUoW(options => options.UseSqlite(_connectionString)); var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); + var dbContext = serviceProvider.GetRequiredService(); await dbContext.Database.EnsureCreatedAsync(); - var uoW = new UnitOfWork(serviceProvider); + var uoW = new UnitOfWork(serviceProvider); await Assert.ThrowsExceptionAsync(async () => await uoW.CommitAsync()); } [TestMethod] public async Task TestCommitAsync() { - _options.Object.UseUoW(options => options.UseSqlite(Connection)); + _options.Object.UseUoW(options => options.UseSqlite(Connection)); var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); + var dbContext = serviceProvider.GetRequiredService(); await dbContext.Database.EnsureCreatedAsync(); - var uoW = new UnitOfWork(serviceProvider); + var uoW = new UnitOfWork(serviceProvider); var user = new Users() { Name = "Tom" @@ -128,9 +120,9 @@ public async Task TestCommitAsync() [TestMethod] public async Task TestOpenRollbackAsync() { - _options.Object.UseUoW(options => options.UseSqlite(Connection)); + _options.Object.UseUoW(options => options.UseSqlite(Connection)); var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); + var dbContext = serviceProvider.GetRequiredService(); await dbContext.Database.EnsureCreatedAsync(); var uoW = serviceProvider.GetRequiredService(); var user = new Users(); @@ -145,9 +137,9 @@ public async Task TestOpenRollbackAsync() public async Task TestAddLoggerAndOpenRollbackAsync() { _options.Object.Services.AddLogging(); - _options.Object.UseUoW(options => options.UseSqlite(Connection)); + _options.Object.UseUoW(options => options.UseSqlite(Connection)); var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dbContext = serviceProvider.GetRequiredService(); + var dbContext = serviceProvider.GetRequiredService(); await dbContext.Database.EnsureCreatedAsync(); var uoW = serviceProvider.GetRequiredService(); var user = new Users(); @@ -157,4 +149,34 @@ public async Task TestAddLoggerAndOpenRollbackAsync() Assert.IsTrue(!await dbContext.User.AnyAsync()); } + + [TestMethod] + public void TestDataConnectionString() + { + IConfiguration configuration = new ConfigurationManager(); + _options.Object.Services.AddSingleton(_ => configuration); + _options.Object.UseUoW(options => options.UseSqlite(Connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var dataConnectionStringProvider = serviceProvider.GetRequiredService(); + Assert.IsTrue(dataConnectionStringProvider.DbContextOptionsList.Count == 1 && dataConnectionStringProvider.DbContextOptionsList.Any(option => option.ConnectionString == null && option.Connection == null)); + } + + [TestMethod] + public async Task TestUnitOfWorkManagerAsync() + { + _options.Object.UseUoW(options => options.UseSqlite(Connection)); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var unitOfWorkManager = serviceProvider.GetRequiredService(); + var unitOfWork = serviceProvider.GetRequiredService(); + var dbContext = serviceProvider.GetRequiredService(); + var dbContext2 = serviceProvider.GetRequiredService(); + Assert.IsTrue(dbContext.Equals(dbContext2)); + + var newUnitOfWork = await unitOfWorkManager.CreateDbContextAsync(new Masa.BuildingBlocks.Data.UoW.Options.MasaDbContextOptions(Connection)); + Assert.IsFalse(newUnitOfWork.Equals(unitOfWork)); + var newDbContext = newUnitOfWork.ServiceProvider.GetRequiredService(); + Assert.IsFalse(dbContext.Equals(newDbContext)); + + await Assert.ThrowsExceptionAsync(async () => await unitOfWorkManager.CreateDbContextAsync(new BuildingBlocks.Data.UoW.Options.MasaDbContextOptions(""))); + } } From 831f7498dda9fa3e8de9bf2b190d3dc72ffa91f2 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Tue, 29 Mar 2022 22:33:49 +0800 Subject: [PATCH 04/10] feat(UoW): add IUnitOfWorkAccessor and IUnitOfWorkManager support CreateDbContextAsync --- .../DataConnectionStringProvider.cs | 10 ++---- .../DefaultConnectionStringProvider.cs | 33 +++++++++++++++++++ .../DispatcherOptionsExtensions.cs | 2 ++ .../Masa.Contrib.Data.UoW.EF/UnitOfWork.cs | 9 +++-- .../UnitOfWorkAccessor.cs | 6 ++++ .../UnitOfWorkManager.cs | 16 ++++++--- src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs | 8 +++-- 7 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 src/Data/Masa.Contrib.Data.UoW.EF/DefaultConnectionStringProvider.cs create mode 100644 src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkAccessor.cs diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/DataConnectionStringProvider.cs b/src/Data/Masa.Contrib.Data.UoW.EF/DataConnectionStringProvider.cs index 554ef368c..d4c48ff62 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/DataConnectionStringProvider.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/DataConnectionStringProvider.cs @@ -4,16 +4,10 @@ public class DataConnectionStringProvider : BaseDataConnectionStringProvider { private readonly IOptionsMonitor _options; - public DataConnectionStringProvider(IOptionsMonitor options) - { - _options = options; - } + public DataConnectionStringProvider(IOptionsMonitor options) => _options = options; protected override List GetDbContextOptionsList() { - return new () - { - new(_options.CurrentValue.DefaultConnection) - }; + return new() { new(_options.CurrentValue.DefaultConnection) }; } } diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/DefaultConnectionStringProvider.cs b/src/Data/Masa.Contrib.Data.UoW.EF/DefaultConnectionStringProvider.cs new file mode 100644 index 000000000..425ab70aa --- /dev/null +++ b/src/Data/Masa.Contrib.Data.UoW.EF/DefaultConnectionStringProvider.cs @@ -0,0 +1,33 @@ +namespace Masa.Contrib.Data.UoW.EF; + +public class DefaultConnectionStringProvider : IConnectionStringProvider +{ + private readonly IUnitOfWorkAccessor _unitOfWorkAccessor; + private readonly IOptionsSnapshot _options; + private readonly ILogger? _logger; + + public DefaultConnectionStringProvider( + IUnitOfWorkAccessor unitOfWorkAccessor, + IOptionsSnapshot options, + ILogger? logger = null) + { + _unitOfWorkAccessor = unitOfWorkAccessor; + _options = options; + _logger = logger; + } + + public Task GetConnectionStringAsync() => Task.FromResult(GetConnectionString()); + + public string GetConnectionString() + { + if (_unitOfWorkAccessor.CurrentDbContextOptions != null) + return _unitOfWorkAccessor.CurrentDbContextOptions.ConnectionString; + + var connectionString = _options.Value.DefaultConnection; + if (string.IsNullOrEmpty(connectionString)) + _logger?.LogError("Failed to get database connection string, please check whether the configuration of IOptionsSnapshot is abnormal"); + + _unitOfWorkAccessor.CurrentDbContextOptions = new MasaDbContextConfigurationOptions(connectionString); + return connectionString; + } +} diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs index de2b6f68b..9a3eea31b 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs @@ -16,7 +16,9 @@ public static IDispatcherOptions UseUoW( return options; options.Services.AddSingleton(); + options.Services.TryAddScoped(); options.Services.TryAddSingleton(); + options.Services.TryAddScoped(); options.Services.TryAddSingleton(); options.Services.AddScoped(serviceProvider => new UnitOfWork(serviceProvider) diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs index e7008c2f3..ff9d5cdad 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWork.cs @@ -4,9 +4,7 @@ public class UnitOfWork : IUnitOfWork where TDbContext : MasaDbConte { public IServiceProvider ServiceProvider { get; } - private DbContext? _context; - - protected DbContext Context => _context ??= ServiceProvider.GetRequiredService(); + protected DbContext Context; public DbTransaction Transaction { @@ -35,6 +33,7 @@ public DbTransaction Transaction public UnitOfWork(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; + Context = serviceProvider.GetRequiredService(); } public async Task SaveChangesAsync(CancellationToken cancellationToken = default) @@ -60,7 +59,7 @@ public async Task RollbackAsync(CancellationToken cancellationToken = default) await Context.Database.RollbackTransactionAsync(cancellationToken); } - public async ValueTask DisposeAsync() => await (_context?.DisposeAsync() ?? ValueTask.CompletedTask); + public async ValueTask DisposeAsync() => await Context.DisposeAsync(); - public void Dispose() => _context?.Dispose(); + public void Dispose() => Context.Dispose(); } diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkAccessor.cs b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkAccessor.cs new file mode 100644 index 000000000..edf898add --- /dev/null +++ b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkAccessor.cs @@ -0,0 +1,6 @@ +namespace Masa.Contrib.Data.UoW.EF; + +public class UnitOfWorkAccessor : IUnitOfWorkAccessor +{ + public MasaDbContextConfigurationOptions? CurrentDbContextOptions { get; set; } +} diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs index a4c8e7678..2e60f0a1c 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs @@ -6,13 +6,21 @@ public class UnitOfWorkManager : IUnitOfWorkManager public UnitOfWorkManager(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; - public Task CreateDbContextAsync(Masa.BuildingBlocks.Data.UoW.Options.MasaDbContextOptions dbContextOptions) + public Task CreateDbContextAsync() { - ArgumentNullException.ThrowIfNull(dbContextOptions, nameof(dbContextOptions)); - if (dbContextOptions.Connection == null && string.IsNullOrEmpty(dbContextOptions.ConnectionString)) - throw new ArgumentException($"Invalid {nameof(dbContextOptions)}"); + var scope = _serviceProvider.CreateAsyncScope(); + return Task.FromResult(scope.ServiceProvider.GetRequiredService()); + } + + public Task CreateDbContextAsync(MasaDbContextConfigurationOptions options) + { + ArgumentNullException.ThrowIfNull(options, nameof(options)); + if (string.IsNullOrEmpty(options.ConnectionString)) + throw new ArgumentException($"Invalid {nameof(options)}"); var scope = _serviceProvider.CreateAsyncScope(); + var unitOfWorkAccessor = scope.ServiceProvider.GetRequiredService(); + unitOfWorkAccessor.CurrentDbContextOptions = options; return Task.FromResult(scope.ServiceProvider.GetRequiredService()); } } diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs b/src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs index d65fe8488..4ad2bbc17 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/_Imports.cs @@ -1,12 +1,14 @@ global using Masa.BuildingBlocks.Data.UoW; +global using Masa.BuildingBlocks.Data.UoW.Options; global using Masa.BuildingBlocks.Dispatcher.Events; global using Masa.Utils.Data.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore; global using Microsoft.EntityFrameworkCore.Storage; global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Logging; +global using Microsoft.Extensions.Options; global using System.Data.Common; global using System.Text.Json.Serialization; global using EntityState = Masa.BuildingBlocks.Data.UoW.EntityState; -global using Microsoft.Extensions.Options; -global using DbContextOptions = Masa.BuildingBlocks.Data.UoW.Options.MasaDbContextOptions; -global using Microsoft.Extensions.DependencyInjection.Extensions; +global using DbContextOptions = Masa.BuildingBlocks.Data.UoW.Options.MasaDbContextConfigurationOptions; From 45352f0a46000a15166eee8f694ad31b106281d0 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Tue, 29 Mar 2022 22:34:41 +0800 Subject: [PATCH 05/10] test(uoW): Added UnitOfWorkAccessor unit test --- .../Masa.Contrib.Data.UoW.EF.Tests.csproj | 10 +++++ .../TestUnitOfWork.cs | 38 +++++++++++++++++-- .../appsettings.json | 5 +++ .../ProcessorTest.cs | 16 ++++---- 4 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 test/Masa.Contrib.Data.UoW.EF.Tests/appsettings.json diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj b/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj index 277153b9f..1a9474e95 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/Masa.Contrib.Data.UoW.EF.Tests.csproj @@ -24,10 +24,20 @@ + + + + + + + Always + + + diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs index f2697a9c7..b2eebc7ca 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs @@ -158,7 +158,7 @@ public void TestDataConnectionString() _options.Object.UseUoW(options => options.UseSqlite(Connection)); var serviceProvider = _options.Object.Services.BuildServiceProvider(); var dataConnectionStringProvider = serviceProvider.GetRequiredService(); - Assert.IsTrue(dataConnectionStringProvider.DbContextOptionsList.Count == 1 && dataConnectionStringProvider.DbContextOptionsList.Any(option => option.ConnectionString == null && option.Connection == null)); + Assert.IsTrue(dataConnectionStringProvider.DbContextOptionsList.Count == 1 && dataConnectionStringProvider.DbContextOptionsList.Any(option => option.ConnectionString == null)); } [TestMethod] @@ -172,11 +172,43 @@ public async Task TestUnitOfWorkManagerAsync() var dbContext2 = serviceProvider.GetRequiredService(); Assert.IsTrue(dbContext.Equals(dbContext2)); - var newUnitOfWork = await unitOfWorkManager.CreateDbContextAsync(new Masa.BuildingBlocks.Data.UoW.Options.MasaDbContextOptions(Connection)); + var newUnitOfWork = await unitOfWorkManager.CreateDbContextAsync(new Masa.BuildingBlocks.Data.UoW.Options.MasaDbContextConfigurationOptions(_connectionString)); Assert.IsFalse(newUnitOfWork.Equals(unitOfWork)); var newDbContext = newUnitOfWork.ServiceProvider.GetRequiredService(); Assert.IsFalse(dbContext.Equals(newDbContext)); - await Assert.ThrowsExceptionAsync(async () => await unitOfWorkManager.CreateDbContextAsync(new BuildingBlocks.Data.UoW.Options.MasaDbContextOptions(""))); + await Assert.ThrowsExceptionAsync(async () => await unitOfWorkManager.CreateDbContextAsync(new BuildingBlocks.Data.UoW.Options.MasaDbContextConfigurationOptions(""))); + } + + [TestMethod] + public async Task TestUnitOfWorkAccessorAsync() + { + var services = new ServiceCollection(); + var configurationRoot = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true) + .Build(); + services.AddSingleton(configurationRoot); + _options.Setup(option => option.Services).Returns(services).Verifiable(); + _options.Object.UseUoW(options => options.UseSqlite()); + var serviceProvider = _options.Object.Services.BuildServiceProvider(); + var unitOfWorkAccessor = serviceProvider.GetService(); + Assert.IsTrue(unitOfWorkAccessor is { CurrentDbContextOptions: null }); + var unitOfWork = serviceProvider.GetRequiredService(); + Assert.IsNotNull(unitOfWork); + unitOfWorkAccessor = serviceProvider.GetService(); + Assert.IsTrue(unitOfWorkAccessor!.CurrentDbContextOptions != null && unitOfWorkAccessor.CurrentDbContextOptions.ConnectionString == configurationRoot["ConnectionStrings:DefaultConnection"].ToString()); + + var unitOfWorkManager = serviceProvider.GetRequiredService(); + var unitOfWorkNew = await unitOfWorkManager.CreateDbContextAsync(); + var unitOfWorkAccessorNew = unitOfWorkNew.ServiceProvider.GetService(); + Assert.IsTrue(unitOfWorkAccessorNew!.CurrentDbContextOptions != null && unitOfWorkAccessorNew.CurrentDbContextOptions.ConnectionString == configurationRoot["ConnectionStrings:DefaultConnection"].ToString()); + + var unitOfWorkNew2 = await unitOfWorkManager.CreateDbContextAsync(new BuildingBlocks.Data.UoW.Options.MasaDbContextConfigurationOptions("test")); + var unitOfWorkAccessorNew2 = unitOfWorkNew2.ServiceProvider.GetService(); + Assert.IsTrue(unitOfWorkAccessorNew2!.CurrentDbContextOptions != null && unitOfWorkAccessorNew2.CurrentDbContextOptions.ConnectionString == "test"); + + var connectionString = await unitOfWorkNew2.ServiceProvider.GetRequiredService().GetConnectionStringAsync(); + Assert.IsTrue(connectionString == "test"); } } diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/appsettings.json b/test/Masa.Contrib.Data.UoW.EF.Tests/appsettings.json new file mode 100644 index 000000000..de2c1c208 --- /dev/null +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "DataSource=:memory:" + } +} \ No newline at end of file diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs index e9933b68b..e98b52887 100644 --- a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs @@ -76,11 +76,11 @@ public async Task RetryByDataProcessorExecuteTestAsync() services.AddScoped(_ => uoW.Object); Mock unitOfWorkManager = new(); - unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); services.AddSingleton(_ => unitOfWorkManager.Object); Mock dataConnectionStringProvider = new(); - dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List + dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List { new(string.Empty) }).Verifiable(); @@ -154,11 +154,11 @@ public async Task RetryByDataProcessorExecute2TestAsync() services.AddScoped(_ => uoW.Object); Mock unitOfWorkManager = new(); - unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); services.AddSingleton(_ => unitOfWorkManager.Object); Mock dataConnectionStringProvider = new(); - dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List + dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List { new(string.Empty) }).Verifiable(); @@ -239,11 +239,11 @@ public async Task RetryByDataProcessorExecute2AndNotUseLoggerTestAsync() services.AddScoped(_ => uoW.Object); Mock unitOfWorkManager = new(); - unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); services.AddSingleton(_ => unitOfWorkManager.Object); Mock dataConnectionStringProvider = new(); - dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List() + dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List() { new(string.Empty) }).Verifiable(); @@ -280,11 +280,11 @@ public async Task DeletePublishedExpireEventProcessorExecuteTestAsync() uoW.Setup(uow => uow.ServiceProvider).Returns(_options.Value.Services.BuildServiceProvider()).Verifiable(); Mock unitOfWorkManager = new(); - unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); _options.Value.Services.AddSingleton(_ => unitOfWorkManager.Object); Mock dataConnectionStringProvider = new(); - dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List() + dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List() { new(string.Empty) }).Verifiable(); From de59a290a2c5d4b1b00bb5c8148bf08ea79acc52 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Wed, 30 Mar 2022 09:41:45 +0800 Subject: [PATCH 06/10] chore(uoW): Change class name IDataConnectionStringProvider to IDbConnectionStringProvider --- src/BuildingBlocks/MASA.BuildingBlocks | 2 +- ...ionStringProvider.cs => DbConnectionStringProvider.cs} | 4 ++-- .../DispatcherOptionsExtensions.cs | 2 +- .../Processor/ProcessorBase.cs | 2 +- test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs | 2 +- .../ProcessorTest.cs | 8 ++++---- 6 files changed, 10 insertions(+), 10 deletions(-) rename src/Data/Masa.Contrib.Data.UoW.EF/{DataConnectionStringProvider.cs => DbConnectionStringProvider.cs} (58%) diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks index 70ccb4bd4..3c7e649b6 160000 --- a/src/BuildingBlocks/MASA.BuildingBlocks +++ b/src/BuildingBlocks/MASA.BuildingBlocks @@ -1 +1 @@ -Subproject commit 70ccb4bd42f6e9cf068e909278c2d8b6bcd776e3 +Subproject commit 3c7e649b66564631168d1fd89ea596b37b3e67cd diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/DataConnectionStringProvider.cs b/src/Data/Masa.Contrib.Data.UoW.EF/DbConnectionStringProvider.cs similarity index 58% rename from src/Data/Masa.Contrib.Data.UoW.EF/DataConnectionStringProvider.cs rename to src/Data/Masa.Contrib.Data.UoW.EF/DbConnectionStringProvider.cs index d4c48ff62..4b0eca284 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/DataConnectionStringProvider.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/DbConnectionStringProvider.cs @@ -1,10 +1,10 @@ namespace Masa.Contrib.Data.UoW.EF; -public class DataConnectionStringProvider : BaseDataConnectionStringProvider +public class DbConnectionStringProvider : BaseDbConnectionStringProvider { private readonly IOptionsMonitor _options; - public DataConnectionStringProvider(IOptionsMonitor options) => _options = options; + public DbConnectionStringProvider(IOptionsMonitor options) => _options = options; protected override List GetDbContextOptionsList() { diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs index 9a3eea31b..60ef68e4b 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs @@ -19,7 +19,7 @@ public static IDispatcherOptions UseUoW( options.Services.TryAddScoped(); options.Services.TryAddSingleton(); options.Services.TryAddScoped(); - options.Services.TryAddSingleton(); + options.Services.TryAddSingleton(); options.Services.AddScoped(serviceProvider => new UnitOfWork(serviceProvider) { diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs index 44ee4926f..f59836a97 100644 --- a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs @@ -16,7 +16,7 @@ public virtual async Task ExecuteAsync(CancellationToken stoppingToken) if (ServiceProvider != null) { var unitOfWorkManager = ServiceProvider.GetRequiredService(); - var dataConnectionStringProvider = ServiceProvider.GetRequiredService(); + var dataConnectionStringProvider = ServiceProvider.GetRequiredService(); var optionsList = dataConnectionStringProvider.DbContextOptionsList; foreach (var option in optionsList) { diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs index b2eebc7ca..a1043c417 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs @@ -157,7 +157,7 @@ public void TestDataConnectionString() _options.Object.Services.AddSingleton(_ => configuration); _options.Object.UseUoW(options => options.UseSqlite(Connection)); var serviceProvider = _options.Object.Services.BuildServiceProvider(); - var dataConnectionStringProvider = serviceProvider.GetRequiredService(); + var dataConnectionStringProvider = serviceProvider.GetRequiredService(); Assert.IsTrue(dataConnectionStringProvider.DbContextOptionsList.Count == 1 && dataConnectionStringProvider.DbContextOptionsList.Any(option => option.ConnectionString == null)); } diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs index e98b52887..aa510c229 100644 --- a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs @@ -79,7 +79,7 @@ public async Task RetryByDataProcessorExecuteTestAsync() unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); services.AddSingleton(_ => unitOfWorkManager.Object); - Mock dataConnectionStringProvider = new(); + Mock dataConnectionStringProvider = new(); dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List { new(string.Empty) @@ -157,7 +157,7 @@ public async Task RetryByDataProcessorExecute2TestAsync() unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); services.AddSingleton(_ => unitOfWorkManager.Object); - Mock dataConnectionStringProvider = new(); + Mock dataConnectionStringProvider = new(); dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List { new(string.Empty) @@ -242,7 +242,7 @@ public async Task RetryByDataProcessorExecute2AndNotUseLoggerTestAsync() unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); services.AddSingleton(_ => unitOfWorkManager.Object); - Mock dataConnectionStringProvider = new(); + Mock dataConnectionStringProvider = new(); dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List() { new(string.Empty) @@ -283,7 +283,7 @@ public async Task DeletePublishedExpireEventProcessorExecuteTestAsync() unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); _options.Value.Services.AddSingleton(_ => unitOfWorkManager.Object); - Mock dataConnectionStringProvider = new(); + Mock dataConnectionStringProvider = new(); dataConnectionStringProvider.Setup(provider => provider.DbContextOptionsList).Returns(new List() { new(string.Empty) From 86015a48dad54a8cbafe113540b9579c73b63633 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Wed, 30 Mar 2022 09:47:23 +0800 Subject: [PATCH 07/10] docs(UoW): Modify readme title error --- src/Data/Masa.Contrib.Data.UoW.EF/README.md | 2 +- src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/README.md b/src/Data/Masa.Contrib.Data.UoW.EF/README.md index c8e5c975d..e2e4d5b45 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/README.md +++ b/src/Data/Masa.Contrib.Data.UoW.EF/README.md @@ -1,6 +1,6 @@ [中](README.zh-CN.md) | EN -## Contracts.EF +## UoW.EF Example: diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md b/src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md index 85e11cbb1..f96e110dd 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md +++ b/src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md @@ -1,6 +1,6 @@ 中 | [EN](README.md) -## Contracts.EF +## UoW.EF 用例: From 8f94d2d05e8ffe6f6a988f3dac37dc006aa74433 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Wed, 30 Mar 2022 10:17:25 +0800 Subject: [PATCH 08/10] refactor: CreateDbContext is modified to create sync --- src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs | 8 ++++---- .../Processor/DeleteLocalQueueExpiresProcessor.cs | 4 +--- .../Processor/ProcessorBase.cs | 6 +++--- test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs | 8 ++++---- .../ProcessorTest.cs | 8 ++++---- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs index 2e60f0a1c..8edadd18d 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/UnitOfWorkManager.cs @@ -6,13 +6,13 @@ public class UnitOfWorkManager : IUnitOfWorkManager public UnitOfWorkManager(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; - public Task CreateDbContextAsync() + public IUnitOfWork CreateDbContext() { var scope = _serviceProvider.CreateAsyncScope(); - return Task.FromResult(scope.ServiceProvider.GetRequiredService()); + return scope.ServiceProvider.GetRequiredService(); } - public Task CreateDbContextAsync(MasaDbContextConfigurationOptions options) + public IUnitOfWork CreateDbContext(MasaDbContextConfigurationOptions options) { ArgumentNullException.ThrowIfNull(options, nameof(options)); if (string.IsNullOrEmpty(options.ConnectionString)) @@ -21,6 +21,6 @@ public Task CreateDbContextAsync(MasaDbContextConfigurationOptions var scope = _serviceProvider.CreateAsyncScope(); var unitOfWorkAccessor = scope.ServiceProvider.GetRequiredService(); unitOfWorkAccessor.CurrentDbContextOptions = options; - return Task.FromResult(scope.ServiceProvider.GetRequiredService()); + return scope.ServiceProvider.GetRequiredService(); } } diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs index 3210aab44..1a822fe47 100644 --- a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/DeleteLocalQueueExpiresProcessor.cs @@ -14,11 +14,9 @@ public DeleteLocalQueueExpiresProcessor(IOptions options) : b /// /// Delete expired events /// - /// /// - protected override Task ExecutingAsync(CancellationToken stoppingToken) + protected override void Executing() { LocalQueueProcessor.Default.Delete(_options.Value.LocalRetryTimes); - return Task.CompletedTask; } } diff --git a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs index f59836a97..a329398a9 100644 --- a/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs +++ b/src/Dispatcher/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr/Processor/ProcessorBase.cs @@ -20,13 +20,13 @@ public virtual async Task ExecuteAsync(CancellationToken stoppingToken) var optionsList = dataConnectionStringProvider.DbContextOptionsList; foreach (var option in optionsList) { - await using var unitOfWork = await unitOfWorkManager.CreateDbContextAsync(option); + await using var unitOfWork = unitOfWorkManager.CreateDbContext(option); await ExecuteAsync(unitOfWork.ServiceProvider, stoppingToken); } } else { - await ExecutingAsync(stoppingToken); + Executing(); } } @@ -39,5 +39,5 @@ public virtual async Task ExecuteAsync(CancellationToken stoppingToken) protected virtual Task ExecuteAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken) => Task.CompletedTask; - protected virtual Task ExecutingAsync(CancellationToken stoppingToken) => Task.CompletedTask; + protected virtual void Executing() { } } diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs index a1043c417..2f30b940f 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs @@ -172,12 +172,12 @@ public async Task TestUnitOfWorkManagerAsync() var dbContext2 = serviceProvider.GetRequiredService(); Assert.IsTrue(dbContext.Equals(dbContext2)); - var newUnitOfWork = await unitOfWorkManager.CreateDbContextAsync(new Masa.BuildingBlocks.Data.UoW.Options.MasaDbContextConfigurationOptions(_connectionString)); + var newUnitOfWork = unitOfWorkManager.CreateDbContext(new Masa.BuildingBlocks.Data.UoW.Options.MasaDbContextConfigurationOptions(_connectionString)); Assert.IsFalse(newUnitOfWork.Equals(unitOfWork)); var newDbContext = newUnitOfWork.ServiceProvider.GetRequiredService(); Assert.IsFalse(dbContext.Equals(newDbContext)); - await Assert.ThrowsExceptionAsync(async () => await unitOfWorkManager.CreateDbContextAsync(new BuildingBlocks.Data.UoW.Options.MasaDbContextConfigurationOptions(""))); + Assert.ThrowsException( () => unitOfWorkManager.CreateDbContext(new BuildingBlocks.Data.UoW.Options.MasaDbContextConfigurationOptions(""))); } [TestMethod] @@ -200,11 +200,11 @@ public async Task TestUnitOfWorkAccessorAsync() Assert.IsTrue(unitOfWorkAccessor!.CurrentDbContextOptions != null && unitOfWorkAccessor.CurrentDbContextOptions.ConnectionString == configurationRoot["ConnectionStrings:DefaultConnection"].ToString()); var unitOfWorkManager = serviceProvider.GetRequiredService(); - var unitOfWorkNew = await unitOfWorkManager.CreateDbContextAsync(); + var unitOfWorkNew = unitOfWorkManager.CreateDbContext(); var unitOfWorkAccessorNew = unitOfWorkNew.ServiceProvider.GetService(); Assert.IsTrue(unitOfWorkAccessorNew!.CurrentDbContextOptions != null && unitOfWorkAccessorNew.CurrentDbContextOptions.ConnectionString == configurationRoot["ConnectionStrings:DefaultConnection"].ToString()); - var unitOfWorkNew2 = await unitOfWorkManager.CreateDbContextAsync(new BuildingBlocks.Data.UoW.Options.MasaDbContextConfigurationOptions("test")); + var unitOfWorkNew2 = unitOfWorkManager.CreateDbContext(new BuildingBlocks.Data.UoW.Options.MasaDbContextConfigurationOptions("test")); var unitOfWorkAccessorNew2 = unitOfWorkNew2.ServiceProvider.GetService(); Assert.IsTrue(unitOfWorkAccessorNew2!.CurrentDbContextOptions != null && unitOfWorkAccessorNew2.CurrentDbContextOptions.ConnectionString == "test"); diff --git a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs index aa510c229..1926f474b 100644 --- a/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs +++ b/test/Masa.Contrib.Dispatcher.IntegrationEvents.Dapr.Tests/ProcessorTest.cs @@ -76,7 +76,7 @@ public async Task RetryByDataProcessorExecuteTestAsync() services.AddScoped(_ => uoW.Object); Mock unitOfWorkManager = new(); - unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContext(It.IsAny())).Returns(uoW.Object).Verifiable(); services.AddSingleton(_ => unitOfWorkManager.Object); Mock dataConnectionStringProvider = new(); @@ -154,7 +154,7 @@ public async Task RetryByDataProcessorExecute2TestAsync() services.AddScoped(_ => uoW.Object); Mock unitOfWorkManager = new(); - unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContext(It.IsAny())).Returns(uoW.Object).Verifiable(); services.AddSingleton(_ => unitOfWorkManager.Object); Mock dataConnectionStringProvider = new(); @@ -239,7 +239,7 @@ public async Task RetryByDataProcessorExecute2AndNotUseLoggerTestAsync() services.AddScoped(_ => uoW.Object); Mock unitOfWorkManager = new(); - unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContext(It.IsAny())).Returns(uoW.Object).Verifiable(); services.AddSingleton(_ => unitOfWorkManager.Object); Mock dataConnectionStringProvider = new(); @@ -280,7 +280,7 @@ public async Task DeletePublishedExpireEventProcessorExecuteTestAsync() uoW.Setup(uow => uow.ServiceProvider).Returns(_options.Value.Services.BuildServiceProvider()).Verifiable(); Mock unitOfWorkManager = new(); - unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContextAsync(It.IsAny()).Result).Returns(uoW.Object).Verifiable(); + unitOfWorkManager.Setup(uoWManager => uoWManager.CreateDbContext(It.IsAny())).Returns(uoW.Object).Verifiable(); _options.Value.Services.AddSingleton(_ => unitOfWorkManager.Object); Mock dataConnectionStringProvider = new(); From d6f594977b85dc457192eebee17d5ec7f1600c5b Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Wed, 30 Mar 2022 11:22:47 +0800 Subject: [PATCH 09/10] feat(uoW): Extend UseUoW method for IEventBusBuilder --- .../DispatcherOptionsExtensions.cs | 53 +++++++++++++------ .../TestUnitOfWork.cs | 21 +++++++- .../_Imports.cs | 1 + 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs index 60ef68e4b..5f38ec2f5 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs +++ b/src/Data/Masa.Contrib.Data.UoW.EF/DispatcherOptionsExtensions.cs @@ -2,6 +2,17 @@ namespace Masa.Contrib.Data.UoW.EF; public static class DispatcherOptionsExtensions { + public static IEventBusBuilder UseUoW( + this IEventBusBuilder eventBusBuilder, + Action? optionsBuilder = null, + bool disableRollbackOnFailure = false, + bool useTransaction = true) + where TDbContext : MasaDbContext + { + eventBusBuilder.Services.UseUoW(nameof(eventBusBuilder.Services), optionsBuilder, disableRollbackOnFailure, useTransaction); + return eventBusBuilder; + } + public static IDispatcherOptions UseUoW( this IDispatcherOptions options, Action? optionsBuilder = null, @@ -9,33 +20,43 @@ public static IDispatcherOptions UseUoW( bool useTransaction = true) where TDbContext : MasaDbContext { - if (options.Services == null) - throw new ArgumentNullException(nameof(options.Services)); + options.Services.UseUoW(nameof(options.Services), optionsBuilder, disableRollbackOnFailure, useTransaction); + return options; + } + + private static IServiceCollection UseUoW( + this IServiceCollection services, + string paramName, + Action? optionsBuilder = null, + bool disableRollbackOnFailure = false, + bool useTransaction = true) + where TDbContext : MasaDbContext + { + if (services == null) + throw new ArgumentNullException(paramName); - if (options.Services.Any(service => service.ImplementationType == typeof(UoWProvider))) - return options; + if (services.Any(service => service.ImplementationType == typeof(UoWProvider))) + return services; - options.Services.AddSingleton(); - options.Services.TryAddScoped(); - options.Services.TryAddSingleton(); - options.Services.TryAddScoped(); - options.Services.TryAddSingleton(); + services.AddSingleton(); + services.TryAddScoped(); + services.TryAddSingleton(); + services.TryAddScoped(); + services.TryAddSingleton(); - options.Services.AddScoped(serviceProvider => new UnitOfWork(serviceProvider) + services.AddScoped(serviceProvider => new UnitOfWork(serviceProvider) { DisableRollbackOnFailure = disableRollbackOnFailure, UseTransaction = useTransaction }); - if (options.Services.All(service => service.ServiceType != typeof(MasaDbContextOptions))) - options.Services.AddMasaDbContext(optionsBuilder); - - options.Services.AddScoped(); + if (services.All(service => service.ServiceType != typeof(MasaDbContextOptions))) + services.AddMasaDbContext(optionsBuilder); - return options; + services.AddScoped(); + return services; } private class UoWProvider { - } } diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs index 2f30b940f..cd8a3b4eb 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/TestUnitOfWork.cs @@ -1,5 +1,3 @@ -using Microsoft.Extensions.Configuration; - namespace Masa.Contrib.Data.UoW.EF.Tests; [TestClass] @@ -211,4 +209,23 @@ public async Task TestUnitOfWorkAccessorAsync() var connectionString = await unitOfWorkNew2.ServiceProvider.GetRequiredService().GetConnectionStringAsync(); Assert.IsTrue(connectionString == "test"); } + + [TestMethod] + public void TestUnitOfWOrkByEventBusBuilder() + { + var services = new ServiceCollection(); + var configurationRoot = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", true, true) + .Build(); + services.AddSingleton(configurationRoot); + Mock eventBuilder = new(); + eventBuilder.Setup(builder=>builder.Services).Returns(services).Verifiable(); + eventBuilder.Object.UseUoW(options => options.UseSqlite()); + + var serviecProvider = services.BuildServiceProvider(); + Assert.IsNotNull(serviecProvider.GetService()); + Assert.IsNotNull(serviecProvider.GetService()); + Assert.IsNotNull(serviecProvider.GetService()); + } } diff --git a/test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs b/test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs index 24516a06e..76de8a730 100644 --- a/test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs +++ b/test/Masa.Contrib.Data.UoW.EF.Tests/_Imports.cs @@ -10,3 +10,4 @@ global using Moq; global using System; global using System.Threading.Tasks; +global using Microsoft.Extensions.Configuration; From 81ccaae4e3de2fe2b2b8f413733214674fcc8553 Mon Sep 17 00:00:00 2001 From: zhenlei520 Date: Wed, 30 Mar 2022 11:23:18 +0800 Subject: [PATCH 10/10] docs(uoW): Adjust uoW documentation --- src/BuildingBlocks/MASA.BuildingBlocks | 2 +- src/Data/Masa.Contrib.Data.UoW.EF/README.md | 15 ++++++++++++--- .../Masa.Contrib.Data.UoW.EF/README.zh-CN.md | 17 +++++++++++++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/BuildingBlocks/MASA.BuildingBlocks b/src/BuildingBlocks/MASA.BuildingBlocks index 3c7e649b6..ed8277ca0 160000 --- a/src/BuildingBlocks/MASA.BuildingBlocks +++ b/src/BuildingBlocks/MASA.BuildingBlocks @@ -1 +1 @@ -Subproject commit 3c7e649b66564631168d1fd89ea596b37b3e67cd +Subproject commit ed8277ca02b1259277836f34568719bd37bf705a diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/README.md b/src/Data/Masa.Contrib.Data.UoW.EF/README.md index e2e4d5b45..0906c4601 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/README.md +++ b/src/Data/Masa.Contrib.Data.UoW.EF/README.md @@ -5,12 +5,21 @@ Example: ```C# +Install-Package Masa.Contrib.Dispatcher.Events Install-Package Masa.Contrib.Data.UoW.EF Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` +1. Configure appsettings.json +``` appsettings.json +{ + "ConnectionStrings": { + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity" + } +} +``` + +2. Use UoW ```C# -builder.Services.AddEventBus(options => { - options.UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity")); -}); +builder.Services.AddEventBus(eventBusBuilder => eventBusBuilder.UseUoW(dbOptions => dbOptions.UseSqlServer())); ``` \ No newline at end of file diff --git a/src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md b/src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md index f96e110dd..d240d916d 100644 --- a/src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md +++ b/src/Data/Masa.Contrib.Data.UoW.EF/README.zh-CN.md @@ -5,12 +5,21 @@ 用例: ```C# +Install-Package Masa.Contrib.Dispatcher.Events Install-Package Masa.Contrib.Data.UoW.EF Install-Package Masa.Utils.Data.EntityFrameworkCore.SqlServer ``` +1. 配置appsettings.json +``` appsettings.json +{ + "ConnectionStrings": { + "DefaultConnection": "server=localhost;uid=sa;pwd=P@ssw0rd;database=identity" + } +} +``` + +2. 使用UoW ```C# -builder.Services.AddEventBus(options => { - options.UseUoW(dbOptions => dbOptions.UseSqlServer("server=localhost;uid=sa;pwd=P@ssw0rd;database=identity")); -}); -``` \ No newline at end of file +builder.Services.AddEventBus(eventBusBuilder => eventBusBuilder.UseUoW(dbOptions => dbOptions.UseSqlServer())); +```