fullstackhero/dotnet-starter-kit · error · NotFoundException

Group with ID ' ' not found.

Error message

Group with ID '{id}' not found.

What it means

GetGroupAsync queries Groups (including GroupRoles) by Id and, when no row matches, throws this NotFoundException. It is the guard at the top of UpdateGroupCommandHandler.Handle — no update proceeds if the group ID does not exist in the database.

Solutions

  1. Verify the group ID is correct and exists: query the group list endpoint or the Groups table for that tenant before updating.
  2. Refresh the client's cached group list and remove entries for deleted groups.
  3. Confirm the request is hitting the correct tenant — tenant-scoped DbContext filters hide groups belonging to other tenants.

Example fix

// before
await mediator.Send(new UpdateGroupCommand { Id = idFromStaleCache, Name = newName });

// after
var group = await groupApi.GetByIdAsync(idFromStaleCache);
if (group is null)
{
    await groupListRefetch(); // drop stale entry
    return;
}
await mediator.Send(new UpdateGroupCommand { Id = group.Id, Name = newName });
Defensive patterns

Strategy: validation

Validate before calling

var exists = await groupApi.ExistsAsync(id); // or GetByIdAsync != null
if (!exists)
{
    await refreshGroupList(); // drop stale cache entry
    return;
}

Type guard

Group? tryGetGroup(IEnumerable<Group> groups, Guid id) => groups.FirstOrDefault(g => g.Id == id);

Try / catch

try
{
    await mediator.Send(new UpdateGroupCommand { Id = id, /* ... */ });
}
catch (NotFoundException)
{
    // group vanished (deleted or different tenant): refresh list and inform the user
    await refreshGroupList();
    notify("This group no longer exists.");
}

Prevention

When it happens

Trigger: Sending UpdateGroupCommand with an Id that does not exist in the Groups table (already deleted, wrong tenant, typo/GUID from another environment).

Common situations: Stale frontend cache showing a group that was deleted by another admin; copying an ID from logs of a different tenant (tenant isolation filters the row out); pasting a group ID from a dev database into a staging environment.

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/5dbc1cad46a1222c. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Groups/UpdateGroup/UpdateGroupCommandHandler.cs:72

            var memberIds = await _dbContext.UserGroups
                .Where(ug => ug.GroupId == command.Id)
                .Select(ug => ug.UserId)
                .ToListAsync(cancellationToken);
            foreach (var memberId in memberIds)
            {
                await _userPermissionService.InvalidatePermissionCacheAsync(memberId, cancellationToken).ConfigureAwait(false);
            }
        }

        return await BuildResponseAsync(group, newRoleIds, cancellationToken);
    }

    private async Task<Group> GetGroupAsync(Guid id, CancellationToken cancellationToken)
    {
        return await _dbContext.Groups
            .Include(g => g.GroupRoles)
            .FirstOrDefaultAsync(g => g.Id == id, cancellationToken)
            ?? throw new NotFoundException($"Group with ID '{id}' not found.");
    }

    private async Task ValidateUniqueNameAsync(Guid excludeId, string name, CancellationToken cancellationToken)
    {
        var nameExists = await _dbContext.Groups
            .AnyAsync(g => g.Name == name && g.Id != excludeId, cancellationToken);

        if (nameExists)
        {
            throw new CustomException($"Group with name '{name}' already exists.", (IEnumerable<string>?)null, System.Net.HttpStatusCode.Conflict);
        }
    }

    private async Task ValidateRoleIdsAsync(IReadOnlyList<string>? roleIds, CancellationToken cancellationToken)
    {
        if (roleIds is not { Count: > 0 })
        {
            return;

View on GitHub (pinned to 3f2959e683)