我在数据库中有一个状态表,其值在整个应用程序中使用。状态表将具有(ID,NAME)。我想创建一个StatusEnum,我可以在我的应用程序代码中使用它。如何使用数据库中的值创建ENUM?
目前我有这样的枚举
enum StatusCode: int
{
Open = 20,
Received = 21,
Delivered= 22,
Cancelled = 23
}
但我想从数据库中设置值。
答案 0 :(得分:1)
你可以
手动保持枚举定义与数据库同步。这是最简单的方法。
您可以write a single file generator用于Visual Studio(也称为"Custom Tool"),并从一些参考数据库生成枚举定义。单个文件生成器获取一些源文件(例如,* .aspx)并从中生成代码(例如* .designer.cs)。能够做到这一点非常有用。
第三种技术是前两种技术的雌雄同体变体:编写一个独立的工具来从数据库生成枚举定义。针对参考DB运行该工具以重新生成文件并将其重新签入。
无论哪种方式,您都无法在不影响应用程序的情况下更改数据库中的查找表。应用程序将不知道新添加的值;删除或更改的值可能会破坏事物。
但据推测,你想要枚举的各种东西都是相对稳定的东西。
答案 1 :(得分:0)
我打算回答一下。
如果你的想法是你的数据库中有魔术数字,本质上是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,那么您可以开箱即用。只需在数据模型上定义枚举属性,它就会正确映射。
您需要考虑谁是数据的所有者。这些数据是来自另一个系统,还是您的应用程序生成它?
如果它是外国的,您需要手动保持数据和应用程序枚举。 如果它是本地的,只需确保为每个枚举成员分配一个整数值,并且永远不要从语义上应该表示的整数值进行更改。
答案 2 :(得分:-1)
你真的不能这样做。你需要一些辅助函数来帮助你。
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.
}
答案 3 :(得分:-1)
在技术上,您所要求的是文本模板(示例是在折叠之后)。但是,正如已经指出的那样,良好的文本模板并不容易,通常需要与应用程序代码一样多的维护。
相反,请查看并确定您的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 */ }
您还可以为流程使用数据库约束(例如,确保项目在交付之前无法标记,如果已经交付,则无法取消)。
我也改变了“开放式”#39;关闭语义是关闭的,但这只是个人偏好做某事(即结束)比没有做任何事情(即开放)更重要。
我还注意到,有时你需要软件用户可维护状态。但是,我建议这些应仅用于显示目的,并且您不应该写“难以”。代码围绕着这些&#39; soft&#39;状态。
(如果您的应用程序旨在成为高度可定制的现成产品,您可能会考虑为脚本或规则引擎提供软状态,因此定义状态的用户也可以定义业务围绕它们的规则,但这远远超出了范围。)
尽管如此,如果你真的觉得你需要按照自己的意愿行事,那么下面就有一种可能的方法......
首先,您需要一个规范的枚举值来源。您的C#Enum是正确的,或者SQL定义是正确的。
一旦决定了,我可能会使用某种形式的文本模板,比如T4或自定义脚本文件/编译的exe来生成不是规范源的那个。< / p>
使用Enum.GetValues(typeof(StatusCode))
和Enum.GetName(typeof(StatusCode), value)
反映您的枚举,然后使用它在目标表格中生成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
将构建工具作为Post-Build事件运行,因此代码在约束/表之前更新。
这个更简单,虽然我会确保表源在生产迭代的生命周期内不会改变(即仅在部署时定义)。为此,我将我的枚举值定义为内联表函数,而不是表:
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事件运行(因此代码在构建之前生成)。
(正如我所说,上面的代码是未经测试的,并且没有尝试确保注射安全。使用风险并彻底测试)