From 39851bf29497605337763ea51e3ed8246dc01bec Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Mon, 7 Jul 2025 23:20:21 +0100 Subject: [PATCH] Refactor database context resolution and improve logging This commit refactors the database context resolution in controllers and services, replacing `Func` with `IDbContextResolver`. This change enhances flexibility and testability. Key updates include: - Utilization of `ResolvedDbContext` in `DeveloperController`, `PataPawaPrePaidController`, and `TestBankController` for better context management. - Updates to `PendingPrePaymentProcessor` to adopt the new context resolution method. - Addition of `MigrateAsync` in `TestBankContext` for asynchronous database migrations. - Upgrade of the `Shared` package version in `TestHosts.csproj`. - Modifications to logging configurations in `nlog.config` to include correlation IDs for improved request tracing. These changes aim to enhance maintainability, performance, and observability across the application. --- .../Controllers/DeveloperController.cs | 53 ++++++++----------- .../Controllers/PataPawaPrePaidController.cs | 53 +++++++++---------- .../Controllers/TestBankController.cs | 42 +++++++-------- .../Database/TestBank/TestBankContext.cs | 22 +++++++- TestHosts/TestHosts/Program.cs | 8 +-- .../SoapServices/PataPawaPostPayService.cs | 33 ++++++------ TestHosts/TestHosts/Startup.cs | 49 +++++++---------- TestHosts/TestHosts/TestHosts.csproj | 5 +- TestHosts/TestHosts/nlog.config | 2 +- TestHosts/TestHosts/nlog.development.config | 2 +- 10 files changed, 133 insertions(+), 136 deletions(-) diff --git a/TestHosts/TestHosts/Controllers/DeveloperController.cs b/TestHosts/TestHosts/Controllers/DeveloperController.cs index b563b12..7919b99 100644 --- a/TestHosts/TestHosts/Controllers/DeveloperController.cs +++ b/TestHosts/TestHosts/Controllers/DeveloperController.cs @@ -4,34 +4,34 @@ namespace TestHosts.Controllers { - using System.Linq; - using System.Threading; - using System.Threading.Tasks; using Database.PataPawa; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; + using Shared.EntityFramework; using Shared.General; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; [Route("api/developer")] [ApiController] public class DeveloperController : ControllerBase { - private readonly Func ContextResolver; + private readonly IDbContextResolver ContextResolver; - public DeveloperController(Func contextResolver) { + public DeveloperController(IDbContextResolver contextResolver) { this.ContextResolver = contextResolver; } [HttpPost] [Route("patapawaprepay/createuser")] public async Task CreatePrepayUser([FromBody] CreatePatapawaPrePayUser request, CancellationToken cancellationToken){ - String connectionString = ConfigurationReader.GetConnectionString("PataPawaReadModel"); - PataPawaContext context = this.ContextResolver(connectionString); - + + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); Guid userId = Guid.NewGuid(); - PrePayUser user = await context.PrePayUsers.SingleOrDefaultAsync(u => u.UserName == request.UserName, cancellationToken); + PrePayUser user = await resolvedContext.Context.PrePayUsers.SingleOrDefaultAsync(u => u.UserName == request.UserName, cancellationToken); if (user == null){ @@ -39,7 +39,7 @@ public async Task CreatePrepayUser([FromBody] CreatePatapawaPrePa string base64String = Convert.ToBase64String(bytes); // Create the user - await context.PrePayUsers.AddAsync(new PrePayUser + await resolvedContext.Context.PrePayUsers.AddAsync(new PrePayUser { Balance = 0, Key = base64String, @@ -47,7 +47,7 @@ await context.PrePayUsers.AddAsync(new PrePayUser UserId = userId, UserName = request.UserName, }, cancellationToken); - await context.SaveChangesAsync(cancellationToken); + await resolvedContext.Context.SaveChangesAsync(cancellationToken); } return this.Ok(); @@ -57,13 +57,9 @@ await context.PrePayUsers.AddAsync(new PrePayUser [Route("patapawaprepay/adduserdebt")] public async Task AddUserDebt([FromBody] AddPatapawaPrePayUserDebt request, CancellationToken cancellationToken) { - String connectionString = ConfigurationReader.GetConnectionString("PataPawaReadModel"); - PataPawaContext context = this.ContextResolver(connectionString); - - - Guid userId = Guid.NewGuid(); - - PrePayUser user = await context.PrePayUsers.SingleOrDefaultAsync(u => u.UserName == request.UserName, cancellationToken); + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); + + PrePayUser user = await resolvedContext.Context.PrePayUsers.SingleOrDefaultAsync(u => u.UserName == request.UserName, cancellationToken); if (user == null){ return this.NotFound(); @@ -71,7 +67,7 @@ public async Task AddUserDebt([FromBody] AddPatapawaPrePayUserDeb user.Balance += request.DebtAmount; - await context.SaveChangesAsync(cancellationToken); + await resolvedContext.Context.SaveChangesAsync(cancellationToken); return this.Ok(); } @@ -79,24 +75,22 @@ public async Task AddUserDebt([FromBody] AddPatapawaPrePayUserDeb [HttpPost] [Route("patapawaprepay/createmeter")] public async Task CreatePrepayMeter([FromBody] CreatePatapawaPrePayMeter request, CancellationToken cancellationToken){ - String connectionString = ConfigurationReader.GetConnectionString("PataPawaReadModel"); - PataPawaContext context = this.ContextResolver(connectionString); - - + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); + Guid meterId = Guid.NewGuid(); - PrePayMeter meter = await context.PrePayMeters.SingleOrDefaultAsync(m => m.MeterNumber == request.MeterNumber, cancellationToken); + PrePayMeter meter = await resolvedContext.Context.PrePayMeters.SingleOrDefaultAsync(m => m.MeterNumber == request.MeterNumber, cancellationToken); if (meter == null) { // Create the meter - await context.PrePayMeters.AddAsync(new PrePayMeter + await resolvedContext.Context.PrePayMeters.AddAsync(new PrePayMeter { MeterNumber = request.MeterNumber, CustomerName = request.CustomerName, MeterId = meterId }, cancellationToken); - await context.SaveChangesAsync(cancellationToken); + await resolvedContext.Context.SaveChangesAsync(cancellationToken); } return this.Ok(); @@ -107,14 +101,13 @@ await context.PrePayMeters.AddAsync(new PrePayMeter public async Task CreateHostConfiguration([FromBody] CreatePataPawaPostPayBill request, CancellationToken cancellationToken) { - String connectionString = ConfigurationReader.GetConnectionString("PataPawaReadModel"); - PataPawaContext context = this.ContextResolver(connectionString); + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); Guid billIdentifier = Guid.NewGuid(); // TODO: check for a duplicate bill?? - await context.PostPaidBills.AddAsync(new PostPaidBill { + await resolvedContext.Context.PostPaidBills.AddAsync(new PostPaidBill { Amount = request.Amount, AccountNumber = request.AccountNumber, DueDate = request.DueDate, @@ -124,7 +117,7 @@ await context.PostPaidBills.AddAsync(new PostPaidBill { }, cancellationToken); - await context.SaveChangesAsync(cancellationToken); + await resolvedContext.Context.SaveChangesAsync(cancellationToken); return this.Ok(new { diff --git a/TestHosts/TestHosts/Controllers/PataPawaPrePaidController.cs b/TestHosts/TestHosts/Controllers/PataPawaPrePaidController.cs index e8ee07e..91485d0 100644 --- a/TestHosts/TestHosts/Controllers/PataPawaPrePaidController.cs +++ b/TestHosts/TestHosts/Controllers/PataPawaPrePaidController.cs @@ -1,30 +1,31 @@ namespace TestHosts.Controllers{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; using Database.PataPawa; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; + using Shared.EntityFramework; using Shared.General; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; [Route("api/patapawaprepay")] [ApiController] public class PataPawaPrePaidController : ControllerBase{ - #region Fields - - private readonly Func ContextResolver; + private readonly IDbContextResolver ContextResolver; - private List<(String username, String password, String key, String balance)> users = new(); + #region Fields + + //private List<(String username, String password, String key, String balance)> users = new(); #endregion #region Constructors - public PataPawaPrePaidController(Func contextResolver){ + public PataPawaPrePaidController(IDbContextResolver contextResolver) { this.ContextResolver = contextResolver; } @@ -168,21 +169,15 @@ private VendResponse CreateVendResponse(Database.PataPawa.Transaction transactio return response; } - - private PataPawaContext GetPataPawaContext(){ - String connectionString = ConfigurationReader.GetConnectionString("PataPawaReadModel"); - PataPawaContext context = this.ContextResolver(connectionString); - return context; - } - + private async Task HandleBalanceRequest(IFormCollection requestForm, CancellationToken cancellationToken){ String username = requestForm["username"].ToString(); String key = requestForm["key"].ToString(); String meter = requestForm["meter"].ToString(); - PataPawaContext context = this.GetPataPawaContext(); + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); - PrePayUser user = await context.PrePayUsers.SingleOrDefaultAsync(u => u.UserName == username && u.Key == key, cancellationToken); + PrePayUser user = await resolvedContext.Context.PrePayUsers.SingleOrDefaultAsync(u => u.UserName == username && u.Key == key, cancellationToken); var response = new BalanceResponse{ status = 0, @@ -201,9 +196,9 @@ private async Task HandleLastVendRequest(RequestType xlatedReques if (meterValidation.result != null) return meterValidation.result; - PataPawaContext context = this.GetPataPawaContext(); + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); - IQueryable transactions = context.Transactions.Where(t => t.MeterNumber == meter).AsQueryable(); + IQueryable transactions = resolvedContext.Context.Transactions.Where(t => t.MeterNumber == meter).AsQueryable(); transactions = xlatedRequestType switch{ RequestType.lastvendfull => transactions.Where(t => t.Status == 0), @@ -228,9 +223,9 @@ private async Task HandleLoginRequest(IFormCollection requestForm String username = requestForm["username"].ToString(); String password = requestForm["password"].ToString(); - PataPawaContext context = this.GetPataPawaContext(); + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); - PrePayUser user = await context.PrePayUsers.SingleOrDefaultAsync(u => u.UserName == username && u.Password == password, cancellationToken); + PrePayUser user = await resolvedContext.Context.PrePayUsers.SingleOrDefaultAsync(u => u.UserName == username && u.Password == password, cancellationToken); if (user == default){ LoginResponse errorResponse = new LoginResponse{ @@ -277,16 +272,16 @@ private async Task HandleVendRequest(IFormCollection requestForm, if (meterValidation.result != null) return meterValidation.result; - PataPawaContext context = this.GetPataPawaContext(); + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); Database.PataPawa.Transaction transaction = this.CreateTransactionRecord(amount, meterValidation.meterDetails); - await context.Transactions.AddAsync(transaction, cancellationToken); + await resolvedContext.Context.Transactions.AddAsync(transaction, cancellationToken); if (transaction.Charges != null){ - await context.TransactionCharges.AddRangeAsync(transaction.Charges, cancellationToken); + await resolvedContext.Context.TransactionCharges.AddRangeAsync(transaction.Charges, cancellationToken); } - await context.SaveChangesAsync(cancellationToken); + await resolvedContext.Context.SaveChangesAsync(cancellationToken); // Now build the response object VendResponse response = this.CreateVendResponse(transaction); @@ -325,9 +320,9 @@ private RequestType TranslateRequestType(String formRequest){ })); } - PataPawaContext context = this.GetPataPawaContext(); + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); - PrePayMeter meterDetails = await context.PrePayMeters.SingleOrDefaultAsync(m => m.MeterNumber == meterNumber, cancellationToken); + PrePayMeter meterDetails = await resolvedContext.Context.PrePayMeters.SingleOrDefaultAsync(m => m.MeterNumber == meterNumber, cancellationToken); if (meterDetails == default){ MeterResponse errorReponse = new MeterResponse{ diff --git a/TestHosts/TestHosts/Controllers/TestBankController.cs b/TestHosts/TestHosts/Controllers/TestBankController.cs index c531607..86531c5 100644 --- a/TestHosts/TestHosts/Controllers/TestBankController.cs +++ b/TestHosts/TestHosts/Controllers/TestBankController.cs @@ -1,34 +1,36 @@ namespace TestHosts.Controllers { - using System; - using System.Linq; - using System.Net.Http; - using System.Text; - using System.Threading; - using System.Threading.Tasks; using Database.TestBank; using DataTransferObjects.TestBank; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; + using Shared.EntityFramework; using Shared.General; using Shared.Logger; + using System; + using System.Linq; + using System.Net.Http; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using TestHosts.Database.PataPawa; using Deposit = Database.TestBank.Deposit; [Route("api/testbank")] [ApiController] public class TestBankController : ControllerBase { + private readonly IDbContextResolver ContextResolver; + #region Fields - private readonly Func ContextFactory; #endregion #region Constructors - public TestBankController(Func contextFactory) - { - this.ContextFactory = contextFactory; + public TestBankController(IDbContextResolver contextResolver) { + this.ContextResolver = contextResolver; } #endregion @@ -40,12 +42,11 @@ public TestBankController(Func contextFactory) public async Task CreateHostConfiguration([FromBody] CreateHostConfigurationRequest createHostConfigurationRequest, CancellationToken cancellationToken) { - var connectionString = ConfigurationReader.GetConnectionString("TestBankReadModel"); - var context = this.ContextFactory(connectionString); + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("TestBankReadModel"); Guid hostIdentifier = Guid.NewGuid(); - var host = context.HostConfigurations.SingleOrDefault(h => h.AccountNumber == createHostConfigurationRequest.AccountNumber && + var host = resolvedContext.Context.HostConfigurations.SingleOrDefault(h => h.AccountNumber == createHostConfigurationRequest.AccountNumber && h.SortCode == createHostConfigurationRequest.SortCode); if (host != null) @@ -60,8 +61,8 @@ public async Task CreateHostConfiguration([FromBody] CreateHostCo HostIdentifier = hostIdentifier, SortCode = createHostConfigurationRequest.SortCode }; - await context.HostConfigurations.AddAsync(hostConfiguration, cancellationToken); - await context.SaveChangesAsync(cancellationToken); + await resolvedContext.Context.HostConfigurations.AddAsync(hostConfiguration, cancellationToken); + await resolvedContext.Context.SaveChangesAsync(cancellationToken); return this.Ok(new { @@ -76,9 +77,8 @@ public async Task MakeDeposit([FromBody] MakeDepositRequest makeD { Logger.LogInformation(JsonConvert.SerializeObject(makeDepositRequest)); - String connectionString = ConfigurationReader.GetConnectionString("TestBankReadModel"); - TestBankContext context = this.ContextFactory(connectionString); - HostConfiguration host = context.HostConfigurations.SingleOrDefault(h => h.AccountNumber == makeDepositRequest.ToAccountNumber && + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("TestBankReadModel"); + HostConfiguration host = resolvedContext.Context.HostConfigurations.SingleOrDefault(h => h.AccountNumber == makeDepositRequest.ToAccountNumber && h.SortCode == makeDepositRequest.ToSortCode); Guid depositId = Guid.Empty; if (host == null) @@ -98,8 +98,8 @@ public async Task MakeDeposit([FromBody] MakeDepositRequest makeD HostIdentifier = host.HostIdentifier, SentToHost = false }; - await context.Deposits.AddAsync(deposit, cancellationToken); - await context.SaveChangesAsync(cancellationToken); + await resolvedContext.Context.Deposits.AddAsync(deposit, cancellationToken); + await resolvedContext.Context.SaveChangesAsync(cancellationToken); // Send to the call back Url (if specificed) if (host.CallbackUri != null) @@ -123,7 +123,7 @@ public async Task MakeDeposit([FromBody] MakeDepositRequest makeD if (response.IsSuccessStatusCode) { deposit.SentToHost = true; - await context.SaveChangesAsync(cancellationToken); + await resolvedContext.Context.SaveChangesAsync(cancellationToken); } } diff --git a/TestHosts/TestHosts/Database/TestBank/TestBankContext.cs b/TestHosts/TestHosts/Database/TestBank/TestBankContext.cs index e0752d6..c4d8104 100644 --- a/TestHosts/TestHosts/Database/TestBank/TestBankContext.cs +++ b/TestHosts/TestHosts/Database/TestBank/TestBankContext.cs @@ -1,9 +1,11 @@ namespace TestHosts.Database.TestBank { - using System; - using System.Collections.Generic; using Microsoft.EntityFrameworkCore; using Shared.General; + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; public class TestBankContext : DbContext { @@ -63,5 +65,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); } + + public virtual async Task MigrateAsync(CancellationToken cancellationToken) + { + if (this.Database.IsSqlServer()) + { + try + { + await this.Database.MigrateAsync(cancellationToken); + } + catch (Exception ex) + { + // Log the exception or handle it as needed + throw new InvalidOperationException("An error occurred while migrating the database.", ex); + } + } + } } } diff --git a/TestHosts/TestHosts/Program.cs b/TestHosts/TestHosts/Program.cs index 06f1704..8c5e40b 100644 --- a/TestHosts/TestHosts/Program.cs +++ b/TestHosts/TestHosts/Program.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Shared.EntityFramework; namespace TestHosts { @@ -46,10 +47,9 @@ public static IHostBuilder CreateHostBuilder(string[] args) { services.AddHostedService(provider => { - Func contextResolver = provider.GetRequiredService>(); - PendingPrePaymentProcessor worker = - new PendingPrePaymentProcessor(contextResolver); - //worker.TraceGenerated += Worker_TraceGenerated; + IDbContextResolver contextResolver = provider.GetRequiredService>(); + PendingPrePaymentProcessor worker = new (contextResolver); + return worker; }); }); diff --git a/TestHosts/TestHosts/SoapServices/PataPawaPostPayService.cs b/TestHosts/TestHosts/SoapServices/PataPawaPostPayService.cs index 3d4a7b4..d3a188b 100644 --- a/TestHosts/TestHosts/SoapServices/PataPawaPostPayService.cs +++ b/TestHosts/TestHosts/SoapServices/PataPawaPostPayService.cs @@ -1,4 +1,6 @@ -namespace TestHosts.SoapServices; +using Shared.EntityFramework; + +namespace TestHosts.SoapServices; using System; using System.Linq; @@ -9,15 +11,11 @@ public class PataPawaPostPayService : IPataPawaPostPayService { - #region Fields - - private readonly Func ContextResolver; - - #endregion + private readonly IDbContextResolver ContextResolver; #region Constructors - public PataPawaPostPayService(Func contextResolver) { + public PataPawaPostPayService(IDbContextResolver contextResolver) { this.ContextResolver = contextResolver; } @@ -28,13 +26,13 @@ public PataPawaPostPayService(Func contextResolver) { public LoginResponse Login(String username, String password) { // Check if we have an api key - using PataPawaContext context = this.ContextResolver("PataPawaReadModel"); - PostPaidAccount account = PataPawaPostPayService.GetPostPaidAccount(username, context); + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); + PostPaidAccount account = PataPawaPostPayService.GetPostPaidAccount(username, resolvedContext.Context); if (account == null) { // this is a first time request // Create an account - account = PataPawaPostPayService.CreatePostPaidAccount(username, password, context); + account = PataPawaPostPayService.CreatePostPaidAccount(username, password, resolvedContext.Context); // return the key in the response return new LoginResponse { @@ -69,8 +67,8 @@ public ProcessBillResponse ProcessBill(String username, String mobile_no, String customer_name, Decimal amount) { - using PataPawaContext context = this.ContextResolver("PataPawaReadModel"); - PostPaidAccount account = PataPawaPostPayService.GetAccount(username, api_key, context); + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); + PostPaidAccount account = PataPawaPostPayService.GetAccount(username, api_key, resolvedContext.Context); if (account == null) { // TODO: this might not be the correct way to respond in this case return new ProcessBillResponse { @@ -79,7 +77,7 @@ public ProcessBillResponse ProcessBill(String username, }; } - PostPaidBill bill = PataPawaPostPayService.GetBill(account_no, context); + PostPaidBill bill = PataPawaPostPayService.GetBill(account_no, resolvedContext.Context); if (bill == null) { // Bill not found @@ -90,7 +88,7 @@ public ProcessBillResponse ProcessBill(String username, }; } - PataPawaPostPayService.MakeBillPayment(amount, bill, context); + PataPawaPostPayService.MakeBillPayment(amount, bill, resolvedContext.Context); // return the response return new ProcessBillResponse { @@ -106,8 +104,8 @@ public ProcessBillResponse ProcessBill(String username, public VerifyResponse VerifyAccount(String username, String api_key, String account_no) { - using(PataPawaContext context = this.ContextResolver("PataPawaReadModel")) { - PostPaidAccount account = PataPawaPostPayService.GetAccount(username, api_key, context); + using ResolvedDbContext? resolvedContext = this.ContextResolver.Resolve("PataPawaReadModel"); + PostPaidAccount account = PataPawaPostPayService.GetAccount(username, api_key, resolvedContext.Context); if (account == null) { // TODO: this might not be the correct way to respond in this case return new VerifyResponse { @@ -119,7 +117,7 @@ public VerifyResponse VerifyAccount(String username, } // We have now found an account, lets get the first due bill for the customer account number - PostPaidBill bill = PataPawaPostPayService.GetBill(account_no, context); + PostPaidBill bill = PataPawaPostPayService.GetBill(account_no, resolvedContext.Context); if (bill == null) { // Bill not found @@ -139,7 +137,6 @@ public VerifyResponse VerifyAccount(String username, AccountNumber = bill.AccountNumber, DueDate = bill.DueDate }; - } } private static PostPaidAccount CreatePostPaidAccount(String username, diff --git a/TestHosts/TestHosts/Startup.cs b/TestHosts/TestHosts/Startup.cs index b9ab441..cda188b 100644 --- a/TestHosts/TestHosts/Startup.cs +++ b/TestHosts/TestHosts/Startup.cs @@ -84,28 +84,20 @@ public void ConfigureServices(IServiceCollection services) // { // c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); // }); - + services.AddSingleton(typeof(IDbContextResolver<>), typeof(DbContextResolver<>)); if (Startup.WebHostEnvironment.IsEnvironment("IntegrationTest") || Startup.Configuration.GetValue("ServiceOptions:UseInMemoryDatabase") == true) { services.AddDbContext(builder => builder.UseInMemoryDatabase("TestBankReadModel")); - DbContextOptionsBuilder bankContextBuilder = new DbContextOptionsBuilder(); - bankContextBuilder = bankContextBuilder.UseInMemoryDatabase("TestBankReadModel"); - services.AddSingleton>(cont => (connectionString) => { return new TestBankContext(bankContextBuilder.Options); }); - services.AddDbContext(builder => builder.UseInMemoryDatabase("PataPawaReadModel")); - DbContextOptionsBuilder pataPawaBuilder = new DbContextOptionsBuilder(); - pataPawaBuilder = pataPawaBuilder.UseInMemoryDatabase("PataPawaReadModel"); - services.AddSingleton>(cont => (connectionString) => { return new PataPawaContext(pataPawaBuilder.Options); }); + } else { String testBankConnectionString = ConfigurationReader.GetConnectionString("TestBankReadModel"); services.AddDbContext(builder => builder.UseSqlServer(testBankConnectionString)); - services.AddSingleton>(cont => (connectionString) => { return new TestBankContext(testBankConnectionString); }); String pataPawaConnectionString = ConfigurationReader.GetConnectionString("PataPawaReadModel"); services.AddDbContext(builder => builder.UseSqlServer(pataPawaConnectionString)); - services.AddSingleton>(cont => (connectionString) => { return new PataPawaContext(pataPawaConnectionString); }); } services.AddSingleton(); @@ -145,7 +137,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF ILogger logger = loggerFactory.CreateLogger("TestHosts"); Logger.Initialise(logger); - + app.UseMiddleware(); app.AddRequestLogging(); app.AddResponseLogging(); app.AddExceptionHandler(); @@ -164,7 +156,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF // }); // this will do the initial DB population - this.InitializeDatabase(app); + InitializeDatabase(app).Wait(CancellationToken.None); app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapHealthChecks("health", new HealthCheckOptions() @@ -194,7 +186,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF } - private void InitializeDatabase(IApplicationBuilder app) + async Task InitializeDatabase(IApplicationBuilder app) { using (IServiceScope serviceScope = app.ApplicationServices.GetService().CreateScope()) { @@ -202,7 +194,13 @@ private void InitializeDatabase(IApplicationBuilder app) if (testbankDbContext.Database.IsRelational()) { testbankDbContext.Database.SetCommandTimeout(TimeSpan.FromMinutes(5)); - testbankDbContext.Database.Migrate(); + try { + await testbankDbContext.MigrateAsync(CancellationToken.None); + } + catch (Exception ex) + { + + } } PataPawaContext pataPawaContext = serviceScope.ServiceProvider.GetRequiredService(); @@ -217,31 +215,24 @@ private void InitializeDatabase(IApplicationBuilder app) [ExcludeFromCodeCoverage] public class PendingPrePaymentProcessor : BackgroundService{ - private readonly Func ContextResolver; + private readonly IDbContextResolver Resolver; - public PendingPrePaymentProcessor(Func contextResolver){ - this.ContextResolver = contextResolver; + public PendingPrePaymentProcessor(IDbContextResolver resolver){ + this.Resolver = resolver; } - - private PataPawaContext GetPataPawaContext() - { - String connectionString = ConfigurationReader.GetConnectionString("PataPawaReadModel"); - PataPawaContext context = this.ContextResolver(connectionString); - return context; - } - + protected override async Task ExecuteAsync(CancellationToken stoppingToken){ while (stoppingToken.IsCancellationRequested == false){ // TODO: may introduce a date filter - PataPawaContext context = this.GetPataPawaContext(); + using ResolvedDbContext? resolvedContext = this.Resolver.Resolve("PataPawaReadModel"); - var pendingTransactions = await context.Transactions.Where(t => t.IsPending).OrderBy(t => t.Date).ToListAsync(stoppingToken); + var pendingTransactions = await resolvedContext.Context.Transactions.Where(t => t.IsPending).OrderBy(t => t.Date).ToListAsync(stoppingToken); if (pendingTransactions.Any()){ // Process the pending transactions foreach (Transaction pendingTransaction in pendingTransactions){ - PrePayMeter meter = await context.PrePayMeters.SingleAsync(m => m.MeterNumber == pendingTransaction.MeterNumber, stoppingToken); + PrePayMeter meter = await resolvedContext.Context.PrePayMeters.SingleAsync(m => m.MeterNumber == pendingTransaction.MeterNumber, stoppingToken); pendingTransaction.Status = 0; pendingTransaction.Messaage = "success"; @@ -270,7 +261,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken){ pendingTransaction.Reference = DateTime.Now.ToString("yyyyMMddhhmmsssfff"); pendingTransaction.IsPending = false; - await context.SaveChangesAsync(stoppingToken); + await resolvedContext.Context.SaveChangesAsync(stoppingToken); } } diff --git a/TestHosts/TestHosts/TestHosts.csproj b/TestHosts/TestHosts/TestHosts.csproj index bae9274..14c3cc2 100644 --- a/TestHosts/TestHosts/TestHosts.csproj +++ b/TestHosts/TestHosts/TestHosts.csproj @@ -21,7 +21,7 @@ - + @@ -40,6 +40,9 @@ + + Always + Always diff --git a/TestHosts/TestHosts/nlog.config b/TestHosts/TestHosts/nlog.config index 1c22182..05e6a8e 100644 --- a/TestHosts/TestHosts/nlog.config +++ b/TestHosts/TestHosts/nlog.config @@ -5,7 +5,7 @@