【问题标题】:Best practices for using and persisting enums使用和持久化枚举的最佳实践
【发布时间】:2010-10-19 07:25:49
【问题描述】:

我在这里看到了几个关于处理和保留类枚举值的最佳方法(例如 Persisting data suited for enumsHow to persist an enum using NHibernate )的问题/讨论,我想问一下一般共识是什么。

特别是:

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

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

【问题讨论】:

    标签: c# java database enums persistence


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      最初的文章对我来说看起来不错。尽管如此,基于 cmets,似乎一些有关 Java 枚举的 cmets 可能会澄清一些事情。

      Java 中的枚举类型在定义上是一个类,但许多程序员倾向于忘记这一点,因为他们宁愿将它与其他一些语言中的“允许值列表”相关联。不止于此。

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

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

      如果您由于速度或任何原因想在数据库中将枚举值显示为整数,则该映射也应该驻留在 Java 枚举中。默认情况下,您将获得字符串名称映射,我对此很满意。每个枚举值都有一个序数,但直接将其用作代码和数据库之间的映射并不是很明智,因为如果有人对源代码中的值重新排序,该序数会发生变化。或者在现有值之间添加额外的枚举值。或删除一些价值。

      (当然,如果有人在源代码中更改了枚举的名称,默认的字符串映射也会变质,但这不太可能意外发生。如果必要的话,你可以更容易地防止这种情况发生运行时检查和检查数据库中的约束,如这里已经建议的那样。)

      【讨论】:

      • 有两种情况需要支持:有人在我的文件中重新排序枚举,或者有人在进行重构(以澄清糟糕的初始名称选择)并破坏持久数据。我认为后者更重要,序数是数据持久化的方式。
      【解决方案3】:

      我试图总结一下我的理解。如果您有任何更正,请随时编辑。就这样吧:

      在代码中

      在代码中,应该使用语言的本机枚举类型(至少在 Java 和 C# 中)或使用类似 "typesafe enum pattern" 的东西来处理枚举。不鼓励使用普通常量(整数或类似常量),因为您会失去类型安全性(并且很难理解哪些值是合法输入,例如方法)。

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

      • 如果您想将大量功能放入枚举中(这很好,因为您避免一直对其进行 switch() 操作),类通常更合适。
      • 另一方面,对于简单的类似枚举的值,语言的枚举通常更清晰。

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

      持久化枚举

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

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

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

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

      【讨论】:

        【解决方案4】:

        在 C# 的代码处理中,您错过了定义 0 值的定义。 我几乎总是将我的第一个值声明为:

        public enum SomeEnum
        {
            None = 0,
        }
        

        以便作为空值。因为支持类型是一个整数,而一个整数默认为 0,所以它在很多地方都非常有用,可以了解枚举是否实际上已以编程方式设置。

        【讨论】:

        • 我不同意。这只有在您有时未初始化变量时才有意义,我认为这是非常糟糕的做法。我经常看到这种“无”值的想法,但我相信它只会隐藏真正的问题(未初始化的变量)。
        • 如何隐藏问题?它使它像一个可为空的 int 一样显式。我在代码中保留未初始化的值,因为我知道 CLR 默认会将它们设置为什么。他们仍然被初始化它只是隐含的。
        • 嗯,这可能是风格问题。我坚信在声明时完全初始化所有变量(或最多在声明后直接在 if-else 中)。否则你可能会忘记初始化它们,尤其是在代码流很复杂的情况下。另请参阅c2.com/cgi/wiki?SingleStepConstructor
        【解决方案5】:

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

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

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

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

        如果需要为枚举成员应用自定义逻辑,您有一些选择:

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

        我没有尝试过,但是使用 SQL Server 2005 或更高版本,理论上您可以将 C# 代码注册到包含枚举信息的数据库以及将值转换为枚举以用于视图或其他构造的能力,从而形成一种方法以更易于 DBA 使用的方式翻译数据。

        【讨论】:

        • +1 显式赋值是更改枚举时避免“损坏”的唯一方法
        【解决方案6】:

        恕我直言,至于代码部分:

        你应该总是在你的枚举中使用'enum'类型,如果你这样做,基本上你会得到很多免费的东西:类型安全、封装和避免开关,一些集合的支持,比如@987654321 @ 和 EnumMap 和代码清晰。

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

        【讨论】:

        • 原则上同意,但至少在 Java 中“枚举”受到限制,因为它不能有超类(如上所述),所以有时“类型安全枚举”类可能更好。
        【解决方案7】:

        在数据库中存储枚举的文本值不如存储整数,因为需要额外的空间和较慢的搜索。它的价值在于它比数字更有意义,但是数据库是用于存储的,而表示层是为了让事物看起来更好。

        【讨论】:

        • 不保证枚举的 int 值随着时间的推移是相同的。
        • 另外,如果你使用短字符串,性能应该是一样的。 char(2) 占用 2 个字节,int 通常也占用 2 或 4 个字节。
        • @Miguel Ping:我们的想法是明确为每个枚举分配一个 ID(int 或 char)。使用 enum 内部生成的 int 确实很危险。
        • 如果在数据库中需要超出整数值的含义,我会使用将整数值映射到人类可读字符串的表。另外,是的,整数值以后不得更改。不过应该不是问题;底层枚举整数值应该具有的唯一相关性是它们与枚举的其他成员不同。 (即:他们应该没有理由改变)。如果整数值具有唯一标识之外的意义,则可能应该使用不同的数据结构。
        【解决方案8】:

        嗯,根据我的经验,除了将选项(作为标志)传递给即时方法调用之外,将枚举用于任何其他事情,在某些时候会导致 switch-ing。

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

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

        • 要使您的类表现得像一个枚举,您可以强制每个派生类实例化为单例,或覆盖 Equals 以允许不同实例的值比较。
        • 如果您的类类似于枚举,则应该意味着它不应该包含可序列化的状态 - 反序列化应该可以仅从其类型(如您所说的一种“ID”)中进行。
        • 持久性逻辑应仅限于基类,否则扩展您的“枚举”将是一场噩梦。如果您选择单例模式,则需要确保正确反序列化为单例实例。

        【讨论】:

          【解决方案9】:

          每次您发现自己在代码中使用“幻数”时都会更改为枚举。除了节省时间(因为当错误出现时魔法会消失......)它会节省你的眼睛和记忆(有意义的枚举使代码更具可读性和自我记录),因为你猜怎么着 - 你很可能是维护和开发的人你自己的代码

          【讨论】:

            【解决方案10】:

            我知道这是一个旧论坛,如果数据库可能直接集成了其他东西怎么办?例如。当生成的数据库是代码的唯一目的时。然后,您将在每次集成时定义枚举。最好将它们放在数据库中。否则,我同意原帖。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-03-21
              • 1970-01-01
              • 2012-11-01
              • 2013-11-09
              • 2020-09-26
              • 1970-01-01
              相关资源
              最近更新 更多