使用和持久化枚举的最佳实践

我在这里看到了一些关于处理和持久化枚举类值(例如 适合枚举的持久化数据如何使用 NHibernate 持久化枚举)的最佳方法的问题/讨论,我想问一下大家的共识是什么。

特别是:

  • 在代码中应该如何处理这些值?
  • 如何将它们保存到数据库(以文本/数字形式) ?
  • 不同解决方案之间的权衡是什么?

注意: 我把这个问题最初包含的解释移到了一个答案中。

26307 次浏览

至于代码部分:

你应该为你的枚举使用‘ enum’类型,基本上你会得到很多免费的东西,如果你这样做: 类型安全,封装和交换机避免,支持一些集合,如 EnumSetEnumMap和代码清晰度。

至于持久化部分,您总是可以持久化枚举的字符串表示形式,并使用枚举.valueOf (String)方法将其加载回来。

在数据库中存储枚举的文本值不如存储整数更可取,因为需要额外的空间和较慢的搜索速度。它的价值在于它比数字有更多的意义,然而数据库是用于存储的,而表示层是用于使事物看起来很漂亮。

Java 或 C # 应该总是在代码中使用枚举。免责声明: 我的背景是 C # 。

如果要将该值保存到数据库中,则应显式定义每个枚举成员的整数值,以便以后代码中的更改不会意外地改变已翻译的枚举值,从而改变应用程序行为。

值应该始终作为整数值保存到数据库中,以防止枚举名重构。在 wiki 中保存每个枚举的文档,并向数据库字段添加注释,指向记录类型的 wiki 页面。还要将 XML 文档添加到包含 wiki 条目链接的枚举类型,以便通过 Intellisense 获得它。

如果您使用一个工具来生成 CRUD 代码,那么它应该能够定义一个用于列的枚举类型,以便生成的代码对象始终使用枚举成员。

如果需要为枚举成员应用自定义逻辑,可以使用以下选项:

  • 如果您有一个 enum MyEnum,那么创建一个静态类 MyEnumInfo,它提供实用工具方法,通过 switch 语句或任何必要的方法来发现有关 enum 成员的附加信息。将“ Info”追加到类名称中的枚举名称的末尾,可以确保它们在 IntelliSense 中彼此相邻。
  • 使用属性装饰枚举成员以指定其他参数。例如,我们已经开发了一个 EnumDropDown 控件,它创建了一个充满枚举值的 ASP.NET 下拉列表,并且一个 EnumDisplayAttribute 指定了用于每个成员的格式良好的显示文本。

我还没有尝试过这种方法,但是在 SQL Server 2005或更高版本中,理论上可以将 C # 代码注册到包含枚举信息的数据库中,并且可以将值转换为枚举,以便在视图或其他构造中使用,这使得数据转换的方法更易于 DBA 使用。

在 C # 的代码处理中,你忽略了去关注0值的定义。 我总是毫无例外地声明我的第一个价值:

public enum SomeEnum
{
None = 0,
}

以便作为一个空值。因为支持类型是一个整数,而整数默认为0,所以在很多地方知道枚举是否已经通过编程设置非常有用。

好吧,根据我的经验,对任何东西使用枚举,而不是将选项(作为标志)传递给一个直接的方法调用,在某些时候会导致 switch-ing。

  • 如果您打算在所有代码中都使用枚举,那么您可能会得到不易维护的代码(臭名昭著的 switch语句)
  • 扩展枚举是一种痛苦。您添加一个新的枚举项,并最终遍历所有代码以检查所有条件。
  • 和。NET 3.5,您可以向枚举添加扩展方法,使它们的行为更像类。然而,以这种方式添加真正的功能并不那么容易,因为它仍然不是一个类(如果没有其他地方,您最终将在扩展方法中使用 switch-es)。

因此,对于一个具有更多功能的类似枚举的实体,您应该花一些时间并将其创建为一个类,要记住以下几点:

  • 要使类的行为像枚举一样,可以强制每个派生类实例化为 Singleton,或者重写 Equals 以允许不同实例的值比较。
  • 如果您的类是枚举类型的,那么它应该意味着它不应该包含任何可序列化的状态——反序列化应该可以单独从它的类型进行(如您所说,是一种“ ID”)。
  • 持久性逻辑应该仅限于基类,否则扩展您的“ enum”将是一场噩梦。如果你想使用单例模式,你需要确保正确的反序列化到单例实例中。

每次您发现自己在代码更改为枚举时使用“魔术数字”时。除了节省时间之外(因为当 bug 出现时魔法就会消失... ...) ,它还可以节省你的眼睛和内存(有意义的枚举可以使代码更具可读性和自编文档) ,因为你猜怎么着——你很可能是维护和开发自己的代码的人

最初的文章对我来说看起来很好。不过,基于这些评论,似乎有些关于 Java 枚举的评论可以澄清一些事情。

Java 中的 Enum 类型从定义上来说是一个类,但是许多程序员往往忘记了这一点,因为他们更愿意将其与其他一些语言中的“允许值列表”联系起来。不仅如此。

因此,为了避免这些 switch 语句,在枚举类中放置一些代码和其他方法可能是合理的。几乎没有必要创建一个单独的“类似枚举的实类”。

还要考虑文档的要点——是否要在数据库中记录枚举的实际含义?在反映值的源代码中(您的枚举类型)还是在某些外部文档中?我个人比较喜欢源代码。

如果由于速度或其他原因,希望将枚举值表示为数据库中的整数,那么该映射也应该驻留在 Java 枚举中。默认情况下,您将获得字符串名称映射,我对此很满意。每个枚举值都有一个序数,但是直接使用它作为代码和数据库之间的映射并不明智,因为如果有人对源代码中的值进行重新排序,那个序数就会改变。或者在现有值之间添加其他枚举值。或者失去一些价值。

(当然,如果有人更改了源代码中枚举的名称,默认的字符串映射也会失效,但这种情况不太可能意外发生。如果需要的话,可以通过在数据库中添加一些运行时检查和检查约束来更容易地防止这种情况的发生。)

我同意你所说的大部分内容。不过,关于枚举的持久性,我想补充一点: 我不认为在构建时从 DB 值生成枚举是可以接受的,但是我也认为运行时检查不是一个好的解决方案。我将定义第三种方法: 使用单元测试来检查枚举的值与数据库之间的关系。这可以防止“偶然”发散,并避免每次运行代码时根据数据库检查枚举的开销。

我知道这是一个老论坛,如果数据库可能有其他东西直接集成到它?例如,当结果数据库是代码的 SOLE 用途时。然后,您将在每次集成时定义枚举。总比把他们关在数据库里好。否则,我同意原来的帖子。

我试图总结我的理解。如果你有任何更正,请随意编辑这篇文章。所以这里是:

在密码里

在代码中,枚举应该使用语言的本机枚举类型(至少在 Java 和 C # 中)或者使用类似于 “类型安全枚举模式”的东西来处理。不建议使用普通常量(Integer 或类似的) ,因为这样会失去类型安全性(并且很难理解哪些值对于方法来说是合法输入)。

这两者之间的选择取决于枚举需要附加多少附加功能:

  • 如果您想将大量功能放入枚举中(这很好,因为您可以避免总是在枚举中使用 switch ()) ,那么类通常更合适。
  • 另一方面,对于简单的枚举类值,语言的枚举通常更清晰。

特别是,至少在 Java 中,一个枚举不能从另一个类继承,所以如果您有几个具有类似行为的枚举,并且您希望将它们放入一个超类中,那么您就不能使用 Java 的枚举。

持久枚举

为了持久化枚举,应该为每个枚举值分配一个唯一的 ID。这可以是一个整数,也可以是一个短字符串。短字符串是首选的,因为它可以是助记符(使 DBA 等更容易理解数据库中的原始数据)。

  • 在软件中,每个枚举都应该有映射函数来在枚举(用于软件内部)和 ID 值(用于持久化)之间进行转换。一些框架(例如(N) Hibernate)对于自动执行此操作的支持有限。否则,必须将其放入枚举类型/类中。
  • 理想情况下,数据库应该为每个枚举包含一个表,列出合法值。一列是 ID (见上文) ,即 PK。额外的列可能对描述有意义。所有包含该枚举值的表列都可以使用这个“枚举表”作为 FK。这保证了不正确的枚举值永远不会被持久化,并允许 DB“独立存在”。

这种方法的一个问题是合法枚举值的列表存在于两个位置(代码和数据库)。这很难避免,因此往往被认为是可以接受的,但有两种选择:

  • 只在 DB 中保留值列表,在构建时生成枚举类型。优雅,但是意味着运行构建需要 DB 连接,这似乎有问题。
  • 定义代码中具有权威性的值列表。在运行时(通常在启动时)检查 DB 中的值,对不匹配进行抱怨/中止。