給定一個記憶體內建立的 DataTable(非資料庫來源),包含 Name、Quantity 兩欄,需產出四種不同維度的報表:
// 建立來源 DataTable
DataTable GetData()
{
var dt = new DataTable();
dt.Columns.Add("Name", typeof(string));
dt.Columns.Add("Quantity", typeof(int));
dt.Rows.Add("a", 1); dt.Rows.Add("a", 2);
dt.Rows.Add("b", 2); dt.Rows.Add("b", 2);
dt.Rows.Add("c", 1); dt.Rows.Add("c", 2);
dt.Rows.Add("c", 3); dt.Rows.Add("c", 4);
return dt;
}
// 頁面載入繫結原始資料到 GridView1
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack) GridView1.DataSource = GetData(); GridView1.DataBind();
}
定義 Item 類別承載彙總欄位:
public class Item
{
public string Name { get; set; }
public int Quantity { get; set; } // 最後一筆或單筆用
public int Amount { get; set; } // 總和
public int RowCount { get; set; } // 筆數
}
按鈕事件中以 SortedList<string, Item> 彙總:
protected void ButtonReport1_Click(object sender, EventArgs e)
{
var sl = new SortedList<string, Item>();
foreach (DataRow dr in GetData().Rows)
{
string key = dr["Name"].ToString();
int qty = Convert.ToInt32(dr["Quantity"]);
if (sl.ContainsKey(key))
{
sl[key].Amount += qty;
sl[key].RowCount++;
}
else
{
sl.Add(key, new Item { Name = key, Amount = qty, RowCount = 1 });
}
}
GridView2.DataSource = sl.Values;
GridView2.DataBind();
}
外層 Repeater 綁定唯一 Name 清單,內層 Repeater 依 Name 篩選明細。
<asp:Repeater ID="Repeater1" runat="server" OnItemDataBound="Repeater1_ItemDataBound">
<ItemTemplate>
<asp:HiddenField ID="HiddenField1" Value='<%# Container.DataItem %>' />
<asp:Repeater ID="Repeater2" runat="server">
<HeaderTemplate><table><tr><td>Name</td><td>Quantity</td></tr></HeaderTemplate>
<ItemTemplate><tr><td><%# Eval("Name") %></td><td><%# Eval("Quantity") %></td></tr></ItemTemplate>
<FooterTemplate></table></FooterTemplate>
</asp:Repeater>
</ItemTemplate>
</asp:Repeater>
// 取得不重複 Name 清單
List<string> GetNames()
{
var list = new List<string>();
foreach (DataRow dr in GetData().Rows)
if (!list.Contains(dr["Name"].ToString())) list.Add(dr["Name"].ToString());
return list;
}
// 依 Name 取得明細
List<Item> GetDetails(string name)
{
var list = new List<Item>();
foreach (DataRow dr in GetData().Rows)
if (dr["Name"].ToString() == name)
list.Add(new Item { Name = name, Quantity = Convert.ToInt32(dr["Quantity"]) });
return list;
}
protected void ButtonReport2_Click(object sender, EventArgs e)
{
Repeater1.DataSource = GetNames();
Repeater1.DataBind();
}
protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
var hf = e.Item.FindControl("HiddenField1") as HiddenField;
var rpt = e.Item.FindControl("Repeater2") as Repeater;
if (hf != null && rpt != null)
{
rpt.DataSource = GetDetails(hf.Value);
rpt.DataBind();
}
}
}
| 技巧 | 說明 |
|---|---|
| LINQ to DataTable | var groups = dt.AsEnumerable().GroupBy(r => r.Field<string>("Name")); 可大幅簡化分組邏輯。 |
| DataView.RowFilter | 內層 Repeater 可用 new DataView(dt) { RowFilter = "Name='a'" } 避免重複迴圈。 |
| 快取 DataTable | 若來源資料不常變,放入 Cache["SourceData"] 減少重建開銷。 |
| 虛擬化分頁 | 資料量大時,搭配 DataPager 或自訂分頁邏輯,避免一次綁定過多列。 |
透過 SortedList 快速彙總、巢狀 Repeater + HiddenField 傳遞分組鍵值、ItemDataBound 動態綁定子資料源,即可在純記憶體 DataTable 上靈活產出多維報表。實務上建議配合 LINQ 與快取機制,進一步提升開發效率與執行效能。