I am Joshua Poehls. Say hello Archives (not so) silent thoughts

Using enum flags to model user roles in C#, SQL, and Go

Using an enum flags property to hold users' roles is just one trick I’ve learned from @SeiginoRaikou, one of my co-geniuses at InterWorks.

Historically I have always modeled user roles with a Role table and a many-to-many relationship with my User table.

    +-------------------+
    | User              |
    |-------------------|
    | UserId   : int    +-------------+---------------+
    | UserName : varchar|             | UserRole      |
    +-------------------+             |---------------|
                                      + UserId : int  |
    +-------------------+             + RoleId : int  |
    | Role              |             |---------------+
    |-------------------|             |
    | RoleId   : int    +-------------+
    | RoleName : varchar|
    +-------------------+

Compared to a flags enum approach this is downright wasteful. With flags, there is only ever one column to work with and it is an integer. No second table. No joins or second query.

    +--------------------+
    | User               |
    |--------------------|
    | UserId   : int     |
    | UserName : varchar |
    | Roles    : int     |
    +--------------------+

Modeling this in C# is easy.

[Flags] // Values must always be powers of two
enum UserRole : int {
  Employee = 1,
  Manager = 2,
  Admin = 4
}

class User {
  public UserRole Role { get; set; }
 
  public void AddRole(UserRole roleToAdd) {
    this.Role |= roleToAdd;
  }
 
  public void RemoveRole(UserRole roleToRemove) {
    this.Role &= ~roleToRemove;
  }
  
  public bool IsInRole(UserRole roleToCheck) {
    return this.Role.HasFlag(roleToCheck);
  }
}

Flag enumerations are just as accessible in SQL.

-- Add users to the Admin role
UPDATE [User] SET [Roles] = ([Roles] | 4)

-- Remove users from the Admin role
UPDATE [User] SET [Roles] = ([Roles] - ([Roles] & 4))

-- Find all users in the Admin role
SELECT * FROM [User] WHERE [Roles] & 4 <> 0

Go forth and simplify, my friends.

Update for Go

Bonus! Here is how you might model this in Go.

type User struct {
    Roles    Roles
}

type Roles uint

const (
    // Roles must be powers of two.
    RoleEmployee Roles = 1
    RoleManager        = 2
    RoleAdmin          = 4
)

func (user *User) AddRole(role Roles) {
  user.Roles |= role
}

func (user *User) RemoveRole(role Roles) {
  user.Roles &^= role
}

func (user *User) IsInRole(role Roles) bool {
  return (user.Roles & role) != 0
}

An even cleaner way would be to use iota to have Go automatically assign the flag values in powers of two. You can see this method used by Go’s own text/tabwriter package.

const (
  // Roles must be powers of two.
  RoleEmployee Roles = 1 << iota
  RoleManager
  RoleAdmin
)
⦿