{"record":{"id":"2958664438360976","repo":"fullstackhero/dotnet-starter-kit","slug":"roles-not-found-string-join-invalidroleids-295866","errorCode":null,"errorMessage":"Roles not found: {string.Join(\", \", invalidRoleIds)}","messagePattern":"Roles not found: (.+?)","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Features/v1/Groups/UpdateGroup/UpdateGroupCommandHandler.cs","lineNumber":101,"sourceCode":"        }\n    }\n\n    private async Task ValidateRoleIdsAsync(IReadOnlyList<string>? roleIds, CancellationToken cancellationToken)\n    {\n        if (roleIds is not { Count: > 0 })\n        {\n            return;\n        }\n\n        var existingRoleIds = await _dbContext.Roles\n            .Where(r => roleIds.Contains(r.Id))\n            .Select(r => r.Id)\n            .ToListAsync(cancellationToken);\n\n        var invalidRoleIds = roleIds.Except(existingRoleIds).ToList();\n        if (invalidRoleIds.Count > 0)\n        {\n            throw new NotFoundException($\"Roles not found: {string.Join(\", \", invalidRoleIds)}\");\n        }\n    }\n\n    private static HashSet<string> UpdateRoleAssignments(Group group, IReadOnlyList<string>? roleIds)\n    {\n        var currentRoleIds = group.GroupRoles.Select(gr => gr.RoleId).ToHashSet();\n        var newRoleIds = roleIds?.ToHashSet() ?? [];\n\n        var rolesToRemove = group.GroupRoles.Where(gr => !newRoleIds.Contains(gr.RoleId)).ToList();\n        foreach (var role in rolesToRemove)\n        {\n            group.GroupRoles.Remove(role);\n        }\n\n        foreach (var roleId in newRoleIds.Where(id => !currentRoleIds.Contains(id)))\n        {\n            group.GroupRoles.Add(GroupRole.Create(group.Id, roleId));\n        }","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Features/v1/Groups/UpdateGroup/UpdateGroupCommandHandler.cs#L83-L119","documentation":"ValidateRoleIdsAsync loads the IDs of roles matching the requested roleIds and throws NotFoundException listing every requested ID that does not exist. UpdateGroup therefore refuses partial role updates: all supplied RoleIds must resolve to real roles.","triggerScenarios":"Sending UpdateGroupCommand with RoleIds containing one or more IDs that do not exist in the Roles table (deleted roles, IDs from another tenant/environment, malformed GUID strings that parse but match nothing).","commonSituations":"Frontend caches role options and a role was deleted meanwhile; copying role assignments from a template created in another tenant; importing group configuration JSON from a different environment.","solutions":["Remove or correct the invalid role IDs reported in the message before resending the command.","Refresh the role list from the API and rebuild the RoleIds selection from current data.","Create the missing roles first if they are genuinely required, then retry the group update."],"exampleFix":"// before\nvar cmd = new UpdateGroupCommand { Id = groupId, RoleIds = [\"role-1\", \"role-deleted\"] };\n\n// after\nvar roles = await roleApi.ListAsync();\nvar validIds = cmd.RoleIds.Where(id => roles.Any(r => r.Id == id)).ToList();\nif (validIds.Count != cmd.RoleIds.Count)\n    showWarning(\"Some roles no longer exist and were removed.\");\nawait mediator.Send(new UpdateGroupCommand { Id = groupId, RoleIds = validIds });","handlingStrategy":"validation","validationCode":"var roles = await roleApi.ListAsync();\nvar roleIds = new HashSet<string>(selectedRoleIds);\nif (!roleIds.IsSubsetOf(roles.Select(r => r.Id)))\n{\n    showValidationError(\"Some selected roles no longer exist. Refresh and reselect.\");\n}","typeGuard":"string[] validRoleIds(IEnumerable<string> requested, IEnumerable<Role> existing) =>\n    requested.Where(id => existing.Any(r => r.Id == id)).ToArray();","tryCatchPattern":"try\n{\n    await mediator.Send(new UpdateGroupCommand { Id = id, RoleIds = roleIds });\n}\ncatch (NotFoundException ex)\n{\n    // message lists the missing role IDs\n    await refreshRoleList();\n    notify($\"Roles not found — reselect: {ex.Message}\");\n}","preventionTips":["Load RoleIds selection options live from the roles API, not from a static cache.","Listen for role-deleted events/queries invalidation and prune them from open forms.","Never copy role IDs across tenants or environments."],"tags":["identity","groups","roles","not-found"],"backgroundTag":"record-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"}