存储用户设置 - 使用“Flags”或“Bits”而不是一堆bool有什么问题?

时间:2013-12-01 15:56:29

标签: c# asp.net asp.net-mvc database-design database-schema

我正在设计我的MVC应用程序的用户设置,现在我有~20个布尔设置,用户可以切换。由于每个用户总是会进行所有设置,因此我考虑将每个设置存储为User表中的布尔值。虽然随着应用程序需求的增长,这将变得难以处理。

第一个问题 - 在这种情况下,你的桌子上有大量的栏目是否有任何问题?

然后我考虑使用Flags,并将设置存储为数组中的一位:

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

然后每个用户在其用户行中将有一个“设置”列,如果有20个设置,则存储值为2 ^ 20。

我会用它来指导我的工作:What does the [Flags] Enum Attribute mean in C#?

这比前一种做法好吗?欢迎任何建议。

6 个答案:

答案 0 :(得分:15)

从数据管理的角度来看,这取决于应该被视为 atomic

  • 如果您总是从/向数据库一起搜索,读取和写入所有设置,那么整套设置可以被视为原子设置,并且可以一起存储在数据库中。
  • 但是,如果你需要在子集设置上做任何这些事情(例如只设置一个标志而不修改其他标志),那么它们不是原子的(从数据管理的角度来看),所以将它们存储在同一个数据库字段中会违反atomicity的原则,因此违反了1NF。

请注意,某些DBMS(例如MS SQL Server)在存储布尔值时非常有效(在理想情况下,每个布尔字段只有一位)。即使那些不完美的那些通常也不会花费超过一个字节的每个布尔值(甲骨文,我正在看着你),这可能只会在你有数百万或数十亿用户的情况下成为一个问题。

答案 1 :(得分:4)

如果你只有20个值并且某天没有机会它将是100个值 - 可以使用其中任何一个(最好是枚举)。但是如果有机会获得100个值 - 你真的需要构建一个键值对表(有一个表,用户,表设置和用户设置表映射到另一个)。

答案 2 :(得分:0)

是的,这是一种更好的保存和检索数据的方法,因为每次出现新设置时都不必更改数据库。这意味着对系统进行更改将花费更少。

只需确保为你的枚举分配适当的整数值(2的幂),C#不会自动执行,如果你做错了它将不会按预期工作。

答案 3 :(得分:0)

我认为有两种情况会导致阻碍:

1 - 未来的用户设置是否有任何范围不是布尔值?除非已经单独处理,否则你必须想出一种存储这种非布尔设置的新方法,最终可能会为你的设置设置两个不同的存储容器 - 一个用于bool,另一个用于其他任何东西。你的原始问题只是指定bool,所以我假设你已经想到了这个;)

2 - 删除设置可能是个问题?如果在两年后,用户再也无法收到通知或吃烤宽面条,那么您将不得不仔细管理对此枚举的修改,以确保当您从中删除项目时,您的位标记是向后兼容的。

从历史上看,我已经使用了一个键值对“用户设置”表。架构基本上包括:

[数据库身份] (长/身份)
[UserId] (FK引用用户表)
[SettingId] (给定设置的任何标识符 - 数据类型取决于标识符)
[SettingValue] (NVarChar(?) - 设置值的字符串表示 - 字段大小取决于req's)

这不会像位标志一样好,并且需要对字符串值进行一些应用程序端解析以重新保持用户设置,但它确实为您提供了一个可扩展的解决方案:

1 - 几乎可以处理任何给定设置的核心数据类型。
2 - 轻松处理每个用户不同数量的设置 3 - 可以使用与应用程序默认值不同的设置进行稀疏填充。

到目前为止,我已经成功地在几个生产应用程序中使用了它,但是如果您在数十万用户的区域内进行讨论,则此解决方案可能不适用。

答案 4 :(得分:0)

以你问他们的方式回答你的问题:

“在这种情况下你的桌子上有大量的栏目有什么不妥吗?”

以这种方式存储数据绝对没有错。你可以拥有比你提出的更多的东西,没有任何麻烦。

“这比前一种做法好吗?”

这就是我做这些事情的方式,但这是因为它适合我管理这类数据的方式而不是更好或更好的情况。布兰科提出的一个论点是,这不是1NF并且他是完全正确的,但即使是正常化的支持者也接受有时候规范化并不总是最好的方法,有时你需要去规范化获得更好的表现。

拥有单独的双边投资协定的优点: 您可以在SQL查询中引用每个属性(列),并确保选择正确的位,否则需要在应用程序代码中引用您的枚举器,以了解每个位在SQL表中的含义。

您可以更轻松地自动填充实体对象。

报告等可以使用这些设置,而无需进行任何计算来获得单独设置的值。

纠正正常化。

将所有内容放在一个列(标记)中的优点(如果需要更多存储空间,则可以多于一个):

您可以更轻松,更快捷地阅读和编写设置。

操作或读取设置的SQL查询编写起来会更快。

您可以使用较少的代码来设置设置集合。

编写和维护自己的实体对象更容易。

减少内存使用量(但不管你使用哪种方法,这都会很低)。

如果你想将它放入会话对象中,那么载荷要小得多。

我要说的唯一考虑因素是,一旦你的标志总数超过你可以从一个变量(2 ^ 64)获得的总内存空间,你就失去了使用标志的一些优点,因为你的数据必须被传播无论您使用何种方法,都可以跨多个列。

答案 5 :(得分:0)

去吧。
记住63个标志然后你去扩展所以什么?
我在项目Oracle 10g 3.8m行中做到了这一点,在SQL方面表现出色 MS SQL 15.2M行在SQL方面表现出色 我没有在Azure SQL中测试它,但通常在SQL中你有WHERE userID = XXX 然后在选择你做MASK找到像INROLE ...

之类的东西

它很难获得支持,但是如果你做了额外的努力并编写了一些代码来将任何数字转换为很好的解释.... USE Descriptions以及

请记住在SQL方面没有索引可以帮助您进行全面扫描...但是对于用户设置权限...没问题,您可以脱机执行报告...

在C#方面,我做了描述并用它来动态构建下拉列表...

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();
        }
    }

   }

}