Introduction
In this article, I present a set of classes that can be used to create a generic List<T> from a specified DataSet object. This could be useful in cases where we have data for a specified entity loaded into a DataSet and need to easily construct a generic List<> of that entity type.
To demonstrate the usage, we will consider the following scenario: We have to get an Address Book application running. We are using a database for storage, and have created the following tables:

- Contact - Contains details about a contact
- Address - Contains details about an address
- ContactAddress - Maps a contact to multiple addresses
With this data model in mind, we proceed to write two stored procs, one to get all contacts in the system, and one to get the contact details given the contact ID. While listing all contacts, we only need the contact ID and the contact name to be displayed. While getting the contact details, we need all the contact details and the list of addresses the contact has.
GetContactById - Gets details of a contact. Also gets all the addresses belonging to that contact.
CREATE PROCEDURE dbo.GetContacts
AS
SET NOCOUNT ON
SELECT ContactId, ContactName
FROM Contact
RETURN
GetContactById - Gets details of a contact. Also gets all the addresses belonging to that contact.
CREATE PROCEDURE dbo.GetContactById
(
@ContactId UNIQUEIDENTIFIER
)
AS
SET NOCOUNT ON
SELECT ContactId, ContactEmail, ContactName
FROM Contact
WHERE (ContactId = @ContactId)
SELECT Address.AddressId, Address.AddressLine2,
Address.AddressLine1, Address.AddressLine3, Address.AddressType
FROM Address INNER JOIN
ContactAddress ON Address.AddressId = ContactAddress.AddressId
WHERE (ContactAddress.ContactId = @ContactId)
We have simple entity types defined for Contact, Address, and ContactInfo. The Citrus.Data.Core namespace includes helper classes that will allow us to easily map these entity types with the results of these Stored Procedures.
In our context, an entity is anything that represents a business object. Contact, ContactInfo, and Address are entity types. An entity type can contain other entity types (either as a singular instance or as a list). The Contact entity contains a list of Address entities.
Pseudo Logic
The core classes handle all the details of iterating through the DataTables found in the DataSet and mapping DataColumns to the entity properties. It provides functions that can then load any given entity object from a specified DataSet.
While the core classes can be used as is with your existing entity classes, you can also alter their behaviour if required by marking up the properties of your entities with Data* attributes. This is described in detail in the next section. For example, you can let the EntityLoader know that a particular property maps to a specific data column using the DataColumn attribute on that property.
The key method for the EntityLoader class is Load<T>(DataSet dataSet). This method requires the DataSet with the necessary data and the type T for which we need to create an entity list. We start the load process with the first data table in the data set:
- Inspect the provided entity type and get a list of all its properties.
- For every property, check if we can map the property to a specified column in the current data table. This is called a column property map.
- Create a new list of the entity type.
- For every data row in the current data table:
- Create an entity of type
T.
- Load all simple properties (
string, int, bool, double, Guid etc.) of the new entity based on the data in the current row, by using the map we created.
- For all complex properties contained in the current entity (another entity or list of entities):
- If the property is an entity, the data to be loaded would be present in the current data row itself, so get the map for the contained entity and populate it.
- If the property is an entity list, the current data table will not contain the data. If the data table is specified from where this contained entity list can be loaded, do steps 1-4 with that data table.
- Once all the properties of the entity have been loaded, add it to the entity list.
- Return the entity list.
Complete Example with Supported Features
The download sample contains a sample website application which demonstrates the setup and usage of the EntityLoader class.
Note: The download sample contains the core assemblies, sample web application. and the standalone Address Book database. It also includes binaries off of Microsoft Enterprise Library 4.1 (for Data Access). The solution was created in VS 2008 Express Edition.
In the sample application for the Address Book, we have the following entities:
public class ContactInfo
{
[DataColumn("ContactId")]
public Guid Id { get; set; }
[DataColumn("ContactName")]
public string Name { get; set; }
}
public class Contact
{
[DataColumn("ContactId")]
public Guid Id { get; set; }
[DataColumn("ContactName")]
public string Name { get; set; }
[DataColumn("ContactEmail")]
public string EmailAddress { get; private set; }
[DataTable(DataTableSource.Next)]
public List<Address> Addresses { get; set; }
}
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string AddressLine3 { get; set; }
public string AddressLine4 { get; set; }
[DataConverter(typeof(AddressTypeConverter))]
public string AddressType { get; set; }
}
The three classes show various combinations of usage of the allowed attributes. In the ContactInfo class, we use the DataColumn attribute to specify which column a property should map to explicitly. In the Contact class, we use the DataTable attribute to let the EntityLoader know that the List of Address data exists in another data table (in this case, the next one). In the Address entity class, we see an example of implicit column mapping based on the property name. We also use the DataConverter attribute to specify that a property needs extra work to be populated - the AddressType is stored in the database as an int, but the Address entity has a string value for it. The implementation of the AddressTypeConverter class is shown below:
public class AddressTypeConverter : DataConverterBase<string, int>
{
public override string GetEntity(int entityData)
{
switch (entityData)
{
case 1: return "Home";
case 2: return "Office";
default: return "Unknown";
}
}
public override int GetEntityData(string entity)
{
switch (entity)
{
case "Home": return 1;
case "Office": return 2;
default: return -1;
}
}
}
These attributes help in establishing the mapping between data columns and entity properties.
Data Access Layer
The sample application has a simple data access layer built on top of the Microsoft Enterprise Library 4.1 Data Access Application Block.
The data access layer is defined as:
public static class AddressBook
and has a static constructor like so:
static AddressBook()
{
CachedEntityDefinitionProvider addressBookEntityProvider =
new CachedEntityDefinitionProvider();
addressBookEntityProvider.AddEntityType(typeof(ContactInfo));
addressBookEntityProvider.AddEntityType(typeof(Contact));
addressBookEntityProvider.AddEntityType(typeof(Address));
EntityInspector.EntityProvider = addressBookEntityProvider;
}
The CachedEntityDefinitionProvider is a simple extension class that is used to tell the entity loading system which types are entity types in the system. In our example, we specify that Contact, ContactInfo, and Address types are entity types. You can create your own version of an entity definition provider and hook it up with the EntityInspector's EntityProvider property by implementing the IEntityDefinitionProvider interface.
The core method that utilizes EntityLoader is implemented as an extension method on the Database type as follows:
static List<T> LoadEntity<T>(this Database db,
string commandName, params object[] commandArguments)
{
return EntityLoader.Load<T>(db.ExecuteDataSet(commandName, commandArguments));
}
You can change this in the sample if you do not have EL4.1.
And finally, the data access methods:
public static List<ContactInfo> GetContacts()
{
return DbContext.LoadEntity<ContactInfo>(StoredProcedures.GetContacts);
}
public static List<Contact> GetContactById(Guid id)
{
return DbContext.LoadEntity<Contact>(StoredProcedures.GetContactsById, id);
}
Note that all methods return a list of the specified entity.
Data Attributes
This section enumerates all the attributes available for use:
DataColumn - Applied on a property. Specifies which data column in a data table maps to this property.
DataTable - Applied on a list<> property. Specifies the source data table from where the list should be populated. Use as DataTable(DataTableSource.Next) or DataTable(2) to specify the relative table position.
DataConverter - Applied to a property. Tells the EntityLoader system that a specific data converter class needs to be used to load the property data.
EntityLoader - Applied to the entity class. This controls the way in which properties are checked to be loaded with data from a DataSet. By default, all properties of a given entity will be used for column mapping and data load. You can change this behaviour by specifying an InspectionPolicy parameter to the EntityLoader attribute. Possible uses are EntityLoader(InspectionPolicy.OptIn) or EntityLoader(InspectionPolicy.OptOut). If OptIn is specified, you need to mark properties explicitly with the DataInclude attribute to ensure that EntityLoader will use that property. If OptOut is specified, all properties will be inspected, unless marked with DataExclude. OptOut is the default behaviour.
Improvements
Of course, this is just a very early implementation. There are lots more to do in terms of argument validation, better exception handling, and so on.
Also included in the Citrus.Data.Core assembly is a utility class that can do the opposite thing of what we have seen here, convert a List<T> to a DataSet.. It's not complete yet, but works for simple entities (ones that do not contain properties that are lists of entities).
In case you can suggest some, please do.
References
Where possible, I have included references in the code itself. Some to look at are:
I work as a Technology Lead for an IT services company based in India.
Passions include programming methodologies, compiler theory, cartooning and calligraphy.