I usually use a nested repeater:
PHP Code:
public class YourPage Page
{
  private 
DataTable _parentsAndChildren;
  protected 
Repeater rptrParents;
  protected 
override void OnLoad(System.EventArgs e)
  {
    if(!
Page.IsPostBackbindData();
  }
  private 
void bindData()
  {
    
//code to fill up _parentsAndChildren with data goes here.
    
DataView parents = new DataView(_parentsAndChildren);
    
parents.RowFilter "ParentID = ChildID";
    
rptrParents.DataSource parents;
    
rptrParents.DataBind();
  }
  protected 
DataView GetChildren(object dataItem)
  {
    
DataView children null;
    
DataRowView parentRow dataItem as DataRowView;
    if(
parentRow != null)
    {
      
string rowFilter "ParentID<>ChildID and ParentID=" 
        
parentRow["ParentID"].ToString();
      
children = new DataView(_parentsAndChildren);
      
children.RowFilter rowFilter;
    }
    return 
children;
  }

the repeater on the aspx page would look something like this:
Code:
<asp:Repeater ID="rptrParents" Runat="server">
  <HeaderTemplate>
    <table>
      <tr>
        <td>Name</td>
      </tr>
  </HeaderTemplate>
  <ItemTemplate>
    <tr>
      <td>
        <%# DataBinder.Eval(Container.DataItem, "Name") %>
      </td>
    </tr>
    <!-- Now get the children for the current parent -->
    <asp:Repeater ID="rptrChildren" Runat="server" 
      DataSource='<%# GetChildren(Container.DataItem) %>'>
      <ItemTemplate>
        <tr>
          <td>
            <%# DataBinder.Eval(Container.DataItem, "Name") %>
          </td>
        </tr>
      </ItemTemplate>
    </asp:Repeater>
  </ItemTemplate>
  <FooterTemplate>
    </table>
  </FooterTemplate>
</asp:Repeater>