본문 바로가기

SW/ASP.NET

[ASP.NET] Nested Repeater(중첩리피터)를 사용 - 3중 리피터

오랜만에 글을 쓰는 듯 하다..ㅡ.ㅡ;
이번에는 중첩 리피터에 대해서 알아 보겠다.
중첩 리피터에 대해서 생소해 하는 개발자를 위해 정말 친절하게도..아래 스샷을 떳다..
이제 알겠지???

 

 

이게..생각보다 꽤 자주 쓰인다..
이런 아웃풋을 내기 위한 방법은 엄청 많다..
내가 알고 있는 것도 대략 4가지 정도???

 

하나씩 차근 차근(??) 살펴 보도록 하겠다.

 

1. ASP 적인 코딩법

 

    1 <% for(int j=""0;j"" <myStatic[i=""].Length;j=""++){%>

    2 

    3 

    4     <tr>

    5       <td width="140" height="30" align="center" bgcolor="edf6fc" style="padding:5 0 5 0;">

    6         <strong>

    7           <%=myStatic[i][j].Name%>

    8         </strong>

    9       </td>

   10       <td width="206" align="center" bgcolor="f5f5f5" style="padding:5 0 5 0;">

   11         <table width="80%"  border="0" cellspacing="0" cellpadding="0">

   12           <tr>

   13             <td> </td>

   14           </tr>

   15           <tr>

   16             <td>

   17               <%

   18               for(intk=0;k<myStatic[i=""][j=""].Length;k=""++){

   19    Response.Write=""(myStatic=""[i=""][j=""][k=""].Name=""+"<br>");

   20                                     }

   21                                 %></td>

   22           </tr>

   23           <tr>

   24             <td> </td>

   25           </tr>

   26         </table>

   27       </td>

 


소스코드의 내용은 보지 말고..패턴만 보도록 한다.
대부분의 ASP 출신 개발자들이 아마도 즐겨 쓰는 법이라 생각 된다. 대략 초난감한 패턴이다.
솔직히 나는 이런 패턴을 써본적도 없지만..읽기에도 심히 부담스럽고 어렵다.
이렇게 사용하는 개발자가 있는가????
많겠지..ㅡ.ㅡ;;;

 

이런 패턴을 구현 하기위해서는 SQL프로시저의 아웃풋이 아래와 같이 나와야 한다.

 

    1 SELECT D.ProductID, P.productname,D.OrderID, O.OrderDate, D.Quantity, D.UnitPrice,D.Quantity*D.UnitPrice Revenue

    2 FROM [Order Details] D

    3     JOIN Orders O ON D.OrderID = O.OrderID

    4     JOIN Products P ON P.ProductId = D.ProductId

    5 where D.ProductId IN (select productid from products WHERE CategoryID IN (4,6))

    6 ORDER BY O.OrderDate

 

한번에 쿼리 해온다는 장점은 있어 보이나 쿼리문에 JOIN이 많이 들어 가게 된다.
위의 예시는 Northwind DB이다.

 

2. 유저 컨트롤을 이용하는 방법

 

가장 손쉬운 방법이다.
그러나 성능은 나쁜 편이다.
먼저 페이지에서는 아래와 같이 코딩하게 된다.

 

    1 <asp:Repeater ID="rptCategory" runat="server">

    2   <ItemTemplate>

    3     <table border="1">

    4       <tr>

    5         <td style="vertical-align: text-top">

    6           <%#Eval("CategoryName") %>

    7           </td>

    8         <td>

    9           <uc1:WebUserControl ID="WebUserControl1" runat="server" CategoryId='<%#Eval("CategoryId") %>' />

   10             </td>

   11       </tr>

   12     </table>

   13   </ItemTemplate>

   14 </asp:Repeater>

 

보이는가?
중간에 보게 되면 유저 컨트롤이 포함되어 있다.
이 컨트롤에 CategoryId라는 프라퍼티를 생성하고 이 값에 바인딩된 CategoryId를 입력 하게 된다.

 

    1 <asp:Repeater ID="rptProduct" runat="server">

    2   <ItemTemplate>

    3     <table border="1">

    4       <tr>

    5         <td style="vertical-align: text-top">

    6           <%#Eval("ProductName") %>

    7           </td>

    8       </tr>

    9     </table>

   10   </ItemTemplate>

   11 </asp:Repeater>

 

그리고 유저 컨트롤 내부에서는 위 처럼 다시 바인딩을 하게 된다.

 

이 패턴의 가장 큰 장점은 앞서 말했듯이 사용하기가 엄청 쉽다는 것이다.
그러나 큰 단점이 있는데 그것은 바로..하나의 Row에 바인딩이 될때마다 커넥션을 하여 다시 데이터를 조회 하는 것이다..ㅡ.ㅡ;
데이터가 10개라면 총 11번의 커넥션을 하게 된다.

 

3. 컬렉션(?)을 이용하는 방법(?)

 

이름을 어떻게 붙여야 하나 고심하다가..대충 이리 붙였다.
이 방법은..꽤나 골치 아프며 난해한 방법이라고 개인적으로 생각한다..ㅡ.ㅡ;
개략적인 방법을 풀어 보자면
먼저 쿼리문은 1에서 사용한 것과 같다.
이 결과물을 컬렉션으로 분배하여 쪼개 넣고 바인딩을 하는 방법이다.
뭔소린지 모를것이다..ㅡ.ㅡ;;;

 

    1 <ItemTemplate>

    2   <tr>

    3     <td width="140" height="30" align="center" bgcolor="edf6fc" style="padding: 5 0 5 0;">

    4       <strong>

    5         <%#Eval("Key")%>

    6         </strong>

    7     </td>

    8     <td align="center" bgcolor="f5f5f5" style="padding: 5 0 5 0;">

    9       <table width="80%" border="0" cellspacing="0" cellpadding="0">

   10         <%#NameList(Eval("Key").ToString=""()) %>

   11         </table>

   12     </td>

   13     <td align="center" bgcolor="FFFFFF" style="padding: 5 0 5 0;">

   14       <table width="80%" border="0" cellspacing="0" cellpadding="0">

   15         <%#CountList(Eval("Key").ToString=""()) %>

   16         </table>

   17     </td>

   18   </tr>

   19 </ItemTemplate>

 

위의 예시를 보게 되면 바인딩을 할때 NameList 나 CountList와 같은 메서드를 호출 하여 바인딩을 하게 된다.
이 부분의 코드를 살펴 보자.

 

    1 private StatisticsGenerics<StatisticsGenerics<StatisticsItemCollection>> list;

    2 

    3 protected string CountList(string groupName)

    4 {

    5     StringBuilder sb = new StringBuilder();

    6     foreach (StatisticsItemCollection col in this.list[groupName].Values)

    7     {

    8         foreach (StatisticsItem item in col.Values)

    9         {

   10             if (item.AreaId == Convert.ToInt32(this.ddlSort.SelectedValue))

   11             {

   12                 sb.Append("<tr><td>" + item.Count + "</td></tr>");

   13             }

   14         }

   15     }

   16     return sb.ToString();

   17 }

 

예를 들면 CountList메서드의 경우 위와 같다.
쿼리의 결과물을 비즈니스 계층에서 StatisticsGenerics<StatisticsGenerics<StatisticsItemCollection>> 개체로 담아내고 이를 바인딩 하는 방법이다.
쿼리의 결과물을 적절한 컬렉션으로 만들기가 생각보다 수월하지가 않다.
어떻게 보면 1의 ASP식의 닷넷 식이라고 보여지기도 한다.
실제로 프로젝트에서 한번 써봤는데 컬렉션에 쿼리의 결과물을 담는 클래스를 만드는데 꽤 많은 시간이 걸렸다..ㅡ.ㅠ;

 

4. 오늘의 주제!!! (Nested Repeater)

 

오늘 하고자 하는 얘기는 바로 이것이다..
이전의 것은 모두 잊어 버려라..ㅡ.ㅡ;; 라고 자신있게 말해보고는 싶지만.. 성능테스트는 못 해 보았다..ㅋ~
주 내용은 리피터 않에 리피터를 만드는 것이다.
아래 예제는 이 방법의 2가지 패턴을 보여 준다.
골라서 선택하면 되겠다.

 

먼저 테스트를 위해 Northwind 데이터베이스에 프로시저를 하나 만들어 본다.
내용은 아래와 같다.

 

SET QUOTED_IDENTIFIER ON

GO

SET ANSI_NULLS ON

GO

ALTER PROCEDURE GetOrderDetails

AS

 

-------- Get Category List -------------------

SELECT CategoryId, CategoryName

FROM Categories

WHERE CategoryId IN (4,6)

ORDER BY CategoryName

 

-------- Get Product List ------------------------------

SELECT CategoryId, ProductId, ProductName

FROM Products

WHERE CategoryId IN (4,6)

ORDER BY ProductName

 

-------- Get Order List ---------------------------------

SELECT D.ProductID, D.OrderID, O.OrderDate, D.Quantity, D.UnitPrice,D.Quantity*D.UnitPrice Revenue

FROM [Order Details] D

    JOIN Orders O ON D.OrderID = O.OrderID

where D.ProductId IN (select productid from products WHERE CategoryID IN (4,6))

ORDER BY O.OrderDate

 

GO

SET QUOTED_IDENTIFIER OFF

GO

SET ANSI_NULLS ON

GO

 

이제 aspx파일을 설정해 보도록 하겠다.

 

    1 <asp:Repeater ID="rptCategory" runat="server"  OnItemDataBound="OnCategoryItemBound">

    2   <ItemTemplate>

    3     <table border="1">

    4       <tr>

    5         <td style="vertical-align:text-top">

    6           <%#Eval("CategoryName") %>

    7           </td>

    8         <td>

    9           <asp:Repeater runat="server" ID="rptProduct" OnItemDataBound="OnProductItemBound">

   10             <ItemTemplate>

   11               <table border="1">

   12                 <tr>

   13                   <td style="width:100px">

   14                     <%#Eval("ProductName") %>

   15                     </td>

   16                   <td style="width:300px">

   17                     <asp:Repeater runat="server" ID="rptOrder">

   18                       <ItemTemplate>

   19                         <table border="1">

   20                           <tr>

   21                             <td style="width:50px">

   22                               <%#Eval("OrderId") %>

   23                               </td>

   24                             <td style="width:150px">

   25                               <%#Convert.ToDateTime(Eval("OrderDate")).ToString=""("yyyy-MM-dd") %>

   26                               </td>

   27                             <td style="width:50px">

   28                               <%#Eval("Quantity") %>

   29                               </td>

   30                             <td style="width:50px">

   31                               <%#Eval("UnitPrice") %>

   32                               </td>

   33                           </tr>

   34                         </table>

   35                       </ItemTemplate>

   36                     </asp:Repeater>

   37                   </td>

   38                 </tr>

   39               </table>

   40             </ItemTemplate>

   41           </asp:Repeater>

   42         </td>

   43       </tr>

   44     </table>

   45   </ItemTemplate>

   46 </asp:Repeater>

 

위의 구조를 잘 살펴 보도록 하자.

 

최상단의 rptCategory아래 rptProduct가 있으며 그 아래 rptOrder가 있다.
여기서 보여줄 2개의 패턴은 하나는 rptCategory와 rptProduct가 사용할테고, 또 하나는 rptProduct와 rptOrder가 사용할 것이다.

 

먼저 rptCategory와 rptProduct가 사용하는 패턴을 보도록 하자.

 

    1 /// <summary>

    2 /// Gets the data set.

    3 /// </summary>

    4 private void GetDataSet()

    5 {

    6     SqlDB db = new SqlDB("con");

    7     this.ds = db.ExcuteDataSet(false, true, "GetOrderDetails");

    8 

    9     ds.Relations.Add("CategoryProduct", ds.Tables[0].Columns["CategoryId"], ds.Tables[1].Columns["CategoryId"]);

   10     ds.Relations["CategoryProduct"].Nested = true;

   11 

   12     this.rptCategory.DataSource = ds.Tables[0].DefaultView;

   13     this.rptCategory.DataBind();

   14 }

 

6~7 라인은 DataSet에 해당 데이터를 담는 것이다.
담겨 지는 데이터는 위에서 만든 프로시저를 Excute시켜 보기 바란다.
3개의 테이블이 생성된다.
7라인의 코드는 여기서 설명과 함께 다운 받기 바란다.

 

주목할 코드는 9~10 라인이다.
CategoryProduct라는 네임을 가진 관계를 생성한다.
마지막으로 12~13라인처럼 첫번째 테이블을 rptCategory에 바인딩 한다.

 

    1 /// <summary>

    2 /// Called when [category item bound].

    3 /// </summary>

    4 /// <param name="source">The source.</param>

    5 /// <param name="e">The <see cref="System.Web.UI.WebControls.RepeaterItemEventArgs"/> instance containing the event data.</param>

    6 protected void OnCategoryItemBound(object source, RepeaterItemEventArgs e)

    7 {

    8     DataRowView dv = e.Item.DataItem as DataRowView;

    9     if (dv != null)

   10     {

   11         Repeater rpt = e.Item.FindControl("rptProduct") as Repeater;

   12         if (rpt != null)

   13         {

   14             rpt.DataSource = dv.CreateChildView("CategoryProduct");

   15             rpt.DataBind();

   16         }

   17     }

   18 }

 

이제 rptCategory의 ItemBound이벤트에서 위와 같이 코딩한다.
핵심은 14라인의 CreateChildView메서드이며 이 메서드의 파라메터로 처음 생성했던 관계의 네임을 넣으면 된다.

 

이제 하나의 패턴을 설명했다.
다른 하나의 패턴을 설명해 보겠다.

 

rptProduct의 ItemBound이벤트에서 아래와 같이 코딩한다.

 

    1 /// <summary>

    2 /// Called when [product item bound].

    3 /// </summary>

    4 /// <param name="source">The source.</param>

    5 /// <param name="e">The <see cref="System.Web.UI.WebControls.RepeaterItemEventArgs"/> instance containing the event data.</param>

    6 protected void OnProductItemBound(object source, RepeaterItemEventArgs e)

    7 {

    8     this.productiId = Convert.ToInt32(DataBinder.Eval(e.Item.DataItem, "ProductId"));

    9     this.filter = "ProductId=" + this.productiId.ToString();

   10     this.ds.Tables[2].DefaultView.RowFilter = this.filter;

   11 

   12     Repeater rpt = e.Item.FindControl("rptOrder") as Repeater;

   13     if (rpt != null)

   14     {

   15         rpt.DataSource = this.ds.Tables[2].DefaultView;

   16         rpt.DataBind();

   17     }

   18 }

 

쉽게 얘기 해서 필터링 하는 것이다.

 

위 2가지 패턴을 직접 꼭~~~ 테스트 해보기 바란다.
어느것이 더 깔끔하며 친숙한지. 검토해 보고 선택하여 사용하기 바란다.