Dapper筆記:列舉轉VARCHAR研究

一個用資料表保存C# Model的常見問題,列舉型別屬性該怎麼處理?

例如有個BlogUser資料物件,包含Id、Name及Role三個屬性,其中Role是列舉,包含Admin、Editor、Blogger、Reader等項目。保存BlogUser的資料表設計如下,Role欄位定義為VARCHAR(8),目標為直接保存"Admin"、"Blogger"等字串內容,以期在SQL可使用WHERE Role = 'Blogger'進行篩選。

CREATE TABLE [dbo].[BlogUser] (
    [Id]   INT          NOT NULL,
    [Name] VARCHAR (16) NOT NULL,
    [Role] VARCHAR (8)  NOT NULL,
    CONSTRAINT [PK_BlogUser] PRIMARY KEY CLUSTERED ([Id] ASC)
);

使用Dapper執行資料更新及查詢的程式範例如下:

using Dapper;
using System;
using System.Data.SqlClient;
using System.Linq;
 
namespace DapperLab
{
    class Program
    {
        static string cnStr = "...由config取得連線字串(記得要加密),此處省略...";
 
        public enum Roles
        {
            Admin,
            Editor,
            Blogger,
            Reader
        }
 
        public class BlogUser
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public Roles Role { get; set; }
        }
 
        static void 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()。

private static T Parse<T>(object value)
{
    if (value == null || value is DBNull) return default(T);
    if (value is T) return (T)value;
    var type = typeof(T);
    type = Nullable.GetUnderlyingType(type) ?? type;
    if (type.IsEnum())
    {
        if (value is float || value is double || value is decimal)
        {
            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)。程式範例如下:

        public class BlogUser
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public Roles Role { get; set; }
 
            [JsonIgnore]
            public string RoleText {
                get
                {
                    return Role.ToString();
                }
                set
                {
                    Roles res;
                    if (!Enum.TryParse((string)value, out res))
                    {
                        throw new ApplicationException(string.Format(
                            "Can't convert '{0}' to type [{1}]", value, typeof(Roles)));
                    }
                    Role = res;
                }
            }
        }

以上是我處理Dapper儲存列舉型別的經驗供參,大家如果知道其他妙計,歡迎回饋!

歡迎推文分享:
Published 18 July 2016 09:46 PM 由 Jeffrey
Filed under:
Views: 4,013



意見

沒有意見

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<July 2016>
SunMonTueWedThuFriSat
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456
 
RSS
創用 CC 授權條款
【廣告】
twMVC

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication