Codus – C# and VB.NET 2005 Using Statement – Builder Design Pattern

Let’s dig a little deeper into Codus, an O/R Mapper from Adapdev Technologies, which I originally talked about in the following post:



As I mentioned in the previous post, Codus essentially generates a Data Access Object (DAO) and a Transfer Object (TO) for each table or view you choose to map in your database.  Each generated DAO basically implements the following 4 CRUD-type methods and 1 mapping method, which are abstract methods of the base class, AbstractDAO:


 



AbstractDAO Abstract Methods
#region Abstract Items

/// <summary>
/// Maps an individual object to a DataReader row
/// </summary>
/// <param name=”dr”></param>
/// <returns></returns>
protected abstract object MapObject(IDataReader dr);

/// <summary>
/// Selects one record, using the specified id
/// </summary>
/// <param name=”id”>The id of the record to select</param>
/// <returns></returns>
protected abstract IDbCommand CreateSelectOneCommand(object id);

/// <summary>
/// Updates a record, using the values from the specified object
/// </summary>
/// <param name=”o”></param>
/// <returns></returns>
protected abstract IDbCommand CreateUpdateCommand(object o);

/// <summary>
/// Inserts a record, using the values from the specified object
/// </summary>
/// <param name=”o”></param>
/// <returns></returns>
protected abstract IDbCommand CreateInsertCommand(object o);

/// <summary>
/// Deletes one record, using the specified id
/// </summary>
/// <param name=”id”></param>
/// <returns></returns>
protected abstract IDbCommand CreateDeleteOneCommand(object id);

#endregion



 


So when you look at your generated DAO for each table or view you chose to “map,” the code is quite small and easy to understand.  A lot of the functionality that uses these methods is packed into the AbstractDAO class that is part of the Adapdev framework.


Let’s take a peek at one such method, SelectAll().  As you would expect, SelectAll() grabs all the records from a particular table or view and returns an ArrayList, which implements IList.  So, to get all the addresses in the addresses table, we do the following:


 



Get All Addresses
AddressDAO dao = new AddressDAO();
IList addresses
= dao.SelectAll();


 


The SelectAll() method is actually implemented by the AbstractDAO base class as shown below.


 



AbstractDAO.SelectAll()
/// <summary>
/// Selects all records in the underlying datastore
/// </summary>
/// <returns></returns>
public IList SelectAll()
{
IList c;
using (IDbConnection conn = this.CreateConnection())
{
conn.Open();
c
= this.SelectAll(conn);
}
return c;
}

/// <summary>
/// Selects all records in the underlying datastore,
/// using the specified open IDbConnection
/// </summary>
/// <param name=”conn”>The open IDbConnection to use</param>
/// <returns></returns>
public IList SelectAll(IDbConnection conn)
{
IList c;
IDataReader dr
= DbProviderFactory.CreateDataReader(conn,
this.CreateSelectAllCommand(), this.provider);
c
= this.MapObjects(dr);
dr.Close();
return c;
}



 


The bulk of the work is done within the call to MapObjects, which we will talk about in a second.  These methods go out and construct a proper “Select * from [Address]” query and a DataReader for the appropriate database type.  As an aside, notice the C# using statement in the SelectAll() method.  IDbConnection implements IDisposable, and the using statement in C# is a nice way to make sure all unmanaged resources are cleaned up after we extract the data from the database.  The using statement essentially adds a try/finally block and calls Dispose() under the covers so you don’t have to (VB.NET 2005 has the using keyword now as well).  For more information on implementing the using statement with ADO.NET, you can read the following post:  C# Using Statement – Try / Finally – IDisposable.


However, IDataReader also implements IDisposable, and at first glance, I would like to see a using statement around IDataReader as well.  It would look something like this:


 



SelectAll with Using Statement
public IList SelectAll(IDbConnection conn)
{
IList c;
using (IDataReader dr = DbProviderFactory.CreateDataReader(conn, this.CreateSelectAllCommand(), this.provider))
{
c
= this.MapObjects(dr);
}
return c;
}


 


One thing worth mentioning is the this.CreateSelectAllCommand().  Sean has this pretty cool method of wrapping queries using a query object that essentially eliminates the need for creating the SQL.  The idea here is to construct a query in more of an OOP methodology using methods and properties on a Query object, and then calling the object’s GetText() method to emit the proper SQL for the database type.  I would call this a good example of the Builder Design Pattern. The Builder Design Pattern stores the data in an intermediate object (ISelectQuery) until the application is ready to ask (GetText()) the storage object to construct the target object (SQL).  Very nice!


 



Building a Select All Query
/// <summary>
/// Creates a command to select all records
/// </summary>
/// <returns></returns>
protected IDbCommand CreateSelectAllCommand()
{
IDbCommand command
= DbProviderFactory.CreateCommand(this.provider);
ISelectQuery s
= QueryFactory.CreateSelectQuery(this.db);
s.AddAll();
s.SetTable(
this.Table);
command.CommandText
= s.GetText();
return command;
}

 


I suggested that Eric do something similar to this with his EasyAssets architecture, and I think he can certainly use Sean’s implementation for inspiration if he considers it at a later date.  Although only very slightly related, I also talked briefly about how Community Server uses a BlogPostQuery object to pass parameters, which has a slight (very slight) similar look and feel.  You can read about that in Community Server Source Code – Abstract Classes, Reflection and Data Providers.


As mentioned above, the MapObjects method does most of the work.  It iterates through the DataReader and calls the MapObject(IDataReader dr) method that is generated by Codus for each specific table and view you chose in the GUI:


 



MapObjects Method
#region IDataReaderMapper Members

/// <summary>
/// Maps a collection of objects, using the specified DataReader
/// </summary>
/// <param name=”dr”>The DataReader to use for the object mapping</param>
/// <returns></returns>
public IList MapObjects(IDataReader dr)
{
ArrayList al
= new ArrayList();
while (dr.Read())
{
al.Add(
this.MapObject(dr));
}
return al;
}

#endregion


 


The MapObject method does the data mapping and object creation.


 



MapObject Method
protected override object MapObject(System.Data.IDataReader r) {

AddresstEntity entity = new AddressEntity();

try{
int ordinal = r.GetOrdinal(ID);
if (!r.IsDBNull(ordinal)) entity.ID =
((System.Int32)(r.GetValue(ordinal)));
}
catch(Exception ex){}

try{
int ordinal = r.GetOrdinal(Firstname);
if (!r.IsDBNull(ordinal)) entity.Firstname =
((System.String)(r.GetValue(ordinal)));
}
catch(Exception ex){}

try{
int ordinal = r.GetOrdinal(Lastname);
if (!r.IsDBNull(ordinal)) entity.Lastname =
((System.String)(r.GetValue(ordinal)));
}
catch(Exception ex){}

try{
int ordinal = r.GetOrdinal(Title);
if (!r.IsDBNull(ordinal)) entity.Title =
((System.String)(r.GetValue(ordinal)));
}
catch(Exception ex){}

return entity;
}


 


That’s pretty clean and well-refactored code!  SelectAll() is a pretty trivial example, but most of the underlying methods implemented by AbstractDAO share this same concept, which makes this O/R Mapper really easy to maintain.


One of the things I would like to see is record paging.  It does SelectAll() and SelectAllWithLimit(int maxRecords), but I don’t see a paging mechanism that allows you to choose records for a particular page index.  You can certainly add it yourself, but this is something so basic that it should be added to the AbstractDAO class.  Great tool!  Now on to another open source project :)

This entry was posted in C#, Design Patterns. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

7 Responses to Codus – C# and VB.NET 2005 Using Statement – Builder Design Pattern

  1. One thing I forgot to mention is that we’re working on generation of NHibernate mappings and classes, so if you don’t like the Codus DAO, you’ll soon be able to generate all of your nHibernate code (to include unit tests, build file, etc.) using Codus. In the near future, we also have plans for Gentle.NET and iBatis.

    Sean

  2. Sam,

    Thanks for your response. You don’t need to go through all those steps to get records…David was just walking you through the internals.

    Here’s the example you provided:
    using(ISession session = Database.GetSession()) {
    return session.CreateCriteria(typeof(MyClass)).List();
    // or…
    return session.Find(“From MyClass”);
    }

    For Codus, this is all you would have to write:

    MyClassDAO dao = new MyClassDAO();
    MyClassCollection collection = new MyClassCollection(dao.SelectAll());

    Codus handles the connections and everything else for you behind the scenes. You’ll also note that it automatically generates a strongly-typed collection for you…the method call above will return a collection of MyClass objects.

    The reason methods do exist to pass in the connection is in case you’re dealing with transactions, or want to execute several queries using the same open connection.

    Also, you’ll note that you don’t have to create any mapping files, or write any classes…all that is generated for you.

    If you have things you’d like added, please email me and let me know!

    Sean

  3. David Hayden says:

    Chris,

    At first glance, I think I like the fact that Codus doesn’t set the table. This gives a bit more flexibility in the creation of queries.

    Unless you need a specialized query, you really don’t need to set the table. Other than paging, most of the functionality can be handled with the built-in functions for tables and views that don’t require you to set the table.

  4. David Hayden says:

    Hey Sam,

    You’re right, it is a bit ADOish, but that is probably part of its charm :)

    It is definitely different than NHibernate, but I view it as a good introduction to O/R Mapping and a tool that is easy to maintain and easy to extend without a huge learning curve.

    And you’re right, kudos to Sean for building an O/R Mapper, it is no easy task.

  5. Looks like I spoke too soon. Codus does have a pretty robust query engine as I just found. What I don’t like is for example if I’m using a SystemDAO object to run the query (this maps to a System table in the DB), I have to manually specify the table to select from. I think it should know which table to pull from based on the object I’m using to run the query. All I should have to do is specify the fields I want, and any where or order by clause and then run the query. I imagine having to specify the table explicitly is so you can run joins and get results from more than one table at a time but maybe that belongs in a generic DAO object instead of a concrete one?

  6. I played with Codus some a while back and redownloaded it to play with yesterday. I also downloaded MyGeneration to play with their dOOdads framework. I think the query engine for dOOdads is the best and most robust I’ve seen but where it fails IMHO is using tables as opposed to strongly typed custom objects. Codus on the other hand is a very good mapper/code-gen tool but you’re fairly limited to what you’ve posted as far as getting certain result sets.

  7. Sam says:

    Looks a bit ADO-ish. Why should you have to get a connection, map the table manually, etc in the SelectAll?

    The corresponding query in NHibernate would look something like this: (after writing your class & mapping file)

    using(ISession session = Database.GetSession()) {
    return session.CreateCriteria(typeof(MyClass)).List();
    // or…
    return session.Find(“From MyClass”);
    }

    Or .CreateQuery or .CreateSqlQuery, both of which I haven’t had a chance to dig in to.

    Of course having tried to write a simple O/R Mapper, I can appreciate the skill & effort involved in putting something like Codus together. It’s certainly a lot cleaner than anything I came up with.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>