Click here to Skip to main content
Click here to Skip to main content

Dynamically Map SQL Resultset to Business Object

By , 21 Feb 2013
 

Introduction

Comments and votes are always appreciated! Also help AweSam.Net in supporting struggling Non-Profit Organizations and charities by joining our network.  AweSam.Net is a supporter of Open Source Software so look for many great .NET libraries to come!  Follow us on twitter at @AweSamNet or on Facebook.

A common issue in our company is working with non-type-safe data. Our DBA writes very efficient stored procedures which often join with related tables to reduce DB calls. Our developers must in turn cycle through the result sets and manually map each column to an appropriate class and property. The issue is the amount of redundant code and room for human error. For example, there may be 3 different stored procedures that all return rows of the School table:

  • A Stored Procedure called to get a school

  • A Stored Procedure to get a student with the linked school information

  • A Stored Procedure to get a teacher with the linked school information

For each of these stored procedures a coder might write a method to map the result sets to their corresponding classes.

The coder must also ensure that he is properly casting and handle any potential cast exceptions each time. This leaves more responsibility in the hands of the developer than most developers want.

The DynamicResultSet library allows you to use the data returned from one stored procedure to populate any number of DynamicClasses. For example, a stored procedure that returns students linked to teachers linked to schools would be processed as such:

DynamicResultSet drs = new DynamicResultSet(myDataTable);


List<Student> students = drs.GetList<Student>();
List<Teacher> teachers = drs.GetList<Teacher>();
List<School> schools = drs.GetList<School>();

//or access the School directly from the properties:
School myScool = students[0].Teacher.School;
Through Reflection, we loop through the class' properties and read all mappings indicated in the attributes of this property. We then make sure the column exists in the table and if so, we do our casting and populate the property based on the result.

Using the code

Before using this code, it is recommended that you install the C# snippets included.

The snippets to install are:

  • DynamicClassSnippet.snippet
  • DynamicPropertySnippet.snippet

The first task is to include the Utilities.DynamicClasses.dll in your project references. Once this is done, be sure to add a:

using Utilities.DynamicClasses;
using System.Data;

to your .cs file.

Now we are ready to create our first class. All classes generated by a DynamicResultSet must inherit the Utilities.DynamicClasses.BusinessLogicBase.

Overrides

By inheriting BusinessLogicBase, you must override three functions.

  • OnLoaded() - this function is executed by the DynamicResultSet once the row has been fully parsed into the new class. Any variables that must be set or methods that must be executed once all the data is loaded should be done in this method.
  • IsEmpty() - this function is what tells the DynamicResultSet to ignore a record or to return it in the GetList(). For example, a stored procedure may be executing an Outer Join in which case it is very possible that all values of a linked table are null. In this case, you would use IsEmpty() to return true if certain ID fields are not included in a record.
  • ToString() - This field is simply to force our developers to properly set the ToString() so that the BusinessLogicBase default comparers can be properly used for sorting.

DynamicProperty Attribute

If you have the snippets installed, the quickest way for us to generate a DynamicProperty (a property that is dynamically mapped to a DataTable column), is to type the snippet shortcut "dynamicProperty" and press tab. Once the Dynamic property is generated, you can tab through the snippet value placeholders to change them. There are 4 values you need to update:

  • Name - this is the property name. E.g., StudentID
  • Type - this is the data type of this property. This must be a nullable type to properly represent a value that might be returned from a data source. E.g., int?
  • ColumnName - this is the name of the column in the datatable that will be mapped to this property
  • SqlDbType - this is the datatype that is expected (or as close as possible) to be returned from the data source.

Once all four variables have been properly set, press enter and you will be left with something that will resemble the following if we were creating a Student class with a StudentID, StudentName, SchoolID and a TeacherID:

#region StudentID
private int? _StudentID;

[DynamicProperty(ColumnName = "st_studentId", DatabaseType = SqlDbType.Int)]
public int? StudentID
{
    get
    {
        return _StudentID;
    }
    set
    {
        IsStudentIDSet = true;
    }
}


public bool IsStudentIDSet { get; set; }
#endregion

#region StudentName
private String _StudentName;

[DynamicProperty(ColumnName = "st_studentName", DatabaseType = SqlDbType.NVarChar)]
public String StudentName
{
    get
    {
        return _StudentName;
    }
    set
    {
        IsStudentNameSet = true;
    }
}


public bool IsStudentNameSet { get; set; }
#endregion

#region SchoolID
private int? _SchoolID;

[DynamicProperty(ColumnName = "st_schoolId", DatabaseType = SqlDbType.Int)]
public int? SchoolID
{
    get
    {
        return _SchoolID;
    }
    set
    {
        IsSchoolIDSet = true;
    }
}


public bool IsSchoolIDSet { get; set; }
#endregion

#region TeacherID
private int? _TeacherID;

[DynamicProperty(ColumnName = "st_teacherId", DatabaseType = SqlDbType.Int)]
public int? TeacherID
{
    get
    {
        return _TeacherID;
    }
    set
    {
        IsTeacherIDSet = true;
    }
}

public bool IsTeacherIDSet { get; set; }
#endregion

#region TeacherName
private String _TeacherName;

[DynamicProperty(ColumnName = "st_teacherName", DatabaseType = SqlDbType.Int)]
public String TeacherName
{
    get
    {
        return _TeacherName;
    }
    set
    {
        IsTeacherNameSet = true;
    }
}


public bool IsTeacherNameSet { get; set; }
#endregion

DynamicClass Attribute

A DynamicClass property is another type that inherits BusinessLogicBase that is included in the current class as a property. So for example working with our above Student class, a stored procedure may return a school that is joined to the student record on the same row of the student data. In this case we have enough data to populate the student and it's related school. In this case we would include a Student.RelatedSchool property and would add the DynamicClass attribute to tell the DynamicResultSet to parse the school data into the Student.RelatedSchool property.

Creating a DynamicClass property of a class is very similar to creating a DynamicProperty when using the dynamicClass snippet. Type "dynamicClass" and press tab. Once the Dynamic Class is generated, you can tab through the snippet value placeholders to change them. There are 3 values you need to update:

  • Name - this is the property name. E.g. RelatedSchool
  • Type - this is the data type of this property. This must be a type that inherits from BusinessLogicBase. E.g. School
  • IDFieldName - this is the field that links the DynamicClass property to the class we are creating. E.g. SchoolID

Once all three variables have been properly set, press enter and you will be left with something that will resemble the following if we were creating Student.RelatedSchool property that is linked by the above SchoolID property:

#region RelatedSchool
private School _RelatedSchool;

[ScriptIgnore]
[DynamicClass(typeof(School), "SchoolID")]
public School RelatedSchool
{
    get
    {
        return _RelatedSchool;
    }
    private set
    {
        _RelatedSchool = value; // Set cached variable to the value
        if (value == null) // If it's null set the identifier to null
            this.SchoolID = null;
        else // Otherwise set the identifier to the value's ID
            this.SchoolID = value.ID;
    }
}
#endregion

DynamicResultSet

Now that we have our classes defined, it's time to see how we connect our data to them. The DynamicResultSet constructor accepts a DataTable as a parameter. For the sake of this demonstration we will manually create a DataTable instead of getting it from a stored procedure.

DataTable table = new DataTable("Students");
table.Columns.Add("st_studentId");
table.Columns.Add("st_schoolId");
table.Columns.Add("st_teacherId");

table.Columns.Add("sch_schoolId");
table.Columns.Add("sch_schoolName");


DataRow row = table.NewRow();
row["st_studentId"] = 1;
row["st_studentName"] = "Sam Jr.";
row["st_schoolId"] = 23;
row["st_teacherId"] = 54;

row["te_teacherId"] = 54;
row["te_teacherName"] = "Mr. Smith";

row["sch_schoolId"] = 23;
row["sch_schoolName"] = "Pleasantview High School";

DataRow row2 = table.NewRow();
row2["st_studentId"] = 1;
row2["st_schoolId"] = 232;
row2["st_teacherId"] = 86;

row2["sch_schoolId"] = 232;
row2["sch_schoolName"] = "Colonel Saunders Military High School";

table.Rows.Add(row);
table.Rows.Add(row2);

Now that we have our DataTable, we can pump it into the DynamicResultSet and generate our objects. Notice how above we declared a Student.TeacherName property, however we did not include the appropriate column in the DataTable. The DynamicResultSet will not break if there are columns that are not expected or if there are columns that are null or excluded. Assuming we have created both the School class and the Teacher class, let's take a look at how the DynamicResultSet works.

DynamicResultSet drs = new DynamicResultSet(table);

List<Student> students = drs.GetList<Student>();
List<Teacher> teachers = drs.GetList<Teacher>();
List<School> schools = drs.GetList<School>();

//or access the School directly from the properties:
School myScool = students[0].RelatedSchool;

What we've done is populate the table into a new DynamicResultSet. We then use the generic method GetList<T>() to parse the passed datatable and return all possible objects of the generic type.

Notice that on the last line, we are able to access the linked school directly by accessing the RelatedSchool property (which as we remember above, has the DynamicClass attribute). This is because the DynamicResultSet recognized the attribute and used the DataRow to populate the RelatedSchool property as best as it could.

Is[property name]Set

If you notice the code generated for a DynamicProperty, it creates a second boolean property associated to the main property to store the proper value. This property is the Is[PropertyName]Set property. This property is what lets you know that this column was or was not returned from the stored procedure. In our above case, we declared a property called Student.TeacherName, however we did not add the associated column to the DataTable. Since the Student.TeacherName property was never set, the Student.IsTeacherNameSet will be false.

Update:

Upon request, I have added some new features for convenience.

  • GetPropertyAttributes

The method:

public static T[] GetPropertyAttributes<T>(this object source,  String propertyName) where T : Attribute
has been added as an extension method for all classes that derive from object. It returns an array of type T where T is a System.Attribute.
  • BusinessLogicBase.GetDynamicPropertyAttributes

The method:

public DynamicProperty[] GetDynamicPropertyAttributes(String propertyName)
has been added to the class BusinessLogicBase. This method uses the generic method GetPropertyAttributes to return an array of DynamicProperty for a given property name.
  • BusinessLogicBase.GetDynamicClassAttributes

The method:

public DynamicProperty[] GetDynamicClassAttributes(String propertyName)

has been added to the class BusinessLogicBase. This method uses the generic method GetPropertyAttributes to return an array of DynamicClass for a given property name.

History

  • 2012-02-07 - Initial posting.
  • 2012-09-28 - Added new methods to return arrays of attributes for a given property.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Sam Lombardo
Software Developer (Senior)
Canada Canada
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 4memberJoshua Hightower21 Feb '13 - 6:14 
I could totally use this for my code.
GeneralMy vote of 5memberRutuanie20 Feb '13 - 21:04 
Very good
GeneralRe: My vote of 5memberSam Lombardo21 Feb '13 - 3:54 
Thank you for the vote!
-----------------------------------------
 
Help us help others who help us... and others. Providing free and low-cost IT solutions for NPOs! Join AweSam.Net on facebook!

GeneralMy vote of 5membertikkababy17 Dec '12 - 7:37 
I can use this. Today.
GeneralRe: My vote of 5memberSam Lombardo17 Dec '12 - 8:25 
Good! Enjoy, and thanks for voting.
Help us help others who help us... and others. Providing free and low-cost IT solutions for NPOs! AweSam.Net!

QuestionDoes it work with LINQ2SQL and Entity FrameworkmemberTridip Bhattacharjee2 Oct '12 - 6:10 
put some light for how to implement your class library when people use linq2sql or entity framework etc. thanks
tbhattacharjee

AnswerRe: Does it work with LINQ2SQL and Entity FrameworkmemberSam Lombardo2 Oct '12 - 8:08 
The implementation in this article shows how to use DynamicRecordSet with a standard DataTable. In the case of something like EntityFramework or nHibernate, I'm not sure there is a benefit in using this library unless you wanted to try and return some sort of subset of records(which you can easily acheive through linq2sql, EF, nHibernate without the use of DynamicRecordSet). While I have plans to eventually implement this solutions for more than just datatables, (ex: xml, DataReader, etc...) it was originally designed with a very specific need in mind: Finding a programmer friendly, type-safe solution to using Ado.Net.
 
That being said, since the result is a List of BusinessLogicBase, you can certainaly use it with linq to objects:
 
DynamicResultSet dsr = new DynamicResultSet(myDataTable);
List<Teacher> allTeachers = dsr.GetList<Teacher>();
 
var teachersWithNoSchoolId = from x in allTeachers
                             where !x.SchoolId.HasValue
                             select x;
 
However you do bring up an interesting idea: I could always implement a linq provider to ONLY generate objects that adhere to the query! Thanks for the idea!
-----------------------------------------
 
Help us help others who help us... and others. Providing free and low-cost IT solutions for charities! Vote for AweSam.Net!

GeneralMy vote of 5memberDaniel Cruz27 Sep '12 - 12:08 
Hello excellent article.
Is it possible to access the ColumnName of a property with your code?
I'm trying to do the following
 
Public Class A
���� Private m_IdCustomer
���� Public Property IdCustomer () As Integer
�������� Get
������������ Return m_IdCustomer
�������� End Get
�������� Set (ByVal value As Integer)
������������ m_IdCustomer = value
�������� End Set
���� End Property
End Class
 
Public Class B
���� Private obj As A
 
���� Public Sub WriteCustomer (ByVal Value As Integer)
���� '' Here I get the name of the property no value
�������� Row (obj.IdCustomer.ColumnName) = Value
���� End Sub
End Class
 
'' translated in google
GeneralRe: My vote of 5memberSam Lombardo28 Sep '12 - 6:44 
Good question! I was going to reply with a way you could do it, then post the anseer as a comment, but I figured it might be useful in this project. So I just added it to the source.
 
Changes are:
1:
DynamicProperty[] BusinessLogicBase.GetDynamicPropertyAttributes(String propertyName)
2:
DynamicClass[] BusinessLogicBase.GetDynamicClassAttributes(String propertyName)
3: (your welcome) extension method for all objects:
T[] GetPropertyAttributes<T>(this object source,  String propertyName) where T : Attribute
 
//usage:
MyCustomAttribute[] attributes = school.GetPropertyAttributes<MyCustomAttribute>("myProperty");
-----------------------------------------
 
Help us help others who help us... and others. Providing free and low-cost IT solutions for NPOs! Vote for AweSam.Net each day starting Oct 1st, 2012!

GeneralMy vote of 5memberEric Lapouge25 Sep '12 - 14:11 
My vote 5, since I learned some stuff from your code and even if I don't post a lot, I'm always happy to see people sharing their knowledge Smile | :)
 
I had a similar problem with my actual development (stored procedures to extract data from the DB and to "consume" the extracted data in objects in my C# WebServices) and found a bit easier to do it differently (I hadn't to dwelve with hierarchies for example but had other issues which are too long to explain here). What I did, was "simply" to define my stored-procedures to return xml flow, xml which are defined from xsd files. I then "only" have to deserialize the xml flow to get my objects.
 
Regards,
 
Eric
GeneralRe: My vote of 5memberSam Lombardo26 Sep '12 - 4:59 
Wow! I love that idea! It's interesting that I never came across that solution while researching. Glad I didn't though, then I wouldn't have had as much fun developing this project as I did! Wink | ;)
-----------------------------------------
 
Help us help others who help us... and others. Providing free and low-cost IT solutions for NPOs! Vote for AweSam.Net!

GeneralRe: My vote of 5memberEric Lapouge26 Sep '12 - 7:58 
I guess it's a good idea for an article, I will think about it.
 
Regards,
 
Eric
GeneralRe: My vote of 5memberandegre21 Feb '13 - 7:25 
This is exactly what I've thought of for the application that I support. As long as you can build your resulting xml "blob" in the same structure as your custom objects are defined with the proper xml serialization attributes, etc, this will work GREAT! I'd love to see the article because we have yet to convert to this style...
GeneralRe: My vote of 5memberEric Lapouge21 Feb '13 - 21:45 
So, if somebody is interested, I'll spend some time soon to write it.
GeneralRe: My vote of 5memberEric Lapouge20 May '13 - 0:06 
Hi,
 
Finally I took the time to write Smile | :)
From SQL-Server to Web Service[^]
 
Have a good reading!
Regards,
Eric
QuestionWhat about performance?memberKausik J. Nag10 Jun '12 - 6:07 
Hi,
Thanks for such a good work. I am thinking to use the technique in my next project. At the same time I am bit skeptical about the performance.Does the use of reflection impact in performance? In real project where there are lots of records/columns, there is always a chance to impact performance. Did you done any performance testing?
 
Regards
AnswerRe: What about performance?memberSam Lombardo11 Jun '12 - 4:42 
Thanks for the comment! I have only done minimal performance testing as this project was less about performance and more about how to make working with stored procedures and ADO.net easier and less error prone for our junior developers.
 
However having said that, I will add that to demonstrate this solution to my employers, I chose a database-intensive page for one of our ecommerce sites (coded by a junior) and replaced ALL redundant db calls and field mapping with this solution. It took me only 2 hours to replace everything, and the result was that the page took an average of one second less to load.
 
Whether that was because the junior's code was highly inefficient or my code was much more efficient didn't matter to my employers. The point was that a junior could implement this type-safe solution easily and as a result of following some standards, his code was also more efficient. That mixed with the thoughts that there was less code to write for a faster output and mapping fields was virtually exception-proof, they were sold on implementing it on future projects.
 
I always imagined that, being based on reflection, this would actually be less efficient than manually mapping, but I am curious to see some results! If you have any please post them!
AnswerRe: What about performance?memberKelqualyn7 Oct '12 - 23:05 
Hi,
Most of reflection-prone performance impact can be reduced using minimum of dynamic compilation. Instead of executing reflective code each time you can compile it once into delegate, cache compilation result and call delegate each time, instead of reflection.
For such tasks you can use LCG or Expressions APIs, depending on version of framework you use.
Sample of such technique can be seen here:
Object Relational Mapping via Reflection[^]
 
Regards
SuggestionMy vote of 5 + IEnumerable supportmemberAkram El Assas8 Jun '12 - 14:15 
Nice approach.
 
To gain in performance and add laziness support through IEnumerable, you can add an overload of DynamicResultSet constructor that takes as input an IEnumerable<IDataRecord>, you can see my tip for this: How to use SqlDataReader result in a Layered Architecture without DataSet[^], then you can add a new method GetIEnumerable<T> to DynamicResultSet that returns the dynamic data objects as IEnumerable.
 
Roughly, this would be implemented in a way like this:
 
private IEnumerable<IDataRecord> records { get; set; }
 
public IEnumerable<T> GetIEnumerable<T>()
    where T : BusinessLogicBase, new()
{
    return records.Select(RecordToType<T>);
}
 
static public T RecordToType<T>(IDataRecord record) where T : BusinessLogicBase, new()
{
    T typedRecord = null;
    // todo: construct typedRecord from record...
    return typedRecord;
}

GeneralRe: My vote of 5 + IEnumerable supportmemberSam Lombardo11 Jun '12 - 4:27 
Thanks for the comment! I always welcome suggestions and I like yours! When working with reflection, we must focus on performance. So this is a welcomed addition! I will investigate and likely add it to the solution!
GeneralMy vote of 5memberJF20157 Jun '12 - 17:47 
Excellent article. Well done!
GeneralRe: My vote of 5memberSam Lombardo8 Jun '12 - 6:09 
Thanks for the vote!
SuggestionExactly what I was looking for!memberAndrew Hanigan28 Mar '12 - 4:52 
Sam, I work in a similar company to yours where the database is king & applications need to follow it's lead.
 
I've been pondering and researching ways to introduce a dynamic, strongly typed object collection into our applications without having to go "all in" with the likes of LINQ, etc. What you've made is a very good fit to what I'm loooking for.
 
Just as an FYI, I'm likely to change it to have the DynamicResultSet class talk directly with my Data Access Layer & possibly even use a DataReader to populate my List of BusinessLogicBase objects as this might see a performance improvement, but it could also be impossible, what with the need to know/check the resultset's column names...
 
Also, many of our stored procs return more than 1 resultset (think header & detail of an invoices for a client). I'll be looking to make it so I can map differnt BusinessLogicBase objects to each expected resultset - perhaps even somehow making use of the DynamicClass feature in cases where there is a relationship between the 2 (but there isn't always)
 
Anyways, just thought I'd share my plans in case you want ot jump the gun & write it for me (wishful thinking Laugh | :laugh: ) - otherwise I'll be sure to keep you up to speed on my progress if you're interested.
 
Once again, thanks!
GeneralRe: Exactly what I was looking for!memberSam Lombardo29 Mar '12 - 4:07 
Thanks for your comment! For sure these are scenarios I have been meaning to plot out and apply to the utility, the problem is I haven't had the time, nor will I anytime soon. So by all means, feel free to build on this idea and keep me up to date! A reason I posted this is that it can go in so many directions that I'd really like to take it, but just don't have the time. So maybe others will!
GeneralRe: Exactly what I was looking for!memberAkram El Assas8 Jun '12 - 14:18 
Andrew,
 
This suggestion[^] I made might interest you.
 
Kind regards,

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 21 Feb 2013
Article Copyright 2012 by Sam Lombardo
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid