Bitwise Flags Enum Pattern in .NET Core and Angular
Store multiple roles in one SQL Server column using bitwise flags, then read and manage them cleanly in .NET Core and Angular.

Intro
If you want to store multiple roles like Admin, Editor, Viewer, Manager, SuperAdmin, and RootAdmin in one SQL Server column, bitwise flags are a practical approach.
This pattern lets you keep a single integer column in the database while still supporting many role combinations in .NET Core and Angular.
Problem Statement
You want one database column for roles, but a user can have multiple roles at the same time.
Example:
- User A: Editor + Manager
- User B: Admin + SuperAdmin + RootAdmin
- User C: Viewer only
Instead of storing comma-separated text or a separate mapping table for this scenario, you can store one integer bitmask value.
Why Bitwise Flags Help
Each role is assigned a bit value. Combined roles are represented by adding those values.
For example:
- Editor = 1
- Admin = 2
- Manager = 4
- SuperAdmin = 8
- RootAdmin = 16
If a user is Editor + Manager + RootAdmin, the stored value is:
$$ 1 + 4 + 16 = 21 $$
Role Enum Definition
You asked for this enum shape:
[Flags]public enum Roles{ Viewer = 0, Editor = 1, Admin = 2, Manager = 4, SuperAdmin = 8, RootAdmin = 16}How a Single SQL Column Stores Multiple Roles
Use one INT column, for example RoleMask.
CREATE TABLE Users( Id INT IDENTITY(1,1) PRIMARY KEY, UserName NVARCHAR(120) NOT NULL, RoleMask INT NOT NULL);Sample inserts:
-- Editor + Manager = 1 + 4 = 5INSERT INTO Users (UserName, RoleMask) VALUES ('ravi', 5);
-- Admin + SuperAdmin + RootAdmin = 2 + 8 + 16 = 26INSERT INTO Users (UserName, RoleMask) VALUES ('mohan', 26);
-- Viewer only = 0INSERT INTO Users (UserName, RoleMask) VALUES ('guest', 0);SQL Server Bitwise Queries
Check whether a role exists using &:
-- Users who have Manager (4)SELECT *FROM UsersWHERE (RoleMask & 4) = 4;Add a role using |:
-- Add Admin (2)UPDATE UsersSET RoleMask = RoleMask | 2WHERE Id = 1;Remove a role using & with bitwise complement:
-- Remove Editor (1)UPDATE UsersSET RoleMask = RoleMask & ~1WHERE Id = 1;.NET Core Implementation
Model:
public sealed class AppUser{ public int Id { get; set; } public string UserName { get; set; } = string.Empty; public Roles RoleMask { get; set; }}Service methods:
public static class RoleService{ public static bool HasRole(Roles current, Roles role) => (current & role) == role;
public static Roles AddRole(Roles current, Roles role) => current | role;
public static Roles RemoveRole(Roles current, Roles role) => current & ~role;}Using &, |, and ! in C Sharp
var roles = Roles.Editor | Roles.Manager; // 1 | 4 => 5
bool isManager = (roles & Roles.Manager) == Roles.Manager; // truebool isAdmin = (roles & Roles.Admin) == Roles.Admin; // false
// ! is boolean negation, often used with role checksif (!RoleService.HasRole(roles, Roles.Admin)){ Console.WriteLine("Admin access denied");}
roles = RoleService.AddRole(roles, Roles.RootAdmin); // now 21roles = RoleService.RemoveRole(roles, Roles.Editor); // now 20Angular UI Implementation
export enum Roles { Viewer = 0, Editor = 1, Admin = 2, Manager = 4, SuperAdmin = 8, RootAdmin = 16,}
export function hasRole(current: number, role: Roles): boolean { return (current & role) === role;}
export function addRole(current: number, role: Roles): number { return current | role;}
export function removeRole(current: number, role: Roles): number { return current & ~role;}Ternary Examples in Angular
Using ternary with &, |, and !:
const roleMask = Roles.Editor | Roles.Manager; // 5
const canEdit = (roleMask & Roles.Editor) === Roles.Editor ? 'Yes' : 'No';const showAdminPanel = !hasRole(roleMask, Roles.Admin) ? false : true;const badge = hasRole(roleMask, Roles.RootAdmin) ? 'ROOT' : 'STANDARD';Template usage:
<button [disabled]="!hasRole(user.roleMask, Roles.Editor)">Edit</button><div>{{ hasRole(user.roleMask, Roles.Manager) ? 'Manager Access' : 'User Access' }}</div>Important Note About Viewer = 0
Viewer = 0 means “no bits set”. So it is a special case:
- You cannot detect Viewer using
(mask & 0) == 0, because that expression is always true. - Treat Viewer as default role when no other role bits are set.
public static bool IsViewerOnly(Roles current) => current == Roles.Viewer;If you want Viewer to behave like other combinable flags, use Viewer = 1 and shift other values. But if your requirement is fixed as Viewer = 0, use explicit equality checks.
Detailed Advantages
1) Compact storage in SQL Server
You store many role combinations in one INT column instead of multiple boolean columns or a separate join table.
Why it helps:
- smaller row footprint for simple role models
- easier payload shape in read APIs
- straightforward migration when starting from a single-role schema
2) Fast permission checks
Bitwise checks are efficient and predictable: In SQL,
WHERE (RoleMask & 8) = 8In C#,
(roles & Roles.SuperAdmin) == Roles.SuperAdminWhy it helps:
- low overhead checks in SQL and application code
- clean fit for high-frequency authorization checks
3) Easy role composition
You can add roles with | and remove roles with & ~ without complex branching logic.
Why it helps:
- role updates stay concise
- command handlers/services stay simple
- supports “multi-role in one update” use cases
4) Consistent representation across layers
The same numeric mask can be used in SQL Server, .NET Core, and Angular.
Why it helps:
- no transformation-heavy mapping layer for roles
- easier API contracts (
roleMaskas number) - fewer serialization surprises
5) Good fit for fixed, well-known roles
When role definitions are stable (Viewer, Editor, Admin, Manager, SuperAdmin, RootAdmin), flags are practical and maintainable.
Why it helps:
- clear boundaries for authorization rules
- little schema churn over time
Detailed Disadvantages
1) Readability is lower for humans
A stored value like 26 does not immediately communicate which roles are present.
Impact:
- debugging and support investigations take longer
- ad-hoc SQL analysis is less intuitive
2) Viewer = 0 is a special edge case
Because Viewer is 0, generic bitwise checks do not work for viewer detection.
Impact:
- must use explicit equality checks (
mask == Roles.Viewer) - easy source of bugs if teams assume every role is a standard flag
3) Harder DB-level integrity guarantees
A single integer column cannot express role-level referential integrity like normalized user-role tables.
Impact:
- invalid masks can be inserted unless you add CHECK constraints or service-level validation
- governance can weaken in large teams
4) Reporting and analytics are less friendly
BI/reporting tools usually prefer row-based role assignments.
Impact:
- grouping users by role combinations can become awkward
- business reports often need extra decoding logic
5) Risk of accidental privilege escalation
One incorrect | operation can silently grant an additional role.
Impact:
- security incidents can come from small coding mistakes
- review/testing standards must be strict around role updates
6) Cross-layer enum drift risk
If SQL constants, .NET enum values, and Angular enum values are not synchronized, permission checks become inconsistent.
Impact:
- UI may hide/show wrong controls
- API may authorize differently than expected
7) Limited flexibility for dynamic role systems
Flags are best for fixed roles, not runtime-created custom roles.
Impact:
- evolving to tenant-specific roles is harder
- migration to a normalized permission model may be required later
8) Poor expressiveness for role metadata
Bitmasks represent only yes/no membership.
Impact:
- cannot naturally encode role scope, expiry, approval source, or assignment reason
- advanced auditing needs additional tables
Quick Validation Checklist
- Confirm SQL column type is
INTand contains bitmask values. - Verify
.NETenum values exactly match Angular enum values. - Test add, remove, and check operations with
|,&, and~. - Use
!for boolean negation around role checks. - For Viewer, test using equality (
mask == Roles.Viewer) because Viewer is0.
This setup gives you a compact and high-performance role model when you intentionally want multi-role storage in a single SQL Server column.




