From 284e18ba451016b1dea5550382ba3d7f548a3350 Mon Sep 17 00:00:00 2001 From: "BRUCE.L" Date: Thu, 6 Apr 2023 05:19:02 -0700 Subject: [PATCH 1/2] Add settings infrastructure and refactor repo pattern --- src/Wax.Api/Startup.cs | 3 +- src/Wax.Core/ApplicationModule.cs | 31 +++++-- src/Wax.Core/Data/ApplicationDbContext.cs | 12 +++ src/Wax.Core/Data/IUnitOfWork.cs | 21 +++++ .../Customers/CreateCustomerCommandHandler.cs | 11 +-- .../Customers/DeleteCustomerCommandHandler.cs | 12 +-- .../Customers/UpdateCustomerCommandHandler.cs | 13 ++- .../Customers/GetCustomerRequestHandler.cs | 9 ++- .../UnitOfWorks/UnitOfWorkMiddleware.cs | 23 ++++++ .../UnitOfWorks/UnitOfWorkSpecification.cs | 46 +++++++++++ src/Wax.Core/Repositories/BasicRepository.cs | 80 +++++++++++++++++++ .../Repositories/EfCoreBasicRepository.cs | 68 ---------------- .../Repositories/EfCoreCustomerRepository.cs | 18 ----- src/Wax.Core/Repositories/IBasicRepository.cs | 19 +++-- .../Repositories/ICustomerRepository.cs | 17 +++- src/Wax.Core/Repositories/IUnitOfWork.cs | 7 -- src/Wax.Core/Repositories/UnitOfWork.cs | 28 ------- .../Settings/IConfigurationSetting.cs | 10 +++ src/Wax.Core/Wax.Core.csproj | 1 + .../IntegrationFixture.cs | 8 +- .../IntegrationTestBase.cs | 1 + .../Wax.IntegrationTests.csproj | 14 ++++ tests/Wax.IntegrationTests/appsettings.json | 12 +++ .../Customers/CreateCustomerTests.cs | 2 +- .../Customers/CustomerTestFixture.cs | 4 +- .../Customers/GetCustomerTests.cs | 2 +- .../Customers/UpdateCustomerTests.cs | 2 +- 27 files changed, 307 insertions(+), 167 deletions(-) create mode 100644 src/Wax.Core/Data/IUnitOfWork.cs create mode 100644 src/Wax.Core/Middlewares/UnitOfWorks/UnitOfWorkMiddleware.cs create mode 100644 src/Wax.Core/Middlewares/UnitOfWorks/UnitOfWorkSpecification.cs create mode 100644 src/Wax.Core/Repositories/BasicRepository.cs delete mode 100644 src/Wax.Core/Repositories/EfCoreBasicRepository.cs delete mode 100644 src/Wax.Core/Repositories/EfCoreCustomerRepository.cs delete mode 100644 src/Wax.Core/Repositories/IUnitOfWork.cs delete mode 100644 src/Wax.Core/Repositories/UnitOfWork.cs create mode 100644 src/Wax.Core/Settings/IConfigurationSetting.cs create mode 100644 tests/Wax.IntegrationTests/appsettings.json diff --git a/src/Wax.Api/Startup.cs b/src/Wax.Api/Startup.cs index c360a6a..eeb8ee8 100644 --- a/src/Wax.Api/Startup.cs +++ b/src/Wax.Api/Startup.cs @@ -42,12 +42,11 @@ public void ConfigureContainer(ContainerBuilder builder) var serviceProvider = _serviceCollection.BuildServiceProvider(); var currentUser = serviceProvider.GetRequiredService(); - var connectionString = Configuration.GetConnectionString("Default"); // Register your own things directly with Autofac here. Don't // call builder.Populate(), that happens in AutofacServiceProviderFactory // for you. - builder.RegisterModule(new ApplicationModule(Log.Logger, currentUser, connectionString, + builder.RegisterModule(new ApplicationModule(Log.Logger, currentUser, Configuration, typeof(Startup).Assembly)); } diff --git a/src/Wax.Core/ApplicationModule.cs b/src/Wax.Core/ApplicationModule.cs index c4e4b8c..9112f0d 100644 --- a/src/Wax.Core/ApplicationModule.cs +++ b/src/Wax.Core/ApplicationModule.cs @@ -4,13 +4,16 @@ using Mediator.Net; using Mediator.Net.Autofac; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Serilog; using Wax.Core.Data; using Wax.Core.DependencyInjection; using Wax.Core.Middlewares.FluentMessageValidator; using Wax.Core.Middlewares.Logging; +using Wax.Core.Middlewares.UnitOfWorks; using Wax.Core.Repositories; using Wax.Core.Services.Identity; +using Wax.Core.Settings; using Module = Autofac.Module; namespace Wax.Core @@ -19,15 +22,15 @@ public class ApplicationModule : Module { private readonly ILogger _logger; private readonly ICurrentUser _currentUser; - private readonly string _connectionString; + private readonly IConfiguration _configuration; private readonly Assembly[] _assemblies; - public ApplicationModule(ILogger logger, ICurrentUser currentUser, string connectionString, + public ApplicationModule(ILogger logger, ICurrentUser currentUser, IConfiguration configuration, params Assembly[] assemblies) { _logger = logger; _currentUser = currentUser; - _connectionString = connectionString; + _configuration = configuration; _assemblies = (assemblies ?? Array.Empty()) .Concat(new[] { typeof(ApplicationModule).Assembly }) @@ -44,6 +47,19 @@ protected override void Load(ContainerBuilder builder) RegisterMediator(builder); RegisterValidator(builder); } + + private void RegisterSettings(ContainerBuilder builder) + { + builder.RegisterInstance(_configuration) + .As() + .SingleInstance(); + + var settingTypes = _assemblies.SelectMany(a => a.GetTypes()) + .Where(t => t.IsClass && typeof(IConfigurationSetting).IsAssignableFrom(t)) + .ToArray(); + + builder.RegisterTypes(settingTypes).AsSelf().SingleInstance(); + } private void RegisterAutoMapper(ContainerBuilder builder) { @@ -70,7 +86,9 @@ private void RegisterDatabase(ContainerBuilder builder) { builder.Register(c => { - if (!string.IsNullOrEmpty(_connectionString)) + var connectionString = _configuration.GetConnectionString("Default"); + + if (!string.IsNullOrEmpty(connectionString)) { //Select your database provider } @@ -83,10 +101,10 @@ private void RegisterDatabase(ContainerBuilder builder) }).AsSelf().As() .InstancePerLifetimeScope(); - builder.RegisterGeneric(typeof(EfCoreBasicRepository<>)) + builder.RegisterGeneric(typeof(BasicRepository<>)) .As(typeof(IBasicRepository<>)) .InstancePerLifetimeScope(); - + builder.RegisterType().As().InstancePerLifetimeScope(); } @@ -111,6 +129,7 @@ private void RegisterMediator(ContainerBuilder builder) { c.UseLogger(); c.UseMessageValidator(); + c.UseUnitOfWork(); }); builder.RegisterMediator(mediatorBuilder); diff --git a/src/Wax.Core/Data/ApplicationDbContext.cs b/src/Wax.Core/Data/ApplicationDbContext.cs index 9eabfd9..d5df857 100644 --- a/src/Wax.Core/Data/ApplicationDbContext.cs +++ b/src/Wax.Core/Data/ApplicationDbContext.cs @@ -18,4 +18,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfigurationsFromAssembly(typeof(CustomerEntityTypeConfiguration).Assembly); } + + public bool HasEntitiesChanged { get; private set; } + + public async Task ChangeEntitiesAsync(bool saveNow = false, CancellationToken cancellationToken = default) + { + HasEntitiesChanged = true; + + if (saveNow) + { + await SaveChangesAsync(cancellationToken); + } + } } \ No newline at end of file diff --git a/src/Wax.Core/Data/IUnitOfWork.cs b/src/Wax.Core/Data/IUnitOfWork.cs new file mode 100644 index 0000000..2814ddc --- /dev/null +++ b/src/Wax.Core/Data/IUnitOfWork.cs @@ -0,0 +1,21 @@ +namespace Wax.Core.Data; + +public interface IUnitOfWork +{ + Task CommitAsync(CancellationToken cancellationToken = default); +} + +public class UnitOfWork : IUnitOfWork +{ + private readonly ApplicationDbContext _context; + + public UnitOfWork(ApplicationDbContext context) + { + _context = context; + } + + public Task CommitAsync(CancellationToken cancellationToken = default) + { + return _context.HasEntitiesChanged ? _context.SaveChangesAsync(cancellationToken) : Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs b/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs index 4b8fe04..13d68ce 100644 --- a/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs +++ b/src/Wax.Core/Handlers/CommandHandlers/Customers/CreateCustomerCommandHandler.cs @@ -1,6 +1,7 @@ using AutoMapper; using Mediator.Net.Context; using Mediator.Net.Contracts; +using Wax.Core.Data; using Wax.Core.Domain.Customers; using Wax.Core.Domain.Customers.Exceptions; using Wax.Core.Repositories; @@ -11,18 +12,19 @@ namespace Wax.Core.Handlers.CommandHandlers.Customers public class CreateCustomerCommandHandler : ICommandHandler { private readonly IMapper _mapper; + private readonly ICustomerRepository _customerRepository; private readonly IUnitOfWork _unitOfWork; - public CreateCustomerCommandHandler(IMapper mapper, IUnitOfWork unitOfWork) + public CreateCustomerCommandHandler(IMapper mapper,ICustomerRepository customerRepository) { _mapper = mapper; - _unitOfWork = unitOfWork; + _customerRepository = customerRepository; } public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - var existing = await _unitOfWork.Customers.FindByNameAsync(context.Message.Name, cancellationToken); + var existing = await _customerRepository.FindByNameAsync(context.Message.Name); if (existing != null) { @@ -31,8 +33,7 @@ public async Task Handle(IReceiveContext(context.Message); - await _unitOfWork.Customers.InsertAsync(customer, cancellationToken).ConfigureAwait(false); - await _unitOfWork.SaveChangesAsync(cancellationToken); + await _customerRepository.InsertAsync(customer); return new CreateCustomerResponse { CustomerId = customer.Id }; } diff --git a/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs b/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs index 1b8d94e..e711f94 100644 --- a/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs +++ b/src/Wax.Core/Handlers/CommandHandlers/Customers/DeleteCustomerCommandHandler.cs @@ -1,5 +1,6 @@ using Mediator.Net.Context; using Mediator.Net.Contracts; +using Wax.Core.Data; using Wax.Core.Repositories; using Wax.Messages.Commands.Customers; @@ -7,18 +8,17 @@ namespace Wax.Core.Handlers.CommandHandlers.Customers; public class DeleteCustomerCommandHandler: ICommandHandler { - private readonly IUnitOfWork _unitOfWork; + private readonly ICustomerRepository _customerRepository; - public DeleteCustomerCommandHandler(IUnitOfWork unitOfWork) + public DeleteCustomerCommandHandler(ICustomerRepository customerRepository) { - _unitOfWork = unitOfWork; + _customerRepository = customerRepository; } public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - var customer = await _unitOfWork.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken); + var customer = await _customerRepository.GetByIdAsync(context.Message.CustomerId); - await _unitOfWork.Customers.DeleteAsync(customer, cancellationToken); - await _unitOfWork.SaveChangesAsync(cancellationToken); + await _customerRepository.DeleteAsync(customer); } } \ No newline at end of file diff --git a/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs b/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs index ac5cac8..f14df87 100644 --- a/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs +++ b/src/Wax.Core/Handlers/CommandHandlers/Customers/UpdateCustomerCommandHandler.cs @@ -10,21 +10,21 @@ namespace Wax.Core.Handlers.CommandHandlers.Customers; public class UpdateCustomerCommandHandler : ICommandHandler { private readonly IMapper _mapper; - private readonly IUnitOfWork _unitOfWork; + private readonly ICustomerRepository _customerRepository; - public UpdateCustomerCommandHandler(IMapper mapper, IUnitOfWork unitOfWork) + public UpdateCustomerCommandHandler(IMapper mapper, ICustomerRepository customerRepository) { _mapper = mapper; - _unitOfWork = unitOfWork; + _customerRepository = customerRepository; } public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - var customer = await _unitOfWork.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken); + var customer = await _customerRepository.GetByIdAsync(context.Message.CustomerId); if (customer.Name != context.Message.Name) { - var existing = await _unitOfWork.Customers.FindByNameAsync(context.Message.Name, cancellationToken); + var existing = await _customerRepository.FindByNameAsync(context.Message.Name); if (existing != null) { @@ -34,7 +34,6 @@ public async Task Handle(IReceiveContext context, Cancell _mapper.Map(context.Message, customer); - await _unitOfWork.Customers.UpdateAsync(customer, cancellationToken).ConfigureAwait(false); - await _unitOfWork.SaveChangesAsync(cancellationToken); + await _customerRepository.UpdateAsync(customer); } } \ No newline at end of file diff --git a/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs b/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs index a4dae7f..939ed71 100644 --- a/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs +++ b/src/Wax.Core/Handlers/RequestHandlers/Customers/GetCustomerRequestHandler.cs @@ -1,6 +1,7 @@ using AutoMapper; using Mediator.Net.Context; using Mediator.Net.Contracts; +using Wax.Core.Data; using Wax.Core.Repositories; using Wax.Messages.Dtos.Customers; using Wax.Messages.Requests.Customers; @@ -10,18 +11,18 @@ namespace Wax.Core.Handlers.RequestHandlers.Customers; public class GetCustomerRequestHandler : IRequestHandler { private readonly IMapper _mapper; - private readonly IUnitOfWork _unitOfWork; + private readonly ICustomerRepository _customerRepository; - public GetCustomerRequestHandler(IMapper mapper, IUnitOfWork unitOfWork) + public GetCustomerRequestHandler(IMapper mapper, ICustomerRepository customerRepository) { _mapper = mapper; - _unitOfWork = unitOfWork; + _customerRepository = customerRepository; } public async Task Handle(IReceiveContext context, CancellationToken cancellationToken) { - var customer = await _unitOfWork.Customers.GetByIdAsync(context.Message.CustomerId, cancellationToken); + var customer = await _customerRepository.GetByIdAsync(context.Message.CustomerId); return new GetCustomerResponse { diff --git a/src/Wax.Core/Middlewares/UnitOfWorks/UnitOfWorkMiddleware.cs b/src/Wax.Core/Middlewares/UnitOfWorks/UnitOfWorkMiddleware.cs new file mode 100644 index 0000000..d5ebce7 --- /dev/null +++ b/src/Wax.Core/Middlewares/UnitOfWorks/UnitOfWorkMiddleware.cs @@ -0,0 +1,23 @@ +using Mediator.Net; +using Mediator.Net.Context; +using Mediator.Net.Contracts; +using Mediator.Net.Pipeline; +using Wax.Core.Data; + +namespace Wax.Core.Middlewares.UnitOfWorks; + +public static class UnitOfWorkMiddleware +{ + public static void UseUnitOfWork(this IPipeConfigurator configurator) + where TContext : IContext + { + if (configurator.DependencyScope == null) + { + throw new DependencyScopeNotConfiguredException("IDependencyScope is not configured."); + } + + var uow = configurator.DependencyScope.Resolve(); + + configurator.AddPipeSpecification(new UnitOfWorkSpecification(uow)); + } +} \ No newline at end of file diff --git a/src/Wax.Core/Middlewares/UnitOfWorks/UnitOfWorkSpecification.cs b/src/Wax.Core/Middlewares/UnitOfWorks/UnitOfWorkSpecification.cs new file mode 100644 index 0000000..296570a --- /dev/null +++ b/src/Wax.Core/Middlewares/UnitOfWorks/UnitOfWorkSpecification.cs @@ -0,0 +1,46 @@ +using System.Runtime.ExceptionServices; +using Mediator.Net.Context; +using Mediator.Net.Contracts; +using Mediator.Net.Pipeline; +using Wax.Core.Data; + +namespace Wax.Core.Middlewares.UnitOfWorks; + +public class UnitOfWorkSpecification : IPipeSpecification + where TContext : IContext +{ + private readonly IUnitOfWork _unitOfWork; + + public UnitOfWorkSpecification(IUnitOfWork unitOfWork) + { + _unitOfWork = unitOfWork; + } + + public bool ShouldExecute(TContext context, CancellationToken cancellationToken) + { + return context.Message is ICommand or IEvent; + } + + public Task BeforeExecute(TContext context, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task Execute(TContext context, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task AfterExecute(TContext context, CancellationToken cancellationToken) + { + return ShouldExecute(context, cancellationToken) + ? _unitOfWork.CommitAsync(cancellationToken) + : Task.CompletedTask; + } + + public Task OnException(Exception ex, TContext context) + { + ExceptionDispatchInfo.Capture(ex).Throw(); + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Wax.Core/Repositories/BasicRepository.cs b/src/Wax.Core/Repositories/BasicRepository.cs new file mode 100644 index 0000000..eddd31a --- /dev/null +++ b/src/Wax.Core/Repositories/BasicRepository.cs @@ -0,0 +1,80 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using Wax.Core.Data; +using Wax.Core.Domain; +using Wax.Core.Exceptions; + +namespace Wax.Core.Repositories; + +public class BasicRepository : IBasicRepository where TEntity : class, IEntity +{ + private readonly ApplicationDbContext _dbContext; + + public BasicRepository(ApplicationDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task GetByIdAsync(TKey id) + where TKey : notnull + { + var entity = await _dbContext.Set().FindAsync(new object[] { id }).ConfigureAwait(false); + + if (entity == null) + { + throw new EntityNotFoundException(typeof(TEntity), id); + } + + return entity; + } + + public async Task InsertAsync(TEntity entity, bool saveNow = false) + { + var entry = await _dbContext.Set().AddAsync(entity).ConfigureAwait(false); + + await _dbContext.ChangeEntitiesAsync(saveNow); + + return entry.Entity; + } + + public async Task InsertRangeAsync(IEnumerable entity, bool saveNow = false) + { + await _dbContext.Set().AddRangeAsync(entity).ConfigureAwait(false); + + await _dbContext.ChangeEntitiesAsync(saveNow); + } + + public Task UpdateAsync(TEntity entity, bool saveNow = false) + { + _dbContext.Entry(entity).State = EntityState.Modified; + return _dbContext.ChangeEntitiesAsync(saveNow); + } + + public Task UpdateRangeAsync(IEnumerable entities, bool saveNow = false) + { + _dbContext.Set().UpdateRange(entities); + return _dbContext.ChangeEntitiesAsync(saveNow); + } + + public Task DeleteAsync(TEntity entity, bool saveNow = false) + { + _dbContext.Set().Remove(entity); + return _dbContext.ChangeEntitiesAsync(saveNow); + } + + public Task DeleteRangeAsync(IEnumerable entity, bool saveNow = false) + { + _dbContext.Set().RemoveRange(entity); + return _dbContext.ChangeEntitiesAsync(saveNow); + } + + public Task> ListAsync(Expression> predicate = null) + { + return predicate == null + ? _dbContext.Set().ToListAsync() + : _dbContext.Set().Where(predicate).ToListAsync(); + } + + + public IQueryable Table => _dbContext.Set(); +} \ No newline at end of file diff --git a/src/Wax.Core/Repositories/EfCoreBasicRepository.cs b/src/Wax.Core/Repositories/EfCoreBasicRepository.cs deleted file mode 100644 index f606546..0000000 --- a/src/Wax.Core/Repositories/EfCoreBasicRepository.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore; -using Wax.Core.Data; -using Wax.Core.Domain; -using Wax.Core.Exceptions; - -namespace Wax.Core.Repositories; - -public class EfCoreBasicRepository : IBasicRepository where TEntity : class, IEntity -{ - private readonly ApplicationDbContext _dbContext; - - public EfCoreBasicRepository(ApplicationDbContext dbContext) - { - _dbContext = dbContext; - } - - public async Task GetByIdAsync(TKey id, CancellationToken cancellationToken = default) - where TKey : notnull - { - var entity = await _dbContext.Set() - .FindAsync(new object[] { id }, cancellationToken).ConfigureAwait(false); - - if (entity == null) - { - throw new EntityNotFoundException(typeof(TEntity), id); - } - - return entity; - } - - public async Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddAsync(entity, cancellationToken).ConfigureAwait(false); - return entity; - } - - public async Task> InsertRangeAsync(IEnumerable entity, - CancellationToken cancellationToken = default) - { - await _dbContext.Set().AddRangeAsync(entity, cancellationToken).ConfigureAwait(false); - return entity; - } - - public Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default) - { - _dbContext.Entry(entity).State = EntityState.Modified; - return Task.CompletedTask; - } - - public Task UpdateRangeAsync(IEnumerable entities, - CancellationToken cancellationToken = default) - { - _dbContext.Set().UpdateRange(entities); - return Task.CompletedTask; - } - - public Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default) - { - _dbContext.Set().Remove(entity); - return Task.CompletedTask; - } - - public IQueryable Query(Expression> predicate = null) - { - return predicate != null ? _dbContext.Set().Where(predicate) : _dbContext.Set(); - } -} \ No newline at end of file diff --git a/src/Wax.Core/Repositories/EfCoreCustomerRepository.cs b/src/Wax.Core/Repositories/EfCoreCustomerRepository.cs deleted file mode 100644 index f6b7e75..0000000 --- a/src/Wax.Core/Repositories/EfCoreCustomerRepository.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Wax.Core.Data; -using Wax.Core.DependencyInjection; -using Wax.Core.Domain.Customers; - -namespace Wax.Core.Repositories; - -public class EfCoreCustomerRepository : EfCoreBasicRepository, ICustomerRepository, IScopedDependency -{ - public EfCoreCustomerRepository(ApplicationDbContext dbContext) : base(dbContext) - { - } - - public Task FindByNameAsync(string name, CancellationToken cancellationToken = default) - { - return Query(c => c.Name == name).FirstOrDefaultAsync(cancellationToken); - } -} \ No newline at end of file diff --git a/src/Wax.Core/Repositories/IBasicRepository.cs b/src/Wax.Core/Repositories/IBasicRepository.cs index 1213aaa..e6a1b82 100644 --- a/src/Wax.Core/Repositories/IBasicRepository.cs +++ b/src/Wax.Core/Repositories/IBasicRepository.cs @@ -5,18 +5,21 @@ namespace Wax.Core.Repositories; public interface IBasicRepository where TEntity : class, IEntity { - Task GetByIdAsync(TKey id, CancellationToken cancellationToken = default) where TKey : notnull; + Task GetByIdAsync(TKey id) where TKey : notnull; - Task InsertAsync(TEntity entity, CancellationToken cancellationToken = default); + Task InsertAsync(TEntity entity, bool saveNow = false); - Task> InsertRangeAsync(IEnumerable entity, - CancellationToken cancellationToken = default); + Task InsertRangeAsync(IEnumerable entity, bool saveNow = false); - Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default); + Task UpdateAsync(TEntity entity, bool saveNow = false); - Task UpdateRangeAsync(IEnumerable entities, CancellationToken cancellationToken = default); + Task UpdateRangeAsync(IEnumerable entities, bool saveNow = false); - Task DeleteAsync(TEntity entity, CancellationToken cancellationToken = default); + Task DeleteAsync(TEntity entity, bool saveNow = false); - IQueryable Query(Expression> predicate = null); + Task DeleteRangeAsync(IEnumerable entity, bool saveNow = false); + + Task> ListAsync(Expression> predicate = null); + + IQueryable Table { get; } } \ No newline at end of file diff --git a/src/Wax.Core/Repositories/ICustomerRepository.cs b/src/Wax.Core/Repositories/ICustomerRepository.cs index 1344642..bb2481f 100644 --- a/src/Wax.Core/Repositories/ICustomerRepository.cs +++ b/src/Wax.Core/Repositories/ICustomerRepository.cs @@ -1,8 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using Wax.Core.Data; +using Wax.Core.DependencyInjection; using Wax.Core.Domain.Customers; namespace Wax.Core.Repositories; public interface ICustomerRepository : IBasicRepository { - Task FindByNameAsync(string name, CancellationToken cancellationToken = default); + Task FindByNameAsync(string name); +} + +public class CustomerRepository : BasicRepository, ICustomerRepository, IScopedDependency +{ + public CustomerRepository(ApplicationDbContext dbContext) : base(dbContext) + { + } + + public Task FindByNameAsync(string name) + { + return Table.FirstOrDefaultAsync(c => c.Name == name); + } } \ No newline at end of file diff --git a/src/Wax.Core/Repositories/IUnitOfWork.cs b/src/Wax.Core/Repositories/IUnitOfWork.cs deleted file mode 100644 index 16ad33c..0000000 --- a/src/Wax.Core/Repositories/IUnitOfWork.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Wax.Core.Repositories; - -public interface IUnitOfWork : IDisposable -{ - public ICustomerRepository Customers { get; } - Task SaveChangesAsync(CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/Wax.Core/Repositories/UnitOfWork.cs b/src/Wax.Core/Repositories/UnitOfWork.cs deleted file mode 100644 index bc3a91f..0000000 --- a/src/Wax.Core/Repositories/UnitOfWork.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Autofac; -using Wax.Core.Data; - -namespace Wax.Core.Repositories; - -public class UnitOfWork : IUnitOfWork -{ - private readonly ApplicationDbContext _context; - private readonly IComponentContext _componentContext; - - public UnitOfWork(ApplicationDbContext context, IComponentContext componentContext) - { - _context = context; - _componentContext = componentContext; - } - - public ICustomerRepository Customers => _componentContext.Resolve(); - - public Task SaveChangesAsync(CancellationToken cancellationToken = default) - { - return _context.SaveChangesAsync(cancellationToken); - } - - public void Dispose() - { - _context?.Dispose(); - } -} \ No newline at end of file diff --git a/src/Wax.Core/Settings/IConfigurationSetting.cs b/src/Wax.Core/Settings/IConfigurationSetting.cs new file mode 100644 index 0000000..8e65efd --- /dev/null +++ b/src/Wax.Core/Settings/IConfigurationSetting.cs @@ -0,0 +1,10 @@ +namespace Wax.Core.Settings; + +public interface IConfigurationSetting +{ +} + +public interface IConfigurationSetting : IConfigurationSetting +{ + TValue Value { get; set; } +} \ No newline at end of file diff --git a/src/Wax.Core/Wax.Core.csproj b/src/Wax.Core/Wax.Core.csproj index 131af09..94b7b6b 100644 --- a/src/Wax.Core/Wax.Core.csproj +++ b/src/Wax.Core/Wax.Core.csproj @@ -16,6 +16,7 @@ + diff --git a/tests/Wax.IntegrationTests/IntegrationFixture.cs b/tests/Wax.IntegrationTests/IntegrationFixture.cs index 494e4ef..d6bf12e 100644 --- a/tests/Wax.IntegrationTests/IntegrationFixture.cs +++ b/tests/Wax.IntegrationTests/IntegrationFixture.cs @@ -1,5 +1,7 @@ using System; +using System.IO; using Autofac; +using Microsoft.Extensions.Configuration; using NSubstitute; using Serilog; using Wax.Core; @@ -17,7 +19,11 @@ public IntegrationFixture() var containerBuilder = new ContainerBuilder(); var logger = Substitute.For(); - containerBuilder.RegisterModule(new ApplicationModule(logger, new IntegrationTestUser(), "", + var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", false, true).Build(); + + containerBuilder.RegisterModule(new ApplicationModule(logger, new IntegrationTestUser(), configuration, typeof(IntegrationFixture).Assembly)); LifetimeScope = containerBuilder.Build(); diff --git a/tests/Wax.IntegrationTests/IntegrationTestBase.cs b/tests/Wax.IntegrationTests/IntegrationTestBase.cs index e695ee7..ec4ecd9 100644 --- a/tests/Wax.IntegrationTests/IntegrationTestBase.cs +++ b/tests/Wax.IntegrationTests/IntegrationTestBase.cs @@ -1,6 +1,7 @@ using System; using System.Threading.Tasks; using Autofac; +using Wax.Core.Data; using Wax.Core.Repositories; using Xunit; diff --git a/tests/Wax.IntegrationTests/Wax.IntegrationTests.csproj b/tests/Wax.IntegrationTests/Wax.IntegrationTests.csproj index cfe00cc..81915ea 100644 --- a/tests/Wax.IntegrationTests/Wax.IntegrationTests.csproj +++ b/tests/Wax.IntegrationTests/Wax.IntegrationTests.csproj @@ -8,6 +8,8 @@ + + @@ -26,4 +28,16 @@ + + + true + PreserveNewest + PreserveNewest + + + + PreserveNewest + + + diff --git a/tests/Wax.IntegrationTests/appsettings.json b/tests/Wax.IntegrationTests/appsettings.json new file mode 100644 index 0000000..df6d748 --- /dev/null +++ b/tests/Wax.IntegrationTests/appsettings.json @@ -0,0 +1,12 @@ +{ + "AllowedHosts": "*", + "Serilog": { + "Seq": { + "ServerUrl": "http://localhost:5341", + "ApiKey": "" + } + }, + "ConnectionStrings": { + "Default": "" + } +} diff --git a/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs b/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs index 15200d8..1d66bfb 100644 --- a/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs +++ b/tests/Wax.UnitTests/Customers/CreateCustomerTests.cs @@ -18,7 +18,7 @@ public class CreateCustomerTests : CustomerTestFixture public CreateCustomerTests() { - _handler = new CreateCustomerCommandHandler(Mapper, UnitOfWork); + _handler = new CreateCustomerCommandHandler(Mapper, Customers); } [Fact] diff --git a/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs b/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs index 21bef07..7b547ab 100644 --- a/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs +++ b/tests/Wax.UnitTests/Customers/CustomerTestFixture.cs @@ -1,5 +1,6 @@ using AutoMapper; using NSubstitute; +using Wax.Core.Data; using Wax.Core.Profiles; using Wax.Core.Repositories; @@ -9,13 +10,10 @@ public class CustomerTestFixture { protected readonly IMapper Mapper; protected readonly ICustomerRepository Customers; - protected readonly IUnitOfWork UnitOfWork; protected CustomerTestFixture() { Mapper = new MapperConfiguration(x => x.AddProfile(new CustomerProfile())).CreateMapper(); Customers = Substitute.For(); - UnitOfWork = Substitute.For(); - UnitOfWork.Customers.Returns(Customers); } } \ No newline at end of file diff --git a/tests/Wax.UnitTests/Customers/GetCustomerTests.cs b/tests/Wax.UnitTests/Customers/GetCustomerTests.cs index 8282e77..98220ea 100644 --- a/tests/Wax.UnitTests/Customers/GetCustomerTests.cs +++ b/tests/Wax.UnitTests/Customers/GetCustomerTests.cs @@ -17,7 +17,7 @@ public class GetCustomerTests : CustomerTestFixture public GetCustomerTests() { - _handler = new GetCustomerRequestHandler(Mapper, UnitOfWork); + _handler = new GetCustomerRequestHandler(Mapper, Customers); } [Fact] diff --git a/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs b/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs index 948798d..1c77cb0 100644 --- a/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs +++ b/tests/Wax.UnitTests/Customers/UpdateCustomerTests.cs @@ -19,7 +19,7 @@ public class UpdateCustomerTests : CustomerTestFixture public UpdateCustomerTests() { - _handler = new UpdateCustomerCommandHandler(Mapper, UnitOfWork); + _handler = new UpdateCustomerCommandHandler(Mapper, Customers); } [Fact] From 11894c822579f9796268a317e127692eed9f050a Mon Sep 17 00:00:00 2001 From: "BRUCE.L" Date: Thu, 6 Apr 2023 17:50:23 -0700 Subject: [PATCH 2/2] Override SaveChangesAsync --- src/Wax.Core/Data/ApplicationDbContext.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Wax.Core/Data/ApplicationDbContext.cs b/src/Wax.Core/Data/ApplicationDbContext.cs index d5df857..c73afab 100644 --- a/src/Wax.Core/Data/ApplicationDbContext.cs +++ b/src/Wax.Core/Data/ApplicationDbContext.cs @@ -9,6 +9,8 @@ public ApplicationDbContext(DbContextOptions options) : ba { } + public bool HasEntitiesChanged { get; private set; } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseInMemoryDatabase("__wax_database"); @@ -18,9 +20,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfigurationsFromAssembly(typeof(CustomerEntityTypeConfiguration).Assembly); } - - public bool HasEntitiesChanged { get; private set; } - + + + public override Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) + { + var result = base.SaveChangesAsync(cancellationToken); + + HasEntitiesChanged = false; + + return result; + } + public async Task ChangeEntitiesAsync(bool saveNow = false, CancellationToken cancellationToken = default) { HasEntitiesChanged = true;