What is the best method of storing an Enum in a Database using C# And Visual Studio and MySQL Data Connector.
I am going to be creating a new project with over 100 E
A DB first approach can be used by creating a consistent table for each enum where the Id column name matches table name. It's advantageous to have enum values available within the database to support foreign key constraints and friendly columns in views. We are currently supporting ~100 enum types scattered throughout numerous versioned databases.
For a Code-First preference, the T4 strategy shown below could probably be reversed to write to the database.
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()),
);
Each table can be imported into C# using a T4 template (*.tt) script.
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
}
And finally, the somewhat gnarly T4 script (simplified from numerous workarounds). It will need to be customized to your environment. A debug flag can output messages into the C#. There is also a "Debug T4 Template" option when right clicking the .tt file. 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 schemaToDatabaseNameMap = new Dictionary {
{ "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("\\", ".") #>
{
///
/// 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!
///
[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)
{
#> ///
/// <#= reader[columnDescription] #>
///
<#= Pascalize(reader[columnName]) #> = <#= reader[columnId] #><# loop = reader.Read(); #><#= loop ? ",\r\n" : string.Empty #>
<#
}
#> }
///
/// A helper class to return the Description for each enumeration value
///
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]*)(?[a-zA-Z0-9])(?[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"
: "";
}
#>
Hopefully the entity framework will someday support a combination of these answers to offer the C# enum strong typing within records and database mirroring of values.