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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/746812/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 23:37:00  来源:igfitidea点击:

Best practices for using and persisting enums

c#javadatabaseenumspersistence

提问by sleske

I've seen several questions/discussions here about the best way to handle and persist enum-like values (e.g. Persisting data suited for enums, How to persist an enum using NHibernate), and I'd like to ask what the general consenus is.

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

In particular:

特别是:

  • How should these values be handled in the code?
  • How should they be persisted to a database (as text/as a number)?
  • What are the tradeoffs of different solutions?
  • 应该如何在代码中处理这些值?
  • 它们应该如何持久化到数据库(作为文本/作为数字)?
  • 不同解决方案的权衡是什么?

Note: I moved the explanations originally included in this question to an answer.

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

采纳答案by sleske

I've tried to summarize my understanding. Feel free to edit this if you have any corrections. So here it goes:

我试着总结一下我的理解。如果您有任何更正,请随时编辑。所以这里是这样的:

In the code

在代码中

In the code, enums should be handled using either the language's native enum type (at least in Java and C#), or using something like the "typesafe enum pattern". Using plain constants (Integer or similar) is discouraged, as you lose type safety (and make it hard to understand which values are legal input for e.g. a method).

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

The choice between these two depends on how much additional functionality is to be attached to the enum:

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

  • If you want to put loads of functionality into the enum (which is good, because you avoid switch()ing on it all the time), a class is usually more appropriate.
  • On the other hand, for simple enum-like values, the language's enum is usually clearer.
  • 如果您想将大量功能放入枚举(这很好,因为您一直避免对其进行 switch() 操作),类通常更合适。
  • 另一方面,对于简单的类似枚举的值,语言的枚举通常更清晰。

In particular, at least in Java an enum cannot inherit from another class, so if you have several enums with similar behavior which you'd like to put into a superclass, you cannot use Java's enums.

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

Persisting enums

持久化枚举

To persist enums, each enum value should be assigned a unique ID. This can be either an integer, or a short string. A short string is preferred, since it can be mnemonic (makes it easier for DBAs etc. to understand the raw data in the db).

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

  • In the software, every enum should then have mapping functions to convert between the enum (for use inside the software) and the ID value (for persisting). Some frameworks (e.g. (N)Hibernate) have limited suppport for doing this automatically. Otherwise, you have to put it into the enum type/class.
  • The database should (ideally) contain a table for each enum listing the legal values. One column would be the ID(see above), which is the PK. Additional columns might make sense for e.g. a description. All table columns that will contain values from that enum can then use this "enum table" as a FK. This guarantees that incorrect enum values can never be persisted, and allows the DB to "stand on its own".
  • 在软件中,每个枚举都应该有映射函数来在枚举(用于软件内部)和 ID 值(用于持久化)之间进行转换。某些框架(例如 (N)Hibernate)对自动执行此操作的支持有限。否则,您必须将其放入枚举类型/类中。
  • 数据库应该(理想情况下)包含每个枚举的表,列出合法值。一列是 ID(见上文),即 PK。附加列可能对例如描述有意义。所有包含该枚举值的表列都可以使用这个“枚举表”作为 FK。这保证了不正确的枚举值永远不会被持久化,并允许数据库“独立”。

One problem with this approach is that the list of legal enum values exists in two places (code and database). This is hard to avoid and therefore often considered acceptable, but there are two alternatives:

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

  • Only keep the list of values in the DB, generate the enum type at build time. Elegant, but means that a DB connection is required for a build to run, which seems problematic.
  • Define the list of values in the code to be authoritative. Check against the values in the DB at runtime (usually at startup), complain/abort on mismatch.
  • 只保留数据库中的值列表,在构建时生成枚举类型。优雅,但意味着构建运行需要数据库连接,这似乎有问题。
  • 将代码中的值列表定义为权威。在运行时(通常在启动时)检查数据库中的值,在不匹配时抱怨/中止。

回答by MahdeTo

Imho, as for the code part:

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

You should alwaysuse the 'enum' type for your enumerations, basically you get alot of freebies if you do: Type safety, encapsulation and switch avoidance, the support of some collections such as EnumSetand EnumMapand code clarity.

您应该始终使用“枚举”类型进行枚举,基本上,如果您这样做,您将获得很多免费赠品:类型安全、封装和开关避免、某些集合的支持,例如EnumSetEnumMap和代码清晰度。

as for the persistence part you can always persist the string representation of the enum and load it back using the enum.valueOf(String) method.

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

回答by cjk

Storing the text value of an enum in a database is less preferred to storing an integer, due to the additional space required and slower searching. It is valuable in that it has more meaning than a number, however the database is for storage, and the presentation layer is for making things look nice.

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

回答by David Boike

Java or C# should always use enums in code. Disclaimer: My background is C#.

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

If the value is to be persisted to a database, the integral values of each enumeration member should be explicitly defined so that a later change in code does not accidentally alter translated enum values and thus application behavior.

如果要将该值持久化到数据库中,则应明确定义每个枚举成员的整数值,以便以后代码更改不会意外更改已翻译的枚举值以及应用程序行为。

Values should always be persisted to a database as integral values, to protect against enum name refactoring. Keep documentation on each enumeration in a wiki and add a comment to the database field pointing to the wiki page documenting the type. Also add XML documentation to the enum type containing a link to the wiki entry so that it is available through Intellisense.

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

If you use a tool to generate CRUD code it should be capable of defining an enumeration type to use for a column so that generated code objects always use enumerated members.

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

If custom logic needs to be applied for an enumeration member, you have some options:

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

  • If you have an enum MyEnum, create a static class MyEnumInfo which offers utility methods to discover additional information about the enum member, by switch statements or whatever means necessary. Appending "Info" to the end of the enum name in the class name ensures that they will be next to each other in IntelliSense.
  • Decorate the enumeration members with attributes to specify additional parameters. For example we have developed an EnumDropDown control which creates an ASP.NET dropdown filled with enumeration values, and an EnumDisplayAttribute specifies the nicely formatted display text to use for each member.
  • 如果您有一个枚举 MyEnum,请创建一个静态类 MyEnumInfo,它提供实用方法以通过 switch 语句或任何必要的方式发现有关枚举成员的其他信息。将“信息”附加到类名中枚举名称的末尾可确保它们在 IntelliSense 中彼此相邻。
  • 用属性修饰枚举成员以指定附加参数。例如,我们开发了一个 EnumDropDown 控件,它创建了一个填充了枚举值的 ASP.NET 下拉列表,一个 EnumDisplayAttribute 指定了用于每个成员的格式良好的显示文本。

I have not tried this, but with SQL Server 2005 or later, you could theoretically register C# code with the database that would contain enum information and the ability to convert values to enums for use in views or other constructs, making a method of translating the data in a manner easier for DBAs to use.

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

回答by Quibblesome

In the code handling for C# you've missed out defining delcaring the 0 value. I almost without fail always declare my first value as:

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

public enum SomeEnum
{
    None = 0,
}

So as to serve as a null value. Because the backing type is an integer and an integer defaults to 0 so it is massively useful in a lot of places to know if an enum has actually been programatically set or not.

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

回答by Groo

Well, from my experience, using enums for anything other than for passing options (as flags) to an immediate method call, results in switch-ing at some point.

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

  • If you are going to use the enum all over your code, then you might end up with code that is not so easy to maintain (the infamous switchstatement)
  • Extending enums is a pain. You add a new enum item and end up in going through all of your code to check for all conditions.
  • With .NET 3.5, you can add extension methods to enums to make them behave a bit more like classes. However, adding real functionality this way is not so easy since it's still not a class (you would end up using switch-es in your extension methods if not elsewhere.
  • 如果您打算在整个代码中使用枚举,那么您最终可能会得到不太容易维护的代码(臭名昭著的switch声明)
  • 扩展枚举是一种痛苦。您添加了一个新的枚举项,并最终遍历所有代码以检查所有条件。
  • 使用 .NET 3.5,您可以向枚举添加扩展方法,使它们的行为更像类。但是,以这种方式添加真正的功能并不容易,因为它仍然不是一个类(switch如果不在其他地方,您最终会在扩展方法中使用-es。

So for an enum-like entity with a bit more of functionality you should take some time and create it as a class, with several things in mind:

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

  • To make your class behave like an enum, you can either force each derived class to instantiate as a Singleton, or override Equals to allow value comparison of different instances.
  • If your class is enum-like, it should mean that it should contain no serializable state - deserialization should be possible from its type alone (a sort of an "ID", as you said).
  • Persistence logic should be confined to the base class only, otherwise extending your "enum" would be a nightmare. In case that you went for the Singleton pattern, you would need to ensure proper deserialization into singleton instances.
  • 为了让你的类表现得像一个枚举,你可以强制每个派生类实例化为单例,或者覆盖 Equals 以允许不同实例的值比较。
  • 如果您的类是类似枚举的,则应该意味着它不应该包含可序列化的状态 - 仅从其类型就可以进行反序列化(正如您所说的一种“ID”)。
  • 持久性逻辑应仅限于基类,否则扩展“枚举”将是一场噩梦。如果您采用单例模式,则需要确保正确反序列化为单例实例。

回答by Yordan Georgiev

Each time you find your self using "magic numbers" in code change to enums. Besides time savings ( since magic will disappear when the bugs come ...) it will save your eyes and memory (meaningful enums make code more readable and self-documenting), since guess what - you are most probably the person to maintain and develop your own code

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

回答by lokori

The initial article looks fine to me. Still, based on the comments, it seems some comments concerning Java enums might clarify few things.

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

Enum type in Java is a class by definition, but many programmers tend to forget this, because they rather relate it to "a list of allowed values" as in some other languages. It's more than that.

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

So, to avoid those switch statements it might be reasonable to put some code and additional methods in the enum class. There's almost never a need to create a separate "enum-like real class".

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

Consider also the point of documentation - do you want to document the actual meaning of your enum in the database? In the source code reflecting the values (your enum type) or in some external documentation? I personally prefer the source code.

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

If you want to present enum values as integers in the database due to speed or whatever reason, that mapping should also reside in the Java enum. You'll get string-name mapping by default, and I've been content with that. There's an ordinal number associated with each enum value, but using that directly as a mapping between code and database is not very bright, because that ordinal number will change if someone reorders the values in the source code. Or adds additional enum values in between existing values. Or removes some value.

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

(Of course, if someone changes the name of the enum in the source code, the default string-mapping goes sour too, but that's less likely to happen accidentally. And you can more easily protect against that if necessary by putting some runtime-checking and check constraints in the database as suggested here already. )

(当然,如果有人在源代码中更改了枚举的名称,默认的字符串映射也会变坏,但这不太可能意外发生。如果有必要,您可以通过进行一些运行时检查来更轻松地防止这种情况发生并按照此处的建议检查数据库中的约束。)

回答by Paul Sonier

I agree with much of what you say. One thing I'd like to append, though, about the persistence of enums: I don't believe the generation of the enums at build time from the DB values is acceptable, but I also think that the runtime check is not a good solution. I'd define a third means: have a unit test which will check the values of the enum against the database. This prevents "casual" divergence, and avoids the overhead of checking the enums against the database every time the code is run.

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

回答by Chalky

I know this is an old forum, what if the database might have other things integrating directly to it? E.g. when the resulting DB is the SOLE purpose of the code. Then, you will be defining the enums at every integration. Better then to have them in the DB. Otherwise, I agree with the original post.

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