Ok, so the question is simple this:
I have a list of names, and for each name, I may have a child table of phone numbers. They might have no phone numbers, they might have 5 phone numbers.
What is a good way to display this master reocrd, and then a repeating child record for each row?
There are LOT of ways to do this. I would often suggest that for the main display of rows, we use a listview, and then nest for the child rows a gridview.
However, as always, which road will depend on how much data, and how many rows (complex) the child data display has to be?
But, lets go with a simple grid. As such a setup becomes more complex, then I STRONG suggest going with a listview. The listview is better since it FAR more flexiblilty in terms of layout. and while the "set up" is a bit more markup, adding new columns and complex controls for each row is MUCH better. In the long run the listview in fact can wind up being less overall markup.
However, we only have two extra columns of "child repeating" data.
So we have in effect this:
table people - our list of contacts/people
table Phones - our child list of phone numbers for each "people".
So, lets whip up a grid with the table people. I will often fire up and let the wizard create that grid. I THEN blow out (delete) the data source in the web page, and then start using the delete keys to remove extra junk.
So, we now have this markup:
<div style="width:30%">
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table">
<Columns>
<asp:BoundField DataField="Firstname" HeaderText="Firstname" />
<asp:BoundField DataField="LastName" HeaderText="LastName" />
<asp:BoundField DataField="City" HeaderText="City" />
</Columns>
</asp:GridView>
</div>
And our code to fill this grid is this:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
LoadGrid();
}
void LoadGrid()
{
DataTable rst = MyRst("SELECT * from People Order by FirstName");
GridView1.DataSource = rst;
GridView1.DataBind();
}
public DataTable MyRst(string strSQL)
{
DataTable rstData = new DataTable();
using (SqlCommand cmdSQL = new SqlCommand(strSQL,
new SqlConnection(Properties.Settings.Default.TEST4)))
{
cmdSQL.Connection.Open();
rstData.Load(cmdSQL.ExecuteReader());
}
return rstData;
}
And our output is this:
Ok, so we have this setup of course:
now, we could have done a query join, but then again, that would repeat the main row for each child row (and then we would have to "hide" that.
So, lets go to the markup and add the two rows.
(PhoneType and PhoneNumber).
So we now have this:
<div style="width:40%">
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table">
<Columns>
<asp:BoundField DataField="Firstname" HeaderText="Firstname" />
<asp:BoundField DataField="LastName" HeaderText="LastName" />
<asp:BoundField DataField="City" HeaderText="City" />
<asp:TemplateField HeaderText="Type">
<ItemTemplate>
<asp:label ID="txtType" runat="server" Text = '<%# Eval("PhoneType") %>'></asp:label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Phone">
<ItemTemplate>
<asp:label ID="txtPhone" runat="server" Text = '<%# Eval("Phone") %>' ></asp:label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</div>
(this is what I mean by using gridview - to drop in nice plane jane asp.net controls (a label in this case), you have to surround it with the itemtemplate - I find that a bit of pain if you have "lots" of columns. so if you need lots of custom columns then a list view is better. but we not too bad so far.
So, our code now becomes this:
We simply pull the data for each child row, and shove the results of this child table into our two new columns we add.
this:
void LoadGrid()
{
DataTable rst = MyRst("SELECT * from People Order by FirstName");
rst.Columns.Add("PhoneType", typeof(string));
rst.Columns.Add("Phone", typeof(string));
foreach (DataRow OneRow in rst.Rows)
{
// get child rows for this main row
DataTable ChildRows = MyRst("SELECT * from Phones where People_ID = " + OneRow["ID"]);
foreach (DataRow ChildRow in ChildRows.Rows)
{
if (OneRow["PhoneType"].ToString() != "")
{
// start new line in this cell
OneRow["PhoneType"] += "<br/>";
OneRow["Phone"] += "<br/>";
}
OneRow["PhoneType"] += ChildRow["PhoneType"].ToString();
OneRow["Phone"] += ChildRow["PhoneNumber"].ToString();
}
}
GridView1.DataSource = rst;
GridView1.DataBind();
}
So this was a bit of extra code to loop! In fact, in MOST cases, if we are writing looping code, then we tend to be doing this the wrong way.
We now have this:
And it also depends on what we want to do here. Say we needed a click button for each of the child phone numbers (maybe a email also included). So if we had to click on the child email/phone to take action, then obvious the above approach is a bit quick and dirty, but would not give us the ability to click on a particular phone number row to select.
So, say if for some reason we wanted a click event, or button to select the given child row from this display?
Then I would then move over to nesting a child grid. We would not thus need a loop to fill the child data - but in fact would bind a new grid in a simular fashion to how we built the main grid.
and as noted, I would probably jump over to a list view for the main grid.
If you wish, I can and will post a working example of a nesting a grid view inside of list view - say with a extra button to click on the child row as a "action" we might want to take.