ASP.NET: Have a meaningful relationship with your DataSet

I'm a big fan of the XSD Schema or DataSet Designer in Visual Studio 2005.  Knowing how to use this tool effectively can save you hundreds of hours in writing database layer code.  Some balk at the idea, but I say I'm not a plumber - I'd rather spend time polishing the user interface than mucking around in the bowels of database connections.

One thing I've wondered since I first saw the Designer, was how to leverage the Relationships defined in the designer.  For example, here is a simple DataSet based on some tables from the AdvertureWorks example database:

Products.xsd

Now, our goal here is to leverage these relations while keeping our ObjectDataSource and GridView/DetailView features.  Otherwise, what's the point of a tool that requires a code behind file for every page?  Normally, an ObjectDataSource invokes a TableAdapter method to load a single DataTable.  What we need to do is also load the other tables in the DataSet, and return the DataTable attached to the DataSet.  Since TableAdapters are partial classes, we have a perfect place to do this.

using System;
using System.ComponentModel;

namespace ProductsTableAdapters {
    public partial class ProductTableAdapter {

        [DataObjectMethod(DataObjectMethodType.Select, false)]
        public Products.ProductDataTable GetAllWithDataSet() {
            Products products = new Products();

            ProductModelTableAdapter taModel = new ProductModelTableAdapter();
            ProductInventoryTableAdapter taInv = new ProductInventoryTableAdapter();

            taModel.Connection = this.Connection;
            taInv.Connection = this.Connection;

            this.Connection.Open();

            this.FillAll(products.Product);
            taModel.FillAll(products.ProductModel);
            taInv.FillAll(products.ProductInventory);

            this.Connection.Close();

            return products.Product;
        }
    }
}

Here is a new TableAdapter method for the Product table.  The first step in the method is to create the products DataSet.  This DataSet holds an empty DataTable for all the tables in the DataSet, and also includes all the relationship among the tables - we just need to add the data.  This is done by getting TableAdapters for the other tables, and then using the Fill methods to fill the existing DataTables.  Then we return just the Product DataTable as normal, but this table now has access to the other tables in the schema.

Okay, I skipped over the connection part.  While it's not required, if I did nothing each TableAdapter would create, open and close it's own connection (or at least suck up a connection from the pool, then replace it).  Since I know I'm about to make three database calls, it would make baby Jesus cry to allow the TableAdapters to act this way.  TableAdapters are smart though, if you invoke a method with an already open connection it will not create a new connection, and leave the connection open when done.

By the way, if you need to alter a connection outside of a TableAdapter, then in the DataSet Designer, click on each of your TableAdapters and change the ConnectionModifer from "Assembly" to "Public".

Now all we need to do is connect up our web controls:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    SelectMethod="GetAllWithDataSet" TypeName="ProductsTableAdapters.ProductTableAdapter">
</asp:ObjectDataSource>
<asp:DetailsView ID="DetailsView1" runat="server" AllowPaging="True" AutoGenerateRows="False"
    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1">
    <FieldHeaderStyle Font-Bold="true" />
    <Fields>
        <asp:BoundField DataField="ProductNumber" HeaderText="Product Number"/>
        <asp:TemplateField HeaderText="Model">
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server" 
                Text='<%# ((System.Data.DataRowView)Container.DataItem).Row.GetParentRow("Product_ProductModel")["Name"] %>' />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
        <asp:BoundField DataField="ListPrice" DataFormatString="{0:c}" HeaderText="List Price"
            HtmlEncode="False" />
        <asp:BoundField DataField="StandardCost" DataFormatString="{0:c}" HeaderText="Standard Cost"
            HtmlEncode="False" />
        <asp:BoundField DataField="Color" HeaderText="Color"/>
        <asp:TemplateField ShowHeader="False">
            <ItemTemplate>
                <b>Inventory Status</b>
                <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" Width="100%"
                    DataSource='<%# ((System.Data.DataRowView)Container.DataItem).Row.GetChildRows("ProductInventory_Product") %>'>
                    <Columns>
                        <asp:BoundField DataField="Name" HeaderText="Name" />
                        <asp:BoundField DataField="Shelf" HeaderText="Shelf" />
                        <asp:BoundField DataField="Bin" HeaderText="Bin" />
                        <asp:BoundField DataField="Quantity" HeaderText="Quantity" />
                    </Columns>
                </asp:GridView>
            </ItemTemplate>
        </asp:TemplateField>
    </Fields>
</asp:DetailsView>

For the most part, nothing special here.  In the Model field, we need to walk the relationship to a parent record.  The syntax it a bit long, but it gets the job done:

((System.Data.DataRowView)Container.DataItem).Row.GetParentRow("Product_ProductModel")["Name"]
First part casts the record we are on as a DataRowView so we can call GetParentRow which takes the name of the relationship.  Since we don't want the whole row, just a field, we call the index of the field.  To get the child records of the ProductInventory table, the syntax is very similar:
((System.Data.DataRowView)Container.DataItem).Row.GetChildRows("ProductInventory_Product")

Note we assign this to the DataSource not DataSourceID attribute of the GridView.  The output will look something like this:

Now that you know how to connect a DataSet easily to your ASP.NET view controls, it's worth the looking deeper into the DataSet.  Then on your next project, let the framework do the plumbing while you go out and play.

Posted By Mike On Friday, April 20, 2007
Filed under datasets | Comments (3)

Submit this story to DotNetKicks   

Dan - Wednesday, November 28, 2007 4:55:04 AM

You can shorten the long databinding syntax in the aspx page by creating a protected method in the codebehind, such that:

Text='<%# ((System.Data.DataRowView)Container.DataItem).Row.GetParentRow("Product_ProductModel")["Name"] %>'

and

DataSource='<%# ((System.Data.DataRowView)Container.DataItem).Row.GetChildRows("ProductInventory_Product") %>'

turns into

Text='<%# GetModelName(Container.DataItem) %>'

DataSource='<%# GetInventoryStatus(Container.DataItem) %>'

and

private DataRow GetDataRow(object dataItem)
{
return ((System.Data.DataRowView)dataItem).Row;
}

protected string GetModelName(object dataItem)
{
return GetDataRow(dataItem).GetParentRow("Product_ProductModel")["Name"];
}

protected object GetModelName(object dataItem)
{
return GetDataRow(dataItem).GetParentRow("Product_ProductModel")["Name"];
}

protected string GetInventoryStatus(object dataItem) {
return GetDataRow(dataItem).GetChildRows("ProductInventory_Product");
}

Mike - Wednesday, November 28, 2007 2:37:24 PM

Great tip Dan! Sometimes... okay many times I miss the obvious.

Steveo - Friday, March 07, 2008 11:08:44 PM

Thank you so much! This is just what I was trying to figure out how to do.

Leave a comment



Your name:
 

Your email (not shown):
 
Will display your Gravatar image.

Your website (optional):



About Me

Michael C. Neel, born 1976 in Houston, TX and now live in Knoxvile, TN. Software developer, currently .Net focused. Board member and President of ETNUG, and organizes CodeStock, East Tennessee's annual developers conference. .Net speaker, a Microsoft ASP.NET MVP and ASPInsider. Founder of FuncWorks, LLC and Feel The Func podcast.

Proud father of two amazing girls, Rachel and Hannah.

 Subscribe to ViNull.com |  Comments

Follow me on Twitter | Contact Me

Related Posts

You'll have my SQL when you pry my keyboard from my cold dead hands

Background - Some people don't like the Entity Framework, some do.  Many see the need to blog about it.  You can guess the rest, or JFGI if your ... Read more

ASP.NET: Totaling a GridView part 2, the SQL

Not long ago, I wrote a quick guide on adding a total to a GridView.  It was based upon building a cumulative total while looping though the GridView ... Read more

Birmingham Slides and Code

I had a great time last night in Birmingham hanging out with Robert Cain and speaking to the user group on DataSets.  it may not have been standing ... Read more

Birmingham Software Developers Association, May 14th

Next Wednesday I'll be in Birmingham, Alabama speaking on DataSets.   Yes, DataSets.  I actually have quite a fondness for DataSets and ... Read more

Presentation: DataSets - Code Less, Do More

Download DataSets - Code Less, Do More Example Code and Slides Abstract: DataSets are Evil. They will hog your CPU, steal your RAM, and rob your home. ... Read more

FeelTheFunc Podcast

CodeStock
Are you going?

ASPInsiders Member

ETNUG Member