使用するEF5の列挙型と、対応するルックアップテーブルを定義したいと思います。 EF5が列挙型をサポートするようになったことは知っていますが、すぐに使用できるので、これはオブジェクトレベルでのみサポートされているようで、デフォルトではこれらのルックアップ値のテーブルは追加されません。
たとえば、次のユーザーエンティティがあります。
public class User
{
int Id { get; set; }
string Name { get; set; }
UserType UserType { get; set; }
}
そしてUserType列挙型:
public enum UserType
{
Member = 1,
Moderator = 2,
Administrator = 3
}
データベースを生成して、次のようなテーブルを作成したいと思います。
create table UserType
(
Id int,
Name nvarchar(max)
)
これは可能ですか?
直接はできません。 EFは.NETと同じレベルの列挙型をサポートしているため、列挙型の値は整数と呼ばれます=>クラスの列挙型プロパティは常にデータベースの整数列です。テーブルも必要な場合は、独自のデータベース初期化子でUser
の外部キーと一緒に手動で作成し、列挙値を入力する必要があります。
より複雑なマッピングを可能にするために、いくつかの ユーザーの声に関する提案 を作成しました。役に立つと思ったら、提案に投票することができます。
これは、ルックアップテーブルを生成して外部キーを適用し、ルックアップテーブルの行を列挙型と同期させておく、以前に作成したnugetパッケージです。
https://www.nuget.org/packages/ef-enum-to-lookup
それをプロジェクトに追加し、Applyメソッドを呼び出します。
Githubのドキュメント: https://github.com/timabell/ef-enum-to-lookup
UserEntitiesクラスで指定された列挙型のデータベーステーブルを作成する小さなヘルパークラスを作成しました。また、列挙型を参照するテーブルに外部キーを作成します。
だからここにあります:
public class EntityHelper
{
public static void Seed(DbContext context)
{
var contextProperties = context.GetType().GetProperties();
List<PropertyInfo> enumSets = contextProperties.Where(p =>IsSubclassOfRawGeneric(typeof(EnumSet<>),p.PropertyType)).ToList();
foreach (var enumType in enumSets)
{
var referencingTpyes = GetReferencingTypes(enumType, contextProperties);
CreateEnumTable(enumType, referencingTpyes, context);
}
}
private static void CreateEnumTable(PropertyInfo enumProperty, List<PropertyInfo> referencingTypes, DbContext context)
{
var enumType = enumProperty.PropertyType.GetGenericArguments()[0];
//create table
var command = string.Format(
"CREATE TABLE {0} ([Id] [int] NOT NULL,[Value] [varchar](50) NOT NULL,CONSTRAINT pk_{0}_Id PRIMARY KEY (Id));", enumType.Name);
context.Database.ExecuteSqlCommand(command);
//insert value
foreach (var enumvalue in Enum.GetValues(enumType))
{
command = string.Format("INSERT INTO {0} VALUES({1},'{2}');", enumType.Name, (int)enumvalue,
enumvalue);
context.Database.ExecuteSqlCommand(command);
}
//foreign keys
foreach (var referencingType in referencingTypes)
{
var tableType = referencingType.PropertyType.GetGenericArguments()[0];
foreach (var propertyInfo in tableType.GetProperties())
{
if (propertyInfo.PropertyType == enumType)
{
var command2 = string.Format("ALTER TABLE {0} WITH CHECK ADD CONSTRAINT [FK_{0}_{1}] FOREIGN KEY({2}) REFERENCES {1}([Id])",
tableType.Name, enumProperty.Name, propertyInfo.Name
);
context.Database.ExecuteSqlCommand(command2);
}
}
}
}
private static List<PropertyInfo> GetReferencingTypes(PropertyInfo enumProperty, IEnumerable<PropertyInfo> contextProperties)
{
var result = new List<PropertyInfo>();
var enumType = enumProperty.PropertyType.GetGenericArguments()[0];
foreach (var contextProperty in contextProperties)
{
if (IsSubclassOfRawGeneric(typeof(DbSet<>), contextProperty.PropertyType))
{
var tableType = contextProperty.PropertyType.GetGenericArguments()[0];
foreach (var propertyInfo in tableType.GetProperties())
{
if (propertyInfo.PropertyType == enumType)
result.Add(contextProperty);
}
}
}
return result;
}
private static bool IsSubclassOfRawGeneric(Type generic, Type toCheck)
{
while (toCheck != null && toCheck != typeof(object))
{
var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
if (generic == cur)
{
return true;
}
toCheck = toCheck.BaseType;
}
return false;
}
public class EnumSet<T>
{
}
}
コードの使用:
public partial class UserEntities : DbContext{
public DbSet<User> User { get; set; }
public EntityHelper.EnumSet<UserType> UserType { get; set; }
public static void CreateDatabase(){
using (var db = new UserEntities()){
db.Database.CreateIfNotExists();
db.Database.Initialize(true);
EntityHelper.Seed(db);
}
}
}
パッケージを作成しました
https://www.nuget.org/packages/SSW.Data.EF.Enums/1.0.
使用する
EnumTableGenerator.Run("your object context", "Assembly that contains enums");
「オブジェクトコンテキスト」-EntityFrameworkDbContext「列挙型を含むアセンブリ」-列挙型を含むアセンブリ
シード関数の一部としてEnumTableGenerator.Runを呼び出します。これにより、各列挙型のSQLサーバーにテーブルが作成され、正しいデータが入力されます。
@HerrKaterからいくつかの追加の変更を加えたので、この回答を含めました
Herr Katerの回答 (これもTim Abellのコメントに基づいています)に少し追加しました。更新は、メソッドを使用してDisplayName属性から列挙値を取得することです(存在する場合)。それ以外の場合は、PascalCase列挙値を分割します。
private static string GetDisplayValue(object value)
{
var fieldInfo = value.GetType().GetField(value.ToString());
var descriptionAttributes = fieldInfo.GetCustomAttributes(
typeof(DisplayAttribute), false) as DisplayAttribute[];
if (descriptionAttributes == null) return string.Empty;
return (descriptionAttributes.Length > 0)
? descriptionAttributes[0].Name
: System.Text.RegularExpressions.Regex.Replace(value.ToString(), "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", "$1 ");
}
Herr Katersの例を更新して、メソッドを呼び出します。
command = string.Format("INSERT INTO {0} VALUES({1},'{2}');", enumType.Name, (int)enumvalue,
GetDisplayValue(enumvalue));
列挙型の例
public enum PaymentMethod
{
[Display(Name = "Credit Card")]
CreditCard = 1,
[Display(Name = "Direct Debit")]
DirectDebit = 2
}