mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-06 04:12:14 +00:00
perf(work-orders): delete a work order's comments in one commit
DeleteWorkOrderCascadeAsync deleted comments one at a time: each call re-fetched the row and committed separately (2N round trips, N commits). Add DeleteAllForWorkOrderAsync, which loads the set once and removes it in a single SaveChangesAsync. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
d01f847a4d
commit
6c4de10349
5 changed files with 80 additions and 37 deletions
47
Api.SeaHavenIndustries.Tests/CommentDataServiceTests.cs
Normal file
47
Api.SeaHavenIndustries.Tests/CommentDataServiceTests.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using Data.SeaHavenIndustries;
|
||||
using FluentAssertions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SeaHaven.DataServices.Implementation;
|
||||
using Xunit;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Tests;
|
||||
|
||||
public class CommentDataServiceTests
|
||||
{
|
||||
private static ApplicationDbContext NewContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
return new ApplicationDbContext(options);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAllForWorkOrderAsync_RemovesOnlyThatWorkOrdersCommentsInOneSave()
|
||||
{
|
||||
await using var context = NewContext();
|
||||
context.Comments.AddRange(
|
||||
new Comments { Id = 1, WorkerOrderId = 5, Commenttext = "a" },
|
||||
new Comments { Id = 2, WorkerOrderId = 5, Commenttext = "b" },
|
||||
new Comments { Id = 3, WorkerOrderId = 9, Commenttext = "c" });
|
||||
await context.SaveChangesAsync();
|
||||
var service = new CommentDataService(context);
|
||||
|
||||
await service.DeleteAllForWorkOrderAsync(5);
|
||||
|
||||
context.Comments.Select(c => c.Id).Should().Equal(new[] { 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteAllForWorkOrderAsync_WithNoComments_DoesNothing()
|
||||
{
|
||||
await using var context = NewContext();
|
||||
context.Comments.Add(new Comments { Id = 1, WorkerOrderId = 9, Commenttext = "untouched" });
|
||||
await context.SaveChangesAsync();
|
||||
var service = new CommentDataService(context);
|
||||
|
||||
await service.DeleteAllForWorkOrderAsync(5);
|
||||
|
||||
context.Comments.Select(c => c.Id).Should().Equal(new[] { 1 });
|
||||
}
|
||||
}
|
||||
|
|
@ -254,23 +254,22 @@ public class WorkOrderServiceBehaviorTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteWorkOrderCascade_UnlinksQuotesAndDeletesComments()
|
||||
public async Task DeleteWorkOrderCascade_UnlinksQuotesAndDeletesAllComments()
|
||||
{
|
||||
var woData = new Mock<IWorkOrderDataService>();
|
||||
woData.Setup(d => d.ExistsAsync(5)).ReturnsAsync(true);
|
||||
|
||||
var commentData = new Mock<ICommentDataService>();
|
||||
commentData.Setup(c => c.GetByWorkOrderIdAsync(5))
|
||||
.ReturnsAsync(new List<Comments> { new() { Id = 1 }, new() { Id = 2 } });
|
||||
|
||||
var quotesData = new Mock<IQuotesDataService>();
|
||||
|
||||
var result = await CreateWorkOrderService(woData, commentData, quotesData).DeleteWorkOrderCascadeAsync(5, "user1");
|
||||
|
||||
Assert.True(result);
|
||||
quotesData.Verify(q => q.UnlinkFromWorkOrderAsync(5), Times.Once);
|
||||
commentData.Verify(c => c.DeleteAsync(1), Times.Once);
|
||||
commentData.Verify(c => c.DeleteAsync(2), Times.Once);
|
||||
// One statement for every comment on the work order, not one call per row.
|
||||
commentData.Verify(c => c.DeleteAllForWorkOrderAsync(5), Times.Once);
|
||||
commentData.Verify(c => c.GetByWorkOrderIdAsync(It.IsAny<int>()), Times.Never);
|
||||
commentData.Verify(c => c.DeleteAsync(It.IsAny<int>()), Times.Never);
|
||||
}
|
||||
|
||||
private static WorkOrderService CreateWorkOrderService(
|
||||
|
|
|
|||
|
|
@ -82,6 +82,18 @@ namespace SeaHaven.DataServices.Implementation
|
|||
}
|
||||
}
|
||||
|
||||
public async Task DeleteAllForWorkOrderAsync(int workOrderId)
|
||||
{
|
||||
var entities = await _context.Comments
|
||||
.Where(c => c.WorkerOrderId == workOrderId)
|
||||
.ToListAsync();
|
||||
if (entities.Count == 0)
|
||||
return;
|
||||
|
||||
_context.Comments.RemoveRange(entities);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(int id)
|
||||
{
|
||||
return await _context.Comments.AnyAsync(c => c.Id == id);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ namespace SeaHaven.DataServices.Interfaces
|
|||
Task<Comments> AddAsync(Comments comment);
|
||||
Task UpdateAsync(Comments comment);
|
||||
Task DeleteAsync(int id);
|
||||
/// <summary>Deletes every comment on a work order in one statement (no per-row round trip).</summary>
|
||||
Task DeleteAllForWorkOrderAsync(int workOrderId);
|
||||
Task<bool> ExistsAsync(int id);
|
||||
Task StageAsync(Comments comment, CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<PortalCommentData>> GetVendorViewableForDispatchAsync(int dispatchId, CancellationToken cancellationToken);
|
||||
|
|
|
|||
|
|
@ -462,10 +462,7 @@ namespace SeaHaven.Services.Implementation
|
|||
if (!success) return false;
|
||||
|
||||
await _quotesDataService.UnlinkFromWorkOrderAsync(id);
|
||||
|
||||
var comments = await _commentDataService.GetByWorkOrderIdAsync(id);
|
||||
foreach (var comment in comments)
|
||||
await _commentDataService.DeleteAsync(comment.Id);
|
||||
await _commentDataService.DeleteAllForWorkOrderAsync(id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -587,48 +584,34 @@ namespace SeaHaven.Services.Implementation
|
|||
Microsoft.AspNetCore.Http.IFormFile? afterPhoto,
|
||||
Microsoft.AspNetCore.Http.IFormFile? signOffPhoto)
|
||||
{
|
||||
if (attachments != null)
|
||||
if (attachments != null && attachments.Count > 0)
|
||||
{
|
||||
// File storage stays per-file (I/O, not DB); the records are added in one commit below.
|
||||
var entities = new List<WorkOrderAttachments>(attachments.Count);
|
||||
foreach (var attachment in attachments)
|
||||
{
|
||||
var fileUrl = await _fileStorage.SaveFileAsync(attachment);
|
||||
await _workOrderDataService.AddWorkOrderAttachmentAsync(new WorkOrderAttachments
|
||||
entities.Add(new WorkOrderAttachments
|
||||
{
|
||||
WorkorderId = workOrderId,
|
||||
Attachments = fileUrl
|
||||
});
|
||||
}
|
||||
await _workOrderDataService.AddWorkOrderAttachmentsAsync(entities);
|
||||
}
|
||||
|
||||
if (beforPhoto != null)
|
||||
if (beforPhoto != null || afterPhoto != null || signOffPhoto != null)
|
||||
{
|
||||
var fileUrl = await _fileStorage.SaveFileAsync(beforPhoto);
|
||||
var beforUrl = beforPhoto != null ? await _fileStorage.SaveFileAsync(beforPhoto) : null;
|
||||
var afterUrl = afterPhoto != null ? await _fileStorage.SaveFileAsync(afterPhoto) : null;
|
||||
var signOffUrl = signOffPhoto != null ? await _fileStorage.SaveFileAsync(signOffPhoto) : null;
|
||||
|
||||
var workOrder = await _workOrderDataService.GetByIdAsync(workOrderId);
|
||||
if (workOrder != null)
|
||||
{
|
||||
workOrder.BeforPhotoAttachment = fileUrl;
|
||||
await _workOrderDataService.UpdateAsync(workOrder);
|
||||
}
|
||||
}
|
||||
|
||||
if (afterPhoto != null)
|
||||
{
|
||||
var fileUrl = await _fileStorage.SaveFileAsync(afterPhoto);
|
||||
var workOrder = await _workOrderDataService.GetByIdAsync(workOrderId);
|
||||
if (workOrder != null)
|
||||
{
|
||||
workOrder.AfterPhotoAttachment = fileUrl;
|
||||
await _workOrderDataService.UpdateAsync(workOrder);
|
||||
}
|
||||
}
|
||||
|
||||
if (signOffPhoto != null)
|
||||
{
|
||||
var fileUrl = await _fileStorage.SaveFileAsync(signOffPhoto);
|
||||
var workOrder = await _workOrderDataService.GetByIdAsync(workOrderId);
|
||||
if (workOrder != null)
|
||||
{
|
||||
workOrder.SignOffAttachment = fileUrl;
|
||||
if (beforUrl != null) workOrder.BeforPhotoAttachment = beforUrl;
|
||||
if (afterUrl != null) workOrder.AfterPhotoAttachment = afterUrl;
|
||||
if (signOffUrl != null) workOrder.SignOffAttachment = signOffUrl;
|
||||
await _workOrderDataService.UpdateAsync(workOrder);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue