diff --git a/Groceries/Home/HomePage.razor b/Groceries/Home/HomePage.razor index a37f0d7..89ebaa4 100644 --- a/Groceries/Home/HomePage.razor +++ b/Groceries/Home/HomePage.razor @@ -4,7 +4,7 @@ @layout Layout -@inject AppDbContext DbContext +@inject IDbContextFactory DbContextFactory @inject IHttpContextAccessor HttpContextAccessor @@ -52,7 +52,8 @@ protected override async Task OnInitializedAsync() { - model = await DbContext.ItemTagQuantities + using var dbContext = DbContextFactory.CreateDbContext(); + model = await dbContext.ItemTagQuantities .FromSqlRaw(@" SELECT tag, quantity, coalesce(unit_name, unit) AS unit, is_metric, is_divisible FROM ( diff --git a/Groceries/Items/ItemsPage.razor b/Groceries/Items/ItemsPage.razor index b5e8e98..938d0cd 100644 --- a/Groceries/Items/ItemsPage.razor +++ b/Groceries/Items/ItemsPage.razor @@ -2,7 +2,9 @@ @using Microsoft.EntityFrameworkCore @layout Layout -@inject AppDbContext DbContext + +@implements IDisposable +@inject IDbContextFactory DbContextFactory Groceries – Items @@ -44,6 +46,7 @@ public DateTime? LastPurchasedAt { get; init; } } + private AppDbContext? dbContext; private IQueryable items = null!; private PaginationState pagination = new(); @@ -52,7 +55,9 @@ protected override void OnParametersSet() { - var itemsQuery = DbContext.Items.AsQueryable(); + dbContext ??= DbContextFactory.CreateDbContext(); + + var itemsQuery = dbContext.Items.AsQueryable(); if (!string.IsNullOrEmpty(Search)) { var searchPattern = $"%{Search}%"; @@ -61,7 +66,7 @@ items = itemsQuery .GroupJoin( - DbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase), + dbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase), item => item.Id, purchase => purchase.ItemId, (item, purchases) => new { item, purchases }) @@ -78,4 +83,9 @@ .OrderBy(item => item.Brand) .ThenBy(item => item.Name); } + + public void Dispose() + { + dbContext?.Dispose(); + } } diff --git a/Groceries/Program.cs b/Groceries/Program.cs index 5b0acb0..75d62c1 100644 --- a/Groceries/Program.cs +++ b/Groceries/Program.cs @@ -42,7 +42,7 @@ builder.Services.AddDistributedMemoryCache(); builder.Services.AddHttpContextAccessor(); builder.Services.AddSession(); -builder.Services.AddDbContextPool(options => options +builder.Services.AddPooledDbContextFactory(options => options .EnableDetailedErrors(env.IsDevelopment()) .EnableSensitiveDataLogging(env.IsDevelopment()) .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) diff --git a/Groceries/Stores/StoreForm.razor b/Groceries/Stores/StoreForm.razor index 5166e47..bfdd32e 100644 --- a/Groceries/Stores/StoreForm.razor +++ b/Groceries/Stores/StoreForm.razor @@ -1,7 +1,7 @@ @using Groceries.Data @using Microsoft.EntityFrameworkCore -@inject AppDbContext DbContext +@inject IDbContextFactory DbContextFactory
@@ -45,7 +45,8 @@ protected override async Task OnInitializedAsync() { - retailers = await DbContext.Retailers + using var dbContext = DbContextFactory.CreateDbContext(); + retailers = await dbContext.Retailers .OrderBy(retailer => retailer.Name) .ToArrayAsync(); } diff --git a/Groceries/Stores/StoresController.cs b/Groceries/Stores/StoresController.cs index db2888e..5fbe151 100644 --- a/Groceries/Stores/StoresController.cs +++ b/Groceries/Stores/StoresController.cs @@ -8,11 +8,11 @@ using Microsoft.EntityFrameworkCore; [Route("/stores")] public class StoresController : Controller { - private readonly AppDbContext dbContext; + private readonly IDbContextFactory dbContextFactory; - public StoresController(AppDbContext dbContext) + public StoresController(IDbContextFactory dbContextFactory) { - this.dbContext = dbContext; + this.dbContextFactory = dbContextFactory; } [HttpGet] @@ -32,6 +32,8 @@ public class StoresController : Controller [HttpPost("new")] public async Task NewStore(Guid retailerId, string name, string? address) { + using var dbContext = dbContextFactory.CreateDbContext(); + var store = new Store(retailerId, name, address); dbContext.Stores.Add(store); @@ -45,6 +47,8 @@ public class StoresController : Controller [HttpGet("edit/{id}")] public async Task EditStore(Guid id) { + using var dbContext = dbContextFactory.CreateDbContext(); + var store = await dbContext.Stores .SingleOrDefaultAsync(store => store.Id == id, HttpContext.RequestAborted); @@ -61,6 +65,8 @@ public class StoresController : Controller [HttpPost("edit/{id}")] public async Task EditStore(Guid id, Guid retailerId, string name, string? address, string? returnUrl) { + using var dbContext = dbContextFactory.CreateDbContext(); + var store = new Store(id, retailerId, name, address); dbContext.Stores.Update(store); diff --git a/Groceries/Stores/StoresPage.razor b/Groceries/Stores/StoresPage.razor index 9581cde..0d32845 100644 --- a/Groceries/Stores/StoresPage.razor +++ b/Groceries/Stores/StoresPage.razor @@ -2,7 +2,9 @@ @using Microsoft.EntityFrameworkCore @layout Layout -@inject AppDbContext DbContext + +@implements IDisposable +@inject IDbContextFactory DbContextFactory Groceries – Stores @@ -39,6 +41,7 @@ public int TransactionsCount { get; init; } } + private AppDbContext? dbContext; private IQueryable stores = null!; private PaginationState pagination = new(); @@ -47,7 +50,9 @@ protected override void OnParametersSet() { - var storesQuery = DbContext.Stores.AsQueryable(); + dbContext ??= DbContextFactory.CreateDbContext(); + + var storesQuery = dbContext.Stores.AsQueryable(); if (!string.IsNullOrEmpty(Search)) { var searchPattern = $"%{Search}%"; @@ -66,4 +71,9 @@ .OrderBy(store => store.Retailer) .ThenBy(store => store.Name); } + + public void Dispose() + { + dbContext?.Dispose(); + } } diff --git a/Groceries/Transactions/EditTransactionItemPage.razor b/Groceries/Transactions/EditTransactionItemPage.razor index a38985f..6eb71f5 100644 --- a/Groceries/Transactions/EditTransactionItemPage.razor +++ b/Groceries/Transactions/EditTransactionItemPage.razor @@ -3,7 +3,7 @@ @layout Layout -@inject AppDbContext DbContext +@inject IDbContextFactory DbContextFactory Groceries – Edit Transaction Item @@ -19,7 +19,7 @@
- Cancel + Cancel
@@ -35,7 +35,8 @@ protected override async Task OnParametersSetAsync() { - store = await DbContext.Stores + using var dbContext = DbContextFactory.CreateDbContext(); + store = await dbContext.Stores .Where(store => store.Id == Transaction.StoreId) .Select(store => string.Concat(store.Retailer!.Name, " ", store.Name)) .SingleAsync(); diff --git a/Groceries/Transactions/EditTransactionPromotionPage.razor b/Groceries/Transactions/EditTransactionPromotionPage.razor index 768d24b..7ff4b9c 100644 --- a/Groceries/Transactions/EditTransactionPromotionPage.razor +++ b/Groceries/Transactions/EditTransactionPromotionPage.razor @@ -3,7 +3,7 @@ @layout Layout -@inject AppDbContext DbContext +@inject IDbContextFactory DbContextFactory Groceries – Edit Transaction Promotion @@ -35,7 +35,8 @@ protected override async Task OnParametersSetAsync() { - store = await DbContext.Stores + using var dbContext = DbContextFactory.CreateDbContext(); + store = await dbContext.Stores .Where(store => store.Id == Transaction.StoreId) .Select(store => string.Concat(store.Retailer!.Name, " ", store.Name)) .SingleAsync(); diff --git a/Groceries/Transactions/NewTransactionItemPage.razor b/Groceries/Transactions/NewTransactionItemPage.razor index 5873dc1..8ff73af 100644 --- a/Groceries/Transactions/NewTransactionItemPage.razor +++ b/Groceries/Transactions/NewTransactionItemPage.razor @@ -3,7 +3,7 @@ @layout Layout -@inject AppDbContext DbContext +@inject IDbContextFactory DbContextFactory Groceries – New Transaction Item @@ -16,7 +16,7 @@
- Cancel + Cancel
@@ -31,7 +31,8 @@ protected override async Task OnParametersSetAsync() { - store = await DbContext.Stores + using var dbContext = DbContextFactory.CreateDbContext(); + store = await dbContext.Stores .Where(store => store.Id == Transaction.StoreId) .Select(store => string.Concat(store.Retailer!.Name, " ", store.Name)) .SingleAsync(); diff --git a/Groceries/Transactions/NewTransactionItemsPage.razor b/Groceries/Transactions/NewTransactionItemsPage.razor index ae0e22b..4a2bc68 100644 --- a/Groceries/Transactions/NewTransactionItemsPage.razor +++ b/Groceries/Transactions/NewTransactionItemsPage.razor @@ -2,7 +2,8 @@ @using Microsoft.EntityFrameworkCore @layout Layout -@inject AppDbContext DbContext + +@inject IDbContextFactory DbContextFactory Groceries – New Transaction @@ -78,13 +79,15 @@ protected override async Task OnParametersSetAsync() { - store = await DbContext.Stores + using var dbContext = DbContextFactory.CreateDbContext(); + + store = await dbContext.Stores .Where(store => store.Id == Transaction.StoreId) .Select(store => string.Concat(store.Retailer!.Name, " ", store.Name)) .SingleAsync(); var itemIds = Transaction.Items.Select(item => item.ItemId); - itemNames = await DbContext.Items + itemNames = await dbContext.Items .Where(item => itemIds.Contains(item.Id)) .ToDictionaryAsync(item => item.Id, item => string.Concat(item.Brand, " ", item.Name)); } diff --git a/Groceries/Transactions/NewTransactionPage.razor b/Groceries/Transactions/NewTransactionPage.razor index 46fd39a..f8cbb96 100644 --- a/Groceries/Transactions/NewTransactionPage.razor +++ b/Groceries/Transactions/NewTransactionPage.razor @@ -3,7 +3,7 @@ @layout Layout -@inject AppDbContext DbContext +@inject IDbContextFactory DbContextFactory Groceries – New Transaction @@ -41,7 +41,8 @@ protected override async Task OnInitializedAsync() { - stores = await DbContext.Stores + using var dbContext = DbContextFactory.CreateDbContext(); + stores = await dbContext.Stores .OrderBy(store => store.Retailer!.Name) .ThenBy(store => store.Name) .Select(store => new StoreModel(store.Id, string.Concat(store.Retailer!.Name, " ", store.Name))) diff --git a/Groceries/Transactions/NewTransactionPromotionPage.razor b/Groceries/Transactions/NewTransactionPromotionPage.razor index 1b29a8a..18970c1 100644 --- a/Groceries/Transactions/NewTransactionPromotionPage.razor +++ b/Groceries/Transactions/NewTransactionPromotionPage.razor @@ -3,7 +3,7 @@ @layout Layout -@inject AppDbContext DbContext +@inject IDbContextFactory DbContextFactory Groceries – New Transaction Promotion @@ -28,7 +28,8 @@ protected override async Task OnParametersSetAsync() { - store = await DbContext.Stores + using var dbContext = DbContextFactory.CreateDbContext(); + store = await dbContext.Stores .Where(store => store.Id == Transaction.StoreId) .Select(store => string.Concat(store.Retailer!.Name, " ", store.Name)) .SingleAsync(); diff --git a/Groceries/Transactions/NewTransactionPromotionsPage.razor b/Groceries/Transactions/NewTransactionPromotionsPage.razor index 8a86769..681699b 100644 --- a/Groceries/Transactions/NewTransactionPromotionsPage.razor +++ b/Groceries/Transactions/NewTransactionPromotionsPage.razor @@ -2,7 +2,8 @@ @using Microsoft.EntityFrameworkCore @layout Layout -@inject AppDbContext DbContext + +@inject IDbContextFactory DbContextFactory Groceries – New Transaction @@ -64,7 +65,8 @@ protected override async Task OnParametersSetAsync() { - store = await DbContext.Stores + using var dbContext = DbContextFactory.CreateDbContext(); + store = await dbContext.Stores .Where(store => store.Id == Transaction.StoreId) .Select(store => string.Concat(store.Retailer!.Name, " ", store.Name)) .SingleAsync(); diff --git a/Groceries/Transactions/TransactionItemForm.razor b/Groceries/Transactions/TransactionItemForm.razor index f15e3ad..1ffba55 100644 --- a/Groceries/Transactions/TransactionItemForm.razor +++ b/Groceries/Transactions/TransactionItemForm.razor @@ -1,7 +1,7 @@ @using Groceries.Data @using Microsoft.EntityFrameworkCore -@inject AppDbContext DbContext +@inject IDbContextFactory DbContextFactory @* Ensure form action/method are used for implicit submission instead of barcode button *@ @@ -93,11 +93,12 @@ { barcode = TransactionItem.Item?.Barcodes.FirstOrDefault(); - items = await DbContext.Items + using var dbContext = DbContextFactory.CreateDbContext(); + items = await dbContext.Items .OrderBy(item => item.Brand) .ThenBy(item => item.Name) .GroupJoin( - DbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase), + dbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase), item => item.Id, lastPurchase => lastPurchase.ItemId, (item, purchases) => new { item, purchases }) diff --git a/Groceries/Transactions/TransactionPromotionForm.razor b/Groceries/Transactions/TransactionPromotionForm.razor index a50f14b..80b4e83 100644 --- a/Groceries/Transactions/TransactionPromotionForm.razor +++ b/Groceries/Transactions/TransactionPromotionForm.razor @@ -1,7 +1,7 @@ @using Groceries.Data @using Microsoft.EntityFrameworkCore -@inject AppDbContext DbContext +@inject IDbContextFactory DbContextFactory
@@ -56,8 +56,9 @@ { selectedItemIds = Promotion?.Items.Select(item => item.Id).ToArray() ?? []; + using var dbContext = DbContextFactory.CreateDbContext(); var itemIds = Transaction.Items.Select(item => item.ItemId); - itemNames = await DbContext.Items + itemNames = await dbContext.Items .Where(item => itemIds.Contains(item.Id)) .ToDictionaryAsync(item => item.Id, item => string.Concat(item.Brand, " ", item.Name)); } diff --git a/Groceries/Transactions/TransactionsController.cs b/Groceries/Transactions/TransactionsController.cs index 5a2faf6..cdcd990 100644 --- a/Groceries/Transactions/TransactionsController.cs +++ b/Groceries/Transactions/TransactionsController.cs @@ -9,11 +9,11 @@ using System.Text.Json; [Route("/transactions")] public class TransactionsController : Controller { - private readonly AppDbContext dbContext; + private readonly IDbContextFactory dbContextFactory; - public TransactionsController(AppDbContext dbContext) + public TransactionsController(IDbContextFactory dbContextFactory) { - this.dbContext = dbContext; + this.dbContextFactory = dbContextFactory; } [HttpGet] @@ -70,6 +70,8 @@ public class TransactionsController : Controller Item? item = null; if (barcodeData != null && barcodeFormat != null) { + using var dbContext = dbContextFactory.CreateDbContext(); + item = await dbContext.Items .Where(item => item.Barcodes.Any(barcode => barcode.BarcodeData == barcodeData)) .OrderByDescending(item => item.UpdatedAt) @@ -105,6 +107,8 @@ public class TransactionsController : Controller return Results.LocalRedirect("/transactions/new"); } + using var dbContext = dbContextFactory.CreateDbContext(); + var itemId = await dbContext.Items .Where(item => EF.Functions.ILike(item.Brand, brand) && EF.Functions.ILike(item.Name, name)) .Select(item => item.Id) @@ -171,6 +175,8 @@ public class TransactionsController : Controller return Results.LocalRedirect("/transactions/new/items"); } + using var dbContext = dbContextFactory.CreateDbContext(); + var itemId = await dbContext.Items .Where(item => EF.Functions.ILike(item.Brand, brand) && EF.Functions.ILike(item.Name, name)) .Select(item => item.Id) @@ -235,6 +241,8 @@ public class TransactionsController : Controller return Results.LocalRedirect("/transactions/new"); } + using var dbContext = dbContextFactory.CreateDbContext(); + // Work around EF trying to insert items by explicitly tracking them as unchanged dbContext.Items.AttachRange( transaction.Items diff --git a/Groceries/Transactions/TransactionsPage.razor b/Groceries/Transactions/TransactionsPage.razor index 8c2f425..e98f103 100644 --- a/Groceries/Transactions/TransactionsPage.razor +++ b/Groceries/Transactions/TransactionsPage.razor @@ -1,7 +1,10 @@ @using Groceries.Data +@using Microsoft.EntityFrameworkCore @layout Layout -@inject AppDbContext DbContext + +@implements IDisposable +@inject IDbContextFactory DbContextFactory Groceries – Transactions @@ -35,14 +38,16 @@ public int TotalItems { get; init; } } + private AppDbContext? dbContext; private IQueryable transactions = null!; private PaginationState pagination = new(); protected override void OnParametersSet() { - transactions = DbContext.Transactions + dbContext ??= DbContextFactory.CreateDbContext(); + transactions = dbContext.Transactions .Join( - DbContext.TransactionTotals, + dbContext.TransactionTotals, transaction => transaction.Id, transactionTotal => transactionTotal.TransactionId, (transaction, transactionTotal) => new TransactionModel @@ -55,4 +60,9 @@ }) .OrderByDescending(transaction => transaction.CreatedAt); } + + public void Dispose() + { + dbContext?.Dispose(); + } }