繁体   English   中英

如何使用数据库C#中的值创建ENUM

[英]How to create ENUM using values from the database C#

我在数据库中有一个状态表,我正在整个应用程序中使用其值。 状态表将具有(ID,NAME)。 我想创建一个StatusEnum,可以在应用程序的代码中使用它。 如何使用数据库中的值创建ENUM?

目前我有这样的枚举

enum StatusCode: int
{
    Open = 20,
    Received = 21,
    Delivered= 22,
    Cancelled = 23
}

但是我想从数据库设置值。

您可以

  • 手动使您的枚举定义与数据库同步。 这是最简单的方法。

  • 您可以为Visual Studio 编写一个文件生成器 (也称为“自定义工具” ),并从某个参考数据库生成枚举定义。 单个文件生成器获取一些源文件(例如* .aspx),并从中生成代码(例如* .designer.cs)。 能够做到这一点非常有用。

  • 第三种技术是前面两种技术的一种雌雄同体的变种:编写一个独立的工具来从数据库中生成您的枚举定义。 针对您的参考数据库运行该工具以重新生成文件并重新签入。

无论哪种方式,您都无法在不影响应用程序的情况下随意更改数据库中的查找表。 应用程序将不知道新添加的值; 删除或更改值可能会破坏事情。

但是大概,您想要枚举要完成的事情都是相对稳定的事情。

我将尝试一个答案。

如果您的想法是您的数据库中有魔术数字,它们本质上是const ,这意味着它们很少更改,那么我可以理解为什么您希望将它们表示为代码中可读的东西。

根据查询数据的方式,有多种方法可以将数据表示为枚举值:对于SqlDataReader只需将记录中的整数值转换为枚举即可:

int statusCodeValue = row["status"];
// But BEWARE as undefined values also upcast to the enum just fine
// but just won't have any of the defined values
if (!Enum.IsDefined(typeof(StatusCode), statusCodeValue)) throw new Exception();
StatusCode statusCode = (StatusCode)statusCodeValue;

如果您使用的是nHibernate或EF之类的ORM,则可以直接使用此映射。 只需在数据模型上定义enum属性,它将正确映射。

注意

您需要考虑谁是数据的所有者。 这些数据是来自另一个系统,还是您的应用程序生成了它?

如果是外部数据,则需要手动保持数据和应用程序枚举同步。 如果它是本地的,则只需确保为每个枚举成员分配一个整数值,并且切勿更改该整数值的语义表示形式。

你真的做不到。 您将需要一些辅助功能来帮助您。

enum StatusCode
{
   Open,
   Received,
   Delivered,
   Cancelled
}

private Dictionary<StatusCode, int> storedCodes = new Dictionary<StatusCode, int>();

public static int GetValue(StatusCode code)
{
    //Return code from database
    return storedCodes[code];
}

public static void SetValue(StatusCode code, int value)
{
    storedCodes[code] = value;
    //Set values from database
    //Note:  you can't set the value of your enums here
    //Just a place to set some other variables to remember what the values are.
}

从技术上讲,您可以通过使用文本模板来实现(例如折叠后的示例)。 但是,正如已经指出的那样,好的文本模板化并不容易,通常需要与应用程序代码一样多的维护。

相反,请查看并确定您的Enum是否真的是所有可能状态的受限列表。 如果您将状态存储在数据库表中,我会说不是。 在表中,从技术上来说,这些值将有可能更改(可以插入新值,删除旧值,甚至更改名称以使其不再反映其原始含义)。 只有用户约定才能阻止这种情况,并且在您的应用程序代码中不会立即显现出来。

重新考虑数据库设计可能会更好。 下一个陈述对我来说很难解释,但是我不希望基于子关系来推断实体状态。 相反,我更喜欢根据给定数据行的各个列值进行代码决策。

例如,而不是按照两个表:

CREATE TABLE status_codes (
  key INT PRIMARY KEY, 
  value VARCHAR(32)
);
CREATE TABLE entity (
  id INT PRIMARY KEY, 
  name VARCHAR(32), 
  status INT 
  CONSTRAINT fk_status FOREIGN KEY status REFERENCES status_codes(id)
);
INSERT INTO status_codes 
VALUES (20, 'Open')
      ,(21, 'Received')
      ,(22, 'Delivered')
      ,(23, 'Cancelled');

我会比较喜欢:

CREATE TABLE entity (
  id INT,
  name VARCHAR(32),
  is_received BIT NOT NULL,
  is_delivered BIT NOT NULL,
  is_cancelled BIT NOT NULL,
  is_closed BIT NOT NULL
);

对我来说,我发现在没有幻数的情况下更容易编写逻辑代码。

这样,您就不再需要执行以下操作:

/* SQL */
SELECT * FROM entity WHERE status = 20;

/* C# */
if (entity.Status == StatusCodes.Open) { /* do something */ }
if (entity.Status == 20) { /* do something */ }

而是这样做:

/* SQL */
SELECT * FROM entity WHERE is_closed = 0;

/* C# */
if (!entity.IsClosed) { /* do something */ }

您也可以采用数据库约束进行处理(例如,确保项目在交付之前不能被标记为已接收,并且如果已经交付则不能取消)。

我也将“是开放的”语义更改为“已关闭”,但这只是个人喜好,使做某事(即关闭)比什么都不做(即开放)更重要。

我还注意到有时您确实需要事物的“软”,用户可维护状态。 但是,我建议这些仅用于显示目的,并且不应该围绕这些“软”状态编写“硬”代码。

(如果您的应用程序打算成为高度可定制的现成产品,则可以考虑使软状态可用于脚本或规则引擎,因此定义状态的用户还可以围绕它们定义业务规则,但这超出了范围。)


综上所述,如果您真的真的需要做自己想做的事,下面是一种可能的方法...

首先,您需要一个枚举值的规范来源。 您的C#枚举正确,或者SQL定义正确。

一旦确定,我可能会使用某种形式的文本模板,例如T4或自定义脚本文件/已编译的exe来生成规范源代码。

C#代码是规范的

使用Enum.GetValues(typeof(StatusCode))Enum.GetName(typeof(StatusCode), value)反映您的Enum,然后使用它在目标表中生成CHECK约束。 将基本int类型存储在数据库表中(例如, ALTER TABLE my_table ADD status_code INT )。

// (untested, pseudo-ish code -- WATCH FOR INJECTION!)

StringBuilder sql = new StringBuilder();
sql.Append(@"ALTER TABLE [my_table] ADD CONSTRAINT chk_status_code CHECK (status_code IN (");

bool first = true;
foreach (var v in Enum.GetValues(typeof(StatusCode)) {
    if (!first) sql.Append(", ");
    sql.Append(v);
    first = false;
}

sql.Append("));");

// write sql to file, or run against the development database

这将使您非常接近可以在构建/安装上运行的SQL语句。 请注意,此操作不打算在数据库的正常操作期间运行。

如果需要此功能,则可能还需要生成一个内联表函数以将数字映射到名称,例如:

// (untested, pseudo-ish code -- WATCH FOR INJECTION!)

StringBuilder sql = new StringBuilder();
sql.AppendLine(@"IF OBJECT_ID('dbo.tf_status_codes') IS NULL EXECUTE('
                     CREATE FUNCTION dbo.tf_status_codes RETURNS TABLE AS RETURN (
                         SELECT ''not yet built'' AS err
                     )
                 ')");
sql.AppendLine(@"ALTER FUNCTION dbo.tf_status_codes RETURNS TABLE AS RETURN (")
   .AppendLine(@"  SELECT value, name FROM (VALUES ")

bool first = true;
foreach (var v in Enum.GetValues(typeof(StatusCode)) {
    if (!first) sql.AppendLine(",");
    sql.Append(@"    ({0}, '{1}')", 
               v,
               Enum.GetName(typeof(StatusCode), v));
    first = false;
}

sql.AppendLine(@"  ) e(value, name);")
   .AppendLine(@")";

// write sql to file, or run against the development database

作为构建后事件运行构建工具,以便在约束/表之前更新代码。

SQL表是规范的

尽管我会确保表源在生产迭代的生存期内不会更改(即仅在部署时定义),但这一点更为简单。 为此,我将枚举值定义为内联表函数,而不是表:

CREATE FUNCTION dbo.status_codes 
RETURNS TABLE 
AS RETURN (
    SELECT value, name
    FROM (VALUES (20, 'Open')
                ,(21, 'Received')
                ,(22, 'Delivered')
                ,(23, 'Cancelled')) AS v(value, name)
)

然后,在我的构建工具中,连接到数据库,检索值并生成枚举:

// untested, pseudo, assumes an existing database connection routine 
IDataReader reader = DB.GetReader("SELECT value, name FROM dbo.status_codes()");

StringBuilder code = new StringBuilder();
code.AppendLine("namespace MyApp {")
    .AppendLine("  public enum StatusCodes : int {");

bool first = true
while (reader.Read()) {
    if (!first) code.AppendLine(",");
    code.Append("    {0} = {1}", reader["name"], reader["value"]);
    first = false;
}

code.AppendLine("  }")
    .AppendLine("}");

// ...write the code to the Enum class file, and exit with 0 code

将构建工具作为Pre-Build事件运行(以便在构建代码之前生成代码)。

(正如我所说,上面的代码未经测试,也没有试图确保将其注入的安全性。使用风险自负并进行彻底测试)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM