字串 Split() 結果出現 System.Array 不支援 LINQ 方法錯誤
| | 5 | |
對同事做了一場 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 時其實早已遇過多次:
- 【茶包射手日記】CSHTML ViewBag無法使用擴充方法
- ViewBag dynamic 特性導致無法使用 LINQ 語法)
- 擴充方法參數傳入 dynamic 型別出錯
- 方法多載(Method Overloading)與 dynamic
當未指定泛型型別,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); ```