diff --git a/CutList.Web/Program.cs b/CutList.Web/Program.cs index 7a5ffd8..c842d38 100644 --- a/CutList.Web/Program.cs +++ b/CutList.Web/Program.cs @@ -3,7 +3,13 @@ using CutList.Web.Data; using CutList.Web.Services; using Microsoft.EntityFrameworkCore; -var builder = WebApplication.CreateBuilder(args); +var builder = WebApplication.CreateBuilder(new WebApplicationOptions +{ + Args = args, + ContentRootPath = AppContext.BaseDirectory +}); + +builder.Host.UseWindowsService(); // Add services to the container. builder.Services.AddControllers(); @@ -13,8 +19,8 @@ builder.Services.AddRazorComponents() builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); -// Add Entity Framework -builder.Services.AddDbContext(options => +// Add Entity Framework (factory pattern for Blazor Server circuit safety) +builder.Services.AddDbContextFactory(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); // Add application services @@ -38,10 +44,8 @@ if (app.Environment.IsDevelopment()) else { app.UseExceptionHandler("/Error", createScopeForErrors: true); - app.UseHsts(); } -app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); diff --git a/CutList.Web/Services/CatalogService.cs b/CutList.Web/Services/CatalogService.cs index 506abe3..3a3f49b 100644 --- a/CutList.Web/Services/CatalogService.cs +++ b/CutList.Web/Services/CatalogService.cs @@ -7,30 +7,32 @@ namespace CutList.Web.Services; public class CatalogService { - private readonly ApplicationDbContext _context; + private readonly IDbContextFactory _factory; private readonly MaterialService _materialService; - public CatalogService(ApplicationDbContext context, MaterialService materialService) + public CatalogService(IDbContextFactory factory, MaterialService materialService) { - _context = context; + _factory = factory; _materialService = materialService; } public async Task ExportAsync() { - var suppliers = await _context.Suppliers + await using var context = _factory.CreateDbContext(); + + var suppliers = await context.Suppliers .Where(s => s.IsActive) .OrderBy(s => s.Name) .AsNoTracking() .ToListAsync(); - var cuttingTools = await _context.CuttingTools + var cuttingTools = await context.CuttingTools .Where(t => t.IsActive) .OrderBy(t => t.Name) .AsNoTracking() .ToListAsync(); - var materials = await _context.Materials + var materials = await context.Materials .Include(m => m.Dimensions) .Include(m => m.StockItems.Where(s => s.IsActive)) .ThenInclude(s => s.SupplierOfferings.Where(o => o.IsActive)) @@ -157,18 +159,19 @@ public class CatalogService { var result = new ImportResultDto(); - await using var transaction = await _context.Database.BeginTransactionAsync(); + await using var context = _factory.CreateDbContext(); + await using var transaction = await context.Database.BeginTransactionAsync(); try { // 1. Suppliers - upsert by name - var supplierMap = await ImportSuppliersAsync(data.Suppliers, result); + var supplierMap = await ImportSuppliersAsync(context, data.Suppliers, result); // 2. Cutting tools - upsert by name - await ImportCuttingToolsAsync(data.CuttingTools, result); + await ImportCuttingToolsAsync(context, data.CuttingTools, result); // 3. Materials + stock items + offerings - await ImportAllMaterialsAsync(data.Materials, supplierMap, result); + await ImportAllMaterialsAsync(context, data.Materials, supplierMap, result); await transaction.CommitAsync(); } @@ -182,11 +185,11 @@ public class CatalogService } private async Task> ImportSuppliersAsync( - List suppliers, ImportResultDto result) + ApplicationDbContext context, List suppliers, ImportResultDto result) { var map = new Dictionary(StringComparer.OrdinalIgnoreCase); - var existingSuppliers = await _context.Suppliers.ToListAsync(); + var existingSuppliers = await context.Suppliers.ToListAsync(); foreach (var dto in suppliers) { @@ -212,8 +215,8 @@ public class CatalogService Notes = dto.Notes, CreatedAt = DateTime.UtcNow }; - _context.Suppliers.Add(supplier); - await _context.SaveChangesAsync(); + context.Suppliers.Add(supplier); + await context.SaveChangesAsync(); existingSuppliers.Add(supplier); map[dto.Name] = supplier.Id; result.SuppliersCreated++; @@ -225,14 +228,14 @@ public class CatalogService } } - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); return map; } private async Task ImportCuttingToolsAsync( - List tools, ImportResultDto result) + ApplicationDbContext context, List tools, ImportResultDto result) { - var existingTools = await _context.CuttingTools.ToListAsync(); + var existingTools = await context.CuttingTools.ToListAsync(); foreach (var dto in tools) { @@ -255,7 +258,7 @@ public class CatalogService KerfInches = dto.KerfInches, IsDefault = false }; - _context.CuttingTools.Add(tool); + context.CuttingTools.Add(tool); existingTools.Add(tool); result.CuttingToolsCreated++; } @@ -266,71 +269,71 @@ public class CatalogService } } - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } private async Task ImportAllMaterialsAsync( - CatalogMaterialsDto materials, Dictionary supplierMap, ImportResultDto result) + ApplicationDbContext context, CatalogMaterialsDto materials, Dictionary supplierMap, ImportResultDto result) { - var existingMaterials = await _context.Materials + var existingMaterials = await context.Materials .Include(m => m.Dimensions) .Include(m => m.StockItems) .ThenInclude(s => s.SupplierOfferings) .ToListAsync(); foreach (var dto in materials.Angles) - await ImportMaterialAsync(dto, MaterialShape.Angle, existingMaterials, supplierMap, result, + await ImportMaterialAsync(context, dto, MaterialShape.Angle, existingMaterials, supplierMap, result, () => new AngleDimensions { Leg1 = dto.Leg1, Leg2 = dto.Leg2, Thickness = dto.Thickness }, dim => { var d = (AngleDimensions)dim; d.Leg1 = dto.Leg1; d.Leg2 = dto.Leg2; d.Thickness = dto.Thickness; }); foreach (var dto in materials.Channels) - await ImportMaterialAsync(dto, MaterialShape.Channel, existingMaterials, supplierMap, result, + await ImportMaterialAsync(context, dto, MaterialShape.Channel, existingMaterials, supplierMap, result, () => new ChannelDimensions { Height = dto.Height, Flange = dto.Flange, Web = dto.Web }, dim => { var d = (ChannelDimensions)dim; d.Height = dto.Height; d.Flange = dto.Flange; d.Web = dto.Web; }); foreach (var dto in materials.FlatBars) - await ImportMaterialAsync(dto, MaterialShape.FlatBar, existingMaterials, supplierMap, result, + await ImportMaterialAsync(context, dto, MaterialShape.FlatBar, existingMaterials, supplierMap, result, () => new FlatBarDimensions { Width = dto.Width, Thickness = dto.Thickness }, dim => { var d = (FlatBarDimensions)dim; d.Width = dto.Width; d.Thickness = dto.Thickness; }); foreach (var dto in materials.IBeams) - await ImportMaterialAsync(dto, MaterialShape.IBeam, existingMaterials, supplierMap, result, + await ImportMaterialAsync(context, dto, MaterialShape.IBeam, existingMaterials, supplierMap, result, () => new IBeamDimensions { Height = dto.Height, WeightPerFoot = dto.WeightPerFoot }, dim => { var d = (IBeamDimensions)dim; d.Height = dto.Height; d.WeightPerFoot = dto.WeightPerFoot; }); foreach (var dto in materials.Pipes) - await ImportMaterialAsync(dto, MaterialShape.Pipe, existingMaterials, supplierMap, result, + await ImportMaterialAsync(context, dto, MaterialShape.Pipe, existingMaterials, supplierMap, result, () => new PipeDimensions { NominalSize = dto.NominalSize, Wall = dto.Wall, Schedule = dto.Schedule }, dim => { var d = (PipeDimensions)dim; d.NominalSize = dto.NominalSize; d.Wall = (decimal?)dto.Wall; d.Schedule = dto.Schedule; }); foreach (var dto in materials.RectangularTubes) - await ImportMaterialAsync(dto, MaterialShape.RectangularTube, existingMaterials, supplierMap, result, + await ImportMaterialAsync(context, dto, MaterialShape.RectangularTube, existingMaterials, supplierMap, result, () => new RectangularTubeDimensions { Width = dto.Width, Height = dto.Height, Wall = dto.Wall }, dim => { var d = (RectangularTubeDimensions)dim; d.Width = dto.Width; d.Height = dto.Height; d.Wall = dto.Wall; }); foreach (var dto in materials.RoundBars) - await ImportMaterialAsync(dto, MaterialShape.RoundBar, existingMaterials, supplierMap, result, + await ImportMaterialAsync(context, dto, MaterialShape.RoundBar, existingMaterials, supplierMap, result, () => new RoundBarDimensions { Diameter = dto.Diameter }, dim => { var d = (RoundBarDimensions)dim; d.Diameter = dto.Diameter; }); foreach (var dto in materials.RoundTubes) - await ImportMaterialAsync(dto, MaterialShape.RoundTube, existingMaterials, supplierMap, result, + await ImportMaterialAsync(context, dto, MaterialShape.RoundTube, existingMaterials, supplierMap, result, () => new RoundTubeDimensions { OuterDiameter = dto.OuterDiameter, Wall = dto.Wall }, dim => { var d = (RoundTubeDimensions)dim; d.OuterDiameter = dto.OuterDiameter; d.Wall = dto.Wall; }); foreach (var dto in materials.SquareBars) - await ImportMaterialAsync(dto, MaterialShape.SquareBar, existingMaterials, supplierMap, result, + await ImportMaterialAsync(context, dto, MaterialShape.SquareBar, existingMaterials, supplierMap, result, () => new SquareBarDimensions { Size = dto.SideLength }, dim => { var d = (SquareBarDimensions)dim; d.Size = dto.SideLength; }); foreach (var dto in materials.SquareTubes) - await ImportMaterialAsync(dto, MaterialShape.SquareTube, existingMaterials, supplierMap, result, + await ImportMaterialAsync(context, dto, MaterialShape.SquareTube, existingMaterials, supplierMap, result, () => new SquareTubeDimensions { Size = dto.SideLength, Wall = dto.Wall }, dim => { var d = (SquareTubeDimensions)dim; d.Size = dto.SideLength; d.Wall = dto.Wall; }); } private async Task ImportMaterialAsync( - CatalogMaterialBaseDto dto, MaterialShape shape, + ApplicationDbContext context, CatalogMaterialBaseDto dto, MaterialShape shape, List existingMaterials, Dictionary supplierMap, ImportResultDto result, Func createDimensions, @@ -384,9 +387,9 @@ public class CatalogService result.MaterialsCreated++; } - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); - await ImportStockItemsAsync(material, dto.StockItems, supplierMap, result); + await ImportStockItemsAsync(context, material, dto.StockItems, supplierMap, result); } catch (Exception ex) { @@ -395,10 +398,10 @@ public class CatalogService } private async Task ImportStockItemsAsync( - Material material, List stockItems, + ApplicationDbContext context, Material material, List stockItems, Dictionary supplierMap, ImportResultDto result) { - var existingStockItems = await _context.StockItems + var existingStockItems = await context.StockItems .Include(s => s.SupplierOfferings) .Where(s => s.MaterialId == material.Id) .ToListAsync(); @@ -432,8 +435,8 @@ public class CatalogService Notes = dto.Notes, CreatedAt = DateTime.UtcNow }; - _context.StockItems.Add(stockItem); - await _context.SaveChangesAsync(); + context.StockItems.Add(stockItem); + await context.SaveChangesAsync(); existingStockItems.Add(stockItem); result.StockItemsCreated++; } @@ -473,7 +476,7 @@ public class CatalogService Price = offeringDto.Price, Notes = offeringDto.Notes }; - _context.SupplierOfferings.Add(offering); + context.SupplierOfferings.Add(offering); stockItem.SupplierOfferings.Add(offering); result.OfferingsCreated++; } @@ -486,7 +489,7 @@ public class CatalogService } } - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } catch (Exception ex) { diff --git a/CutList.Web/Services/CutListPackingService.cs b/CutList.Web/Services/CutListPackingService.cs index e26d9a7..cbb83b0 100644 --- a/CutList.Web/Services/CutListPackingService.cs +++ b/CutList.Web/Services/CutListPackingService.cs @@ -8,11 +8,11 @@ namespace CutList.Web.Services; public class CutListPackingService { - private readonly ApplicationDbContext _context; + private readonly IDbContextFactory _factory; - public CutListPackingService(ApplicationDbContext context) + public CutListPackingService(IDbContextFactory factory) { - _context = context; + _factory = factory; } public async Task PackAsync(IEnumerable parts, decimal kerfInches) @@ -22,6 +22,7 @@ public class CutListPackingService public async Task PackAsync(IEnumerable parts, decimal kerfInches, IEnumerable? jobStock) { + await using var context = _factory.CreateDbContext(); var result = new MultiMaterialPackResult(); // Group parts by material @@ -37,7 +38,7 @@ public class CutListPackingService var materialParts = group.ToList(); // Get the material - var material = await _context.Materials + var material = await context.Materials .FirstOrDefaultAsync(m => m.Id == materialId); if (material == null) continue; @@ -63,7 +64,7 @@ public class CutListPackingService else { // No job-specific stock - use all available stock items for this material - var stockItems = await _context.StockItems + var stockItems = await context.StockItems .Where(s => s.MaterialId == materialId && s.IsActive) .ToListAsync(); @@ -172,10 +173,12 @@ public class CutListPackingService return result; } - public MultiMaterialPackResult? LoadSavedResult(string json) + public async Task LoadSavedResultAsync(string json) { var saved = System.Text.Json.JsonSerializer.Deserialize(json); - return saved?.ToPackResult(_context); + if (saved == null) return null; + await using var context = _factory.CreateDbContext(); + return await saved.ToPackResultAsync(context); } public string SerializeResult(MultiMaterialPackResult result) @@ -344,13 +347,13 @@ public class SavedOptimizationResult return saved; } - public MultiMaterialPackResult ToPackResult(ApplicationDbContext context) + public async Task ToPackResultAsync(ApplicationDbContext context) { var result = new MultiMaterialPackResult(); foreach (var savedMr in MaterialResults) { - var material = context.Materials.Find(savedMr.MaterialId); + var material = await context.Materials.FindAsync(savedMr.MaterialId); if (material == null) continue; var packResult = new PackResult(); diff --git a/CutList.Web/Services/JobService.cs b/CutList.Web/Services/JobService.cs index ed6f36e..7a08235 100644 --- a/CutList.Web/Services/JobService.cs +++ b/CutList.Web/Services/JobService.cs @@ -6,16 +6,17 @@ namespace CutList.Web.Services; public class JobService { - private readonly ApplicationDbContext _context; + private readonly IDbContextFactory _factory; - public JobService(ApplicationDbContext context) + public JobService(IDbContextFactory factory) { - _context = context; + _factory = factory; } public async Task> GetAllAsync() { - return await _context.Jobs + await using var context = _factory.CreateDbContext(); + return await context.Jobs .Include(p => p.CuttingTool) .Include(p => p.Parts) .ThenInclude(pt => pt.Material) @@ -25,7 +26,8 @@ public class JobService public async Task GetByIdAsync(int id) { - return await _context.Jobs + await using var context = _factory.CreateDbContext(); + return await context.Jobs .Include(p => p.CuttingTool) .Include(p => p.Parts.OrderBy(pt => pt.SortOrder)) .ThenInclude(pt => pt.Material) @@ -38,17 +40,24 @@ public class JobService public async Task CreateAsync(Job? job = null) { + await using var context = _factory.CreateDbContext(); job ??= new Job(); - job.JobNumber = await GenerateJobNumberAsync(); + job.JobNumber = await GenerateJobNumberAsync(context); job.CreatedAt = DateTime.UtcNow; - _context.Jobs.Add(job); - await _context.SaveChangesAsync(); + context.Jobs.Add(job); + await context.SaveChangesAsync(); return job; } public async Task GenerateJobNumberAsync() { - var maxNumber = await _context.Jobs + await using var context = _factory.CreateDbContext(); + return await GenerateJobNumberAsync(context); + } + + private static async Task GenerateJobNumberAsync(ApplicationDbContext context) + { + var maxNumber = await context.Jobs .Where(j => j.JobNumber.StartsWith("JOB-")) .Select(j => j.JobNumber) .MaxAsync() as string; @@ -71,46 +80,61 @@ public class JobService public async Task UpdateAsync(Job job) { + await using var context = _factory.CreateDbContext(); job.UpdatedAt = DateTime.UtcNow; job.OptimizationResultJson = null; job.OptimizedAt = null; - _context.Jobs.Update(job); - await _context.SaveChangesAsync(); + context.Jobs.Update(job); + await context.SaveChangesAsync(); } public async Task LockAsync(int id) { - var job = await _context.Jobs.FindAsync(id); + await using var context = _factory.CreateDbContext(); + var job = await context.Jobs.FindAsync(id); if (job != null) { job.LockedAt = DateTime.UtcNow; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } public async Task UnlockAsync(int id) { - var job = await _context.Jobs.FindAsync(id); + await using var context = _factory.CreateDbContext(); + var job = await context.Jobs.FindAsync(id); if (job != null) { job.LockedAt = null; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } public async Task DeleteAsync(int id) { - var job = await _context.Jobs.FindAsync(id); + await using var context = _factory.CreateDbContext(); + var job = await context.Jobs.FindAsync(id); if (job != null) { - _context.Jobs.Remove(job); - await _context.SaveChangesAsync(); + context.Jobs.Remove(job); + await context.SaveChangesAsync(); } } public async Task DuplicateAsync(int id) { - var original = await GetByIdAsync(id); + await using var context = _factory.CreateDbContext(); + + var original = await context.Jobs + .Include(p => p.CuttingTool) + .Include(p => p.Parts.OrderBy(pt => pt.SortOrder)) + .ThenInclude(pt => pt.Material) + .Include(p => p.Stock.OrderBy(s => s.SortOrder)) + .ThenInclude(s => s.Material) + .Include(p => p.Stock) + .ThenInclude(s => s.StockItem) + .FirstOrDefaultAsync(p => p.Id == id); + if (original == null) { throw new ArgumentException("Job not found", nameof(id)); @@ -118,7 +142,7 @@ public class JobService var duplicate = new Job { - JobNumber = await GenerateJobNumberAsync(), + JobNumber = await GenerateJobNumberAsync(context), Name = string.IsNullOrWhiteSpace(original.Name) ? null : $"{original.Name} (Copy)", Customer = original.Customer, CuttingToolId = original.CuttingToolId, @@ -126,13 +150,13 @@ public class JobService CreatedAt = DateTime.UtcNow }; - _context.Jobs.Add(duplicate); - await _context.SaveChangesAsync(); + context.Jobs.Add(duplicate); + await context.SaveChangesAsync(); // Copy parts foreach (var part in original.Parts) { - _context.JobParts.Add(new JobPart + context.JobParts.Add(new JobPart { JobId = duplicate.Id, MaterialId = part.MaterialId, @@ -146,7 +170,7 @@ public class JobService // Copy stock selections foreach (var stock in original.Stock) { - _context.JobStocks.Add(new JobStock + context.JobStocks.Add(new JobStock { JobId = duplicate.Id, MaterialId = stock.MaterialId, @@ -159,52 +183,55 @@ public class JobService }); } - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); return duplicate; } // Optimization result persistence public async Task SaveOptimizationResultAsync(int jobId, string resultJson, DateTime optimizedAt) { - var job = await _context.Jobs.FindAsync(jobId); + await using var context = _factory.CreateDbContext(); + var job = await context.Jobs.FindAsync(jobId); if (job != null) { job.OptimizationResultJson = resultJson; job.OptimizedAt = optimizedAt; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } public async Task ClearOptimizationResultAsync(int jobId) { - var job = await _context.Jobs.FindAsync(jobId); + await using var context = _factory.CreateDbContext(); + var job = await context.Jobs.FindAsync(jobId); if (job != null && job.OptimizationResultJson != null) { job.OptimizationResultJson = null; job.OptimizedAt = null; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } // Parts management public async Task AddPartAsync(JobPart part) { - var maxOrder = await _context.JobParts + await using var context = _factory.CreateDbContext(); + var maxOrder = await context.JobParts .Where(p => p.JobId == part.JobId) .MaxAsync(p => (int?)p.SortOrder) ?? -1; part.SortOrder = maxOrder + 1; - _context.JobParts.Add(part); - await _context.SaveChangesAsync(); + context.JobParts.Add(part); + await context.SaveChangesAsync(); // Update job timestamp and clear stale results - var job = await _context.Jobs.FindAsync(part.JobId); + var job = await context.Jobs.FindAsync(part.JobId); if (job != null) { job.UpdatedAt = DateTime.UtcNow; job.OptimizationResultJson = null; job.OptimizedAt = null; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } return part; @@ -212,35 +239,37 @@ public class JobService public async Task UpdatePartAsync(JobPart part) { - _context.JobParts.Update(part); - await _context.SaveChangesAsync(); + await using var context = _factory.CreateDbContext(); + context.JobParts.Update(part); + await context.SaveChangesAsync(); - var job = await _context.Jobs.FindAsync(part.JobId); + var job = await context.Jobs.FindAsync(part.JobId); if (job != null) { job.UpdatedAt = DateTime.UtcNow; job.OptimizationResultJson = null; job.OptimizedAt = null; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } public async Task DeletePartAsync(int id) { - var part = await _context.JobParts.FindAsync(id); + await using var context = _factory.CreateDbContext(); + var part = await context.JobParts.FindAsync(id); if (part != null) { var jobId = part.JobId; - _context.JobParts.Remove(part); - await _context.SaveChangesAsync(); + context.JobParts.Remove(part); + await context.SaveChangesAsync(); - var job = await _context.Jobs.FindAsync(jobId); + var job = await context.Jobs.FindAsync(jobId); if (job != null) { job.UpdatedAt = DateTime.UtcNow; job.OptimizationResultJson = null; job.OptimizedAt = null; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } } @@ -248,21 +277,22 @@ public class JobService // Stock management public async Task AddStockAsync(JobStock stock) { - var maxOrder = await _context.JobStocks + await using var context = _factory.CreateDbContext(); + var maxOrder = await context.JobStocks .Where(s => s.JobId == stock.JobId) .MaxAsync(s => (int?)s.SortOrder) ?? -1; stock.SortOrder = maxOrder + 1; - _context.JobStocks.Add(stock); - await _context.SaveChangesAsync(); + context.JobStocks.Add(stock); + await context.SaveChangesAsync(); - var job = await _context.Jobs.FindAsync(stock.JobId); + var job = await context.Jobs.FindAsync(stock.JobId); if (job != null) { job.UpdatedAt = DateTime.UtcNow; job.OptimizationResultJson = null; job.OptimizedAt = null; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } return stock; @@ -270,42 +300,45 @@ public class JobService public async Task UpdateStockAsync(JobStock stock) { - _context.JobStocks.Update(stock); - await _context.SaveChangesAsync(); + await using var context = _factory.CreateDbContext(); + context.JobStocks.Update(stock); + await context.SaveChangesAsync(); - var job = await _context.Jobs.FindAsync(stock.JobId); + var job = await context.Jobs.FindAsync(stock.JobId); if (job != null) { job.UpdatedAt = DateTime.UtcNow; job.OptimizationResultJson = null; job.OptimizedAt = null; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } public async Task DeleteStockAsync(int id) { - var stock = await _context.JobStocks.FindAsync(id); + await using var context = _factory.CreateDbContext(); + var stock = await context.JobStocks.FindAsync(id); if (stock != null) { var jobId = stock.JobId; - _context.JobStocks.Remove(stock); - await _context.SaveChangesAsync(); + context.JobStocks.Remove(stock); + await context.SaveChangesAsync(); - var job = await _context.Jobs.FindAsync(jobId); + var job = await context.Jobs.FindAsync(jobId); if (job != null) { job.UpdatedAt = DateTime.UtcNow; job.OptimizationResultJson = null; job.OptimizedAt = null; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } } public async Task> GetAvailableStockForMaterialAsync(int materialId) { - return await _context.StockItems + await using var context = _factory.CreateDbContext(); + return await context.StockItems .Include(s => s.Material) .Where(s => s.MaterialId == materialId && s.IsActive) .OrderBy(s => s.LengthInches) @@ -315,7 +348,8 @@ public class JobService // Cutting tools public async Task> GetCuttingToolsAsync(bool includeInactive = false) { - var query = _context.CuttingTools.AsQueryable(); + await using var context = _factory.CreateDbContext(); + var query = context.CuttingTools.AsQueryable(); if (!includeInactive) { query = query.Where(t => t.IsActive); @@ -325,53 +359,58 @@ public class JobService public async Task GetCuttingToolByIdAsync(int id) { - return await _context.CuttingTools.FindAsync(id); + await using var context = _factory.CreateDbContext(); + return await context.CuttingTools.FindAsync(id); } public async Task GetDefaultCuttingToolAsync() { - return await _context.CuttingTools.FirstOrDefaultAsync(t => t.IsDefault && t.IsActive); + await using var context = _factory.CreateDbContext(); + return await context.CuttingTools.FirstOrDefaultAsync(t => t.IsDefault && t.IsActive); } public async Task CreateCuttingToolAsync(CuttingTool tool) { + await using var context = _factory.CreateDbContext(); if (tool.IsDefault) { // Clear other defaults - var others = await _context.CuttingTools.Where(t => t.IsDefault).ToListAsync(); + var others = await context.CuttingTools.Where(t => t.IsDefault).ToListAsync(); foreach (var other in others) { other.IsDefault = false; } } - _context.CuttingTools.Add(tool); - await _context.SaveChangesAsync(); + context.CuttingTools.Add(tool); + await context.SaveChangesAsync(); return tool; } public async Task UpdateCuttingToolAsync(CuttingTool tool) { + await using var context = _factory.CreateDbContext(); if (tool.IsDefault) { - var others = await _context.CuttingTools.Where(t => t.IsDefault && t.Id != tool.Id).ToListAsync(); + var others = await context.CuttingTools.Where(t => t.IsDefault && t.Id != tool.Id).ToListAsync(); foreach (var other in others) { other.IsDefault = false; } } - _context.CuttingTools.Update(tool); - await _context.SaveChangesAsync(); + context.CuttingTools.Update(tool); + await context.SaveChangesAsync(); } public async Task DeleteCuttingToolAsync(int id) { - var tool = await _context.CuttingTools.FindAsync(id); + await using var context = _factory.CreateDbContext(); + var tool = await context.CuttingTools.FindAsync(id); if (tool != null) { tool.IsActive = false; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } } diff --git a/CutList.Web/Services/MaterialService.cs b/CutList.Web/Services/MaterialService.cs index 584f4d3..ef3df1b 100644 --- a/CutList.Web/Services/MaterialService.cs +++ b/CutList.Web/Services/MaterialService.cs @@ -6,16 +6,17 @@ namespace CutList.Web.Services; public class MaterialService { - private readonly ApplicationDbContext _context; + private readonly IDbContextFactory _factory; - public MaterialService(ApplicationDbContext context) + public MaterialService(IDbContextFactory factory) { - _context = context; + _factory = factory; } public async Task> GetAllAsync(bool includeInactive = false) { - var query = _context.Materials + await using var context = _factory.CreateDbContext(); + var query = context.Materials .Include(m => m.Dimensions) .AsQueryable(); if (!includeInactive) @@ -27,16 +28,18 @@ public class MaterialService public async Task GetByIdAsync(int id) { - return await _context.Materials + await using var context = _factory.CreateDbContext(); + return await context.Materials .Include(m => m.Dimensions) .FirstOrDefaultAsync(m => m.Id == id); } public async Task CreateAsync(Material material) { + await using var context = _factory.CreateDbContext(); material.CreatedAt = DateTime.UtcNow; - _context.Materials.Add(material); - await _context.SaveChangesAsync(); + context.Materials.Add(material); + await context.SaveChangesAsync(); return material; } @@ -45,6 +48,7 @@ public class MaterialService /// public async Task CreateWithDimensionsAsync(Material material, MaterialDimensions dimensions) { + await using var context = _factory.CreateDbContext(); material.CreatedAt = DateTime.UtcNow; // Auto-generate Size string from dimensions if not provided @@ -56,13 +60,13 @@ public class MaterialService // Set sort order from primary dimension material.SortOrder = dimensions.GetSortOrder(); - _context.Materials.Add(material); - await _context.SaveChangesAsync(); + context.Materials.Add(material); + await context.SaveChangesAsync(); // Link dimensions to the created material dimensions.MaterialId = material.Id; - _context.MaterialDimensions.Add(dimensions); - await _context.SaveChangesAsync(); + context.MaterialDimensions.Add(dimensions); + await context.SaveChangesAsync(); material.Dimensions = dimensions; return material; @@ -70,9 +74,10 @@ public class MaterialService public async Task UpdateAsync(Material material) { + await using var context = _factory.CreateDbContext(); material.UpdatedAt = DateTime.UtcNow; - _context.Materials.Update(material); - await _context.SaveChangesAsync(); + context.Materials.Update(material); + await context.SaveChangesAsync(); } /// @@ -80,6 +85,7 @@ public class MaterialService /// public async Task UpdateWithDimensionsAsync(Material material, MaterialDimensions dimensions, bool regenerateSize = false) { + await using var context = _factory.CreateDbContext(); material.UpdatedAt = DateTime.UtcNow; if (regenerateSize) @@ -98,14 +104,14 @@ public class MaterialService if (dimensions.Id > 0) { // Already tracked, just save - _context.Entry(material).State = EntityState.Modified; + context.Entry(material).State = EntityState.Modified; } else { - _context.Materials.Update(material); + context.Materials.Update(material); // Check if dimensions already exist for this material - var existingDimensions = await _context.MaterialDimensions + var existingDimensions = await context.MaterialDimensions .AsNoTracking() .FirstOrDefaultAsync(d => d.MaterialId == material.Id); @@ -113,30 +119,32 @@ public class MaterialService { // Copy the existing Id to update in place dimensions.Id = existingDimensions.Id; - _context.MaterialDimensions.Update(dimensions); + context.MaterialDimensions.Update(dimensions); } else { - _context.MaterialDimensions.Add(dimensions); + context.MaterialDimensions.Add(dimensions); } } - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } public async Task DeleteAsync(int id) { - var material = await _context.Materials.FindAsync(id); + await using var context = _factory.CreateDbContext(); + var material = await context.Materials.FindAsync(id); if (material != null) { material.IsActive = false; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } public async Task ExistsAsync(MaterialShape shape, string size, int? excludeId = null) { - var query = _context.Materials.Where(m => m.Shape == shape && m.Size == size && m.IsActive); + await using var context = _factory.CreateDbContext(); + var query = context.Materials.Where(m => m.Shape == shape && m.Size == size && m.IsActive); if (excludeId.HasValue) { query = query.Where(m => m.Id != excludeId.Value); @@ -149,10 +157,11 @@ public class MaterialService /// public async Task> SearchRoundBarByDiameterAsync(decimal targetDiameter, decimal tolerance) { + await using var context = _factory.CreateDbContext(); var minValue = targetDiameter - tolerance; var maxValue = targetDiameter + tolerance; - return await _context.Set() + return await context.Set() .Include(d => d.Material) .Where(d => d.Material.IsActive) .Where(d => d.Diameter >= minValue && d.Diameter <= maxValue) @@ -166,10 +175,11 @@ public class MaterialService /// public async Task> SearchRoundTubeByODAsync(decimal targetOD, decimal tolerance) { + await using var context = _factory.CreateDbContext(); var minValue = targetOD - tolerance; var maxValue = targetOD + tolerance; - return await _context.Set() + return await context.Set() .Include(d => d.Material) .Where(d => d.Material.IsActive) .Where(d => d.OuterDiameter >= minValue && d.OuterDiameter <= maxValue) @@ -183,10 +193,11 @@ public class MaterialService /// public async Task> SearchFlatBarByWidthAsync(decimal targetWidth, decimal tolerance) { + await using var context = _factory.CreateDbContext(); var minValue = targetWidth - tolerance; var maxValue = targetWidth + tolerance; - return await _context.Set() + return await context.Set() .Include(d => d.Material) .Where(d => d.Material.IsActive) .Where(d => d.Width >= minValue && d.Width <= maxValue) @@ -200,10 +211,11 @@ public class MaterialService /// public async Task> SearchSquareBarBySizeAsync(decimal targetSize, decimal tolerance) { + await using var context = _factory.CreateDbContext(); var minValue = targetSize - tolerance; var maxValue = targetSize + tolerance; - return await _context.Set() + return await context.Set() .Include(d => d.Material) .Where(d => d.Material.IsActive) .Where(d => d.Size >= minValue && d.Size <= maxValue) @@ -217,10 +229,11 @@ public class MaterialService /// public async Task> SearchSquareTubeBySizeAsync(decimal targetSize, decimal tolerance) { + await using var context = _factory.CreateDbContext(); var minValue = targetSize - tolerance; var maxValue = targetSize + tolerance; - return await _context.Set() + return await context.Set() .Include(d => d.Material) .Where(d => d.Material.IsActive) .Where(d => d.Size >= minValue && d.Size <= maxValue) @@ -234,10 +247,11 @@ public class MaterialService /// public async Task> SearchRectangularTubeByWidthAsync(decimal targetWidth, decimal tolerance) { + await using var context = _factory.CreateDbContext(); var minValue = targetWidth - tolerance; var maxValue = targetWidth + tolerance; - return await _context.Set() + return await context.Set() .Include(d => d.Material) .Where(d => d.Material.IsActive) .Where(d => d.Width >= minValue && d.Width <= maxValue) @@ -251,10 +265,11 @@ public class MaterialService /// public async Task> SearchAngleByLegAsync(decimal targetLeg, decimal tolerance) { + await using var context = _factory.CreateDbContext(); var minValue = targetLeg - tolerance; var maxValue = targetLeg + tolerance; - return await _context.Set() + return await context.Set() .Include(d => d.Material) .Where(d => d.Material.IsActive) .Where(d => d.Leg1 >= minValue && d.Leg1 <= maxValue) @@ -268,10 +283,11 @@ public class MaterialService /// public async Task> SearchChannelByHeightAsync(decimal targetHeight, decimal tolerance) { + await using var context = _factory.CreateDbContext(); var minValue = targetHeight - tolerance; var maxValue = targetHeight + tolerance; - return await _context.Set() + return await context.Set() .Include(d => d.Material) .Where(d => d.Material.IsActive) .Where(d => d.Height >= minValue && d.Height <= maxValue) @@ -285,10 +301,11 @@ public class MaterialService /// public async Task> SearchIBeamByHeightAsync(decimal targetHeight, decimal tolerance) { + await using var context = _factory.CreateDbContext(); var minValue = targetHeight - tolerance; var maxValue = targetHeight + tolerance; - return await _context.Set() + return await context.Set() .Include(d => d.Material) .Where(d => d.Material.IsActive) .Where(d => d.Height >= minValue && d.Height <= maxValue) @@ -302,10 +319,11 @@ public class MaterialService /// public async Task> SearchPipeByNominalSizeAsync(decimal targetNPS, decimal tolerance) { + await using var context = _factory.CreateDbContext(); var minValue = targetNPS - tolerance; var maxValue = targetNPS + tolerance; - return await _context.Set() + return await context.Set() .Include(d => d.Material) .Where(d => d.Material.IsActive) .Where(d => d.NominalSize >= minValue && d.NominalSize <= maxValue) @@ -319,7 +337,8 @@ public class MaterialService /// public async Task> GetByShapeAsync(MaterialShape shape, bool includeInactive = false) { - var query = _context.Materials + await using var context = _factory.CreateDbContext(); + var query = context.Materials .Include(m => m.Dimensions) .Where(m => m.Shape == shape); diff --git a/CutList.Web/Services/PurchaseItemService.cs b/CutList.Web/Services/PurchaseItemService.cs index fc77119..f56120a 100644 --- a/CutList.Web/Services/PurchaseItemService.cs +++ b/CutList.Web/Services/PurchaseItemService.cs @@ -6,16 +6,18 @@ namespace CutList.Web.Services; public class PurchaseItemService { - private readonly ApplicationDbContext _context; + private readonly IDbContextFactory _factory; - public PurchaseItemService(ApplicationDbContext context) + public PurchaseItemService(IDbContextFactory factory) { - _context = context; + _factory = factory; } public async Task> GetAllAsync(PurchaseItemStatus? status = null) { - var query = _context.PurchaseItems + await using var context = _factory.CreateDbContext(); + + var query = context.PurchaseItems .Include(p => p.StockItem) .ThenInclude(s => s.Material) .Include(p => p.Supplier) @@ -35,7 +37,9 @@ public class PurchaseItemService public async Task GetByIdAsync(int id) { - return await _context.PurchaseItems + await using var context = _factory.CreateDbContext(); + + return await context.PurchaseItems .Include(p => p.StockItem) .ThenInclude(s => s.Material) .Include(p => p.Supplier) @@ -45,59 +49,71 @@ public class PurchaseItemService public async Task CreateAsync(PurchaseItem item) { + await using var context = _factory.CreateDbContext(); + item.CreatedAt = DateTime.UtcNow; - _context.PurchaseItems.Add(item); - await _context.SaveChangesAsync(); + context.PurchaseItems.Add(item); + await context.SaveChangesAsync(); return item; } public async Task CreateBulkAsync(List items) { + await using var context = _factory.CreateDbContext(); + var now = DateTime.UtcNow; foreach (var item in items) { item.CreatedAt = now; } - _context.PurchaseItems.AddRange(items); - await _context.SaveChangesAsync(); + context.PurchaseItems.AddRange(items); + await context.SaveChangesAsync(); } public async Task UpdateAsync(PurchaseItem item) { + await using var context = _factory.CreateDbContext(); + item.UpdatedAt = DateTime.UtcNow; - _context.PurchaseItems.Update(item); - await _context.SaveChangesAsync(); + context.PurchaseItems.Update(item); + await context.SaveChangesAsync(); } public async Task UpdateStatusAsync(int id, PurchaseItemStatus status) { - var item = await _context.PurchaseItems.FindAsync(id); + await using var context = _factory.CreateDbContext(); + + var item = await context.PurchaseItems.FindAsync(id); if (item != null) { item.Status = status; item.UpdatedAt = DateTime.UtcNow; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } public async Task UpdateSupplierAsync(int id, int? supplierId) { - var item = await _context.PurchaseItems.FindAsync(id); + await using var context = _factory.CreateDbContext(); + + var item = await context.PurchaseItems.FindAsync(id); if (item != null) { item.SupplierId = supplierId; item.UpdatedAt = DateTime.UtcNow; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } public async Task DeleteAsync(int id) { - var item = await _context.PurchaseItems.FindAsync(id); + await using var context = _factory.CreateDbContext(); + + var item = await context.PurchaseItems.FindAsync(id); if (item != null) { - _context.PurchaseItems.Remove(item); - await _context.SaveChangesAsync(); + context.PurchaseItems.Remove(item); + await context.SaveChangesAsync(); } } } diff --git a/CutList.Web/Services/StockItemService.cs b/CutList.Web/Services/StockItemService.cs index 2eba0c3..754f6de 100644 --- a/CutList.Web/Services/StockItemService.cs +++ b/CutList.Web/Services/StockItemService.cs @@ -6,16 +6,18 @@ namespace CutList.Web.Services; public class StockItemService { - private readonly ApplicationDbContext _context; + private readonly IDbContextFactory _factory; - public StockItemService(ApplicationDbContext context) + public StockItemService(IDbContextFactory factory) { - _context = context; + _factory = factory; } public async Task> GetAllAsync(bool includeInactive = false) { - var query = _context.StockItems + await using var context = _factory.CreateDbContext(); + + var query = context.StockItems .Include(s => s.Material) .AsQueryable(); @@ -33,7 +35,9 @@ public class StockItemService public async Task> GetByMaterialAsync(int materialId, bool includeInactive = false) { - var query = _context.StockItems + await using var context = _factory.CreateDbContext(); + + var query = context.StockItems .Include(s => s.Material) .Where(s => s.MaterialId == materialId); @@ -49,7 +53,9 @@ public class StockItemService public async Task GetByIdAsync(int id) { - return await _context.StockItems + await using var context = _factory.CreateDbContext(); + + return await context.StockItems .Include(s => s.Material) .Include(s => s.SupplierOfferings) .ThenInclude(o => o.Supplier) @@ -58,33 +64,41 @@ public class StockItemService public async Task CreateAsync(StockItem stockItem) { + await using var context = _factory.CreateDbContext(); + stockItem.CreatedAt = DateTime.UtcNow; - _context.StockItems.Add(stockItem); - await _context.SaveChangesAsync(); + context.StockItems.Add(stockItem); + await context.SaveChangesAsync(); return stockItem; } public async Task UpdateAsync(StockItem stockItem) { + await using var context = _factory.CreateDbContext(); + stockItem.UpdatedAt = DateTime.UtcNow; - _context.StockItems.Update(stockItem); - await _context.SaveChangesAsync(); + context.StockItems.Update(stockItem); + await context.SaveChangesAsync(); } public async Task DeleteAsync(int id) { - var stockItem = await _context.StockItems.FindAsync(id); + await using var context = _factory.CreateDbContext(); + + var stockItem = await context.StockItems.FindAsync(id); if (stockItem != null) { stockItem.IsActive = false; stockItem.UpdatedAt = DateTime.UtcNow; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } public async Task ExistsAsync(int materialId, decimal lengthInches, int? excludeId = null) { - var query = _context.StockItems.Where(s => + await using var context = _factory.CreateDbContext(); + + var query = context.StockItems.Where(s => s.MaterialId == materialId && s.LengthInches == lengthInches && s.IsActive); @@ -100,7 +114,9 @@ public class StockItemService // Stock transaction methods public async Task AddStockAsync(int stockItemId, int quantity, int? supplierId = null, decimal? unitPrice = null, string? notes = null) { - var stockItem = await _context.StockItems.FindAsync(stockItemId) + await using var context = _factory.CreateDbContext(); + + var stockItem = await context.StockItems.FindAsync(stockItemId) ?? throw new InvalidOperationException($"Stock item {stockItemId} not found"); var transaction = new StockTransaction @@ -117,15 +133,17 @@ public class StockItemService stockItem.QuantityOnHand += quantity; stockItem.UpdatedAt = DateTime.UtcNow; - _context.StockTransactions.Add(transaction); - await _context.SaveChangesAsync(); + context.StockTransactions.Add(transaction); + await context.SaveChangesAsync(); return transaction; } public async Task GetAverageCostAsync(int stockItemId) { - var transactions = await _context.StockTransactions + await using var context = _factory.CreateDbContext(); + + var transactions = await context.StockTransactions .Where(t => t.StockItemId == stockItemId && t.Type == StockTransactionType.Received && t.UnitPrice.HasValue) .ToListAsync(); @@ -140,7 +158,9 @@ public class StockItemService public async Task GetLastPurchasePriceAsync(int stockItemId) { - return await _context.StockTransactions + await using var context = _factory.CreateDbContext(); + + return await context.StockTransactions .Where(t => t.StockItemId == stockItemId && t.Type == StockTransactionType.Received && t.UnitPrice.HasValue) .OrderByDescending(t => t.CreatedAt) .Select(t => t.UnitPrice) @@ -149,7 +169,9 @@ public class StockItemService public async Task UseStockAsync(int stockItemId, int quantity, int? jobId = null, string? notes = null) { - var stockItem = await _context.StockItems.FindAsync(stockItemId) + await using var context = _factory.CreateDbContext(); + + var stockItem = await context.StockItems.FindAsync(stockItemId) ?? throw new InvalidOperationException($"Stock item {stockItemId} not found"); var transaction = new StockTransaction @@ -165,15 +187,17 @@ public class StockItemService stockItem.QuantityOnHand -= quantity; stockItem.UpdatedAt = DateTime.UtcNow; - _context.StockTransactions.Add(transaction); - await _context.SaveChangesAsync(); + context.StockTransactions.Add(transaction); + await context.SaveChangesAsync(); return transaction; } public async Task AdjustStockAsync(int stockItemId, int newQuantity, string? notes = null) { - var stockItem = await _context.StockItems.FindAsync(stockItemId) + await using var context = _factory.CreateDbContext(); + + var stockItem = await context.StockItems.FindAsync(stockItemId) ?? throw new InvalidOperationException($"Stock item {stockItemId} not found"); var difference = newQuantity - stockItem.QuantityOnHand; @@ -190,15 +214,17 @@ public class StockItemService stockItem.QuantityOnHand = newQuantity; stockItem.UpdatedAt = DateTime.UtcNow; - _context.StockTransactions.Add(transaction); - await _context.SaveChangesAsync(); + context.StockTransactions.Add(transaction); + await context.SaveChangesAsync(); return transaction; } public async Task ScrapStockAsync(int stockItemId, int quantity, string? notes = null) { - var stockItem = await _context.StockItems.FindAsync(stockItemId) + await using var context = _factory.CreateDbContext(); + + var stockItem = await context.StockItems.FindAsync(stockItemId) ?? throw new InvalidOperationException($"Stock item {stockItemId} not found"); var transaction = new StockTransaction @@ -213,15 +239,17 @@ public class StockItemService stockItem.QuantityOnHand -= quantity; stockItem.UpdatedAt = DateTime.UtcNow; - _context.StockTransactions.Add(transaction); - await _context.SaveChangesAsync(); + context.StockTransactions.Add(transaction); + await context.SaveChangesAsync(); return transaction; } public async Task> GetTransactionHistoryAsync(int stockItemId, int? limit = null) { - var query = _context.StockTransactions + await using var context = _factory.CreateDbContext(); + + var query = context.StockTransactions .Include(t => t.Job) .Include(t => t.Supplier) .Where(t => t.StockItemId == stockItemId) @@ -238,10 +266,12 @@ public class StockItemService public async Task RecalculateQuantityAsync(int stockItemId) { - var stockItem = await _context.StockItems.FindAsync(stockItemId) + await using var context = _factory.CreateDbContext(); + + var stockItem = await context.StockItems.FindAsync(stockItemId) ?? throw new InvalidOperationException($"Stock item {stockItemId} not found"); - var calculatedQuantity = await _context.StockTransactions + var calculatedQuantity = await context.StockTransactions .Where(t => t.StockItemId == stockItemId) .SumAsync(t => t.Quantity); @@ -249,7 +279,7 @@ public class StockItemService { stockItem.QuantityOnHand = calculatedQuantity; stockItem.UpdatedAt = DateTime.UtcNow; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } return calculatedQuantity; diff --git a/CutList.Web/Services/SupplierService.cs b/CutList.Web/Services/SupplierService.cs index bed6c3f..db1078c 100644 --- a/CutList.Web/Services/SupplierService.cs +++ b/CutList.Web/Services/SupplierService.cs @@ -6,16 +6,17 @@ namespace CutList.Web.Services; public class SupplierService { - private readonly ApplicationDbContext _context; + private readonly IDbContextFactory _factory; - public SupplierService(ApplicationDbContext context) + public SupplierService(IDbContextFactory factory) { - _context = context; + _factory = factory; } public async Task> GetAllAsync(bool includeInactive = false) { - var query = _context.Suppliers.AsQueryable(); + await using var context = _factory.CreateDbContext(); + var query = context.Suppliers.AsQueryable(); if (!includeInactive) { query = query.Where(s => s.IsActive); @@ -25,7 +26,8 @@ public class SupplierService public async Task GetByIdAsync(int id) { - return await _context.Suppliers + await using var context = _factory.CreateDbContext(); + return await context.Suppliers .Include(s => s.Offerings) .ThenInclude(o => o.StockItem) .ThenInclude(si => si.Material) @@ -34,32 +36,36 @@ public class SupplierService public async Task CreateAsync(Supplier supplier) { + await using var context = _factory.CreateDbContext(); supplier.CreatedAt = DateTime.UtcNow; - _context.Suppliers.Add(supplier); - await _context.SaveChangesAsync(); + context.Suppliers.Add(supplier); + await context.SaveChangesAsync(); return supplier; } public async Task UpdateAsync(Supplier supplier) { - _context.Suppliers.Update(supplier); - await _context.SaveChangesAsync(); + await using var context = _factory.CreateDbContext(); + context.Suppliers.Update(supplier); + await context.SaveChangesAsync(); } public async Task DeleteAsync(int id) { - var supplier = await _context.Suppliers.FindAsync(id); + await using var context = _factory.CreateDbContext(); + var supplier = await context.Suppliers.FindAsync(id); if (supplier != null) { supplier.IsActive = false; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } // Offering management public async Task> GetOfferingsForSupplierAsync(int supplierId) { - return await _context.SupplierOfferings + await using var context = _factory.CreateDbContext(); + return await context.SupplierOfferings .Include(o => o.StockItem) .ThenInclude(si => si.Material) .Where(o => o.SupplierId == supplierId && o.IsActive) @@ -71,7 +77,8 @@ public class SupplierService public async Task> GetOfferingsForStockItemAsync(int stockItemId) { - return await _context.SupplierOfferings + await using var context = _factory.CreateDbContext(); + return await context.SupplierOfferings .Include(o => o.Supplier) .Where(o => o.StockItemId == stockItemId && o.IsActive && o.Supplier.IsActive) .OrderBy(o => o.Supplier.Name) @@ -80,7 +87,8 @@ public class SupplierService public async Task GetOfferingByIdAsync(int id) { - return await _context.SupplierOfferings + await using var context = _factory.CreateDbContext(); + return await context.SupplierOfferings .Include(o => o.StockItem) .ThenInclude(si => si.Material) .Include(o => o.Supplier) @@ -89,30 +97,34 @@ public class SupplierService public async Task AddOfferingAsync(SupplierOffering offering) { - _context.SupplierOfferings.Add(offering); - await _context.SaveChangesAsync(); + await using var context = _factory.CreateDbContext(); + context.SupplierOfferings.Add(offering); + await context.SaveChangesAsync(); return offering; } public async Task UpdateOfferingAsync(SupplierOffering offering) { - _context.SupplierOfferings.Update(offering); - await _context.SaveChangesAsync(); + await using var context = _factory.CreateDbContext(); + context.SupplierOfferings.Update(offering); + await context.SaveChangesAsync(); } public async Task DeleteOfferingAsync(int id) { - var offering = await _context.SupplierOfferings.FindAsync(id); + await using var context = _factory.CreateDbContext(); + var offering = await context.SupplierOfferings.FindAsync(id); if (offering != null) { offering.IsActive = false; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } public async Task OfferingExistsAsync(int supplierId, int stockItemId, int? excludeId = null) { - var query = _context.SupplierOfferings.Where(o => + await using var context = _factory.CreateDbContext(); + var query = context.SupplierOfferings.Where(o => o.SupplierId == supplierId && o.StockItemId == stockItemId && o.IsActive);