Manikandan — Manikandan
Updated on 6 min read Manikandan Design Patterns

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.

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 = 5
INSERT INTO Users (UserName, RoleMask) VALUES ('ravi', 5);
-- Admin + SuperAdmin + RootAdmin = 2 + 8 + 16 = 26
INSERT INTO Users (UserName, RoleMask) VALUES ('mohan', 26);
-- Viewer only = 0
INSERT INTO Users (UserName, RoleMask) VALUES ('guest', 0);

SQL Server Bitwise Queries

Check whether a role exists using &:

-- Users who have Manager (4)
SELECT *
FROM Users
WHERE (RoleMask & 4) = 4;

Add a role using |:

-- Add Admin (2)
UPDATE Users
SET RoleMask = RoleMask | 2
WHERE Id = 1;

Remove a role using & with bitwise complement:

-- Remove Editor (1)
UPDATE Users
SET RoleMask = RoleMask & ~1
WHERE 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; // true
bool isAdmin = (roles & Roles.Admin) == Roles.Admin; // false
// ! is boolean negation, often used with role checks
if (!RoleService.HasRole(roles, Roles.Admin))
{
Console.WriteLine("Admin access denied");
}
roles = RoleService.AddRole(roles, Roles.RootAdmin); // now 21
roles = RoleService.RemoveRole(roles, Roles.Editor); // now 20

Angular 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) = 8

In C#,

(roles & Roles.SuperAdmin) == Roles.SuperAdmin

Why 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 (roleMask as 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 INT and contains bitmask values.
  • Verify .NET enum 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 is 0.

This setup gives you a compact and high-performance role model when you intentionally want multi-role storage in a single SQL Server column.

Share:
Back to Blog

Related Posts

View All Posts »