[食譜好菜] 玩轉 SQL Server 的 FOR JSON

FOR JSON 語法是從 SQL Server 2016 開始支援,跟 FOR XML 一樣,我們可以將整筆記錄或是部分欄位輸出成 JSON 格式,輕鬆解決類別與類別之間一對一及一對多關係的情形,再搭配 Dapper 自定義 TypeHandler,讓關聯式資料庫的欄位與類別的對應轉換無聲無息。

假定我有一個 Food 資料表,對應到 Food 類別,其中 Food 類別的 ShelfLife 屬性是 ShelfLife 型別,而 ShelfLife 的屬性值是來自 Food 資料表中 ShelfLife_MonthsShelfLife_Days 兩個欄位。

FOR JSON PATH

只要標上 FOR JSON PATH 我們就可以輕鬆把結果輸出成 JSON 格式

SELECT
    *
FROM Food f WITH (NOLOCK)
FOR JSON PATH

還可以自訂階層

SELECT
    f.Id
   ,f.[Name]
   ,f.ShelfLife_Months AS 'ShelfLife.Months'
   ,f.ShelfLife_Days AS 'ShelfLife.Days'
FROM Food f WITH (NOLOCK)
FOR JSON PATH

部分輸出也可以,如果輸出結果只有一個,可以加上 WITHOUT_ARRAY_WRAPPER 語法去除中括號,而如果要保留 NULL 值的欄位則可以加上 INCLUDE_NULL_VALUES 語法。

SELECT
    f.Id
   ,f.[Name]
   ,(SELECT
            f.ShelfLife_Months AS Months
           ,f.ShelfLife_Days AS [Days]
        FOR JSON PATH, WITHOUT_ARRAY_WRAPPER)
    AS ShelfLife
FROM Food f WITH (NOLOCK)

Dapper 的 TypeHandler

當我們把資料輸出成 JSON 格式回傳之後,如何對應類別? 當然是做 JSON 反序列化,把資料還原成物件,但是 Dapper 本身並不內建反序列化功能,不過它有提供一個抽象類別 SqlMapper.TypeHanlder<T> 我們可以繼承它自己實作對應的方式。

public class JsonObjectTypeHandler<T> : SqlMapper.TypeHandler<T>
{
    public override void SetValue(IDbDataParameter parameter, T value)
    {
        parameter.Value = (value == null) ? (object)DBNull.Value : JsonConvert.SerializeObject(value);
        parameter.DbType = DbType.String;
    }

    public override T Parse(object value)
    {
        return JsonConvert.DeserializeObject<T>((string)value);
    }
}

使用方式就在執行 Query 之前或是在程式啟動的時候,執行 SqlMapper.AddTypeHandler() 方法去註冊。

SqlMapper.AddTypeHandler(new JsonObjectTypeHandler<ShelfLife>());

然後照著一般我們操作 Dapper 的方式去 Query 就行了

參考資料