對同事做了一場 Dapper 威力展示。

原本打算示範類似 cn.Query("SELECT ColName FROM TblName").First().ColName.Split(',').First() 讀取 CSV 欄位值 Split() 分割成字串陣列再用 First() 得到第一節的 Dapper + LINQ 組合美技。孰料槍枝膛炸傷了手指,糗!

用以下程式重現錯誤:(實際例子比這個複雜些,需取得多個欄位進行處理,有借用 Dapper 不宣告結果型別卻可直接 .ColName 屬性取值的方便性)

using Dapper;
using System;
using System.Data.SqlClient;
using System.Linq;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var cn = new SqlConnection(Constants.CnStr))
            {
                var res = cn.Query("SELECT 'A,B,C' AS CSV, 123 AS C2").Single();
                var a = res.CSV.Split(',').First();
                Console.WriteLine(a);
                Console.Read();
            }
        }
    }
}

Split(',').First() 時拋出 'System.Array' does not contain a definition for 'First' 的怪異訊息:

爬文這通常是忘了 using System.Linq 造成,但程式確實有引用,且編譯也沒出錯,不是嗎?

迷惘了一陣子,我才驀然想起:啊,幹! 是 dynamic!!

類似問題在應用 ViewBag 時其實早已遇過多次:

當未指定泛型型別,Dapper.Query() 會傳回 dynamic 型別,與 ViewBag 特性相似。簡單來說,由於 res 是 dynamic,.CSV、.Split() 要靠執行期間透過 System.Runtime.CompilerServices、System.CSharp.RuntimeBinder 命名空間的物件與方法隔水加熱完成,而由於無法事先知道型別,dynamic 又常會被當成 object 處理,或許可以解釋 Split() 結果為何不是 string[] 而是 System.Array。(以上純屬猜測,.NET 底層運作超出我的守備範圍,屬於沒有槍頭也能捅進去的高人)

知道原因要解決就簡單了。用 (string) 將 res.CSV 轉成 string,搞定!

Hint of using Dapper dynamic type returned by Query() with LINQ methods.


Comments

# by 凱大

dynamic 自 現在大量使用型別推斷作為輔助的情況下 真的是如同自廢武功 dynamic 因為是design time沒有型別 而不是 可能為任意型別的情況下 像是 擴充方法 , 泛型推斷, LINQ 等等 都是惡夢 orz...

# by 卡比

我一直的感覺是因為資料未真正從 db 傳回,只是有個 reference 指在遠處的資源,這個想法好像未足夠反映真實

# by Clark

我都是用帶回傳型別的多載XD

# by SHIH HUNG YANG

黑大有試過在 C# + dapper + Oracle ,的情境下有辦法將如下的T-SQL查詢改成參數式嗎? 目前查詢結果似乎只能回到動態組字串的老路,或是用一堆 OR 去動態組合 select col1 , col2 , col3 from TEMP1 WHERE (col1,col2) IN (('123', '2023'), ('456', '2024'), ('789', '2025'))

# by Jeffrey

to SHIH HUNG YANG, Dapper 不支援 ORACLE 的 Tuple IN 寫法,但仍可用參數傳遞:(不建議用參數動態組字串以免產生資安漏洞) ``` var pairs = new[] { (Col1: "123", Col2: "2023"), (Col1: "456", Col2: "2024"), (Col1: "789", Col2: "2025") }; var tupleConditions = string.Join(", ", pairs.Select((p, i) => $"(:Col1_{i}, :Col2_{i})")); var sql = $"SELECT col1, col2, col3 FROM TEMP1 WHERE (col1, col2) IN ({tupleConditions})"; var parameters = new DynamicParameters(); for (int i = 0; i < pairs.Length; i++) { parameters.Add($"Col1_{i}", pairs[i].Col1); parameters.Add($"Col2_{i}", pairs[i].Col2); } var result = connection.Query(sql, parameters); ```

Post a comment