報表需求常因資料動態導致表頭結構變化 (如不同月份欄位需合併)。本文示範以 XmlDocument 載入現有 .rdlc 定義,動態操作 XML 節點實現 ColSpan 合併、標題修改、多餘欄位移除,最後序列化為 MemoryStream 供 ReportViewer 載入,實現免重新部署的報表靈活定製。
Orders.rdlc (含完整表格結構)。Page_Load 讀取資料,取得合併欄數 (cCount, dCount, jCount)。GenerateRdlc() 動態修改 XML:public MemoryStream GenerateRdlc()
{
var sourceDoc = new XmlDocument();
string path = AppDomain.CurrentDomain.BaseDirectory + "Orders.rdlc";
sourceDoc.Load(path);
// 定位表頭列 XML 節點 (需依實際 rdlc 結構調整 XPath)
XmlNode xHeader = sourceDoc.ChildNodes[1].ChildNodes[13].ChildNodes[1].ChildNodes[0].ChildNodes[4];
XmlNode xCells = xHeader.ChildNodes[0].ChildNodes[0].ChildNodes[0];
// 建設期欄位合併
XmlNode xmlCell = xCells.ChildNodes[1];
XmlElement xeCol = sourceDoc.CreateElement("ColSpan");
xeCol.InnerText = cCount;
xmlCell.InnerXml += xeCol.OuterXml;
// 修改標題文字
XmlNode xmlCellValue = xmlCell.ChildNodes[0].ChildNodes[0].ChildNodes[4];
xmlCellValue.InnerText = "建設期";
// 移除多餘欄位節點
for (int i = 0; i < int.Parse(cCount) - 1; i++)
xCells.RemoveChild(xCells.ChildNodes[2]);
// 投產期、生產期同理處理...
// 序列化為 MemoryStream
var ms = new MemoryStream();
var serializer = new XmlSerializer(typeof(XmlDocument));
serializer.Serialize(ms, sourceDoc);
ms.Position = 0;
return ms;
}
ReportViewer1.Reset();
ReportViewer1.LocalReport.LoadReportDefinition(GenerateRdlc());
ReportViewer1.LocalReport.DataSources.Clear();
ReportViewer1.LocalReport.DataSources.Add(new ReportDataSource("Orders_DataTable1", ds.Tables[0]));
ReportViewer1.LocalReport.Refresh();
| 技巧 | 說明 |
|---|---|
| 先設計好基礎 rdlc | 用報表設計器排版完成,另存為 XML 檢視結構,再決定動態修改節點路徑 |
| 使用 XPath 替代硬編碼索引 | sourceDoc.SelectSingleNode("//Table/TableRows/TableRow/TableCells") 更穩健 |
| 命名空間處理 | RDLC 含命名空間,XmlNamespaceManager 配合 XPath 查詢 |
| 效能考量 | 每次請求動態序列化有開銷,高頻報表建議快取生成的 Stream 或預先生成變體 |
| 資料源名稱一致 | ReportDataSource 名稱必須與 RDLC 內定義的 DataSet 名稱完全一致 |
CustomReportItem 實作複雜渲染邏輯。動態 XML 操作 RDLC 是解決「固定模板、動態結構」的實用技巧,但需謹慎處理命名空間、節點定位脆弱性與效能。長期專案建議評估更現代化報表方案。