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

Dapper小技巧:以資料表保存集合物件JSON

$
0
0

專案常遇到的需求:為指定資料保留修改歷程,以備稽核檢查或追查責任之用,使用機率不高且無統計或隨興查詢需求,不值得另開資料表。此時我偏好的做法是定義成List<HistoryRecord>,在資料表開一個NVARCHAR(MAX)保存其JSON內容,調閱時讀取JSON反序列化還原內容,足以滿足規格所需。

用個實例說明,假設資料物件定義如下:

publicclass HistoryRecord
{
public DateTime Time { get; set; }
publicstring User { get; set; }
publicstring Remark { get; set; }
}
 
publicclass ProjectItem
{
publicint Id { get; set; }
publicstring Name { get; set; }
public List<HistoryRecord> History { get; set; }
}

資料表設計如下:

CREATETABLE [dbo].[ProjectItem] (
    [Id]      INTNOTNULL,
    [Name]    NVARCHAR (64)  NULL,
    [History] NVARCHAR (MAX) NULL
);

資料庫存取部分我主要用Dapper實作,但問題來了,試著將List<HistoryRecord>當成History欄位的輸入參數:

var kernel = new ProjectItem()
{
    Id = 1,
    Name = "KernelModule",
    History = new List<HistoryRecord>()
    {
new HistoryRecord()
        {
            Time = new DateTime(2016, 7, 1),
            User = "Jeffrey",
            Remark = "Initial version"
        },
new HistoryRecord()
        {
            Time = new DateTime(2016,7,11),
            User = "Jeffrey",
            Remark = "Refactoring"
        }
    }
};
cn.Execute("INSERT INTO ProjectItem VALUES(@Id, @Name, @History)", kernel);

卻冒出錯誤:The member  of type DapperLab.Program+HistoryRecord cannot be used as a parameter value。Dapper不知道怎麼將List<HistoryRecord>轉成可以存入資料庫的內容。

針對這類需求,Dapper的解決方案是開放開發者自訂TypeHandler,指定型別該如何對應資料庫內容。實作方法很簡單,宣告一個類別繼承SqlMapper.TypeHandler<T>,提供兩個函式:Parse<T>()函式負責將資料庫內容轉成該型別,SetValue()函式將型別轉型後指定給IDbDataParameter.Value:

publicclass HistoryRecordListHandler : SqlMapper.TypeHandler<List<HistoryRecord>>
{
publicoverride List<HistoryRecord> Parse(objectvalue)
    {
return JsonConvert.DeserializeObject<List<HistoryRecord>>((string)value);
    }
 
publicoverridevoid SetValue(IDbDataParameter parameter, List<HistoryRecord> value)
    {
        parameter.Value = JsonConvert.SerializeObject(value);
    }
}

接著,在執行cn.Execute()之前先透過SqlMapper.AddTypeHandler()註冊,指定由HistoryRecordListHandler負責處理List<HistoryRecord>資料轉換:
SqlMapper.AddTypeHandler<List<HistoryRecord>>(new HistoryRecordListHandler());

如此,Dapper就會將Historyh屬性JSON後寫入資料表,讀取時也能正確由JSON還原回List<HistoryRecord>,大功告成!

以上寫法可以再改良,HistoryRecordListHandler的核心邏輯可抽取成泛型,適用於所有要JSON化存入資料庫的型別,省去為每個要轉JSON型別撰寫專屬TypeConverter的困擾。

publicclass JsonConvertHandler<T> : SqlMapper.TypeHandler<T>
{
publicoverride T Parse(objectvalue)
    {
return JsonConvert.DeserializeObject<T>((string)value);
    }
 
publicoverridevoid SetValue(IDbDataParameter parameter, T value)
    {
        parameter.Value = JsonConvert.SerializeObject(value);
    }
}
 
//...略...
 
SqlMapper.AddTypeHandler<List<HistoryRecord>>(new JsonConvertHandler<List<HistoryRecord>>());

Viewing all articles
Browse latest Browse all 2311

Trending Articles