呈現**階層式資料** (Master-Detail) 最經典案例:分類 → 商品、訂單 → 明細、部門 → 員工、論壇版面 → 主題列表。外層 Repeater 綁定主資料,內層 Repeater 在 ItemDataBound 事件中依主資料鍵值查詢並綁定從資料。
-- 分類表
CREATE TABLE Category (CatID INT PRIMARY KEY, CatName NVARCHAR(50));
-- 商品表
CREATE TABLE Product (ProdID INT PRIMARY KEY, CatID INT FOREIGN KEY REFERENCES Category(CatID), ProdName NVARCHAR(100), Price DECIMAL(10,2));
-- 範例資料
INSERT INTO Category VALUES (1, '3C數位'), (2, '家電'), (3, '書籍');
INSERT INTO Product VALUES (1, 1, '筆電', 30000), (2, 1, '手機', 20000), (3, 2, '冰箱', 40000), (4, 3, 'C#入門', 600);
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="NestedRepeater.aspx.cs" Inherits="Demo.NestedRepeater" %>
<!DOCTYPE html>
<html><head><title>巢狀 Repeater 範例</title>
<style>
.category { border: 1px solid #ddd; margin: 10px 0; padding: 10px; background: #fafafa; }
.cat-header { font-size: 1.2em; font-weight: bold; color: #333; border-bottom: 2px solid #007bff; padding-bottom: 5px; margin-bottom: 10px; }
.product-list { display: flex; flex-wrap: wrap; gap: 10px; }
.product-card { border: 1px solid #ccc; padding: 10px; width: 200px; background: white; box-shadow: 1px 1px 3px rgba(0,0,0,0.1); }
.prod-name { font-weight: bold; }
.prod-price { color: #dc3545; font-size: 1.1em; }
</style></head><body>
<form id="form1" runat="server">
<h1>商品分類目錄</h1>
<asp:Repeater ID="rptCategory" runat="server" OnItemDataBound="rptCategory_ItemDataBound">
<ItemTemplate>
<div class="category">
<div class="cat-header">
<%# Eval("CatName") %> (<%# Eval("ProductCount") %> 項商品)
</div>
<div class="product-list">
<asp:Repeater ID="rptProduct" runat="server">
<ItemTemplate>
<div class="product-card">
<div class="prod-name"><%# Eval("ProdName") %></div>
<div class="prod-price">NT$ <%# Eval("Price", "{0:N0}") %></div>
</div>
</ItemTemplate>
<SeparatorTemplate><!-- 可選分隔 --></SeparatorTemplate>
</asp:Repeater>
</div>
</div>
</ItemTemplate>
<EmptyDataTemplate>
<div class="alert alert-info">目前無任何分類資料</div>
</EmptyDataTemplate>
</asp:Repeater>
</form></body></html>
using System;
using System.Data;
using System.Web.UI.WebControls;
namespace Demo
{
public partial class NestedRepeater : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindCategories();
}
}
private void BindCategories()
{
// 實務應使用參數化查詢、Dapper/EF、快取
string sql = @"
SELECT c.CatID, c.CatName, COUNT(p.ProdID) AS ProductCount
FROM Category c
LEFT JOIN Product p ON c.CatID = p.CatID
GROUP BY c.CatID, c.CatName
ORDER BY c.CatID";
DataTable dt = DbHelper.GetDataTable(sql); // 自行實作的資料存取輔助
rptCategory.DataSource = dt;
rptCategory.DataBind();
}
// 核心:外層每綁定一筆資料,即查詢並綁定內層
protected void rptCategory_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
// 僅處理資料列 (Item / AlternatingItem)
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
// 1. 取得外層資料項 (DataRowView)
DataRowView row = (DataRowView)e.Item.DataItem;
int catId = Convert.ToInt32(row["CatID"]);
// 2. 找到內層 Repeater 控件
Repeater rptProduct = (Repeater)e.Item.FindControl("rptProduct");
if (rptProduct == null) return;
// 3. 查詢該分類下的商品
string sql = "SELECT ProdID, ProdName, Price FROM Product WHERE CatID = @CatID ORDER BY ProdID";
DataTable dtProducts = DbHelper.GetDataTable(sql, new System.Data.SqlClient.SqlParameter("@CatID", catId));
// 4. 綁定內層
rptProduct.DataSource = dtProducts;
rptProduct.DataBind();
}
}
}
}
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
public static class DbHelper
{
private static string ConnStr => ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
public static DataTable GetDataTable(string sql, params SqlParameter[] parameters)
{
using (var conn = new SqlConnection(ConnStr))
using (var cmd = new SqlCommand(sql, conn))
{
if (parameters != null) cmd.Parameters.AddRange(parameters);
using (var da = new SqlDataAdapter(cmd))
{
var dt = new DataTable();
da.Fill(dt);
return dt;
}
}
}
}
上述寫法若有 100 個分類,會執行 **1 次主查詢 + 100 次從查詢 = 101 次資料庫往返**。高流量下極差。解決方案:
private void BindCategoriesOptimized()
{
string sql = @"
SELECT c.CatID, c.CatName, p.ProdID, p.ProdName, p.Price
FROM Category c
LEFT JOIN Product p ON c.CatID = p.CatID
ORDER BY c.CatID, p.ProdID";
DataSet ds = DbHelper.GetDataSet(sql); // 回傳 DataSet 包含單一 DataTable
// 建立主從關聯
DataTable dtCat = ds.Tables[0].DefaultView.ToTable(true, "CatID", "CatName"); // 去重取主表欄位
dtCat.TableName = "Category";
dtCat.Columns.Add("ProductCount", typeof(int), "COUNT(Child.ProdID)"); // 運算欄位
DataTable dtProd = ds.Tables[0].Copy();
dtProd.TableName = "Product";
ds.Tables.Add(dtCat);
ds.Tables.Add(dtProd);
ds.Relations.Add("Cat_Products", dtCat.Columns["CatID"], dtProd.Columns["CatID"]);
// 綁定外層
rptCategory.DataSource = dtCat;
rptCategory.DataBind();
}
// ItemDataBound 中改用 GetChildRows
protected void rptCategory_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
DataRowView row = (DataRowView)e.Item.DataItem;
Repeater rptProduct = (Repeater)e.Item.FindControl("rptProduct");
// 透過 DataRelation 取得子列 (無額外查詢)
DataRow[] childRows = row.Row.GetChildRows("Cat_Products");
rptProduct.DataSource = childRows;
rptProduct.DataBind();
}
}
// 後端一次查詢,組裝巢狀物件
public class CategoryVM {
public int CatID { get; set; }
public string CatName { get; set; }
public List Products { get; set; }
}
public class ProductVM {
public int ProdID { get; set; }
public string ProdName { get; set; }
public decimal Price { get; set; }
}
// Service 層
public List GetCategoriesWithProducts()
{
// Dapper 多重映射 或 EF Include
// 單次查詢分組組裝
}
// 前端改用繫結 List,內層 Repeater.DataSource = ((CategoryVM)Container.DataItem).Products
| 錯誤 | 原因 | 解決 |
|---|---|---|
| 找不到內層 Repeater (FindControl 回傳 null) | ID 拼錯、控件不在命名容器內、ItemType 非 Item/AlternatingItem | 加上 ItemType 判斷、確認 ID 大小寫、使用 e.Item.FindControl("rptProduct") |
| 內層資料不顯示 | DataSource 未指定、DataBind 未呼叫、查詢無資料 | 加上 EmptyDataTemplate 除錯、檢查 SQL 參數 |
| 頁面載入極慢 | N+1 查詢 | 採用方案一 DataRelation 或方案二 ViewModel |
| 巢狀層級超過 2 層 (三層以上) | 邏輯複雜、ViewState 膨脹 | 建議改用遞迴 UserControl 或前端框架 (Vue/React) 渲染 JSON |
GroupTemplate、LayoutTemplate、ItemTemplate,可用 CSS Flex/Grid 直接排版,無需巢狀控件。template 標籤、Vue、React、或原生 forEach 渲染,零 ViewState、可快取、SEO 友善 (配合 SSR)。巢狀 Repeater 是 Web Forms 處理主從資料的經典模式,**核心在 ItemDataBound 事件中動態綁定內層**。生產環境**務必解決 N+1 查詢**,採用 DataRelation 或 ViewModel 單次查詢 方案。新專案建議改用 ListView 或前後端分離架構,獲得更佳效能與開發體驗。