Хранение пользовательских настроек - что-то не так с использованием "Флаги" или "Биты" вместо кучки bools?

Я разрабатываю пользовательские настройки для моего приложения MVC, и сейчас у меня есть ~ 20 логических параметров, которые пользователь может переключить. Поскольку каждый пользователь всегда будет иметь все настройки, я думал о сохранении каждого параметра в качестве логического в таблице User. Хотя это будет громоздким по мере роста требований приложения.

Первый вопрос - есть ли что-то не так, если в этой ситуации есть тонна столбцов на столе?

Затем я рассмотрел использование флагов и сохранил настройки как один бит в массиве:

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

И тогда каждый пользователь будет иметь один столбец "Настройки" в своей строке "Пользователь", который хранит значение 2 ^ 20, если есть 20 настроек.

Я бы использовал это, чтобы направлять свои усилия: Что означает атрибут Enum [Flags] Enum в С#?

Это лучше, чем прежний подход? Любые предложения приветствуются.

Ответ 1

Это зависит от того, что следует считать атомным с точки зрения управления данными.

  • Если вы всегда выполняете поиск, чтение и запись параметров all вместе с/в базу данных, то весь набор параметров можно считать атомарным и может храниться вместе в базе данных.
  • Однако, если вам нужно выполнить какие-либо из этих действий в настройках подмножества (например, установить только один флаг без изменения других), то они не являются атомарными (с точки зрения управления данными) поэтому их хранение вместе в одном поле базы данных будет нарушать принцип atomicity и, следовательно, 1NF.

Обратите внимание, что некоторые СУБД (такие как MS SQL Server) очень эффективны при хранении булевых (всего один бит на логическое поле в идеальных условиях). Даже те, кто менее совершенен, обычно не тратят больше одного байта на Boolean (Oracle, я смотрю на вас), что может стать проблемой, только если у вас есть миллионы или миллиарды пользователей.

Ответ 2

Если у вас есть только 20 значений, и в какой-то день нет шансов, это будет 100 значений - нормально использовать любой из них (желательно перечисление). Но если есть шанс получить 100 значений - вам действительно нужно построить таблицу пар ключ-значение (есть таблица Users, таблица Settings и таблица UsersSettings, которая сопоставляет друг с другом).

Ответ 3

Да, это лучший способ сохранить и получить данные, так как вам не нужно менять базу данных каждый раз при появлении новой настройки. Это означает, что для внесения изменений в систему будет стоить гораздо меньше.

Просто убедитесь, что назначили соответствующие значения целого числа (полномочия 2) для вашего перечисления, С# не будет делать это автоматически, и он не будет работать должным образом, если вы сделаете это неправильно.

Ответ 4

Я думаю, что есть два сценария, где это может стать обструктивным:

1 - Есть ли возможность для будущей настройки пользователя, которая не является логической? Если это уже не обрабатывается отдельно, вам придется придумать новый способ хранения этого небулевого параметра, возможно, в результате с двумя разрозненными контейнерами для хранения ваших настроек - один для bool и другой для чего-нибудь еще. В исходном вопросе просто указывается bools, поэтому, предположим, вы подумали об этом;)

2 - Удаление настроек может быть проблемой? Если через два года после этого пользователи перестанут получать уведомления или есть лазаньи, вам придется тщательно управлять изменениями этого перечисления, чтобы гарантировать, что ваши битовые флаги обратно совместимы при удалении из него элементов.

Исторически я использовал для этого таблицу "Настройки пользователя" для пары "ключ-значение". Схема в основном состоит из:

[Идентификатор базы данных] (длинный/идентичный)
[UserId] (ссылка FK на таблицу пользователя)
[SettingId] (любой идентификатор заданной настройки - тип данных зависит от идентификатора)
[SettingValue] (NVarChar (?) - строковое представление значения настройки - размер поля зависит от req)

Это не работает так же, как битовые флаги, и требует некоторого анализа синтаксиса на стороне приложения для повторного увлажнения пользовательских настроек, но это дает вам расширяемое решение, которое может:

1 - В значительной степени обрабатывайте любой тип данных ядра для любой заданной настройки.
2 - Легко обрабатывать различное количество настроек для каждого пользователя.
3 - Может быть редко заполнено только настройками, которые отличаются от настроек приложения по умолчанию.

Я успешно использовал это в нескольких производственных приложениях на сегодняшний день, однако, если вы говорите в области сотен тысяч пользователей, это решение может оказаться неприемлемым.

Ответ 5

Отвечая на ваши вопросы так, как вы их просили:

"есть ли что-то неправильное, если в этой ситуации есть тонна столбцов на вашем столе?"

Нет ничего плохого в сохранении данных таким образом. У вас может быть гораздо больше, чем вы предлагаете здесь, без каких-либо проблем.

"Это лучше, чем прежний подход?"

Вот как я это делаю, но это потому, что это соответствует тому, как я управляю такими данными, а не случаем лучшего или правильного. Существует аргумент, который выдвинул Бранко, что это не 1NF, и он абсолютно прав, но даже сторонники нормализации согласны с тем, что бывают случаи, когда нормализация не всегда является лучшим способом, а иногда вам нужно денормализовать получить лучшую производительность.

Плюсы от раздельных ДИД: Вы можете ссылаться на каждое свойство (столбец) в SQL-запросах и быть уверенным, что вы выбираете правильный бит, иначе вам нужно будет ссылаться на ваш перечислитель в вашем коде приложения, чтобы знать, что каждый бит означает в таблице SQL.

Вы можете автоматически заполнить объект сущности проще.

Отчеты и т.д. могут использовать эти параметры без необходимости делать какие-либо вычисления, чтобы получить значение отдельных параметров.

Правильная нормализация.

Плюсы наличия всего в одном столбце (флаги) (или более одного, если вам нужно больше памяти):

Вы можете читать и записывать настройки как группы намного проще и быстрее.

Ваши SQL-запросы, которые манипулируют или читают настройки, будут быстрее записываться.

Вы можете повторить набор настроек с меньшим количеством кода.

написание и поддержка вашего объекта сущности проще.

Меньше использования памяти (но, честно говоря, это будет низким, независимо от используемого вами подхода).

Значительно меньшая полезная нагрузка, если вы хотите поместить ее в объект сеанса.

Единственное соображение, которое я бы сказал, заключается в том, что, когда ваше общее количество флагов превышает общее пространство памяти, которое вы можете получить из одной переменной (2 ^ 64), вы теряете часть профи для использования флагов, поскольку ваши данные затем должны быть распределены через несколько столбцов, независимо от используемого вами подхода.

Ответ 6

Пойдите для этого.
Имейте в виду 63 флага, тогда вы идете на Расширение, так что?
Я сделал это в проекте Oracle 10g 3.8m строк работает отлично в SQL-стороне
Строки MS SQL 15.2M отлично работают в SQL-стороне
Я не тестировал его в Azure SQL, но обычно в SQL у вас есть WHERE userID = XXX затем в выборе вы делаете МАСКУ, чтобы найти что-то вроде INROLE...

это сложно для поддержки, но если вы приложите дополнительные усилия и напишите какой-нибудь код, чтобы преобразовать любое число в приятное объяснение... USE Описания также

Имейте в виду, что в SQL-стороне ни один индекс не поможет вам выполнить полное сканирование... но для прав доступа пользователей... Нет проблем, вы делаете свои отчеты в автономном режиме...

На стороне С# я описываю описания и использую их для динамического развертывания зданий...

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

   }

}