[Entity Framework] Code-First 產生 Enumeration Code Table

Code-First 產生 Enumeration Code Table

前言

 

列舉型別 (Enumeration) 是由一組稱為列舉值清單的具名常數所構成的獨特型別,通常會將一些可列舉資料(如: 評等 甲、乙、丙、丁)宣告為此型態類別,以方便在程式中利用其"具名"特性來增加程式碼的可讀性。很不巧的在Entity Framework中,Code-First在生成實體資料庫時僅將列舉型別當作數字型態,因此在DB資料表中對應該列舉型別屬性的欄位型態將為數字,不具任何顯性意義。

 

因此筆者希望有一個對應各列舉型別選項的Code Table被生成(如下圖所示),而其他類別中只要使用到此列舉型別作為屬性型態時,都會與此Code Table建立關聯性。如何實現呢? 請參考以下說明。

 

image

 

 

實作說明

 

首先舉一個非常簡單的實例。筆者需要建立一個用戶(SysUser),其中需要紀載這個用戶偏好的介面語系(Language);因為網站所提供的語系只有三種,因此使用列舉方式記載。目前所需物件類別就是SysUser以及LanguageEnum,但由於EF不會針對Enum型別生成Code Table,因此將先從LanguageEnum著手進行調整。

 

網站提供三種語系,因此建立LanguageEnum來表列各語系

 


public enum LanguageEnum 
{ 
	[Description("zh-TW")] 
	ZhongHua = 1, 
	[Description("en-US")] 
	English = 2, 
	[Description("ja-JP")] 
	Japanese = 3 
}

 

由於筆者希望各列舉(Enum)物件都可生成對應資料表,因此建立一個通用泛型EnumBase基礎類別;其中Id將放置Enum值,而Name及Description將分別放置列舉型別常數名稱及描述。

 


public class EnumBase<TEnum> where TEnum : struct
{
	[Key]
	[DatabaseGenerated(DatabaseGeneratedOption.None)]
	public virtual TEnum Id { get; set; }
	
	[Required]
	[MaxLength(100)]
	public virtual string Name { get; set; }
	
	[MaxLength(100)]
	public virtual string Description { get; set; }

}

 

此時,就可以著手建立Code-First使用的Language類別。

 


 public class Language : EnumBase<LanguageEnum>
{
	// Extra Properties
	// ...
}

 

再建立測試用SysUser類別,其中包含上述Language資訊(建立關聯)。

 


public class SysUser
{
	// Properties
	[Key]
	[DisplayName("User Id")]
	[Required(ErrorMessage = "Please enter user id")]
	[MaxLength(50, ErrorMessage = "The length of member id cannot exceed 50 chars")]
	[Description("Logon system by user id")]
	public string SysUserId { get; set; }

	[DisplayName("Name")]
	[Required(ErrorMessage = "Please enter member name")]
	[MaxLength(10, ErrorMessage = "The length of user name cannot exceed 10 chars")]
	public string Name { get; set; }


	// Foreign Key
	public LanguageEnum LanguageId { get; set; }


	// Navigation Properties
	[JsonIgnore]
	[DisplayName("Language")]
	public virtual Language Language { get; set; }
}

 

由於需依據Enum列舉值清單來產生預設資料,因此建立SeedEnumData通用泛型方法,以此方法新增列舉項目至資料表中(Id: Enum值, Name: Enum名稱, Description: Enum描述),方便初始化資料庫時叫用。

 


public class SeedHelper
{
	public static void SeedEnumData<TData, TEnum>(IDbSet<TData> items)
	where TData : EnumBase<TEnum>
	where TEnum : struct
	{
		// Check if TEnum is Enum
		var etype = typeof(TEnum);
		if (etype.IsEnum == false)
		throw new ArgumentException(string.Format("Type '{0}' must be enum", etype.AssemblyQualifiedName));
		
		// Add each enum item to DB
		foreach (TEnum evalue in Enum.GetValues(etype))
		{
			var item = Activator.CreateInstance<TData>();
			item.Id = evalue;
			item.Name = Enum.GetName(etype, evalue);
			item.Description = GetEnumDescription(evalue);
			items.Add(item);
		}
	}
	  
	public static string GetEnumDescription<TEnum>(TEnum item)
	{
		Type type = item.GetType();
		var attribute = type.GetField(item.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false).Cast<DescriptionAttribute>().FirstOrDefault();
		return attribute == null ? string.Empty : attribute.Description;
	}
}

 

接著建立Custom DB Initialier來產生資料表預設資料。利用上述SeedEnumData方法,在初始資料庫時於Seed中新增列舉項目至DB。如果對Custom DB Initialier不熟悉可參考Code-First 建立資料庫初始值文章。

 


public class GekerDbInitializer : DropCreateDatabaseIfModelChanges<GekerDbContext>
{
	protected override void Seed(GekerDbContext context)
	{
		// add languages
		SeedHelper.SeedEnumData<Language, LanguageEnum>(context.Languages);
		context.SaveChanges();

		// add users
		context.SysUsers.Add(new SysUser
		{
			SysUserId = "wasi.chris",
			Name = "chris chen",
			LanguageId = LanguageEnum.Japanese
		});
		context.SaveChanges();
	}
}

 

最後就來建立DbContext,其中包含SysUser及Language資料表。由於我們希望在初始資料庫時,依據Enum列舉值清單來產生預設資料,因此會在建構子中設定上述建立的GekerDbInitialier作為DB Initialier。

 


public class GekerDbContext : DbContext
{
	// Coustructors
	public GekerDbContext()  : base("GekerDbContext")
	{        
		// Initialize DB by Custom DB Initializer
		Database.SetInitializer<GekerDbContext>(new GekerDbInitializer());
	}
	
	// Properties
	public DbSet<SysUser> SysUsers { get; set; }
	
	public DbSet<Language> Languages { get; set; }      
}

 

 

實作結果

 

執行程式後,資料庫及各資料表確實被建立。

 

image

 

並且確實產生LanguageEnum所有列舉項目於Language資料表中,達成所需功能。

 

image

 

 

參考資訊

 

http://gentlelogic.blogspot.tw/2014/07/ef-code-first-create-table-out-of-enum.html


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !