Store user options using enumeration, bitwise operators, and EF Core

At times you need to store a set of options, settings, or choices for a user. A common approach is to create three tables - Users, Options, and UserOptions - to store the data involved. However, you can also use an alternate approach that involves enumerations, bitwise operators, and EF core value converters. To that end this article discusses such an approach with an example.

Example

Let's assume that you are developing an ASP.NET Core web application that sends notifications to the end users. There are several types of notifications supported by the application - Email, SMS, Popup, Calendar, and Private Message. The application allows a user to decide how to receive the notifications. That means a user can have one or more ways of receiving the notifications. For example, a user can opt to receive only Email notifications whereas some other user may opt for Email and SMS based notifications. You would like to capture these user notification options / settings and store them in a SQL database.

A common approach to this problem is to create three tables - Users, Options, and UserOptions. This approach is great when there is unlimited number of options and there could be any combination of users and options. However, if you have only limited number of options as in the example scenario described above, you may want to avoid Options and UserOptions tables altogether.

Another approach could be to create as many columns in the Users table as the number of notification Options and then assign True / False value to each column. However, this will lead to a lot more columns to the Users table. If you need to add a notification option in the future you will need to change database schema as well as C# code.

Yet another approach is to use C# enumerations, bitwise operators, and EF Core value converters. This approach works well when you have a small set of options that are not going to change frequently. The remainder of this article discusses this approach to accomplish the task.

Now that you have understood the example scenario and what we are trying to accomplish, let's build an application that will demonstrate what we just discussed.

Begin by creating and configuring a new ASP.NET Core web application.

Create the NotificationOptions enumeration

Now add a class named NotificationOptions in the Models folder. The NotificationOptions class holds the possible notification options and is shown below :

[Flags]
public enum NotificationOptions
{
    Email = 1,
    SMS = 2,
    Popup = 4,
    Calendar = 8,
    PrivateMessage = 16
}

Notice a couple of things about this class. Firstly, it is decorated with [Flags] attribute. The [Flags] attribute is used to indicate that the enumeration values can be used with bitwise operators such as | and &. Secondly, the enumeration values have been explicitly assigned to power of 2 (1, 2, 4, 8, 16). This is required for the proper functioning of bitwise operations.

Bitwise operations on NotificationOptions

Before we go ahead and see how to store notification options using Entity Framework Core, it's worthwhile to see how bitwise operators can be used with enumeration flags we just created.

Suppose we want to indicate that a user has two notification options - Email and SMS. This is how you can represent them in your C# code :

NotificationOptions options = 
NotificationOptions.Email | NotificationOptions.SMS;

Notice how the bitwise OR operator ( | ) is used to combine enumeration Email and SMS flags. This way you can combine as many number of flags as you want and store the result in options variable.

So far so good. But how to check whether a particular option exists in the options thus formed? This can be done using bitwise AND ( & ) operator. The following code shows how :

if((options & NotificationOptions.Email) !=0)
{
    //email option exists!
}
if ((options & NotificationOptions.SMS) != 0)
{
    //SMS option exists!
}

If we want to check whether Email notification is part of the selected options, we use & operator and figure that out. Similarly you can check for other notification options.

There is an alternate way to the above task. Instead of using the bitwise AND operator you can use HasFlag() method of an enumeration as follows :

if(options.HasFlag(NotificationOptions.Email))
{
    //email option exists!
}
if (options.HasFlag(NotificationOptions.SMS))
{
    //SMS option exists!
}

The HasFlag() method returns true if a flag is a part of the value being tested, otherwise it returns false.

Convert enumeration values to integers in EF Core

The above code works with NotificationOptions enumeration. This is quite alright in your C# code. But how would you save these enumeration values to the database? Luckily, Entity Framework Core provides an easy way to convert enumeration values to numbers (or strings) so that they can be easily stored to a database. This conversion needs to be enabled for the required entities. Let's see how.

Consider the following entity class named UserNotifications :

public class UserNotifications
{
    public int Id { get; set; }
    public string Name { get; set; }
    public NotificationOptions Options { get; set; }
}

The UserNotifications class stores all the notification options for a user and has three properties namely Id, Name, and Options. The Options property is assigned using the bitwise OR operation as discussed earlier and can have one or more notification options.

The following figure shows the schema of UserNotifications table from a SQL Server database.

As you can see the Options column is an integer column. That means you will need to convert to and from NotificationOptions to integer. Although by default EF Core does that correctly in most of the cases, it's good to ensure that appropriate values are being transferred from our C# code also. This is accomplished using what is known as Value Converter.

The following code shows the AppDbContext that makes use of EnumToNumberConverter inbuilt value converter.

public class AppDbContext : DbContext
{
    protected override void OnConfiguring
(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(
"data source=.;initial catalog=MyDatabase;integrated 
security=true");
    }

    protected override void OnModelCreating
(ModelBuilder modelBuilder)
    {
        EnumToNumberConverter<NotificationOptions,int> 
converter = new EnumToNumberConverter<NotificationOptions,int>();

        modelBuilder.Entity<UserNotifications>()
                    .Property(e => e.Options)
                    .HasConversion(converter);
    }

    public DbSet<UserNotifications> 
UserNotifications { get; set; }
}

Notice a few things about the AppDbContext class.

It sets the database connection string in the OnConfiguring() overridden method. You can also pick this value from a configuration file. More interesting is the OnModelCreating() overridden method.

Inside, we create an instance of EnumToNumberConverter class. The EnumToNumberConverter converts an enumeration value to a number and vice a versa. In this case we want to convert between NotificationOptions and an integer and hence we pass the generic types as shown above.

The code then uses HasConversion() method and passes the EnumToNumberConverter object to it. This way EF Core knows how to convert the values assigned to the Options property.

Ok. Now we have all the pieces ready - NotificationOptions enumeration, UserNotifications table, UserNotifications entity class, and AppDbContext class.

Save notification options to the database

Let's write some code that will add a few records to the UserNotifications table. The following code from Index() action shows how that can be done :

using (AppDbContext db = new AppDbContext())
{
    UserNotifications user1 = new UserNotifications();
    user1.Name = "Nancy Davolio";
    user1.Options = NotificationOptions.Email | 
NotificationOptions.SMS;

    UserNotifications user2 = new UserNotifications();
    user2.Name = "Andrew Fuller";
    user2.Options = NotificationOptions.Popup | 
NotificationOptions.SMS | NotificationOptions.Calendar;

    UserNotifications user3 = new UserNotifications();
    user3.Name = "Janet Leverling";
    user3.Options = NotificationOptions.Email;

    db.UserNotifications.Add(user1);
    db.UserNotifications.Add(user2);
    db.UserNotifications.Add(user3);
    db.SaveChanges();
}

The above code creates three UserNotifications entities and assigns their Name and Options properties. Notice how multiple notification options have been assigned using the OR operator as discussed earlier. The Add() method adds these entities to the UserNotifications DbSet and SaveChanges() saves them to the UserNotifications table.

Retrieve notification options from the database

Now let's read the saved data and conform the working of enumerations and bitwise AND operator.

Add a new view model class to the Models folder named UserNotificationSummary.

public class UserNotificationSummary
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool Email { get; set; }
    public bool SMS { get; set; }
    public bool Popup { get; set; }
    public bool Calendar { get; set; }
    public bool PrivateMessage { get; set; }
}

The UserNotificationSummary class is intended to hold all the notification options for a user in Boolean properties.

Now add the following loop below the SaveChanges() call we wrote earlier :

List<UserNotifications> data = 
db.UserNotifications.ToList();

List<UserNotificationSummary> model = 
new List<UserNotificationSummary>();

foreach(var item in data)
{
    UserNotificationSummary summary = 
new UserNotificationSummary();

    summary.Id = item.Id;
    summary.Name = item.Name;
    if((item.Options & NotificationOptions.Email) !=0)
    {
        summary.Email = true;
    }
    if ((item.Options & NotificationOptions.SMS) != 0)
    {
        summary.SMS = true;
    }
    if ((item.Options & NotificationOptions.Popup) != 0)
    {
        summary.Popup = true;
    }
    if ((item.Options & NotificationOptions.Calendar) != 0)
    {
        summary.Calendar = true;
    }
    if ((item.Options & NotificationOptions.PrivateMessage) != 0)
    {
        summary.PrivateMessage = true;
    }
    model.Add(summary);
}

return View(model);

The code retrieves all the records from the UserNotifications table. It also creates an empty List of UserNotificationSummary to hold  the resultant data. A foreach iterates through the data and checks the notification options for a user. This is done using a series of If statements that assign the Boolean view model properties as per the outcome. The List of UserNotificationSummary is then passed to the Index view.

The Index view simply outputs the model data as shown below :

@model List<UserNotificationSummary>


<h1>Summary</h1>

<table border="1" cellpadding="10">
    <tr>
        <th>Id</th>
        <th>Name</th>
        <th>Email</th>
        <th>SMS</th>
        <th>Popup</th>
        <th>Calendar</th>
        <th>Private Message</th>
    </tr>
    @foreach (var item in Model)
    {
        <tr>
            <td>@item.Id</td>
            <td>@item.Name</td>
            <td>@item.Email</td>
            <td>@item.SMS</td>
            <td>@item.Popup</td>
            <td>@item.Calendar</td>
            <td>@item.PrivateMessage</td>
        </tr>
    }
</table>

The following figure shows a sample run of the application :

That's it for now! Keep coding!!


Bipin Joshi is a software consultant, trainer, author, yoga mentor, and spiritual guide having 24+ years of experience in software development, consulting, and training. He conducts instructor-led online training courses in ASP.NET Core, ASP.NET MVC, and Design Patterns for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get article updates : Facebook  Twitter  LinkedIn

Posted On : 10 December 2018


Tags : ASP.NET ASP.NET Core Data Access MVC .NET Framework C#


Subscribe to our newsletter

Get monthly email updates about new articles, tutorials, code samples, and how-tos getting added to our knowledge base.

  

Receive Weekly Updates