Microsoft & .NET.NETUsing NHibernate as an ORM Solution for .NET

Using NHibernate as an ORM Solution for .NET

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

NHibernate is an ORM (Object-Relational Mapping) solution for .NET that provides an easy to use framework for mapping an object-oriented domain model to a traditional relational database. Handling the myriad of database queries and stored procedures for large applications can become a time consuming endeavor for any application developer. NHibernate helps ease that pain by relieving developers from a significant amount of relational data persistence-related programming tasks. In this article, I will discuss ORM concepts and NHibernate’s implementation of them. I will also walk you through several examples of NHibernate in use.

What Is Object-Relational Mapping?

Object-Relational Mapping (ORM) is the concept of mapping an application’s business objects to relational database tables so that data can be accessed and updated entirely through the object model of an application.

Many popular controls in .NET allow you simply to bind data directly from the database to your controls. This helps allow programmers to quickly set up and create simple applications with .NET. But, what happens when your application starts growing? New business logic is added over time and makes things much more complicated. The database becomes bigger and more complex with each new requirement and it soon starts to take on a life of its own. With each change you make to your application, existing functionality starts to break because the database and front-end are tightly coupled and changing one will affect the other. Soon, you find yourself in a maintenance nightmare with an application that is more band-aid than actual functionality.

Any developer or system architect who has worked with an application after the initial construction phase understands this problem well. One of the best solutions to avoiding a maintenance-nightmare application is a good multi-layered N-tier architecture and adherence to solid object-oriented design principles.

Object-Relational Mapping helps achieve this end by providing a framework for mapping data from a database into application business objects. When you want to access or save data, you simply retrieve the object or save the object representing the needed data. The basic idea is to abstract the database access from the rest of the application by encapsulating the database access within its own layer and representing the data from the database within business objects as opposed to working directly with fields from the database.

How does this help solve the aforementioned maintenance-nightmare application scenario? The biggest benefit comes from the organization of the application into discrete areas of concern, otherwise known as loose coupling. Changing the database only requires a change to the object mapping. Because the rest of the application doesn’t communicate with the database and only communicates with the business objects, the rest of the application is unaffected by the change. This organization doesn’t only help maintenance scenarios. It is also very useful for keeping the development of large enterprise applications manageable because project teams more easily can split up into areas of concern. One or two people can develop the database model and business object mappings while the remainder of the team concerns itself with working with the business objects and their encapsulated functionality to develop the rest of the application.

What Is NHibernate?

NHibernate is an open source ORM (Object-Relational Mapping) solution for .NET. For several years, Hibernate (notice the dropping of the “N”) has been making a name for itself in the Java world as a premier ORM solution for everyday database woes. Hibernate is a proven technology and is currently the standard approach to database architecture for enterprise applications for many top companies around the world.

With such a great solution, it was only a matter of time before Hibernate was ported to .NET and thus NHibernate was born. Although NHibernate is newer to the market and has not made as deep a penetration as its Java counterpart, NHibernate has all the major ORM features of Hibernate and is a solid and viable solution for ORM in the .NET world.

NHibernate implements the ORM concepts by providing a framework to map database tables to business objects using a standard XML format for each mapping and the database configuration. NHibernate is database independent, which means that the mappings are the same whether you are using a MS SQL, Oracle, DB2, or MySQL database. NHibernate also provides mapping solutions for a variety of object-oriented concepts such as inheritance, polymorphism, and the .NET collections framework including generic collections.

How to Implement NHibernate

For this article, I will introduce you to the framework and show you a small sample web application that touches on several of NHibernate’s key principles.

Note: NHibernate is a rather large framework and this sample is far from a complete example of all of Hibernates functionality, but it is a good start.

Figure 1: NHibernate Sample Application—Database Design

NHibernate Configuration

To start using NHibernate, you will need to go to www.hibernate.org and download the latest release. Then, you need to copy the assemblies to your /bin folder and add the following configuration settings to your web.config file:

<configSections>
   <section name="nhibernate"
            type="System.Configuration.NameValueSectionHandler,
                  System, Version=1.0.5000.0, Culture=neutral,
                  PublicKeyToken=b77a5c561934e089"/>
   <section name="log4net"
            type="log4net.Config.Log4NetConfigurationSectionHandler,
                  log4net"/>
</configSections>
<nhibernate>
   <add key="hibernate.connection.provider"
        value="NHibernate.Connection.DriverConnectionProvider" />
   <add key="hibernate.dialect"
        value="NHibernate.Dialect.MsSql2000Dialect" />
   <add key="hibernate.connection.driver_class"
        value="NHibernate.Driver.SqlClientDriver" />
   <add key="hibernate.connection.connection_string" 
        value="{Your Connection String Here}" />
   <add key="hibernate.connection.isolation"
        value="ReadCommitted" />
   <add key="hibernate.show_sql" value="True" />
</nhibernate>
<log4net>
   <appender name="NHibernateFileLog"
             type="log4net.Appender.RollingFileAppender">
      <file value="C:/Logs/nhibernate.txt" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="10" />
      <maximumFileSize value="1000KB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
         <conversionPattern value="%d{HH:mm:ss.fff}
                                   [%t] %-5p %c - %m%n" />
      </layout>
   </appender>
   <logger name="NHibernate" additivity="false">
      <level value="DEBUG"/>
      <appender-ref ref="NHibernateFileLog"/>
   </logger>
</log4net>

Listing 2: Sample Application—NHibernate Configuration

These NHibernate configurations control the database settings and any optional NHibernate settings you may want to set. You will notice the configuration calls for a connection provider, dialect, and driver. There is also a line for a standard connection string. In this example, everything is set up for MS SQL, but this configuration could just as easily be set up for DB2, Oracle, MySQL, and so forth.

Also note that the log4net settings are completely optional, but I highly recommend them. NHibernate is designed to work with log4net and will log what NHibernate is doing internally. Most importantly, all database activity, including the SQL is logged, so you can keep track of potential performance issues related to the type and quantity of SQL queries being run by NHibernate.

NHibernate Mapping

Now that the basic NHibernate configuration is set up, you can map your application’s business objects. NHibernate uses XML files to map the objects to the database. Each object (or collection of objects) has an associated mapping file. For example, the ProductOrder class is paired with the ProductOrder.hbm.xml file. The contents of this file define how NHibernate maps each of the object’s properties to a database column, how the object is identified, and any associations with other objects.

It is important to note that there are many more mapping options with NHibernate than shown here, especially with regard to collections. With the many ways that a database and object structure may be configured, NHibernate has done a pretty good job of providing flexible mapping schemes for pretty much any situation. This example will just cover the basics and touch on a few of the more interesting mapping options.

It is also worth noting that there are several automated tools provided on the NHibernate web site that help automate the process of linking the object model to the database. There are tools that automatically create mapping files and classes from a database definition. There also are tools that let you start with a mapping file and automatically create the database definition and class files. For this example, though, I will take you through the process of creating the mapping files manually with the assumption that the class files and database are already created.

ProductOrder Class Mapping—Simple Example

Take a look at the ProductOrder.hbm.xml mapping file for the ProductOrder class:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">

   <class name="ProductOrder, __Code" table="ProductOrder">

      <id name="ProductOrderID" column="ProductOrderID"
          type="Int32" unsaved-value="null">
         <generator class="native" />
      </id>

      <property name="CustomerID" column="CustomerID" type="Int32" />
      <property name="ProductName" column="ProductName"
                type="String" />
      <property name="Quantity" column="Quantity" type="Int32" />
      <property name="TotalCost" column="TotalCost" type="Decimal" />
   </class>
</hibernate-mapping>

Listing 3: Sample Application—ProductOrder Mapping

The main container for the class is the class tag:

<class name="ProductOrder, __Code" table="ProductOrder">

This tag defines the class name/assembly and the associated database table for the class.

Note: The assembly in this case is “__Code” because that is the default assembly for web sites. In many cases, the classes would be packaged up into a .dll file assembly that would be specified here, but to keep this sample application simple and accessible, I left the code in the default assembly.

The first item specified in the class tag definition is the ID tag:

<id name="ProductOrderID" column="ProductOrderID" type="Int32"
    unsaved-value="null">
   <generator class="native" />
</id>

The ID tag is a required tag for NHibernate. Because you are persisting the object to a database, you need to identify each object as an individual record in the database. When designing a database, it is often best to have a unique non-business related identity column for each record to keep track of the record and also to use in any associations. Although composite identifiers consisting of one or more fields with business meaning (for instance, Order Number, SSN, and so on) make sense in some cases, they often lead to larger than needed foreign key fields and problems if the field should need to change for any reason. NHibernate does support composite identifiers, but recommends they are only used for working with legacy databases.

In this example, you use an integer identity column in the database to identify the record. The ID tag specifies the name of the object property and column name.

The generator tag specifies how this identity is generated for new objects being created. Most databases support some sort of automatically generating identity field. These identity fields often use either an automatically incrementing numeric field or sometimes a GUID. NHibernate works with these fields to generate the identity field for a newly saved object. In the example here, you use a MS SQL identity column for the ProductOrderID. In the generator tag, you specify “native,” which defaults to the standard generator type for the current database (identity in this case). You could also directly specify “identity” or, if you want to use a sequence identifier for DB2/Oracle database, you can set up a “sequence” generator.

The remainder of the mapping file consists of basic property tags. For example:

<property name="ProductName" column="ProductName" type="String" />

Each of these property tags maps the property name to a database column. This is the general mapping type used for primitive types (short, int, long, decimal, string, and the like).

Person Class Mapping—Class Inheritance and Collections Example

The next mapping file for the Person class is a bit more complicated than the mapping file for the ProductOrder class. This example will show one of the methods of handling class inheritance and two methods for handling collections.

Take a look at the Person.hbm.xml mapping file:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">

   <class name="Person, __Code" table="Person">

      <id name="PersonID" column="PersonID" type="Int32"
          unsaved-value="null">
         <generator class="native" />
      </id>

      <property name="FirstName" column="FirstName" type="String"/>
      <property name="LastName"  column="LastName"  type="String"/>

      <joined-subclass name="Customer, __Code" table="Customer">
         <key column="PersonID"/>
         <property name="Address" column="Address" type="String"/>
         <property name="City"    column="City"    type="String"/>
         <property name="State"   column="State"   type="String"/>
         <property name="ZipCode" column="ZipCode" type="String"/>

         <set name="Orders">
            <key column="CustomerID"/>
            <one-to-many class="ProductOrder, __Code"/>
         </set>
      </joined-subclass>

      <joined-subclass name="SalesPerson, __Code"
                       table="SalesPerson">
         <key column="PersonID"/>
         <property name="EmployeeNumber" column="EmployeeNumber"
                   type="String"/>

         <set name="AssignedCustomers" table="AssignedCustomers"
              cascade="save-update">
            <key column="SalesPersonID"/>
            <many-to-many class="Customer, __Code"
                          column="CustomerID"/>
         </set>
      </joined-subclass>
   </class>
</hibernate-mapping>

Listing 4: Sample Application—Person Mapping

The first thing that pops out at you when looking at this mapping is that the class mapping for the Person class includes the Customer and SalesPerson classes within it. This is because the Customer and SalesPerson classes inherit the Person class.

There are three ways to map class inheritance to a database using NHibernate:

  1. Table per class hierarchy
    1. One table with all of the properties for multiple subclasses
    2. Different classes are differentiated by a discriminator/type column
  2. Table per subclass
    1. One table for the parent class and one table for each subclass
  3. Table per concrete class (some limitations)
    1. One table for each subclass; each table includes shared parent class fields

This example uses the second method, table per subclass. In the database design, you have one class for the Person class and one table for each of the subclasses, Customer and SalesPerson. In the mapping, you see this defined in the joined-subclass tag:

<joined-subclass name="Customer, __Code" table="Customer">
   <key column="PersonID"/>

This tag specifies the Customer class and its associated database table as a subclass of the Person class. The key linking the two rows in each classes table is the PersonID column. Inside the joined-subclass tag, the Customer properties are defined as they normally would be for a class. Note that with this mapping, when NHibernate requests a Customer object from the database, NHibernate will use an SQL join in its query to join the appropriate Person and Customer rows into a complete result for the Customer object.

The next items of interest with this mapping are the collection mappings. Both the Customer class and SalesPerson class have collections mapped, one for orders and the other for assigned customers.

Start with the Orders collection in the Customer class:

<set name="Orders">
   <key column="CustomerID"/>
   <one-to-many class="ProductOrder, __Code"/>
</set>

This is an example of a simple one-to-many relationship. Each order is mapped to one customer and each customer can have several orders. The class associated in this one-to-many relationship is the ProductOrder class that is specified in its own mapping file as being mapped to the ProductOrder database table. The key column for determining this relationship is the CustomerID column in the ProductOrder table.

The next collection mapping example is a bit trickier. Here is the mapping for the AssignedCustomers collection in the SalesPerson class:

<set name="AssignedCustomers" table="AssignedCustomers"
     cascade="save-update">
   <key column="SalesPersonID"/>
   <many-to-many class="Customer, __Code" column="CustomerID"/>
</set>

This is an example of a many-to-many relationship. Each sales person may have several assigned customers and each customer may have several sales persons assigned to them. To handle this sort of relationship, a separate table, AssignedCustomers, is specified in the set tag and is delegated the task of keeping track of which customers are assigned to which sales persons. The key column for the SalesPerson table is the SalesPersonID and the key column for the Customer table is the CustomerID.

The cascade attribute shown here is another topic that will be discussed later.

NHibernate Session Factory and Sessions

Now that everything is set up, you can move into how to utilize NHibernate in the application.

NHibernate works based on session objects. When you need to retrieve, save, update, or delete an object in the database, you create a NHibernate session to perform the task. The session encapsulates all the database functionality and the database connection. Sessions are light-weight and should be created and destroyed as they are needed. They are not thread-safe, so keeping them around and sharing them between threads is not a good idea.

NHibernate sessions are managed by a session factory. Generally, there is one session factory per application. Each time a session is needed, the session factory is called to create one. Session factories are heavy-weight and thread-safe objects that read in all configurations and mappings to configure themselves for an application. Because they take a while to create, they need to be held in memory and shared for an application.

A helper and/or manager class is generally used to help broker the NHibernate session factory’s functionality. For this example, I used a standard NHibernateHelper class:

public sealed class NHibernateHelper
{
   private const string sessionKey = "nhibernate.current_session";
   private static readonly ISessionFactory sessionFactory;

   static NHibernateHelper()
   {
      log4net.Config.XmlConfigurator.Configure();

      // Get the mapping file path
      String mappingPath =
         HttpContext.Current.Server.MapPath(
         "/NHibernateExample/App_Code/BusinessObjects/Mappings/");

      // Set up the configuration
      Configuration config = new Configuration()
         .AddFile(mappingPath + "Person.hbm.xml")
         .AddFile(mappingPath + "ProductOrder.hbm.xml");

      // Build the session factory
      sessionFactory = config.BuildSessionFactory();
   }

   public static ISession GetCurrentSession()
   {
      HttpContext context = HttpContext.Current;
      ISession currentSession = (ISession)context.Items[sessionKey];

      if (currentSession == null)
      {
         currentSession = sessionFactory.OpenSession();
         context.Items[sessionKey] = currentSession;
      }

      return currentSession;
   }

   public static void CloseSession()
   {
      HttpContext context = HttpContext.Current;
      ISession currentSession = (ISession)context.Items[sessionKey];

      if (currentSession == null)
      {
         // No current session
         return;
      }

      currentSession.Close();
      context.Items.Remove(sessionKey);
   }

   public static void CloseSessionFactory() 
   {
      if (sessionFactory != null)
      {
         sessionFactory.Close();
      }
   }
}

Listing 5: Sample Application—NHibernateHelper

This class creates and manages a static instance of the session factory for an application. The static constructor reads in the configuration from the web.config file, adds the mappings to it, and then uses the configuration to create a session factory.

Also note how the GetCurrentSession() method stores and retrieves the session in the HttpContext. In web applications, NHibernate often uses the session per request model. This means that one session is created for each request and it is re-used for the duration of the request. When you call GetCurrentSession() the first time during a request, it creates the session and stores it in the context. Each subsequent call to GetCurrentSession() returns the same session stored in the context. The CloseSession() method isn’t called until the Application_EndRequest method of the Global.ascx file.

The session per request model becomes especially important because the session may be used to keep track of the state of an object and what needs to be updated when saving the object. It is also used for lazy loading; I will talk about this in more detail later in the article.

Using NHibernate to Retrieve Objects

Now that you have a mechanism for creating sessions, look at a get call for the Customer class:

public static Customer getCustomer(int? customerID)
{
   // Open the session
   ISession session = NHibernateHelper.GetCurrentSession();

      // Load the order from the database
      Customer customer = (Customer)session.Load(typeof(Customer),
         customerID);

      return customer;
}

Listing 6: Sample Application—Simple Object Retrieval

In this method, you simply get the session instance and then load in the Customer object for the ID defined. This will automatically go to the database and load in whatever data is required to the fill the Customer object with the data from the database.

As was discussed earlier, you don’t close the session in this method because it will be closed in the Application_EndRequest method.

You also can perform more complex queries by using an NHibernate ICriteria object. For example, look at the ProductOrder method to retrieve product orders for a specific customer:

public static List<ProductOrder>
   getProductOrdersForCustomer(int? customerID)
{
   List<ProductOrder> orders = new List<ProductOrder>();

   // Get the session
   ISession session = NHibernateHelper.GetCurrentSession();

   // Load the order from the database
   ICriteria criteria = session.CreateCriteria(typeof(ProductOrder));
      criteria.Add(Expression.Eq("CustomerID", customerID));
      criteria.List(orders);

   return orders;
}

Listing 7: Sample Application—Object Retrieval By Criteria

An ICriteria object basically builds a where clause programmatically. First, you specify that you are creating a criteria object for a certain object type, in this case ProductOrder, and then you add criteria with the Expression class. This will return all ProductOrder objects that match the specified criteria. This is a simple example with a single equals criteria where the CustomerID property of the ProductOrder needs to match the customerID specified. For more complex queries, there are several other types of expressions similar to what you would see in the where clause of an SQL query (Less Than, Greater Than, Like, Between, and so forth).

If using a criteria object doesn’t suit you, you also can use HQL. HQL is NHibernate’s custom query language for dealing with object queries. Here are a few HQL query examples:

  • “from ProductOrder o”: Returns all ProductOrder objects.
  • “from ProductOrder o where o.CustomerID = ?”: Returns the ProductOrder objects ordered by a specific customer.
  • “select distinct o.ProductName from ProductOrder o”: Returns the name of all products ordered.
  • “select distinct o.ProductName from ProductOrder o where o.CustomerID = ?”: Returns the name of all products ordered by a specific customer.

Using NHibernate to Save, Update, and Delete Objects

Now, look at an example for saving/updating an object. Here is the method for saving/updating a customer:

public static void saveOrUpdateCustomer(Customer customer)
{
   ISession session = NHibernateHelper.GetCurrentSession();
   ITransaction transaction = null;

   try
   {
      // Start transaction
      transaction = session.BeginTransaction();

      // Save or update (saves if id is null; otherwise, updates)
      session.SaveOrUpdate(customer);

      // Commit transaction
      transaction.Commit();
   }
   catch (Exception ex)
   {
      transaction.Rollback();
      throw ex;
   }
}

Listing 8: Sample Application—Saving/Updating Objects

Note the use of the session.SaveOrUpdate() method. In NHibernate, saving an object is the same as inserting into the database. Updating an object is the same as updating the database. Using the session.SaveOrUpdate() method frees the programmer from having to make the distinction of whether to save or update the object (though they can use the session.Save() or session.Update() methods if they choose).

If you refer to the ID tag in the mapping file for the Customer class, you will see the unsaved-value=”null” attribute. (Note that if you have an ID that is not a nullable integer, you can set the unsaved-value to 0.) This means that if the object id is null, it has not been saved and a save (insert) needs to be performed. Otherwise, if the ID has a value, the object has previously been saved to the database and needs to be updated instead.

Also, this save/update method shows how to begin and commit a database transaction. Within the NHibernate save/update/delete logic, several queries may be performed in a single save/update/delete call to an object if you have an object mapped to multiple tables. It is important when saving/updating/deleting objects to use transactions to avoid making incomplete updates to the database that leave the database in an invalid state. Transactions are also useful for use in larger units of work that need to be performed together to ensure data consistency.

Deleting an object it is much the same as saving/updating an object. Just use the session.Delete(Object obj) method.

Lazy Loading

When you retrieve an object like the Customer object that has a collection of ProductOrders, or if you retrieve an object for a class that references another object, you might wonder when the collection or referenced object gets loaded. For example, during a standard load action where the Customer object is loaded from the database, two queries are needed. One query is needed to load the customer information from the Customer table and one query is needed to load the orders for this customer from the ProductOrder table. It seems inefficient that the second query to retrieve the orders would be run in every case, especially if you normally just want to look at the customer information. This is where lazy loading comes in.

NHibernate is smart enough to retrieve only a collection or referenced object when it is actually read somewhere in the code. In the case of the Customer object, when it loads the data into the Customer object, it loads only a proxy collection class for the orders. Using this proxy collection class, the query to retrieve the data for the orders is not run right away. The proxy collection class only runs the query to retrieve the collection data if a call is made to the collection to read the data.

Lazy loading works only if the session used to load the object is still active. As discussed earlier in this article, this is one of the reasons why the session per request model is used for session management. By using the session per request model, the session is left open for the duration of the request. This allows lazy loads at any time during the request.

If the session per request model is not feasible or perhaps another need arises, lazy loading is optional and can be turned off by adding the lazy-loading=”false” mapping attribute to either the collection or the entire class. If lazy loading is not being used, the entire object will be loaded at the point of the session.Load() call.

Collection Cascading

Collection cascading is another subject of interest. It is a useful NHibernate feature that allows the collection object to be persisted automatically from a parent object. Cascading is turned off by default; this means that each collection object would need to be saved independently to be added to the collection of the parent object.

In this example application, cascading is turned on for the AssignedCustomers collection in the SalesPerson class. If you refer to the mapping file setup earlier in this article, the cascade attribute of the AssignedCustomers set is set to “save-update.” This means that if you have a SalesPerson object and you add a new Customer object that has not been added to the system before, you can save the SalesPerson object and it will automatically save the Customer object.

NHibernate Performance Notes

In this article, you have gone through a lot of material on NHibernate and some examples of how to utilize it. I would like to finish up by briefly touching on the subject of NHibernate performance.

Using NHibernate may relieve the need to worry about writing the SQL for an application, but in doing so it can hinder a developer’s ability of to tightly control the SQL being performed for an application. If the underlying SQL being run by NHibernate is not watched carefully, there are situations where undesired and/or un-optimized queries may be performed, especially in the case of collection handling. NHibernate also uses a good deal of reflection and does not use stored procedures which may cause a bit of a performance hit on its own.

These performance issues are a concern, but there are several things that can be done to minimize them. If a system is architected correctly, lazy loading is implemented correctly, and the NHibernate logs are checked judiciously for any potential runaway querying, the performance impact can be minimized. NHibernate also supports using native queries for problem areas where NHibernate is not achieving the desired result. In addition, NHibernate has several caching options to further tweak the performance.

Even when used correctly, NHibernate’s performance of may never reach the perfection that customized queries and stored procedures can. A good team of programmers can design and create an incredibly fast custom data access layer for an application, but only if they have ample time and an ideal development environment. Unfortunately, ample time and ideal development environments are not always available. Because of this, custom data access layers for applications are often not optimized to the extent they could be. In fact, in many cases, especially with larger projects and larger teams, the data layer can become a mess with many conflicting and inconsistent practices that can hurt performance far more than NHibernate ever will. With NHibernate, your data access layer will at least be consistent and with minimal effort by the project team.

When it is all said and done, the point of NHibernate is not necessarily to make the performance of the code better, but to make the performance of the programmers better.

Conclusion

NHibernate is a great ORM solution for handling database persistence of business objects in an application. NHibernate is a large framework with many features and options that accommodate a large range of application designs. The example I went through in this article is far from a complete guide to everything NHibernate can do, but it is a good start and touches on many key NHibernate concepts. For continued reading, check out the documentation on the NHibernate web site. Also, in January 2007 the book NHibernate in Action will be released. Its Java counterpart, Hibernate in Action, has proved an invaluable resource on Hibernate functionality for Java applications.

Code Examples

Download the example code in this article here.

About the Author

David Consdorf, who resides in Chicago, Illinois, has been developing Java and ASP.NET web applications as a software consultant for Crowe Chizek and Company LLC for the last three years. Crowe Chizek and Company LLC is a Microsoft Gold Certified Partner.

David graduated with a Bachelor’s Degree in Computer Science from the University of Illinois Urbana-Champaign.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories