{"record":{"id":"e2415a4417ade015","repo":"fullstackhero/dotnet-starter-kit","slug":"product-command-productid-not-found-e2415a","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/SetProductThumbnail/SetProductThumbnailCommandHandler.cs","lineNumber":19,"sourceCode":"using FSH.Framework.Core.Exceptions;\nusing FSH.Modules.Catalog.Contracts.v1.Products.SetProductThumbnail;\nusing FSH.Modules.Catalog.Data;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Catalog.Features.v1.Products.SetProductThumbnail;\n\npublic sealed class SetProductThumbnailCommandHandler(CatalogDbContext dbContext)\n    : ICommandHandler<SetProductThumbnailCommand, Unit>\n{\n    public async ValueTask<Unit> Handle(SetProductThumbnailCommand 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 a\n        // framework-aware 404 so the API surfaces NotFound rather than a 500.\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.SetThumbnail(command.ImageId);\n        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n        return Unit.Value;\n    }\n}\n","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Catalog/Modules.Catalog/Features/v1/Products/SetProductThumbnail/SetProductThumbnailCommandHandler.cs#L1-L33","documentation":"SetProductThumbnailCommandHandler.Handle looks up the product by ProductId and throws FSH's NotFoundException when no row matches, so the API returns a framework-mapped 404 instead of a null-reference/500. It is the module's contract for 'the product this command targets does not exist'.","triggerScenarios":"Mediator command SetProductThumbnailCommand dispatched with a ProductId that has no row in dbContext.Products (hard delete, wrong tenant, or fabricated/typo id). FirstOrDefaultAsync returns null and the ?? throw fires.","commonSituations":"Client cached a product id after the product was deleted; id passed from another tenant under tenant isolation; test fixtures using random Guids; caller swapped ImageId/ProductId fields in the payload.","solutions":["Verify the ProductId exists (query the products list/GET endpoint) and resend with a valid id.","Check the request is going to the correct tenant — tenant isolation filters Products per tenant.","If the id comes from a previous screen, refresh the data instead of reusing a stale id.","If the product should exist, confirm it was not deleted by a concurrent user or cleanup job."],"exampleFix":"// before\nawait mediator.Send(new SetProductThumbnailCommand(productId, imageId));\n// after\nvar exists = await dbContext.Products.AnyAsync(p => p.Id == productId, ct);\nif (!exists) return; // or surface 404 to the user before sending\nawait mediator.Send(new SetProductThumbnailCommand(productId, imageId), ct);","handlingStrategy":"validation","validationCode":"// C# caller\nbool exists = await dbContext.Products.AnyAsync(p => p.Id == command.ProductId, ct);\nif (!exists) throw new NotFoundException($\"Product {command.ProductId} not found.\");","typeGuard":"var product = await dbContext.Products.FirstOrDefaultAsync(p => p.Id == id, ct);\nif (product is null) return null; // narrow before use","tryCatchPattern":"try { await mediator.Send(cmd, ct); }\ncatch (NotFoundException ex) { logger.LogWarning(ex, \"Product missing\"); return Results.NotFound(); }","preventionTips":["Always resolve product ids from a fresh API list, never from long-lived client caches.","Include tenant context in requests so the right rows are visible.","Treat NotFoundException as an expected 404, not a bug, in clients.","Log the attempted ProductId to spot stale-id patterns."],"tags":["catalog","not-found","cqrs","entity-not-found"],"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"}