{"record":{"id":"176e2b8de3ebe1b1","repo":"fullstackhero/dotnet-starter-kit","slug":"product-query-productid-not-found","errorCode":null,"errorMessage":"Product {query.ProductId} not found.","messagePattern":"Product (.+?) not found\\.","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Catalog/Modules.Catalog/Features/v1/Products/GetProductById/GetProductByIdQueryHandler.cs","lineNumber":21,"sourceCode":"using FSH.Modules.Catalog.Contracts.v1.Products;\nusing FSH.Modules.Catalog.Data;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Catalog.Features.v1.Products.GetProductById;\n\npublic sealed class GetProductByIdQueryHandler(CatalogDbContext dbContext)\n    : IQueryHandler<GetProductByIdQuery, ProductDto>\n{\n    public async ValueTask<ProductDto> Handle(GetProductByIdQuery query, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(query);\n\n        var product = await dbContext.Products\n            .AsNoTracking()\n            .FirstOrDefaultAsync(p => p.Id == query.ProductId, cancellationToken)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException($\"Product {query.ProductId} not found.\");\n\n        return product.ToDto();\n    }\n}\n","sourceCodeStart":3,"sourceCodeEnd":26,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Catalog/Modules.Catalog/Features/v1/Products/GetProductById/GetProductByIdQueryHandler.cs#L3-L26","documentation":"GetProductByIdQueryHandler runs an AsNoTracking FirstOrDefault for the given ProductId and throws NotFoundException when no product matches, producing HTTP 404. Because the query uses the default DbContext filters, soft-deleted products and other tenants' products are invisible.","triggerScenarios":"GET /products/{id} with a nonexistent id, a soft-deleted product's id, a wrong-tenant product id, or an id from another environment's database.","commonSituations":"Bookmarked/shared links to products deleted since; navigation after delete without list refresh; tenant switch on the client while old ids remain in state.","solutions":["Validate the id against the products list endpoint before deep-linking, or handle 404 gracefully in the UI with a 'product not available' state.","If the product should exist but was deleted, call the restore endpoint (requires appropriate permissions).","Check tenant context/headers — the product may exist under a different tenant.","Verify environment: dev ids rarely exist in prod and vice versa."],"exampleFix":"// before\nconst product = await api.getProduct(id); // throws 404, crashes page\n// after\nconst product = await api.getProduct(id).catch(e =>\n  e.status === 404 ? null : Promise.reject(e)\n);\nif (!product) return <NotFound message=\"Product not available\" />;","handlingStrategy":"try-catch","validationCode":"// validate GUID format before calling\nconst isGuid = (v) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);\nif (!isGuid(productId)) throw new Error('Invalid product id');","typeGuard":"function isProductDto(x): x is ProductDto {\n  return !!x && typeof x === 'object' && typeof x.id === 'string' && typeof x.name === 'string';\n}","tryCatchPattern":"try {\n  const product = await api.getProduct(productId);\n} catch (e) {\n  if (e.status === 404) return <NotFoundPage />;\n  throw e;\n}","preventionTips":["Render a friendly 404 state instead of letting the page crash","Don't bookmark raw product ids for long-lived links; use slugs where possible","Clear product ids from client state on tenant switch"],"tags":["ef-core","not-found","query","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"}