{"record":{"id":"203f5367e2221938","repo":"fullstackhero/dotnet-starter-kit","slug":"product-command-productid-not-found","errorCode":null,"errorMessage":"Product {command.ProductId} not found.","messagePattern":"Product (.+?) not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"warning","filePath":"src/Modules/Catalog/Modules.Catalog/Features/v1/Products/AddProductImage/AddProductImageCommandHandler.cs","lineNumber":20,"sourceCode":"using FSH.Modules.Catalog.Contracts.Dtos;\nusing FSH.Modules.Catalog.Contracts.v1.Products.AddProductImage;\nusing FSH.Modules.Catalog.Data;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Catalog.Features.v1.Products.AddProductImage;\n\npublic sealed class AddProductImageCommandHandler(CatalogDbContext dbContext)\n    : ICommandHandler<AddProductImageCommand, ProductImageDto>\n{\n    public async ValueTask<ProductImageDto> Handle(AddProductImageCommand 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        var image = product.AddImage(command.FileAssetId, command.Url);\n        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n\n        return new ProductImageDto(image.Id, image.FileAssetId, image.Url, image.IsThumbnail, image.SortOrder, image.CreatedAtUtc);\n    }\n}\n","sourceCodeStart":2,"sourceCodeEnd":28,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Catalog/Modules.Catalog/Features/v1/Products/AddProductImage/AddProductImageCommandHandler.cs#L2-L28","documentation":"AddProductImage loads the product by command.ProductId and throws NotFoundException when no product matches, so an image cannot be attached to a non-existent aggregate. Soft-delete query filters apply, so deleted products also report 'not found'.","triggerScenarios":"POST add-image for a ProductId that: (1) was deleted, (2) is a typo/fabricated Guid, (3) belongs to another tenant.","commonSituations":"Client kept the upload wizard open while the product was deleted elsewhere; wrong id mapped from a list; testing with ids from a different database.","solutions":["Verify the product exists: SELECT * FROM \"Products\" WHERE \"Id\" = '<id>'.","Re-open the product page and retry the image upload from the current product.","Check the product is not soft-deleted; restore it if needed.","Confirm tenant context matches the product's tenant."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const p = await api.getProduct(productId).catch(() => null);\nif (!p) return alert('Product no longer exists');","typeGuard":"const isGuid = (v) => typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);","tryCatchPattern":"try { await api.addProductImage(productId, file); }\ncatch (e) { if (e.status === 404) show('Product was deleted'); else throw e; }","preventionTips":["Re-open the product right before uploading images","Handle deletion of the product while the upload wizard is open","Validate id format before sending"],"tags":["not-found","products","images","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"}