征求意见:对于静态值,是使用枚举更好还是使用实体更好?



我正在努力解决一个困扰了我几个月的难题。

我的同事和我在一个技术问题上意见完全不一致,我想听听我们亲爱的社区对这件事的看法。

简单来说:

是使用枚举(带有诸如"Description"之类的潜在属性)还是使用实体(带有Name和Description属性)更可取?

在细节

:

在我们的领域模型中,我们有很多只包含Id、Name和Description的迷你实体。95%的情况下,"描述"等于"名称"。

为了便于解释,我将举一个例子:在我们的Security实体中,我们有一个AssetClass属性。AssetClass有一个静态的值列表("Equity","Bond"等),并且不会从接口或其他任何东西中改变。

问题是,当你想获得资产类别为"Bond"的所有证券时,NHibernate将不得不加入AssetClass表…考虑到AssetClass不是唯一的属性,你可以想象所有这些连接对性能的影响。

我们目前的解决方案:(我不同意):

我们的代码中有一些硬编码的AssetClass实例,它们具有各自的值和Id(即Id为1的股权,Id为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字段,使用属性。我们还可以对数据库进行约束,以确保不能存储与enum不匹配的随机值。

它们反对使用枚举的理由:

  • 这不是一个对象,所以你不能扩展它,它不是面向对象的,等等。
  • 你不能轻易获得描述,或者有"适当的英文"名称(带有空格或符号),如"我的值"(在enum上将是"MyValue")
  • 枚举了
  • 属性吸

我反对当前解决方案的理由:

  • 代码中的id和数据库中的id可能不匹配
  • 它很难维护(我们需要绝对确保我们拥有的每个硬编码值也在数据库中)
  • 属性和枚举如果使用得当,对于这些静态值不会很糟糕
  • 对于"正确的英文"名称,我们也可以使用属性,并使用一些扩展方法来消费它。

现在,怎么想?

我个人更喜欢第一种解决方案,可能有一个额外的属性,返回所有的值。

更多的是OO -枚举基本上是"命名数字",仅此而已。在代码的其他地方,状态都存储在属性中——那么为什么要使用属性呢?正如Eric Lippert在一篇比较两者的博客文章中所写的那样,"属性是关于机制的事实"。你基本上是把它们作为一种提供关于值的数据的方式,而不是,这对我来说是错误的。

关于代码和数据库之间潜在的不匹配,你对使用poco的前两个反对意见也不正确——因为它们对枚举也是完全相同的,不是吗?此外,编写一个验证数据的测试非常容易——如果你愿意,甚至可以在启动时这样做。

不清楚AssetClass的其余部分是做什么的,但如果它只有一个私有构造函数,那么您就可以在有限的,众所周知的值集方面获得枚举的许多好处,但没有枚举基本上只是数字的问题。

事实上,在值限制方面,POCO解决方案优于——因为唯一的"无效"POCO值是null,而很容易产生无效的enum值:
FooEnum invalid = (FooEnum) 12345;

你要到处检查吗?一般来说,空引用比无效的枚举值更早出现,也更容易检查。

我能想到的POCO方法的一个缺点是你不能打开它。有一些方法可以解决这个问题,但它们不是很令人愉快-你基本上必须也必须有一组众所周知的数字(当然可以是enum)以便某些属性会返回它并且你可以打开它

我真的不喜欢这两个选项,因为代码和数据库之间的ID可能不匹配。你实际上指出了这一点,但出于某种原因,你似乎认为如果你使用枚举,这个问题就会消失,而实际上你会得到完全相同的东西?

如果我必须在两者之间做出选择,我会选择当前实现,原因是Jon Skeet提到的。

然而,我认为最好的方法是创建一个正常的类并从数据库中获取项目,并根据属性编写程序。我不久前写了这篇文章来解释我的意思:针对属性的程序

我个人更喜欢将枚举作为字符串存储在数据库中。您可能需要的任何其他信息都可以在它自己的表中使用enum类型和value作为键。不过,我不会将描述存储为属性。另外,转换Enum也很简单。一个包含"Some Value"的字符串

我更喜欢枚举,主要是因为它们给了你编译器的支持,并且很容易通过简单的重构来修改添加新值,而字符串值在值改变时可能会产生一些隐藏的bug。

if(obj.EnumValue == Enum.Value)

比这个好

if(obj.Value == "SomeValues")

我使用GenericEnumMapper自带的fluent-nhibernate,但如果你使用hibernate,你可以使用EnumString类型。这提供了一种很好的方式将枚举作为数据库中的字符串,这样您的数据对于没有访问代码的人来说是可读的。

最新更新