星期六, 10月 17, 2026

[SQL] 資料表值參數 - DataTable 參數欄位順序

使用 [SQL] 資料表值參數 (Table Value Parameters) 時拋出字串要轉型為整數 SQL Exception,這才發現原來 DataTable 參數欄位順序要搭配 TVP 參數順序,兩者必須手動維持順序一致,沒有辦法自動對應
使用者定義資料表類型 (User-defined Table Type)
CREATE TYPE [dbo].[UserDefinedTableType] AS TABLE(
	Col1 char(4) NOT NULL,
	Col2 char(16) NOT NULL,
	Col3 int NOT NULL
)
該 case 是使用 FastMember ObjectReader 把 List<T> 轉成 DataTable,所以有兩種處理方式
  1. 事先定義欄位順序:在 class 上指定 FastMember.OrdinalAttribute 定義轉換後欄位順序,詳見 [C#] FastMember-ObjectReader
  2. 事後調整欄位順序:產生 DataTable 後,透過 DataColumn.SetOrdinal() 調整 DataTable 欄位順序
// ToDataTable() 則是封裝好的 FastMember ObjectReader 轉換功能
DataTable dt = List<T>.ToDataTable();
dt.Columns[Col1].SetOrdinal(0);
dt.Columns[Col2].SetOrdinal(1);
dt.Columns[Col3].SetOrdinal(2);

星期五, 10月 16, 2026

[SQL] SQL Profile 側錄 Exception

C# AP 端開發時發生 SQL Exception,在本地端開啟 Profile 側錄語法時沒有看見 SQL Exception 資訊,發現使用標準範本的話要特別去開啟 Errors and Warmings 內相關事件才會記錄

C# AP 端拋出 SQL Excpetion
該 case 在 Profile 內開啟 Exception 和 User Error Message 就行
Profile 側錄結果

星期二, 10月 13, 2026

[LINQ] ToLookup

ToLookup 用來將集合轉成一個「一對多」的字典結構(ILookup<TKey, TElement>)

主要特點
  • 一對多對應(One-to-Many):一個鍵(Key)可以對應到多個值(Collection of values),與 ToDictionary(一對一)不同
  • 立即執行(Eager Execution):呼叫 ToLookup() 時會立即計算並產生結果,這點跟 GroupBy()延遲執行 (Deffered Execution) 不同
  • 唯讀結構:產生的 ILookup 是唯讀的,無法修改
  • 安全存取:如果用 Key 去取值,就算該 Key 不存在,它也會回傳一個空的集合而不是丟出例外錯誤(KeyNotFoundException)
簡易範例
namespace ToLookupSample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var books = new List<Book>
            {
                new Book { Title = "C# 實戰", Category = "CSharp", PublishYear = 2025 },
                new Book { Title = "設計模式", Category = "CSharp", PublishYear = 2023 },
                new Book { Title = "MS SQL TSQL 學習手冊", Category = "SQL", PublishYear = 2020 },
                new Book { Title = "MS SQL 效能調校", Category = "SQL", PublishYear = 2025 },
                new Book { Title = "MS SQL 索引深入淺出", Category = "SQL", PublishYear = 2025 },
                new Book { Title = "世界歷史", Category = "History", PublishYear = 1981 }
            };

            // 1. 建立 ILookup:以 Category 為 Key
            ILookup<string, Book> bookLookup = books.ToLookup(b => b.Category);

            // 2. 透過索引子 [Key] 查詢存在的書籍分類
            IEnumerable<Book> sql = bookLookup["SQL"];
            Console.WriteLine($"SQL 書籍:{sql.Count()}");
            foreach (Book book in sql)
                Console.WriteLine($"     ----- {book.Title}");

            // 3. 查詢不存在的書籍分類:回傳空集合,不會拋出 Exception
            IEnumerable<Book> ai = bookLookup["AI"];
            Console.WriteLine($"是否有 AI 分類書籍:{ai.Any()}");

            // 4. 檢查某個 Key 是否存在
            bool hasHistory = bookLookup.Contains("History");
            Console.WriteLine($"是否有 History 分類書籍:{hasHistory}");

            // 5. 忽略大小寫
            ILookup<string, string> books4OrdinalIgnoreCase = books.ToLookup(
                keySelector: b => b.Category,
                elementSelector: b => b.Title,
                comparer: StringComparer.OrdinalIgnoreCase // 忽略大小寫
            );

            IEnumerable<string> csharp = books4OrdinalIgnoreCase["csharp"];
            Console.WriteLine($"C# 書籍 - 不區分大小寫");
            foreach (string title in csharp)
                Console.WriteLine($"     ----- {title}");

            // 6. Key 使用 ValueTuple (Category, PublishYear) 作為複合 Key
            var booksValueTuple = books.ToLookup(b => (
                Category: b.Category,
                Year: b.PublishYear
            ));

            var targetKey = ("SQL", 2025);
            var matchedBooks = booksValueTuple[targetKey].ToList();

            Console.WriteLine($"2005 出版 SQL 書籍 - 複合 Key");
            foreach (Book book in matchedBooks)
                Console.WriteLine($"     ----- {book.Title}");
        }
    }

    public class Book
    {
        public string Title { get; set; }
        public string Category { get; set; }
        public int PublishYear { get; set; }
    }
}

星期一, 10月 05, 2026

[SSMS] 匯入、匯出資料

在 SSMS V22 上發現沒有辦法使用內建匯出資料功能,查發現原來 SSMS V22 的商業智慧功能並不是預設安裝的,安裝起來就可以使用

SSMS V22 上匯入、匯出資料反灰無法使用
在 Visual Studio Installer 內把 [商業智慧] 安裝起來,內含 SQL Server Integration Services (SSIS),就可以正常使用

星期三, 9月 23, 2026

[SSRS] 巢狀清單

以前一直認為清單 (List) 內無法放置資料表 (Tablix),在報表設計階段雖然 Tablix 可以放進 List 內,但只要報表一執行就會出現錯誤
後來發現因為是 List 和 Tablix 資料列群組 (Row Group) 內預設有個明細資料 (Detail Row),因為兩個 Detail Row 才導致報表無法 Render
該筆記就是要把 List 內 Detail Row 轉為 Group Row 當成報表頭,List 內的 Tablix Detail Row 來呈現資料當成報表身,以此來呈現 AdventureWorks2025 內的訂單報表 (Master-Detail)

AsventureWorks2025 資料存取

只抓取兩筆訂單資料
SELECT
    soh.SalesOrderNumber,
    soh.OrderDate,
    sod.OrderQty,
    sod.UnitPrice,
    sod.LineTotal,
    p.ProductNumber,
    p.[Name] AS ProductName
FROM Sales.SalesOrderHeader AS soh
    INNER JOIN Sales.SalesOrderDetail AS sod ON soh.SalesOrderID = sod.SalesOrderID
    INNER JOIN Production.[Product] AS p ON sod.ProductID = p.ProductID
WHERE soh.SalesOrderID IN (43659 , 75123)
把 List Detail Row 轉為 Group Row

兩種方式有可以達到
  • 直接把 Detail Row 轉為 Group Row
  • 在 Detail Row 上建立父群組後,再把 Detail Row 刪除
該筆記內容是以前者為主,直接把 Detail Row 轉為 Group Row

Detail Row => 滑鼠右鍵 => 群組屬性
群組屬性 => 一般內進行修改
  • 名稱:修正為 grpSalesOrderNumber,建議要修改,避免還是維持 Detail 預設名稱,但實際是進行 Group 行為
  • 群組運算式:把 SalesOrderNumber 欄位加入群組對象
設定完成後就可以發現下圖紅箭頭變成 Group 圖示
報表設計
在 List 內的矩形 (Rectangle) 內設定換頁設定
報表執行結果