{"record":{"id":"b3ca287981be6b7f","repo":"fullstackhero/dotnet-starter-kit","slug":"product-command-productid-not-found-b3ca28","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/ChangeProductPrice/ChangeProductPriceCommandHandler.cs","lineNumber":21,"sourceCode":"using FSH.Modules.Catalog.Contracts.v1.Products;\nusing FSH.Modules.Catalog.Data;\nusing FSH.Modules.Catalog.Domain;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Catalog.Features.v1.Products.ChangeProductPrice;\n\npublic sealed class ChangeProductPriceCommandHandler(CatalogDbContext dbContext)\n    : ICommandHandler<ChangeProductPriceCommand, Guid>\n{\n    public async ValueTask<Guid> Handle(ChangeProductPriceCommand 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        product.ChangePrice(new Money(command.Amount, command.Currency));\n        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);\n        return product.Id;\n    }\n}\n","sourceCodeStart":3,"sourceCodeEnd":28,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Catalog/Modules.Catalog/Features/v1/Products/ChangeProductPrice/ChangeProductPriceCommandHandler.cs#L3-L28","documentation":"ChangeProductPrice loads the product by command.ProductId and throws NotFoundException when no product matches, so a price change cannot be applied to a missing aggregate. Soft-deleted products are filtered out and also report 'not found'.","triggerScenarios":"POST price-change for a ProductId that: (1) was deleted, (2) is a typo/fabricated Guid, (3) belongs to another tenant.","commonSituations":"Pricing feed pushing updates for products that were removed since the feed was generated; wrong column mapped to productId in a CSV import; stale browser tab submitting an old id.","solutions":["Verify the product id exists in the target database.","Refresh the pricing source (re-export the feed) so ids are current.","Handle the 404 per row in bulk price updates and log skipped ids.","Check the product was not soft-deleted; restore it if the update should proceed."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const exists = await api.listProducts({ ids: [productId] });\nif (!exists.items.length) return skipPriceUpdate(productId, 'product not found');","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.changeProductPrice(productId, amount, currency); }\ncatch (e) { if (e.status === 404) log(`Product ${productId} missing; skipped`); else throw e; }","preventionTips":["Regenerate pricing feeds shortly before applying them","Verify the productId column mapping in import files","Skip-and-log missing products in bulk updates"],"tags":["not-found","products","pricing","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"}