在数据库中存储枚举的最佳方法
什么是使用C#和Visual Studio和MySQL数据连接器将Enum存储在数据库中的最佳方法。在数据库中存储枚举的最佳方法
我打算创建一个超过100个枚举的新项目,其中大部分都必须存储在数据库中。为每一个创建转换器将是一个漫长的过程,因此我想知道如果视觉工作室或某人有任何方法,我没有听说过。
[Required]
public virtual int PhoneTypeId
{
get
{
return (int)this.PhoneType;
}
set
{
PhoneType = (PhoneTypes)value;
}
}
[EnumDataType(typeof(PhoneTypes))]
public PhoneTypes PhoneType { get; set; }
public enum PhoneTypes
{
Mobile = 0,
Home = 1,
Work = 2,
Fax = 3,
Other = 4
}
就像一个魅力!无需在代码中转换(int)Enum或(Enum)int。首先使用enum和ef代码将为您保存int。 p.s .:“[EnumDataType(typeof(PhoneTypes))]”属性不是必需的,如果需要附加功能,则只需额外添加一个。
或者你可以这样做:
[Required]
public virtual int PhoneTypeId { get; set; }
[EnumDataType(typeof(PhoneTypes))]
public PhoneTypes PhoneType
{
get
{
return (PhoneTypes)this.PhoneTypeId;
}
set
{
this.PhoneTypeId = (int)value;
}
}
最后,您将需要一个伟大的方式来处理重复的编码任务,如枚举转换器。您可以使用代码生成器(例如MyGeneration或CodeSmith等等)或者可以使用ORM mapper like nHibernate来为您处理所有事情。
至于结构......数百枚举我会首先考虑尝试对数据进行组织成一个单一的表,可能是这个样子的:(伪SQL)
MyEnumTable(
EnumType as int,
EnumId as int PK,
EnumValue as int)
,它会让你将枚举信息存储在单个表中。 EnumType也可以是定义不同枚举的表的外键。
您的biz对象将通过EnumId链接到此表。枚举类型仅用于UI中的组织和过滤。利用所有这些当然取决于你的代码结构和问题领域。
顺便说一句,在这种情况下,你想要在EnumType上设置聚簇索引,而不是保留在PKey上创建的默认簇idx。
存储枚举的有趣方式。但为什么我想使用枚举(基于C#)的主要方式是为了可读性,例如。 if(something.Type == Enum.EnumType)这个项目将是一个非常漫长而复杂的项目,并且100枚枚举将很难保持每次跟踪和返回数据库。 – 2010-04-15 15:33:19
我们存储我们的整数或长整数,然后我们可以来回施放它们。可能不是最强大的解决方案,但那是我们所做的。
我们使用强类型DataSet,因此,例如:
eunum BlockTreatmentType
{
All = 0
};
// blockTreatmentType is an int property
blockRow.blockTreatmentType = (int)BlockTreatmentType.All;
BlockTreatmentType btt = (BlockTreatmentType)blockRow.blocktreatmenttype;
你如何投射他们?你存储什么?序号值或自定义属性? – 2010-04-15 15:38:17
添加了一个代码片段 – 2010-04-15 16:10:34
这个接缝就像一个可接受的解决方案。我会暂时把问题留给一些人看看人们想出了什么。我假设您手动设置枚举的int值以防止订单/基数变化? – 2010-04-15 17:14:12
有些事情你应该考虑。
枚举列是否会被其他应用程序直接使用,例如报告。这将限制枚举以整数格式存储的可能性,因为除非报表具有自定义逻辑,否则该值在报表中存在时没有意义。
您的应用程序有哪些国际化需求?如果它只支持一种语言,则可以将枚举另存为文本,并创建一个帮助器方法以从描述字符串进行转换。您可以使用[DescriptionAttribute]
来进行转换,并且可以通过搜索SO来找到转换的方法。
另一方面,如果您需要支持多种语言和外部应用程序访问您的数据,您可以开始考虑枚举是否真的是答案。如果场景更复杂,可以考虑其他选项,如查找表。
枚举非常出色,当它们自身包含在代码中时......当它们跨越边界时,事情往往会变得有点混乱。
更新:
您可以使用Enum.ToObject
法整数转换。这意味着您在转换时知道枚举的类型。如果你想使它完全通用,你需要将枚举的类型和它在数据库中的值一起存储。您可以创建数据字典支持表来告诉您哪些列是枚举以及它们是什么类型。
理想情况下,报告将使用包含枚举的相同库进行构建,因此将整数存储在数据库中是完全正确的。唯一的问题是,当你从数据库中提取如何转换为原始枚举? (100+枚枚举,每枚枚举值为5+) – 2010-04-15 15:37:44
我不确定它是否最灵活,但您可以简单地存储它们的字符串版本。这当然是可读的,但可能难以维护。枚举从字符串转换和背部很容易:
public enum TestEnum
{
MyFirstEnum,
MySecondEnum
}
static void TestEnums()
{
string str = TestEnum.MyFirstEnum.ToString();
Console.WriteLine("Enum = {0}", str);
TestEnum e = (TestEnum)Enum.Parse(typeof(TestEnum), "MySecondEnum", true);
Console.WriteLine("Enum = {0}", e);
}
如果你希望所有的枚举值的一个商店,你可以试试下面的表来存储枚举及其成员,以及代码片段添加这些值。我只会在安装时执行此操作,但是,因为在重新编译之前这些值永远不会改变!
DB表:
create table EnumStore (
EnumKey int NOT NULL identity primary key,
EnumName varchar(100)
);
GO
create table EnumMember (
EnumMemberKey int NOT NULL identity primary key,
EnumKey int NOT NULL,
EnumMemberValue int,
EnumMemberName varchar(100)
);
GO
--add code to create foreign key between tables, and index on EnumName, EnumMemberValue, and EnumMemberName
C#代码片段:
void StoreEnum<T>() where T: Enum
{
Type enumToStore = typeof(T);
string enumName = enumToStore.Name;
int enumKey = DataAccessLayer.CreateEnum(enumName);
foreach (int enumMemberValue in Enum.GetValues(enumToStore))
{
string enumMemberName = Enum.GetName(enumToStore, enumMemberValue);
DataAccessLayer.AddEnumMember(enumKey, enumMemberValue, enumMemberName);
}
}
为什么不尝试从数据库中完全分离枚举?我发现这篇文章是一个很好的参考上类似工作时:不管什么DB使用
http://stevesmithblog.com/blog/reducing-sql-lookup-tables-and-function-properties-in-nhibernate/
在它的想法应该适用。例如,在MySQL中,你可以使用“枚举”数据类型强制遵守的编码枚举:
http://dev.mysql.com/doc/refman/5.0/en/enum.html
干杯
如果你需要存储在枚举领域的DB字符串值,更好地做到像如下所示。 例如,如果您正在使用不支持枚举字段的SQLite,则可能需要此选项。
[Required]
public string PhoneTypeAsString
{
get
{
return this.PhoneType.ToString();
}
set
{
PhoneType = (PhoneTypes)Enum.Parse(typeof(PhoneTypes), value, true);
}
}
public PhoneTypes PhoneType{get; set;};
public enum PhoneTypes
{
Mobile = 0,
Home = 1,
Work = 2,
Fax = 3,
Other = 4
}
根据你转换这些(例如50k对象)的频率,你最好使用一个带有切换方法的转换器/扩展方法作为ToString,而Enum.Parse使用反射。只是FYI – 2016-06-16 07:27:07
是的。我同意。但对于小负载容易的解决方案是适合的。 – trueboroda 2016-06-16 09:14:19
可以通过为ID列名与表名匹配的每个枚举创建一致的表来使用数据库第一种方法。将数据库中的枚举值用于支持视图中的外键约束和友好列是有利的。目前,我们支持分散在大量版本化数据库中的大约100个枚举类型。
对于Code-First首选项,下面显示的T4策略可能会颠倒写入数据库。
create table SomeSchema.SomeEnumType (
SomeEnumTypeId smallint NOT NULL primary key,
Name varchar(100) not null,
Description nvarchar(1000),
ModifiedUtc datetime2(7) default(sysutcdatetime()),
CreatedUtc datetime2(7) default(sysutcdatetime()),
);
可以使用T4 template (*.tt) script将每个表导入到C#中。
- 创建一个“枚举项目”。添加下面显示的.tt文件。
- 为每个数据库架构名称创建一个子文件夹。
- 对于每个枚举类型,创建一个名称为SchemaName.TableName.tt的文件。文件 内容都是一样的一行:<#@包括 文件=“.. \ EnumGenerator.ttinclude”#>
- 然后创建/更新的枚举,用鼠标右键单击1个或更多的文件和 “运行自定义工具“(我们还没有自动更新)。它将添加/更新cs文件到项目:
using System.CodeDom.Compiler; namespace TheCompanyNamespace.Enumerations.Config { [GeneratedCode("Auto Enum from DB Generator", "10")] public enum DatabasePushJobState { Undefined = 0, Created = 1, } public partial class EnumDescription { public static string Description(DatabasePushJobState enumeration) { string description = "Unknown"; switch (enumeration) { case DatabasePushJobState.Undefined: description = "Undefined"; break; case DatabasePushJobState.Created: description = "Created"; break; } return description; } } // select DatabasePushJobStateId, Name, coalesce(Description,Name) as Description // from TheDefaultDatabase.[SchName].[DatabasePushJobState] // where 1=1 order by DatabasePushJobStateId }
最后,有些粗糙的T4脚本(从众多的解决办法简化)。它需要根据您的环境进行定制。调试标志可以将消息输出到C#中。右键单击.tt文件时还有一个“调试T4模板”选项。 EnumGenerator.ttinclude:
<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".generated.cs" #>
<#@ Assembly Name="EnvDTE" #>
<#@ Assembly Name="System.Core" #>
<#@ Assembly Name="System.Data" #>
<#@ assembly name="$(TargetPath)" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Text.RegularExpressions" #>
<#
bool doDebug = false; // include debug statements to appear in generated output
string schemaTableName = Path.GetFileNameWithoutExtension(Host.TemplateFile);
string schema = schemaTableName.Split('.')[0];
string tableName = schemaTableName.Split('.')[1];
string path = Path.GetDirectoryName(Host.TemplateFile);
string enumName = tableName;
string columnId = enumName + "Id";
string columnName = "Name";
string columnDescription = "Description";
string currentVersion = CompanyNamespace.Enumerations.Constants.Constants.DefaultDatabaseVersionSuffix;
// Determine Database Name using Schema Name
//
Dictionary<string, string> schemaToDatabaseNameMap = new Dictionary<string, string> {
{ "Cfg", "SomeDbName" + currentVersion },
{ "Common", "SomeOtherDbName" + currentVersion }
// etc.
};
string databaseName;
if (!schemaToDatabaseNameMap.TryGetValue(schema, out databaseName))
{
databaseName = "TheDefaultDatabase"; // default if not in map
}
string connectionString = @"Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=" + databaseName + @";Data Source=Machine\Instance";
schema = "[" + schema + "]";
tableName = "[" + tableName + "]";
string whereConstraint = "1=1"; // adjust if needed for specific tables
// Get containing project
IServiceProvider serviceProvider = (IServiceProvider)Host;
DTE dte = (DTE)serviceProvider.GetService(typeof(DTE));
Project project = dte.Solution.FindProjectItem(Host.TemplateFile).ContainingProject;
#>
using System;
using System.CodeDom.Compiler;
namespace <#= project.Properties.Item("DefaultNamespace").Value #><#= Path.GetDirectoryName(Host.TemplateFile).Remove(0, Path.GetDirectoryName(project.FileName).Length).Replace("\\", ".") #>
{
/// <summary>
/// Auto-generated Enumeration from Source Table <#= databaseName + "." + schema + "." + tableName #>. Refer to end of file for SQL.
/// Please do not modify, your changes will be lost!
/// </summary>
[GeneratedCode("Auto Enum from DB Generator", "10")]
public enum <#= enumName #>
{
<#
SqlConnection conn = new SqlConnection(connectionString);
// Description is optional, uses name if null
string command = string.Format(
"select {0}, {1}, coalesce({2},{1}) as {2}" + "\n from {3}.{4}.{5}\n where {6} order by {0}",
columnId, // 0
columnName, // 1
columnDescription, // 2
databaseName, // 3
schema, // 4
tableName, // 5
whereConstraint); // 6
#><#= DebugCommand(databaseName, command, doDebug) #><#
SqlCommand comm = new SqlCommand(command, conn);
conn.Open();
SqlDataReader reader = comm.ExecuteReader();
bool loop = reader.Read();
while(loop)
{
#> /// <summary>
/// <#= reader[columnDescription] #>
/// </summary>
<#= Pascalize(reader[columnName]) #> = <#= reader[columnId] #><# loop = reader.Read(); #><#= loop ? ",\r\n" : string.Empty #>
<#
}
#> }
/// <summary>
/// A helper class to return the Description for each enumeration value
/// </summary>
public partial class EnumDescription
{
public static string Description(<#= enumName #> enumeration)
{
string description = "Unknown";
switch (enumeration)
{<#
conn.Close();
conn.Open();
reader = comm.ExecuteReader();
loop = reader.Read();
while(loop)
{#>
case <#= enumName #>.<#= Pascalize(reader[columnName]) #>:
description = "<#= reader[columnDescription].ToString().Replace("\"", "\\\"") #>";
break;
<# loop = reader.Read(); #>
<#
}
conn.Close();
#>
}
return description;
}
}
/*
<#= command.Replace("\n", "\r\n ") #>
*/
}
<#+
private string Pascalize(object value)
{
Regex rxStartsWithKeyWord = new Regex(@"^[0-9]|^abstract$|^as$|^base$|^bool$|^break$|^byte$|^case$|^catch$|^char$|^checked$|^class$|^const$|^continue$|^decimal$|^default$|^delegate$|^do$|^double$|^else$|^enum$|^event$|^explicit$|^extern$|^$false|^finally$|^fixed$|^float$|^for$|^foreach$|^goto$|^if$|^implicit$|^in$|^int$|^interface$|^internal$|^is$|^lock$|^long$|^namespace$|^new$|^null$|^object$|^operator$|^out$|^overrride$|^params$|^private$|^protected$|^public$|^readonly$|^ref$|^return$|^sbyte$|^sealed$|^short$|^sizeof$|^stackalloc$|^static$|^string$|^struct$|^switch$|^this$|^thorw$|^true$|^try$|^typeof$|^uint$|^ulong$|^unchecked$|^unsafe$|^ushort$|^using$|^virtual$|^volatile$|^void$|^while$", RegexOptions.Compiled);
Regex rx = new Regex(@"(?:[^a-zA-Z0-9]*)(?<first>[a-zA-Z0-9])(?<reminder>[a-zA-Z0-9]*)(?:[^a-zA-Z0-9]*)");
string rawName = rx.Replace(value.ToString(), m => m.Groups["first"].ToString().ToUpper() + m.Groups["reminder"].ToString());
if (rxStartsWithKeyWord.Match(rawName).Success)
rawName = "_" + rawName;
return rawName;
}
private string DebugCommand(string databaseName, string command, bool doDebug)
{
return doDebug
? " // use " + databaseName + "; " + command + ";\r\n\r\n"
: "";
}
#>
希望实体框架将支持有一天这些答案的组合来提供记录和数据库值的镜像中的C#枚举强类型。
+1我对这样的事情是否应该用单独的表和FK约束强制执行或者如果任何人有任何意见时只是常规约束感兴趣? – 2010-04-15 15:13:57
@Martin没有计划回答,因为这往往是相当主观的,但对于OLTP/ODS,我会使用具有FK约束的单独表格。对于DSS报告解决方案,我会将枚举的符号名称(或描述)与其他事实进行规范化并存储在报告表中。 – 2010-04-15 16:34:46