Introduction
This article explains how to load custom business objects and bind them to a GridView
. Only one common method is required for loading all business objects. I used a simple attribute class to map data from a database field to an object property. The container for business objects is a generics list. For now, I want to keep it simple. In future, I want to extend the functionality and make it more scalable. This project uses the following new features in .NET 2.0:
- Loading a
DataTable
from a DataReader
.
- Obtaining the
ConnectionString
section from web.config.
- Generics.
- The
ObjectDataSource
control.
Solution walkthrough
This solution contains two projects. The first project (class library) explains how to populate custom business objects from database records. The second project (website) explains how to bind custom business objects to a GridView
using an ObjectDataSource
control. For demo purposes, I used the "AdventureWorks" database which ships with SQL 2005.
Populate Custom Business Objects (Project: AWBusinessObjects)
This project contains a database helper class, a custom attribute class, a business objects base class and two sample business objects.
Database Helper Class: DBHelper.cs
It is useful for loading data from a database to a disconnected DataTable
using a DataReader
. This feature is introduced in .NET 2.0. The ConfigurationManager
of System.Configuration
is used to get the database connection information from the web.config file.
class DBHelper
{
private string strConn;
private SqlCommand dbCmd;
private SqlConnection dbConn;
private SqlDataReader dbReader;
private DataTable dbTable;
public DBHelper()
{
ConnectionStringSettings settings =
ConfigurationManager.ConnectionStrings["DBConn"];
if (settings != null)
{
strConn = settings.ConnectionString;
}
}
public DataTable RetrieveTable(string pSqlText)
{
try
{
dbTable = new DataTable();
dbConn = new SqlConnection(strConn);
dbCmd = dbConn.CreateCommand();
dbCmd.CommandText = pSqlText;
dbConn.Open();
dbReader = dbCmd.ExecuteReader();
if (dbReader != null && dbReader.HasRows)
{
dbTable.Load(dbReader);
}
}
finally
{
dbConn.Close();
}
return dbTable;
}
}
Attribute Class: MapInfo.cs
It defines the information to map data from a database field to a business object property. Nothing special here. It contains two fields. This attribute can be used at class level, or at property level, or at field level. AllowMultiple
controls whether the attribute is multi-use.
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Property |
AttributeTargets.Field,AllowMultiple=true)]
class MapInfo: System.Attribute
{
private string dbFieldName;
private string objPropertyName;
public MapInfo(string pDBFieldName,
string pObjPropertyName)
{
dbFieldName = pDBFieldName;
objPropertyName = pObjPropertyName;
}
public string DBFieldName
{
get { return dbFieldName;}
}
public string ObjPropertyName
{
get { return objPropertyName;}
}
}
Base Class for Business Objects: AWBase.cs
This contains the common method for populating inherited business objects. Reflection is used here for getting custom attributes and properties. It loops through all rows in a DataTable
and sets property values using a custom attribute array. A generic list of the base class type is used as the container for the business objects. If the database value is DBNull
then null
is used as the property value.
public abstract class AWBase
{
protected string SqlSelectString;
protected Type BusObjectType;
protected List<AWBase> LoadData()
{
List<AWBase> _list = new List<AWBase>();
DBHelper dbHelper = new DBHelper();
DataTable dtBusinessObject =
dbHelper.RetrieveTable(SqlSelectString);
object[] attributes =
BusObjectType.GetCustomAttributes(typeof(MapInfo), false);
if (dtBusinessObject != null && dtBusinessObject.Rows.Count > 1)
{
for (int introw = 0; introw <
dtBusinessObject.Rows.Count; introw++)
{
AWBase busObject =
(AWBase)Activator.CreateInstance(BusObjectType);
for (int i = 0; i < attributes.Length; i++)
{
MapInfo mapinfo = (MapInfo)attributes[i];
object DBValue =
dtBusinessObject.Rows[introw][mapinfo.DBFieldName];
PropertyInfo pinfo =
BusObjectType.GetProperty(mapinfo.ObjPropertyName);
pinfo.SetValue(busObject, DBValue.GetType()
== typeof(DBNull) ? null: DBValue, null);
}
_list.Add(busObject);
}
}
return _list;
}
}
Business Object: Product.cs
It is a sample class for business objects. This class will be populated from the "Production.Product" table. It has mapping information and public properties. The "GetProducts
" method returns a generic list of products. Later, this method is used as SelectMethod
in the ObjectDataSource
control. For converting a base class to a child class, the generics ConvertAll
function is used. Typically, all business objects contain mapping information, public properties, a select method, and an explicit casting method:
[MapInfo("ProductID", "ID")]
[MapInfo("Name", "Name")]
[MapInfo("ProductNumber", "Code")]
public class Product : AWBase
{
private int id;
private string name;
private string code;
...
public int ID
{
get { return id; }
set { id = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
public string Code
{
get { return code; }
set { code = value; }
}
....
public Product AWBaseToProduct(AWBase pAWBase)
{
return (Product)pAWBase;
}
public List<Product> GetProducts()
{
base.SqlSelectString =
"select * from Production.Product";
base.BusObjectType = this.GetType();
List<AWBase> list = base.LoadData();
return list.ConvertAll<Product>(new
Converter<AWBase,Product>(AWBaseToProduct));
}
}
The "GetContacts
" method in the Contact
business object would look like:
public List<Contact> GetContacts()
{
base.SqlSelectString = "select * from Person.Contact";
base.BusObjectType = this.GetType();
List<AWBase> list = base.LoadData();
return list.ConvertAll<Contact>(new
Converter<AWBase,Contact>(AWBaseToContact));
}
Bind a Generic list of business objects to a GridView (Project: AWDemo)
No written code is necessary here. Just dragging and configuring controls. I am giving here, the steps in order:
- Create a new website.
- Add a Reference to the previously created project.
- Drag the "
ObjectDataSource
" control to the web page.
- Select
TypeName
and SelectMethod
from the data source configuration wizard.
- Drag the "
GridView
" control to the webpage.
- Set the
DataSourceID
.
- Configure the paging properties and the
GridView
fields.
The code in the aspx page looks like this:
<asp:GridView id="GridView1" runat="server"
CellPadding="4" GridLines="None" ForeColor="#333333"
DataSourceID="ObjectDataSource1"
AutoGenerateColumns="False" AllowPaging="True"
PageSize="20" AllowSorting="True"
EmptyDataText="No Products Found">
<FooterStyle BackColor="#1C5E55"
Font-Bold="True" ForeColor="White" />
<Columns>
<asp:BoundField DataField="ID"
HeaderText="ID" ></asp:BoundField>
<asp:BoundField DataField="Name"
HeaderText="Name" ></asp:BoundField>
<asp:BoundField DataField="Code"
HeaderText="Code" ></asp:BoundField>
<asp:CheckBoxField DataField="Finished"
HeaderText="Finished" ></asp:CheckBoxField>
<asp:BoundField DataField="Size"
HeaderText="Size" NullDisplayText="N/A">
</asp:BoundField>
<asp:BoundField DataField="Cost"
HeaderText="Cost" NullDisplayText="N/A">
</asp:BoundField>
<asp:BoundField DataField="Color"
HeaderText="Color" NullDisplayText="N/A">
</asp:BoundField>
<asp:BoundField DataField="ModifiedDate"
HeaderText="ModifiedDate">
</asp:BoundField>
</Columns>
<RowStyle BackColor="#E3EAEB" />
<EditRowStyle BackColor="#7C6F57" />
<SelectedRowStyle BackColor="#C5BBAF"
Font-Bold="True" ForeColor="#333333" />
<PagerStyle BackColor="#666666"
ForeColor="White" HorizontalAlign="Center" />
<HeaderStyle BackColor="#1C5E55"
Font-Bold="True" ForeColor="White" />
<AlternatingRowStyle BackColor="White" />
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1"
runat="server" SelectMethod="GetProducts"
TypeName="Product"></asp:ObjectDataSource>
I hope I kept it simple, and hopefully you will find it useful.