簡體   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