一個用資料表保存C# Model的常見問題,列舉型別屬性該怎麼處理?
例如有個BlogUser資料物件,包含Id、Name及Role三個屬性,其中Role是列舉,包含Admin、Editor、Blogger、Reader等項目。保存BlogUser的資料表設計如下,Role欄位定義為VARCHAR(8),目標為直接保存"Admin"、"Blogger"等字串內容,以期在SQL可使用WHERE Role = 'Blogger'進行篩選。
CREATETABLE [dbo].[BlogUser] (
[Id] INTNOTNULL,
[Name] VARCHAR (16) NOTNULL,
[Role] VARCHAR (8) NOTNULL,
CONSTRAINT [PK_BlogUser] PRIMARYKEYCLUSTERED ([Id] ASC)
);
使用Dapper執行資料更新及查詢的程式範例如下:
using Dapper;
using System;
using System.Data.SqlClient;
using System.Linq;
namespace DapperLab
{
class Program
{
staticstring cnStr = "...由config取得連線字串(記得要加密),此處省略...";
publicenum Roles
{
Admin,
Editor,
Blogger,
Reader
}
publicclass BlogUser
{
publicint Id { get; set; }
publicstring Name { get; set; }
public Roles Role { get; set; }
}
staticvoid Main(string[] args)
{
using (var cn = new SqlConnection(cnStr))
{
var jeff = new BlogUser()
{
Id = 1,
Name = "Jeffrey",
Role = Roles.Blogger
};
cn.Execute("INSERT INTO BlogUser VALUES(@Id, @Name, @Role)", jeff);
var data = cn.Query<BlogUser>(
"SELECT * FROM BlogUser WHERE Id = @Id",
new { Id = 1 }).Single();
Console.WriteLine("{0} {1} {2}", data.Id, data.Name, data.Role);
}
}
}
}
測試結果Role列舉可以被寫入資料庫並正確還原,但Role欄位寫入的是Blogger列舉項目對應的數值"2"。
實測若在Role欄位存入'Blogger'也能正確還原回Roles.Blogger,但寫入時只能寫入數字讓人頭大。研究很久,一直試不出用列舉項目名稱取代數值寫入資料庫的做法。
昨天曾介紹過SqlMapper.TypeHandler<T>自訂轉換邏輯技巧,可惜無法適用列舉型別。由Dapper原始碼SqlMapper.cs邏輯,發現Dapper一旦偵測出IsEnum(),會無視TypeHandler設定直接使用Enum.ToObject()。
privatestatic T Parse<T>(objectvalue)
{
if (value == null || valueis DBNull) returndefault(T);
if (valueis T) return (T)value;
var type = typeof(T);
type = Nullable.GetUnderlyingType(type) ?? type;
if (type.IsEnum())
{
if (valueisfloat || valueisdouble || valueisdecimal)
{
value = Convert.ChangeType(value, Enum.GetUnderlyingType(type),
CultureInfo.InvariantCulture);
}
return (T)Enum.ToObject(type, value);
}
ITypeHandler handler;
if (typeHandlers.TryGetValue(type, out handler))
{
return (T)handler.Parse(type, value);
}
return (T)Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
}
關於Enum該不該支援TypeHandler範圍,Github上有不少相關討論並無共識,預期短期內此一行為不會有所改變。(查看原始碼時,意外發現Dapper竟動用ILGenerator動態組裝MSIL處理欄位對應,相當變態,也難怪執行效能讓其他Reflection競爭者看不到車尾燈)
找不到克服之道也不想修改Dapper核心,最後我採取的做法是另外宣告一個RoleText屬性,提供以字串讀取及設定Role屬性的管道,其值與Role列舉100%對應,至於資料表欄位則改為RoleText VARCHAR(16)。程式範例如下:
publicclass BlogUser
{
publicint Id { get; set; }
publicstring Name { get; set; }
public Roles Role { get; set; }
[JsonIgnore]
publicstring RoleText {
get
{
return Role.ToString();
}
set
{
Roles res;
if (!Enum.TryParse((string)value, out res))
{
thrownew ApplicationException(string.Format(
"Can't convert '{0}' to type [{1}]", value, typeof(Roles)));
}
Role = res;
}
}
}
以上是我處理Dapper儲存列舉型別的經驗供參,大家如果知道其他妙計,歡迎回饋!