fullstackhero/dotnet-starter-kit · error · NotFoundException

Brand not found.

Error message

Brand {command.BrandId} not found.

What it means

RestoreBrandCommandHandler loads the brand with only the SoftDelete filter disabled, so only non-deleted rows (still tenant-scoped) are visible; it throws NotFoundException when nothing matches, mapped to HTTP 404. This means restoring an already-active brand, an unknown ID, or a foreign-tenant brand all produce this error — you cannot 'restore' something that is not currently soft-deleted.

Solutions

  1. Check whether the brand is already active via GET /brands/v1/{id} — if so, no restore is needed
  2. Verify the BrandId is correct and belongs to the current tenant
  3. If the row was hard-deleted, re-create the brand instead of restoring

Example fix

// before
await mediator.Send(new RestoreBrandCommand(id)); // may already be active
// after
var existing = await mediator.Send(new GetBrandByIdQuery(id)); // 200 => nothing to restore
if (existing is null) await mediator.Send(new RestoreBrandCommand(id));
Defensive patterns

Strategy: validation

Validate before calling

bool isActive = await apiClient.GET<bool>($"/brands/v1/{id}/exists");
if (isActive) return; // nothing to restore

Try / catch

try { await mediator.Send(new RestoreBrandCommand(id)); }
catch (NotFoundException) { /* already active or never existed */ }

Prevention

When it happens

Trigger: Send/RestoreBrand command with a BrandId that is not present as a soft-deleted row: already active, never existed, hard-deleted, or another tenant's brand.

Common situations: Double-invoking restore after the first call succeeded; guessing at IDs to recover data; pointing at a brand in the wrong 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/84fa2a01cd9633fa. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Catalog/Modules.Catalog/Features/v1/Brands/RestoreBrand/RestoreBrandCommandHandler.cs:23

using Mediator;
using Microsoft.EntityFrameworkCore;

namespace FSH.Modules.Catalog.Features.v1.Brands.RestoreBrand;

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

        // Disable only the SoftDelete filter so we can load a deleted row;
        // tenant scoping stays in force, so cross-tenant restores cannot leak.
        var brand = await dbContext.Brands
            .IgnoreQueryFilters([QueryFilters.SoftDelete])
            .FirstOrDefaultAsync(b => b.Id == command.BrandId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException($"Brand {command.BrandId} not found.");

        brand.Restore();
        await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
        return brand.Id;
    }
}

View on GitHub (pinned to 3f2959e683)