10

I'm designing the User Settings for my MVC application, and right now I have ~20 boolean settings that the user can toggle. Since every user will always have every setting, I was thinking about storing each setting as a boolean on the User table. Though this would become unwieldy as the application requirements grow.

First question - is there anything wrong with having a ton of columns on your table in this situation?

I then considered using Flags, and storing the settings as one bit each in an array:

[Flags]
public enum Settings
{
    WantsEmail = 1,
    WantsNotifications = 2,
    SharesProfile = 4,
    EatsLasagna = 8
}

And then each user will have a single "Settings" column in their User row, that stores a value of 2^20, if there are 20 settings.

I'd use this to guide my efforts: What does the [Flags] Enum Attribute mean in C#?

Is this better than the former approach? Any suggestions are welcome.

Community
  • 1
  • 1
SB2055
  • 10,654
  • 29
  • 87
  • 185

6 Answers6

15

It depends on what should be considered atomic from the data management perspective.

  • If you always search for, read and write all the settings together from/to the database, then the whole set of settings can be considered atomic and can be stored together in the database.
  • However, if you need to do any of those things on a subset of settings (e.g. set just one flag without modifying others), then they are not atomic (from the data management perspective), so storing them together in the same database field would violate the principle of atomicity and therefore the 1NF.

Please note that some DBMSes (such as MS SQL Server) are very efficient at storing Booleans (just one bit per Boolean field under ideal circumstances). Even those that are less than perfect will typically not spend more than one byte per Boolean (Oracle, I'm looking at you), which may become a problem only if you have millions or billions of users.

Branko Dimitrijevic
  • 47,349
  • 10
  • 80
  • 152
  • `Oracle, I'm looking at you`, now that's hilarious and ridiculous all at the same time! An entire byte for one bit? – Mike Perrenoud Dec 11 '13 at 13:11
  • @MichaelPerrenoud Oracle doesn't support the concept of "Boolean field", and [it looks like](http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:6263249199595) it never will, which is a mistake IMHO. Instead, you'd typically use something like: `FLAG CHAR(1) CHECK (FLAG IN ('Y', 'N'))`. – Branko Dimitrijevic Dec 11 '13 at 13:50
  • Ugh! Well +1 for this answer anyway my friend. And man am I glad I don't have to work with Oracle! – Mike Perrenoud Dec 11 '13 at 14:03
  • 1
    @MichaelPerrenoud Don't be too harsh on them - they got many things right, and I actually _prefer_ working with Oracle over other DBMSes. But every piece of software has its annoyances and that's just the fact of life... – Branko Dimitrijevic Dec 11 '13 at 14:08
  • That's true. I just have to work with other applications internally that use Oracle and it's slow, bloated, and limited. Every time I ask them to do something it's; uh, we can't do that. But maybe it's not a limitation of Oracle, but those doing the work or their software I suppose. – Mike Perrenoud Dec 11 '13 at 14:12
4

If you have only 20 values and there is no chance some day it will be 100 values - it is ok to use any of these (preferably the enum). But if there is a chance to get 100 values - you really need to build a key-value pairs table (there is a table Users, table Settings and table UsersSettings that maps one to another).

Aleksei Poliakov
  • 1,272
  • 1
  • 13
  • 25
  • This is probably the best approach to follow in given situation. To add on to this you may consider storing your property names in a single column master table (not multiple columns). So to add a new property all you need to do will be insert the new property name in the table. You could use Dictionary type for storing values for each user. – Shashank Chaturvedi Dec 11 '13 at 13:55
0

Yes, it is a better way to save and retrieve data since you don't have to change the database everytime a new setting come up. This means it will cost much less to make change to the system.

Just make sure to assign appropriate integer values (powers of 2) to your enum, C# won't do it automatically and it will not work as expected if you make it wrong.

0

I think there are two scenarios where this could become obstructive:

1 - Is there any scope for a future user setting which is not a boolean? Unless this is already handled separately, you would have to come up with a new way of storing this non-boolean setting, possibly ending up with two disparate storage containers for your settings - one for bool's and another for anything else. Your original question does just specify bools though, so I'll assume you have thought of this ;)

2 - Removal of settings could be an issue? If, two years down the line, users can no longer get notifications or eat lasagne, you are going to have to carefully manage modifications to this enumeration to ensure that your bit flags are backwards compatible when you remove items from it.

Historically, I have used a key-value pair 'User Settings' table for this. The schema basically consists of:

[Database Identity] (long / identity)
[UserId] (FK reference to the user table)
[SettingId] (Any identifier to the given setting - Datatype depends on identifier)
[SettingValue] (NVarChar(?) - string representation of setting value - field size depends on req's)

This is does not perform as well as bit flags, and requires some app-side parsing of the string values to re-hydrate the users settings, but it does give you an extensible solution which can:

1 - Pretty much handle any core data type for any given setting.
2 - Easily handle a differing number of settings per user.
3 - Can be sparsely populated with only the settings which differ from you application defaults.

I have used this successfully on several production applications to date, however if you are talking in the region of hundreds of thousands of users, this solution may not be suitable.

David McNee
  • 117
  • 2
  • 9
0

Answering your questions in the way you have asked them:

"is there anything wrong with having a ton of columns on your table in this situation?"

There is absolutely nothing wrong with storing the data in this way. You can have many more than you are proposing here without any trouble.

"Is this better than the former approach?"

This is how I do these things but it is because it suits the way I manage this sort of data and not a case of being better or right. There is an argument that has been put forward by Branko that this is not 1NF and he is completely correct, but even proponents of normalisation accept that there are times that normalisation isn't always the best way to go and sometimes you need to denormalise to get better performance.

Pros of having seperate BITs: You can reference each property (column) in SQL queries and be sure that you are selecting the correct bit otherwise you need to keep referring to your enumerator in your application code to know what each bit means in the SQL Table.

You can automatically populate an entity object easier.

Reports etc can use these settings without having to do any computation to get the value of seperate settings.

Correct normalisation.

Pros of having it all in one column (flags) (or more than one if you need more storage):

You can read and write the settings as a group much easier and quicker.

Your SQL queries that manipulate or read the settings will be quicker to write.

You can itterate the settings collection with less code.

writting and maintaining your own entity object is easier.

Less memory usage (but to be fair this will be low regardless of the approach you use).

Much smaller payload if you wanted to put it into the session object.

The only consideration I would say is that once your total number of flags exceed the total memory space you can get from one variable (2^64) you lose some of the pros for using flags as your data then has to be spread across multiple columns regardless of the approach you use.

Craig Moore
  • 1,061
  • 1
  • 6
  • 14
0

Go for it.
Keep in mind 63 flags then you go for Extend so what?
I did this in project Oracle 10g 3.8m rows works great performance in SQL side
MS SQL 15.2M rows works great performance in SQL side
I didn't test it in Azure SQL, but normally in SQL you have WHERE userID=XXX then in select you do the MASK to find something like INROLE...

it is hard for support, but if you do additional effort and write some code to convert any number to nice explain.... USE Descriptions as well

keep in mind in SQL side no index help you do full scan...but for user settings permissions...No problem, you do your reports offline...

In C# side I do descriptions and used it for ...building dropdowns dynamically...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel;


namespace common
{


public static class EnumHelper
{

    public enum Speed  //example
    {
        [Description("5 metters per second")]
        Five = 5,
        [Description("10 metters per second")]
        Ten = 10,
        [Description("15 metters per second")]
        Fifteen = 15,
        [Description("20 metters per second")]
        Twenty = 20,
        //[Description("25 metters per second")]
        TwentyFive = 25,
       [Description("30 metters per second")]
        Thirty = 30
    }

    /// <summary>
    /// get the string value of Enum Attribute
    /// </summary>
    /// <param name="EnumConstant"></param>
    /// <returns>
    /// string enumDesctiption = EnumHelper.EnumDescription(EnumHelper.Speed.Thirty);
    ///  enumDesctiption = EnumHelper.EnumDescription(DayOfWeek.Monday); when there is no desc returns as string the ENUM property
    /// </returns>
    public static string EnumDescription(Enum EnumConstant)
    {
        System.Reflection.FieldInfo fi = EnumConstant.GetType().GetField(EnumConstant.ToString());
        DescriptionAttribute[] aattr = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (aattr.Length > 0)
        {
          return aattr[0].Description;
        }
        else
        {
            return EnumConstant.ToString();
        }
    }

   }

}
Valentin Petkov
  • 1,292
  • 15
  • 22