{"record":{"id":"d3c0766686b0e401","repo":"fullstackhero/dotnet-starter-kit","slug":"product-command-productid-not-found-d3c076","errorCode":null,"errorMessage":"Product {command.ProductId} not found.","messagePattern":"Product (.+?) not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Catalog/Modules.Catalog/Features/v1/Products/RemoveProductImage/RemoveProductImageCommandHandler.cs","lineNumber":19,"sourceCode":"using FSH.Framework.Core.Exceptions;\nusing FSH.Modules.Catalog.Contracts.v1.Products.RemoveProductImage;\nusing FSH.Modules.Catalog.Data;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Catalog.Features.v1.Products.RemoveProductImage;\n\npublic sealed class RemoveProductImageCommandHandler(CatalogDbContext dbContext)\n    : ICommandHandler<RemoveProductImageCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(RemoveProductImageCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        var product = await dbContext.Products\n            .FirstOrDefaultAsync(p => p.Id == command.ProductId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Product {command.ProductId} not found.\");\n\n        // Domain throws InvalidOperationException for unknown imageId; translate to 404.\n        if (!product.Images.Any(i => i.Id == command.ImageId))\n        {\n            throw new NotFoundException($\"Image {command.ImageId} not found on product {command.ProductId}.\");\n        }\n\n        product.RemoveImage(command.ImageId);\n        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n        return Unit.Value;\n    }\n}\n","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Catalog/Modules.Catalog/Features/v1/Products/RemoveProductImage/RemoveProductImageCommandHandler.cs#L1-L32","documentation":"RemoveProductImageCommandHandler first loads the owning product and throws NotFoundException if command.ProductId does not match any product row (HTTP 404). The image lookup itself is handled by error [77]; this one fires before any image check when the parent product cannot be found.","triggerScenarios":"DELETE (remove-image) with a ProductId that doesn't exist, was soft-deleted, belongs to another tenant, or an image payload carrying the wrong/zero product id.","commonSituations":"Client holds product ids from a stale list after the product was deleted; wrong entity's image id submitted (image id from product A with product id of product B).","solutions":["Re-fetch the product to confirm the id, then re-read its current image ids before removing.","Treat 404 on product delete flows as idempotent success where appropriate.","Ensure the image id and product id come from the same product object in the client state.","Check tenant context if the product exists but is filtered out."],"exampleFix":"// before\nawait api.removeProductImage(staleProductId, imageId);\n// after\nconst product = await api.getProduct(staleProductId).catch(() => null);\nif (!product) { refreshList(); return; }\nawait api.removeProductImage(product.id, imageId);","handlingStrategy":"try-catch","validationCode":"const product = await api.getProduct(productId).catch(() => null);\nif (!product) throw new Error(`Product ${productId} does not exist`);\nif (!product.images.some(i => i.id === imageId)) throw new Error('Image not on this product');","typeGuard":null,"tryCatchPattern":"try {\n  await api.removeProductImage(productId, imageId);\n} catch (e) {\n  if (e.status === 404) { refreshProduct(); return; }\n  throw e;\n}","preventionTips":["Keep image id and product id sourced from the same fetched product object","Refresh product detail after every mutation","Re-fetch before mutating data loaded more than a few seconds ago"],"tags":["ef-core","not-found","images","http-404","catalog"],"backgroundTag":"entity-not-found","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}