Meilleure méthode pour stocker Enum dans la base de données
Quelle est la meilleure méthode pour stocker une énumération dans une base de données en utilisant C# et Visual Studio et MySQL Data Connector.
Je vais créer un nouveau projet avec plus de 100 énumérations, et la majorité d'entre elles devront être stockées dans la base de données. Créer des convertisseurs pour chacun serait un processus long, donc je me demande si visual studio ou quelqu'un a des méthodes pour cela que je n'ai pas entendues.
9 réponses
[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
}
Fonctionne comme un charme! Pas besoin de convertir (int)Enum ou (Enum)int dans le code. Il suffit d'utiliser le code enum et ef d'abord sauvera l'int pour vous. PS:" [EnumDataType(typeof(PhoneTypes))] " attribut n'est pas nécessaire, juste un supplément si vous voulez des fonctionnalités supplémentaires.
Vous pouvez également faire:
[Required]
public virtual int PhoneTypeId { get; set; }
[EnumDataType(typeof(PhoneTypes))]
public PhoneTypes PhoneType
{
get
{
return (PhoneTypes)this.PhoneTypeId;
}
set
{
this.PhoneTypeId = (int)value;
}
}
Nous stockons les nôtres comme des ints ou des longs et ensuite nous pouvons les lancer d'avant en arrière. Probablement pas la solution la plus robuste, mais ce que nous faisons.
Nous utilisons des jeux de données typés, par exemple:
eunum BlockTreatmentType
{
All = 0
};
// blockTreatmentType is an int property
blockRow.blockTreatmentType = (int)BlockTreatmentType.All;
BlockTreatmentType btt = (BlockTreatmentType)blockRow.blocktreatmenttype;
En fin de compte, vous aurez besoin d'un excellent moyen de gérer les tâches de codage répétitives telles que les convertisseurs enum. Vous pouvez utiliser un générateur de code tel que MyGeneration ou CodeSmith parmi beaucoup d'autres ou peut-être un ORM mapper comme nHibernate pour gérer tout pour vous.
Comme pour la structure... avec des centaines d'énumérations, j'envisagerais d'abord d'essayer d'organiser les données en une seule table qui pourrait ressembler à ceci: (pseudo sql)
MyEnumTable(
EnumType as int,
EnumId as int PK,
EnumValue as int )
Ce serait vous permettent de stocker vos informations enum dans une seule table. EnumType peut également être une clé étrangère à une table qui définit les différentes énumérations.
Vos objets biz seraient liés à cette table via EnumId. Le type enum n'est là que pour l'organisation et le filtrage dans L'interface utilisateur. L'utilisation de tout cela dépend bien sûr de votre structure de code et de votre domaine problématique.
Btw, dans ce scénario, vous souhaitez définir un index en cluster sur EnumType plutôt que de laisser l'IDX de cluster par défaut qui est créé sur la clé PKey.
Certaines choses que vous devriez prendre en considération.
Est-ce que la colonne d'énumération va être utilisée directement par d'autres applications comme par exemple les rapports. Cela limitera la possibilité que l'énumération soit stockée dans son format entier car cette valeur n'aura aucune signification lorsqu'elle est présente dans un rapport, sauf si les rapports ont une logique personnalisée.
Quels sont les besoins i18n pour votre application? S'il ne prend en charge qu'une seule langue vous pouvez enregistrer l'énumération en tant que texte et créer un méthode d'aide pour convertir à partir d'une chaîne de description. Vous pouvez utiliser [DescriptionAttribute]
pour cela et les méthodes de conversion peuvent probablement être trouvées en recherchant SO.
Si d'un autre côté vous avez besoin de prendre en charge l'accès à plusieurs langues et applications externes à vos données, vous pouvez commencer à considérer si l'énumération est vraiment la réponse. Une autre option comme les tables de recherche peut être envisagée si le scénario est plus complexe.
Les énumérations sont excellentes lorsqu'elles sont autonomes dans le code... quand ils traversent cette frontière, les choses ont tendance à devenir un peu en désordre.
Mise à Jour:
Vous pouvez convertir à partir d'un entier en utilisant la méthode Enum.ToObject
. Cela implique que vous connaissez le type de l'énumération lors de la conversion. Si vous voulez le rendre complètement Générique, vous devez stocker le type de l'énumération à côté de sa valeur dans la base de données. Vous pouvez créer des tables de support de dictionnaire de données pour vous indiquer quelles colonnes sont des énumérations et quel type sont-elles.
Si vous voulez un magasin de toutes vos valeurs enums, vous pouvez essayer les tables ci-dessous pour stocker les enums et leurs membres, et l'extrait de code pour ajouter ces valeurs. Je ne le ferais qu'au moment de l'installation, cependant, puisque ces valeurs ne changeront jamais tant que vous ne recompilerez pas!
Table 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
Extrait De Code 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);
}
}
Si vous avez besoin de stocker dans les valeurs de chaîne DB du champ enum, mieux vaut faire comme show ci-dessous. Par exemple, il peut être nécessaire si vous utilisez SQLite, qui ne prend pas en charge les champs enum.
[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
}
Je ne suis pas sûr si c'est le plus flexible, mais vous pouvez simplement stocker les versions de chaîne d'entre eux. Il est certainement lisible, mais peut-être difficile à maintenir. Enums convertir des chaînes et retour assez facilement:
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 );
}
Pourquoi ne pas essayer de séparer complètement les énumérations de la base de données? J'ai trouvé cet article comme une excellente référence tout en travaillant sur quelque chose de similaire:
Http://stevesmithblog.com/blog/reducing-sql-lookup-tables-and-function-properties-in-nhibernate/
Les idées qu'il contient devraient s'appliquer indépendamment de la base de données que vous utilisez. Par exemple, dans MySQL, vous pouvez utiliser le type de données "enum" pour appliquer la conformité avec votre code les énumérations:
Http://dev.mysql.com/doc/refman/5.0/en/enum.html
Santé
Une première approche DB peut être utilisée en créant une table cohérente pour chaque énumération où le nom de la colonne Id correspond au nom de la table. Il est avantageux d'avoir des valeurs enum disponibles dans la base de données pour prendre en charge les contraintes de clé étrangère et les colonnes conviviales dans les vues. Nous supportons actuellement ~100 types enum dispersés dans de nombreuses bases de données versionnées.
Pour une préférence Code-First, La stratégie T4 indiquée ci-dessous pourrait probablement être inversée pour écrire dans le la base de données.
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()),
);
Chaque table peut être importée en C # à l'aide d'un script T4 template (*.tt) .
- créer un "projet D'énumération". Ajoutez le fichier. tt illustré ci-dessous.
- créez un sous-dossier pour chaque nom de schéma de base de données.
- pour chaque type enum, créez un fichier dont le nom est SchemaName.TableName.tt. le fichier le contenu est toujours la même ligne:
- ensuite, pour créer / mettre à jour les énumérations, faites un clic droit sur 1 ou plusieurs fichiers et "Exécuter L'outil personnalisé "(nous n'avons pas encore de mise à jour automatique). Il va ajouter/mettre à jour un .fichier cs au projet:
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 }
Et enfin, le script T4 un peu noueux (simplifié à partir de nombreuses solutions de contournement). Il devra être personnalisé à votre environnement. Un drapeau de débogage peut sortir des messages dans le C#. Il existe également une option "Debug T4 Template" lorsque vous cliquez avec le bouton droit sur le fichier .tt. 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"
: "";
}
#>
Espérons que le cadre d'entité un jour soutenir une combinaison de ces réponses pour offrir le typage c# enum forte dans les enregistrements et la mise en miroir de la base de données des valeurs.