Запрошенное мнение: для статических значений лучше использовать Enums или Entities? - PullRequest
7 голосов
/ 19 августа 2011

Я пытаюсь решить дилемму, которая мучила меня последние несколько месяцев.

Мои коллеги и я полностью не согласны с технической проблемой, и я хотел бы услышать мнение нашего любимого сообщества по этому вопросу.

В двух словах:

Желательно ли использовать перечисления (с потенциально возможными атрибутами, такими как «Описание») или использовать сущности (со свойствами «Имя» и «Описание»)?

Подробнее:

В нашей модели предметной области у нас есть множество мини-сущностей, которые содержат только Id, Name и Description. Описание в 95% случаев соответствует названию.

Для объяснения я собираюсь привести один из множества примеров: в нашем объекте Security у нас есть свойство AssetClass. AssetClass имеет статический список значений («Equity», «Bond» и т. Д.) И не изменяется в интерфейсе или в чем-либо еще.

Проблема в том, что когда вы хотите получить все ценные бумаги с классом активов "Bond", скажем, NHibernate должен будет присоединиться к таблице AssetClass ... и, учитывая, что AssetClass не является единственным таким свойством, вы Можно представить влияние всех этих соединений на производительность.

Наше текущее решение: (с которым я не согласен):

В нашем коде есть несколько жестко запрограммированных экземпляров класса AssetClass со всеми их соответствующими значениями и идентификаторами (т. Е. Эквити с идентификатором 1, связью с идентификатором 2 и т. Д.), Которые соответствуют содержимому базы данных:

public partial class AssetClass
{
    static public AssetClass Equity = new AssetClass(1, "Equity", "Equity");
    static public AssetClass Bond = new AssetClass(2, "Bond", "Bond");
    static public AssetClass Future = new AssetClass(3, "Future", "Future");
    static public AssetClass SomethingElse = new AssetClass(4, "Something else", "This is something else");
}

Мы также создали специальный тип NHibernate (код ниже, если вам интересно), который позволяет нам избегать NHibernate делать соединение, загружая этот жестко запрограммированный экземпляр вместо того, чтобы идти в базу данных, чтобы получить его:

using System;
using System.Data;
using System.Data.Common;
using NHibernate.Dialect;
using NHibernate.SqlTypes;
using NHibernate.Type;

namespace MyCompany.Utilities.DomainObjects
{
    public abstract class PrimitiveTypeBase<T> : PrimitiveType where T : class, IUniquelyNamed, IIdentifiable
    {
        private readonly PrimitiveTypeFactory<T> _factory;

        public PrimitiveTypeBase() : base(SqlTypeFactory.Int32)
        {
            _factory = new PrimitiveTypeFactory<T>();
        }

        public override string Name
        {
            get { return typeof(T).Name; }
        }

        public override Type ReturnedClass
        {
            get { return typeof(T); }
        }

        public override Type PrimitiveClass
        {
            get { return typeof (int); }
        }

        public override object DefaultValue
        {
            get { return null; }
        }

        public override void Set(IDbCommand cmd, object value, int index)
        {
            var type = value as T;
            var param = cmd.Parameters[index] as DbParameter;
            param.Value = type.Id;
        }

        public override object Get(IDataReader rs, int index)
        {
            return GetStaticValue(rs[index]);
        }

        public override object Get(IDataReader rs, string name)
        {
            return GetStaticValue(rs[name]);
        }

        private T GetStaticValue(object val)
        {
            if (val == null)
            {
                return (T)DefaultValue;
            }

            int id = int.Parse(val.ToString());
            T entity = _factory.GetById(id); // that returns, by reflection and based on the type T, the static value with the given Id

            if (entity == null)
            {
                throw new InvalidOperationException(string.Format("Could not determine {0} for id {1}", typeof (T).Name, id));
            }
            return entity;
        }

        public override object FromStringValue(string xml)
        {
            return GetStaticValue(xml);
        }

        public override string ObjectToSQLString(object value, Dialect dialect)
        {
            var type = value as T;
            return type.Id.ToString();
        }
    }
}

Мое решение: (с которым я согласен: -))

Замена этих сущностей перечислениями, и если нам когда-либо понадобится поле Description, используйте атрибут. У нас также может быть ограничение на базу данных, чтобы вы не могли хранить случайные значения, которые не соответствуют перечислению.

Их рациональное использование перечислений:

  • Это не объект, поэтому вы не можете расширить его, это не объектно-ориентированное и т. Д.
  • Вы не можете легко получить описание или иметь «правильные английские» имена (с пробелами или символами), такие как «My Value» (которое в перечислении будет «MyValue»)
  • Enums sucks
  • Атрибут отстой

Мое рациональное против нашего нынешнего решения:

  • У нас может быть несоответствие между идентификаторами в коде и тем, что находится в базе данных
  • Поддерживать его намного сложнее (мы должны быть абсолютно уверены, что все имеющиеся у нас жестко закодированные значения также присутствуют в базе данных)
  • Атрибуты и перечисления не сосут при правильном использовании и для статических значений, подобных этим
  • Для «правильных английских» имен мы также можем использовать атрибут с некоторым методом расширения, чтобы использовать его.

Теперь, что ВЫ думаете?

Ответы [ 3 ]

7 голосов
/ 19 августа 2011

Лично я предпочитаю первое решение, возможно, с дополнительным свойством, которое возвращает все значения.

Это намного больше ОО - перечисления в основном "именованные числа", вот и все. Повсюду в коде состояние хранится в свойствах - так зачем использовать атрибуты? Как писал Эрик Липперт в своем блоге , сравнивая два , «Атрибуты - это факты о механизмах». Вместо этого вы в основном используете их для предоставления данных о значениях, и мне это просто кажется неправильным.

Ваши первые два возражения против использования POCO с точки зрения потенциального несоответствия между кодом и базой данных также не соответствуют действительности - потому что они точно такие же для перечислений, не так ли? Кроме того, очень легко написать тест, который проверяет данные - вы можете даже сделать это при запуске, если хотите.

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

На самом деле, решение POCO на лучше , чем перечисление с точки зрения ограничения значения - потому что единственное "недопустимое" значение POCO - ноль, тогда как легко придумать недопустимое значение перечисления:

FooEnum invalid = (FooEnum) 12345;

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

Один недостаток, который я могу вспомнить в подходе POCO, это то, что вы не можете включить его. Есть способы обойти это, но они не очень приятны - вам, как правило, нужно также иметь набор хорошо известных чисел (которые, конечно, могут быть перечислениями), чтобы какое-то свойство могло верните это, и вы можете включить его.

2 голосов
/ 19 августа 2011

Мне не очень нравятся оба варианта из-за возможного несоответствия идентификаторов между кодом и базой данных.Вы на самом деле указали на это, но по какой-то причине кажется, что эта проблема исчезнет, ​​если вы используете перечисления, когда на самом деле у вас будет точно такая же вещь?

Если бы мне пришлось выбирать между двумявыбрал бы текущую реализацию по причинам, упомянутым Джоном Скитом.

Однако я думаю, что лучший подход - это просто создать нормальный класс и извлечь элементы из базы данных, а запрограммировать атрибуты * 1006.*.Я написал это некоторое время назад, чтобы объяснить, что я имею в виду: Программа против атрибутов

1 голос
/ 19 августа 2011

Я лично предпочитаю хранить перечисления в виде строк в базе данных. Любая дополнительная информация, которая может вам понадобиться, может быть помещена в ее собственную таблицу с типом и значением перечисления в качестве ключа. Я бы не стал хранить описание как атрибут. Также тривиально преобразовать Enum.SomeValue в строку, содержащую «Some Value»

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

Это

if(obj.EnumValue == Enum.Value)

лучше, чем этот

if(obj.Value == "SomeValues")

Я использую GenericEnumMapper, который поставляется с fluent-nhibernate, но если вы используете hibernate, вы можете использовать тип EnumString. Это дает хороший способ иметь перечисления в виде строк в базе данных, чтобы ваши данные были доступны для чтения людям, не имеющим доступа к коду.

...