Quantcast
Channel: 黑暗執行緒
Viewing all articles
Browse latest Browse all 2311

Dapper筆記:列舉轉VARCHAR研究

$
0
0

一個用資料表保存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儲存列舉型別的經驗供參,大家如果知道其他妙計,歡迎回饋!


Viewing all articles
Browse latest Browse all 2311

Trending Articles