fullstackhero/dotnet-starter-kit · error · NotFoundException

Product not found.

Error message

Product {command.ProductId} not found.

What it means

UpdateProductCommandHandler.Handle loads the product by ProductId and throws NotFoundException when FirstOrDefaultAsync finds nothing, mapping the miss to a 404 at the API layer. Same contract as other Catalog handlers: unknown aggregate id in a command = NotFound.

Solutions

  1. Confirm the product id via the GET product endpoint before updating.
  2. Refresh the products list — the row may have been deleted concurrently.
  3. Verify the request targets the right tenant; tenant filters hide other tenants' rows.
  4. Fix client code that may be sending undefined/null mapped to a wrong Guid.

Example fix

// before
var product = await db.Products.FindAsync(productId); // may be null → later NRE
await mediator.Send(new UpdateProductCommand(productId, body));
// after
if (!await db.Products.AnyAsync(p => p.Id == productId, ct))
    return Results.NotFound();
await mediator.Send(new UpdateProductCommand(productId, body), ct);
Defensive patterns

Strategy: validation

Validate before calling

bool exists = await dbContext.Products.AnyAsync(p => p.Id == command.ProductId, ct);
if (!exists) return Results.NotFound();

Type guard

var product = await dbContext.Products.FirstOrDefaultAsync(p => p.Id == id, ct);
if (product is null) return Results.NotFound();

Try / catch

try { await mediator.Send(updateCommand, ct); }
catch (NotFoundException) { return Results.NotFound($"Product {id} not found"); }

Prevention

When it happens

Trigger: UpdateProductCommand dispatched with a ProductId that doesn't exist in dbContext.Products (deleted product, wrong tenant, malformed id from the client).

Common situations: Editing a product that was deleted in another browser tab; stale list page submitting updates for removed rows; multitenant deployment where the id belongs to a different tenant.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/ddc4a40e4a280e97. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Catalog/Modules.Catalog/Features/v1/Products/UpdateProduct/UpdateProductCommandHandler.cs:20

using FSH.Framework.Core.Exceptions;
using FSH.Modules.Catalog.Contracts.v1.Products;
using FSH.Modules.Catalog.Data;
using Mediator;
using Microsoft.EntityFrameworkCore;

namespace FSH.Modules.Catalog.Features.v1.Products.UpdateProduct;

public sealed class UpdateProductCommandHandler(CatalogDbContext dbContext)
    : ICommandHandler<UpdateProductCommand, Guid>
{
    public async ValueTask<Guid> Handle(UpdateProductCommand command, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(command);

        var product = await dbContext.Products
            .FirstOrDefaultAsync(p => p.Id == command.ProductId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException($"Product {command.ProductId} not found.");

        if (product.BrandId != command.BrandId)
        {
            bool brandExists = await dbContext.Brands
                .AnyAsync(b => b.Id == command.BrandId, cancellationToken)
                .ConfigureAwait(false);
            if (!brandExists)
            {
                throw new NotFoundException($"Brand {command.BrandId} not found.");
            }
        }

        if (product.CategoryId != command.CategoryId)
        {
            bool categoryExists = await dbContext.Categories
                .AnyAsync(c => c.Id == command.CategoryId, cancellationToken)
                .ConfigureAwait(false);
            if (!categoryExists)

View on GitHub (pinned to 3f2959e683)