In this article I will explain with an example, how implement GridView inside GridView in ASP.Net using C# and VB.Net.
 
Database
Here I am making use of Microsoft’s Northwind Database. You can download it from here.
 
 
HTML Markup
The HTML Markup consists of:
GridView – For displaying parent records.
Inside the GridView, there another GridView which is used to display child records placed inside an ASP.Net Panel control.
<asp:GridView ID="gvCustomers" runat="server" AutoGenerateColumns="false" CssClass="Grid"
    DataKeyNames="CustomerID" OnRowDataBound="OnRowDataBound">
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <img alt="" style="cursor: pointer" src="images/plus.png" />
                <img alt="" style="cursor: pointer; display:none" src="images/minus.png" />
                <asp:Panel ID="pnlOrders" runat="server" Style="display: none">
                    <asp:GridView ID="gvOrders" runat="server" AutoGenerateColumns="false" CssClass="ChildGrid">
                        <Columns>
                            <asp:BoundField ItemStyle-Width="150px" DataField="OrderId" HeaderText="Order Id" />
                            <asp:BoundField ItemStyle-Width="150px" DataField="OrderDate" HeaderText="Date" />
                        </Columns>
                    </asp:GridView>
                </asp:Panel>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField ItemStyle-Width="150px" DataField="ContactName" HeaderText="Contact Name" />
        <asp:BoundField ItemStyle-Width="150px" DataField="City" HeaderText="City" />
    </Columns>
</asp:GridView>
 
 
Generic function to populate DataTable
The following function is used throughout this article to populate DataTable with Database records.
It accepts the SQL Query as parameter and it returns the DataTable object containing the Database records.
C#
private static DataTable GetData(string query)
{
    string strConnString = ConfigurationManager.ConnectionStrings["constr"].ConnectionString;
    using (SqlConnection con = new SqlConnection(strConnString))
    {
        using (SqlDataAdapter sda = new SqlDataAdapter())
        {
            using (DataTable dt = new DataTable())
            {
                sda.Fill(dt);
                return dt;
            }
        }
    }
}
 
VB.Net
Private Shared Function GetData(query As String) As DataTable
    Dim conString As String = ConfigurationManager.ConnectionStrings("constr").ConnectionString
    Using con As New SqlConnection(conString)
        Using sda As New SqlDataAdapter(query, con)
            Using dt As New DataTable()
                sda.Fill(dt)
                Return dt
            End Using
        End Using
    End Using
End Function
 
 
Binding Parent GridView
Inside the Page Load event, GetData method is called.
Inside GetData method, the records fetched from the Customers table are bound to the parent ASP.Net GridView.
C#
protected void Page_Load(object sender, EventArgs e)
{
    if (!this.IsPostBack)
    {
        gvCustomers.DataSource = GetData("SELECT TOP 10 * FROM Customers");
        gvCustomers.DataBind();
    }
}
 
VB.Net
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
    If Not Me.IsPostBack Then
        gvCustomers.DataSource = GetData("SELECT TOP 10 * FROM Customers")
        gvCustomers.DataBind()
    End If
End Sub
 
 
Binding the Child GridView with the Orders for each Customer in the Parent GridView
Inside the OnRowDataBound event handler, CustomerId is referenced using the DataKeys property and then, the child GridView is referenced.
Note: For more details on using DataKeys, please refer my article Understanding ASP.Net GridView DataKeyNames (DataKeys) with examples.
 
Finally, the child GridView is populated with the records from the Orders table fetched based on CustomerId.
C#
protected void OnRowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        string customerId = gvCustomers.DataKeys[e.Row.RowIndex].Value.ToString();
        GridView gvOrders = e.Row.FindControl("gvOrders") as GridView;
        gvOrders.DataSource = GetData(string.Format("SELECT TOP 3 * FROM Orders WHERE CustomerId='{0}'", customerId));
        gvOrders.DataBind();
    }
}
 
VB.Net
Protected Sub OnRowDataBound(sender As Object, e As GridViewRowEventArgs)
    If e.Row.RowType = DataControlRowType.DataRow Then
        Dim customerId As String = gvCustomers.DataKeys(e.Row.RowIndex).Value.ToString()
        Dim gvOrders As GridView = TryCast(e.Row.FindControl("gvOrders"), GridView)
        gvOrders.DataSource = GetData(String.Format("SELECT TOP 3 * FROM Orders WHERE CustomerId='{0}'", customerId))
        gvOrders.DataBind()
    End If
End Sub
 
 
Client Side Expand Collapse functionality using jQuery
Inside the HTML Markup, the jQuery JS script file is inherited.
Plus Button
The plus image has been assigned with a jQuery click event handler.
Inside this event handler, the child Grid html is referenced and appended to the Parent GridView row and the minus image is displayed and the plus image is made hidden.
 
Minus Button
The minus image has been assigned with a jQuery click event handler.
Inside this event handler, the child Grid is removed from the Parent GridView row and the plus image is displayed and the minus image is made hidden.
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="text/javascript">
    $("[src*=plus]").on("click", function () {
        $(this).closest("tr").after("<tr><td></td><td class='orders' colspan = '999'>" + $(this).closest("tr").find("[id*=Orders]").html() + "</td></tr>");
        $(this).closest("tr").find("[src*=minus]").show();
        $(this).hide();
    });
    $("[src*=minus]").on("click", function () {
        $(this).closest("tr").next().remove();
        $(this).closest("tr").find("[src*=plus]").show();
        $(this).hide();
    });
</script>
 
 
Screenshot
Gridview inside Gridview in ASP.Net using C# and VB.Net
 
 
Demo
 
 
Downloads