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

Test Driving NHibernate 3.0, LINQ and the Entity Framework CTP 5 with the Abstract Factory Design Pattern

, 2 Apr 2011
Rate this:
Please Sign up or sign in to vote.
Developing an N-Tier application with C#

Download CodeProjectORM.zip - 2.75 MB


Introduction

Whether you are developing a small or a big application, you always have to deal with data. It's even a critical part of an application. The problem is that this is a tedious, repetitive work, which consumes a lot of time we would prefer to spend on other parts of the application. To solve these problems, multiple solutions exist. One of these solutions are object-relational mapping tools (O/RM). Their goal is to simplify the creation of data access layers, automate data access, or generate data access code. As in life, we in the Information Technology industry must often have to choose between multiple choices sometimes having to choose between apples and oranges when making technology decisions. This also applies to object-relational mapping tools. The question begs: Which O/RM tool is the best solution? Two of the most popular O/RM tools in the .NET world includes Microsoft’s Entity Framework and NHibernate ( a .NET port from the Java’s Hibernate tool).

Sample Application

This article will demonstrate a sample application that will test drive both Microsoft’s Entity Framework and NHibernate as plug-able options in a N-Tier application. To allow these two O/RM tools to be plug-able, this application will implement the Abstract Factory Design Pattern.

SampleApplication.jpg

The Visual Studio solution for this sample application consists of the following projects.

  • ORMWebApplicationMVC – Contains the front-end MVC 3 web application containing Views and Controllers. The controllers will call the application services layer.

  • ORMApplicationServices – Contains the application business rules and logic.

  • ORMDataServices – Consists of factory classes for the Entity Framework and NHibernate

  • ORMDataModels – Contains the data model classes. This sample application will feature a class for maintaining customer information.

  • ORMNHibernateMaps – Contains the Nhibernate XML data mappings.

The Abstract Factory Design Pattern

Design patterns are recurring solutions to software design problems you find again and again in real-world application development. The Abstract Factory pattern used in this sample application derives from The Gang of Four (GoF) patterns publication.

The Abstract Factory design pattern defines an interface containing factory methods which decouple the client from the concrete (actual) classes required to instantiate a family of related products. Thus the client need know nothing about the actual concrete implementations.

The ORMDataServices project implements a factory of OR/M factories. Implementing an abstract data factory begins with creating an interface. In the application, an IDataFactory interface will be created that makes reference to a more concrete interface called ICustomerDataService.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ORMDataServices.DataServices;

namespace ORMDataServices
{    
    /// <summary>
    /// Abstract factory interface. Creates data access objects.
    /// </summary>
    /// <remarks>
    /// Factory Design Pattern
    /// </remarks>
    public interface IDataFactory
    {
        /// <summary>
        /// Gets a customer data access object.
        /// </summary>
        ICustomerDataService CustomerService { get; }    
    }

}

The ICustomerDataService interface will define the signatures for the Customer Data Service.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ORMDataModel;  

namespace ORMDataServices.DataServices
{
    /// <summary>
    /// Customer Data Service Interface
    /// </summary>
    public interface ICustomerDataService : IDisposable
    {
        List<Customer> GetCustomers(CustomerTransaction customerTransaction);
        List<Customer> ValidateCustomerCode(string customerCode);
        List<Customer> ValidateCustomerCode(string customerCode, Int32? customerID); 

        Customer GetCustomerInformation(Int32? customerID);
                
        void InsertCustomer(Customer customer);       
        void UpdateCustomer(Customer customer);
        
    }
    
}

As stated before, this application defines a factory of factories. A separate discrete factory is defined – one each for the Entity Framework and NHibernate. Each factory will implement the ICustomerDataService and IDataFactory interfaces. Implementing these interfaces will allow the discrete factories to pass through the "food chain" up to the calling client code.

namespace ORMDataServices.DataFactories.NHibernate
{
    class NHibernateDataAccessFactory  : IDataFactory
    {
        /// <summary>
        /// Gets an NHibernate customer data access object.
        /// </summary>
        public ICustomerDataService CustomerService
        {
            get { return new NHCustomerService(); }
        }
   }
 
}   
 
namespace ORMDataServices.DataFactories.EntityFramework
{  
    public class EFDataAccessFactory : IDataFactory
    {     
        /// <summary>
        /// Gets a Entity Framwork customer data access object.
        /// </summary>
        public ICustomerDataService CustomerService
        {
            get { return new EFCustomerService(); }
        }   
    }
 
}

Each concrete factory implements the ICustomerDataService interface. The example below is a snippet of the NHibernate factory implementation.

 
using System;
using System.Collections.Generic; 
using System.Collections;using System.Linq;
using System.Text;
using ORMDataModel;
using ORMDataServices.DataServices;
using ORMDataServices.DataFactories.NHibernate;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Criterion;
using ORMUtilities;  
 
namespace ORMDataServices.DataFactories.NHibernate
{
  
    class NHCustomerService : NHibernateDataService, ICustomerDataService
    { 

        PerformanceLogging _performanceLogging;  

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param></param> 
        public NHCustomerService()
        {
            _performanceLogging = new PerformanceLogging();
        }   

       /// <summary>
       /// Get Customer Information
       /// </summary>
       /// <param name="customerID"></param>
       /// <returns></returns>
       public Customer GetCustomerInformation(Int32? customerID)
       {
           _performanceLogging.StartLogging("NHCustomerService.GetCustomerInformation");  
           Customer customer = Session.Get<Customer>(customerID); 
           _performanceLogging.EndLogging("NHCustomerService.GetCustomerInformation"); 

           return customer;
       }  
        
       /// <summary>
       /// Validate Customer Code - new customer
       /// </summary>
       /// <param name="customerCode"></param>
       /// <returns></returns>
       public List<Customer> ValidateCustomerCode(string customerCode)
       {
           _performanceLogging.StartLogging("NHCustomerService.ValidateCustomerCode"); 
  
           List<Customer> customer; 
  
           ICriteria criteria = Session.CreateCriteria(typeof(Customer))
               .Add(Expression.Eq("CustomerCode", customerCode));
 
           customer = (List<Customer>)criteria.List<Customer>();  

           _performanceLogging.EndLogging("NHCustomerService.ValidateCustomerCode"); 

           return customer; 

       }  
 
       /// <summary>
       /// Insert Customer
       /// </summary>
       /// <param name="customer"></param>
       public void InsertCustomer(Customer customer)
       {
           _performanceLogging.StartLogging("NHCustomerService.InsertCustomer"); 
           Insert(customer);
           _performanceLogging.EndLogging("NHCustomerService.InsertCustomer");
       } 
   
       /// <summary>
       /// Update Customer
       /// </summary>
       /// <param name="customer"></param>
       public void UpdateCustomer(Customer customer)
       {
           _performanceLogging.StartLogging("NHCustomerService.UpdateCustomer");
           Update(customer);
          _performanceLogging.EndLogging("NHCustomerService.UpdateCustomer");
       } 
    }

}

At the very top of the food chain is the DataAccess class. This class will determine which concrete data service factory to use based on a DataFactory appsettings as set in the web.config file in the MVC web application.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using ORMDataServices.DataServices; 
 
namespace ORMDataServices.DataFactory
{
   
    public class DataAccess
    {        
 
        IDataFactory _factory;
 
        /// <summary>
        /// Constructor
        /// </summary>
        public DataAccess()
        {
            string dataFactory = ConfigurationManager.AppSettings["DataFactory"];  
            DataFactories dataFactories = new DataFactories();
            _factory = dataFactories.GetFactory(dataFactory);            
        }
 
        /// <summary>
        /// Gets a provider-specific customer data access object.
        /// </summary>
        public ICustomerDataService CustomerService
        {
            get { return _factory.CustomerService; }
        } 
 
    }  
 
}

The snippet above calls the GetFactory method below, which returns the desired concrete factory.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;  
using ORMDataServices.DataFactories.NHibernate; 
using ORMDataServices.DataFactories.EntityFramework;
 
namespace ORMDataServices.DataFactory
{
    /// <summary>
    /// Data Factories
    /// </summary>
    public class DataFactories
    {
        /// <summary>
        /// Gets a specific factory      
        /// </summary>
        /// <param name="dataProvider">Database provider.</param>
        /// <returns>Data access object factory.</returns>
        public IDataFactory GetFactory(string dataProvider)
        {
            if (dataProvider == "EntityFramework")
                return new EFDataAccessFactory(); 
            else 
                return new NHibernateDataAccessFactory();
        }
    } 
}

Once your data factory is set up, the Application Service layer can now consume the Customer Data Service without actually knowing the actual implementation being returned from the data factory.

 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ORMDataModel;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Configuration;
using ORMDataServices.DataFactory;
using ORMDataServices.DataFactories;
using ORMDataServices.DataServices;
using ORMUtilities; 
 
namespace ORMApplicationServices
{
    /// <summary>
    /// Customer Application Service
    /// </summary>
    public class CustomerApplicationService
    {   
 
        PerformanceLogging _performanceLogging; 
        ICustomerDataService _customerDataService;  
 
        /// <summary>
        /// Constructor
        /// </summary>
        public CustomerApplicationService()
        {
            _performanceLogging = new PerformanceLogging();
            DataAccess dataAccess = new DataAccess();
            _customerDataService = dataAccess.CustomerService;
        } 
 
        /// <summary>
        /// Get Customers 
        /// </summary>
        /// <param name="customerTransaction"></param>
        /// <returns></returns>
        public CustomerTransaction GetCustomers(CustomerTransaction customerTransaction)
        { 
 
            long totalCustomers; 
              
            _performanceLogging.StartLogging("CustomerApplicationServices.GetCustomers"); 
 
            customerTransaction.Customers = _customerDataService.GetCustomers( 
                      customerTransaction, out totalCustomers); 
 
            customerTransaction.TotalCustomers = totalCustomers;
            customerTransaction.TotalPages = Functions.CalculateTotalPages( 
                      totalCustomers, customerTransaction.PageSize);  
 
            customerTransaction.ReturnStatus = true;  

            _performanceLogging.EndLogging("CustomerApplicationServices.GetCustomers");  
 
            return customerTransaction; 
 
        } 
 
        /// <summary>
        /// Get Customer Information
        /// </summary>
        /// <param name="customerTransaction"></param>
        /// <returns></returns>
        public CustomerTransaction GetCustomerInformation(Int32 customerID)
        { 
 
            CustomerTransaction customerTransaction = new CustomerTransaction();  
 
            _performanceLogging.StartLogging(
                 "CustomerApplicationServices.GetCustomerInformation");  
 
            Customer customer = _customerDataService.GetCustomerInformation(customerID);  
                               
            customerTransaction.ReturnStatus = true;
            customerTransaction.Customer = customer; 
 
            _performanceLogging.EndLogging("CustomerApplicationServices.GetCustomerInformation");
            
            return customerTransaction;  
 
        }
  
        
        /// <summary>
        /// Create Customer
        /// </summary>
        /// <param name="customer"></param>
        private void CreateCustomer(Customer customer)
        {  
            _performanceLogging.StartLogging("CustomerApplicationServices.CreateCustomer");

            customer.DateCreated = DateTime.Now;  
 
            TransactionLog transactionLog = new TransactionLog();
              
            transactionLog.TransactionCode = "CREATECUSTOMER";
            transactionLog.TransactionDate = customer.DateCreated;
            transactionLog.TransactionDescription = customer.CompanyName;
            transactionLog.TransactionEntity = "Customer";    
 
            _customerDataService.BeginTransaction(); 
  
            _customerDataService.InsertCustomer(customer); 
    
            transactionLog.CustomerID = customer.CustomerID; 

            _customerDataService.InsertTransactionLog(transactionLog);
    
            _customerDataService.CommitTransaction(true); 
 
            _performanceLogging.EndLogging("CustomerApplicationServices.CreateCustomer"); 
  
        } 
 
        /// <summary>
        /// Update Customer
        /// </summary>
        /// <param name="customer"></param>
        private Customer UpdateCustomer(Customer customer)
        { 
 
            _performanceLogging.StartLogging("CustomerApplicationServices.UpdateCustomer");  
 
            Customer updateCustomer;
                       
            _customerDataService.BeginTransaction();  
 
            updateCustomer = _customerDataService.GetCustomerInformation( 
                             customer.CustomerID);  
 
            updateCustomer.AddressLine1 = customer.AddressLine1;
            updateCustomer.AddressLine2 = customer.AddressLine2;
            updateCustomer.City = customer.City;
            updateCustomer.CompanyName = customer.CompanyName;
            updateCustomer.ContactName = customer.ContactName;
            updateCustomer.ContactTitle = customer.ContactTitle;
            updateCustomer.Country = customer.Country;
            updateCustomer.CustomerCode = customer.CustomerCode;
            updateCustomer.Fax = customer.Fax;
            updateCustomer.Phone = customer.Phone;
            updateCustomer.PostalCode = customer.PostalCode;
            updateCustomer.Region = customer.Region;
            updateCustomer.EmailAddress = customer.EmailAddress;
            
            updateCustomer.DateUpdated = DateTime.Now;
              
            _customerDataService.UpdateCustomer(updateCustomer)   
             
            _customerDataService.CommitTransaction(true); 

            _performanceLogging.EndLogging("CustomerApplicationServices.UpdateCustomer");  
 
            return updateCustomer; 
 
      }        
 
    } 
 
} 

In the above code snippet, the constructor instantiates the data factory and returns the customer data service. The methods of the application service make no direct reference to the lower level factory methods for either the Entity Framework or for NHibernate. The application code is generic in nature, which allows this application to plug any number of object relational mappers.

 
public interface IDataService
{               
    void BeginTransaction();
    void CommitTransaction(Boolean closeSession);   
}  

The data factories also implement the IDataService interface which includes signatures to allow the application services to begin and commit transactional data.

Installing and Setting Up NHibernate 3.0

NHibernate is a mature, open source object-relational mapper for the .NET framework. Downloading the latest version of Nhibernate can be located at http://nhforge.org, the official new home for the NHibernate for the .NET community.

NuGET

Alternately, you can install NHibernate using NuGet. NuGet is a Visual Studio extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects that use the .NET Framework. When you add a library or tool, NuGet copies files to your solution and automatically makes whatever changes are needed in your project, such as adding references and changing your app.config or web.config file. When you remove a library, NuGet removes files and reverses whatever changes it made in your project so that no clutter is left.

The key assemblies for NHibernate include:

  • Nhibernate
  • Nhibernate.ByteCode.Castle
  • Castle.Core
  • Iesi.Collections
  • Antlr3.Runtime
  • Remotion.Data.Linq

WEB.CONFIG/APP.CONFIG For NHibernate

Because NHibernate is designed to operate in many different environments with various different ways to configure and use NHibernate, it was a bit challenging setting it up and getting it up and running. I found several different ways developers were configurating and using NHibernate. I even found many different configuration setups and not all of them seemed to work. Eventually I found the below web.config settings that worked.

Three key pieces in the web.config settings are specifying the namespace urn:nhibernate-configuration-2.2 (and not confusing it with the version of NHibernate), setting a database dialect that NHibernate should use and as per usual, setting up the database connection string.

One of the things that jumps out at you as an advantage of NHibernate, is it's support for many different databases through it's database dialect configuration - including support for the many different versions of Microsoft SQL-Server, Oracle and DB2.

  <configSections>
    <section name="hibernate-configuration"  
            type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate"/>
  </configSections> 
 
  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory> 

      <property name="connection.provider"> 
                NHibernate.Connection.DriverConnectionProvider</property> 

      <property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property> 

      <property name="connection.driver_class"> 
                NHibernate.Driver.SqlClientDriver</property> 

      <property name="connection.connection_string"> 
                Data Source=CAPLIN-SERVER;Initial Catalog=ORMDatabase; 
                Integrated Security=True</property> 

      <property name="show_sql">true</property> 

      <property name="proxyfactory.factory_class"> 
                NHibernate.ByteCode.Castle.ProxyFactoryFactory,  
                NHibernate.ByteCode.Castle</property> 

    </session-factory>
  </hibernate-configuration> 
 

POCO Classes

The Entity Framework comes with a modeling tool that lets you design your domain and database objects - including using either of the following three options:

  • Database First
  • Model First
  • Code First

Since this sample application will be using both the Entity Framework and NHibernate, I have chosen to use a "Code First" approach to creating my domain objects by creating POCO classes for my domain objects.

Creating POCO (Plain Old CLR Objects) means that you can create standard .NET classes (written in any .NET supported language) for defining your domain design - unencumbered by attributes or inheritance required by specific frameworks.

using System;using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Data.Entity;  
 
namespace ORMDataModel
{
    /// <summary>
    /// Customer
    /// </summary>
    public class Customer
    {
        public virtual Int32? CustomerID { get; set; }
        public virtual string CustomerCode { get; set; }
        public virtual string CompanyName { get; set; }
        public virtual string ContactName { get; set; }
        public virtual string ContactTitle { get; set; }
        public virtual string AddressLine1 { get; set; }
        public virtual string AddressLine2 { get; set; }
        public virtual string City { get; set; }
        public virtual string Region { get; set; }
        public virtual string PostalCode { get; set; }
        public virtual string Country { get; set; }
        public virtual string Phone { get; set; }
        public virtual string Fax { get; set; }
        public virtual string EmailAddress { get; set; }
        public virtual DateTime DateCreated { get; set; }
        public virtual DateTime? DateUpdated { get; set; } 
    } 
 
}

The above Customer class will be mapped to the Customer table in SQL-Server through both NHibernate and the Entity Framework.

XML Mapping for NHibernate

Nhiberate uses an XML mapping file to define a mapping between an entity and the corresponding table in the database. There are many ways to implement and configure NHibernate mapping files upon initialization of a NHibernate configuration start up. In this application I created a separate project called ORMNHibernateMaps.

The below xml mapping maps the customer entity. The file has a naming convention of Customer.hbm.xml. One of the tricks to this set-up is to make sure you also save the file as an embedded resource in the Visual Studio project. Searching many blogs I have found this to be a common missed set-up step. Later the ORMNHibernateMaps assembly will be referenced by NHibernate.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  
  <class name="ORMDataModel.Customer, ORMDataModel" 
         table="Customer" dynamic-update="true">
    
    <id name="CustomerID" type="Int32">
      <generator class="identity" />
    </id>
    
    <property name="CustomerCode" type="String" length="100"/>
    <property name="CompanyName" type="String" length="100"/>
    <property name="ContactName" type="String" length="100"/>
    <property name="ContactTitle" type="String" length="100"/>
    <property name="AddressLine1" type="String" length="100"/>
    <property name="AddressLine2" type="String" length="100"/>
    <property name="City" type="String" length="100"/>
    <property name="Region" type="String" length="100"/>
    <property name="PostalCode" type="String" length="100"/>
    <property name="Country" type="String" length="100"/>
    <property name="Phone" type="String" length="100"/>
    <property name="Fax" type="String" length="100"/>
    <property name="EmailAddress" type="String" length="100"/>
    <property name="DateCreated" type="DateTime" />
    <property name="DateUpdated" type="DateTime" /> 
 
  </class>  
 
</hibernate-mapping> 
 

Looking at the mapping file above, you can see references to each column in the Customer table - including a reference to how the CustomerID gets generated - in this case, the CustomerID is defined in SQL-Server as an IDENTITY column.

Two nice pieces in the XML mapping is that your POCO classes can have a different naming convention than your database objects have - which is often the case. You can also add attributes in the XML file that lets you have greater control of the generated CRUD commands. Setting the attribute dynamic-update="true" tells NHibernate to only update those columns that have changed when generating an UPDATE statement.


Initializing NHibernate

The NHibernateDataService initializes NHibernate by creating an NHibernate configuration object and making a reference to the assembly that contains the XML mapping files as embedded resources. Alternately, you can also add the XML mapping files individually as loose files.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; 
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Criterion;
using ORMDataServices.DataFactories.NHibernate; 
 
namespace ORMDataServices.DataFactories.NHibernate
{
    class NHibernateDataService : IDataService, IDisposable
    { 
        ISession _session;     
        Configuration _nHibernateConfiguration;
        ISessionFactory _nHibernateFactory;
       
        ITransaction _dbTransaction;  
 
        /// <summary>
        /// Constructor
        /// </summary>
        public NHibernateDataService()
        {           
            _nHibernateConfiguration = new Configuration();
            _nHibernateConfiguration.AddAssembly("ORMNhibernateMaps");
            _nHibernateFactory = _nHibernateConfiguration.BuildSessionFactory();      
        } 
 
    } 
}

Installing and Setting-Up Entity Framework CTP 5

The latest Entity Framework Feature Community Technology Preview (CTP5) is available for download from Microsoft’s download site. This CTP is a preview of the Code First Programming Model with productivity improvements for Entity Framework 4 (included in .NET 4.0). The Entity Framework CTP5 includes updates to the Code First feature and the simplified API surface (DbContext).

Once downloaded, you can make a reference to the EntityFramework.dll assembly.

 
<connectionStrings> 
    <add name="ORMDatabase" 
         connectionString="Data Source=CAPLIN-SERVER;  
         Initial Catalog=ORMDatabase;Integrated Security=True"   
         providerName="System.Data.SqlClient"  /> 
</connectionStrings>  

 

Entity Framework "code first" uses a convention where DbContext classes by default look for a connection-string that has the same name as the context class. Because the DbContext class is called "ORMDatabase" it by default looks for a "ORMDatabase" connection-string to use. Above the connection-string is configured to use my local SQL-Server 2008 R2 database (stored within the ORMDatabase folder in the attached zip file). To run the sample application for this article, you just need to install SQL-Server Express 2008 R2 and change the connection string where needed.

Initializing Entity Framework

Entity Framwork Code First enables you to easily connect your POCO model classes to a database by creating an API that is based on the DbContext class that exposes public properties that map to the tables within a database. Initializing the DbContext is as simple as instantiating the ORMDatabase class.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; 
 
namespace ORMDataServices.DataFactories.EntityFramework
{
    class EFDataService : IDataService, IDisposable
    {        
        ORMDatabase _factory; 
 
        public EFDataService()
        {
            _factory = new ORMDatabase();
        } 
 
    }
}

The DbContext class has a member called OnModelCreating, which is useful for code first modeling. Any additional configurations you want to apply to your domain model can be defined in OnModelCreating just before the Entity Framework builds the in-memory metadata based on the domain classes. In the example below, I map the POCO classes to the names of the database tables in SQL-Server.

using System;
using System.Collections.Generic;using System.Linq;
using System.Text;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using ORMDataModel; 
 
namespace ORMDataServices.DataFactories.EntityFramework
{
    class ORMDatabase : DbContext
    {
        public DbSet<Customer> Customers { get; set; }
        public DbSet<TransactionLog> Transactions { get; set; } 
 
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder); 
            modelBuilder.Entity<Customer>().ToTable("dbo.Customer");
            modelBuilder.Entity<TransactionLog>().ToTable("dbo.Transactions");
        } 
    }  

} 


Now that I have all the plumbing set-up for NHibernate and the Entity Framework, I will want to expand on this sample application and add some complexity to the underlining domain model and move the sample application towards a more real-world solution. The goal of this application will be to exercise the different object relation mapping tools and see how they perform in terms of response time, ease of implementation and overall flexibility and reliability.

Some real-world domain functions include performing complex queries such as inner and outer joins, sub-quires, unions, dynamic SQL and data pagination.

Dynamic Paging of Data

This sample application uses MVC 3 as the front-end. It contains a page that allows the user to search and page through customer information. Below are sample methods for creating pagnated data using both NHibernate and the Entity Framework.

Paging with NHibernate:

/// <summary>
/// Get Customers
/// </summary>
/// <param name="pageNumber"></param>
/// <param name="pageSize"></param>
/// <param name="totalCustomers"></param>
/// <returns></returns>
public List<Customer> GetCustomers(CustomerTransaction customerTransaction,  
out long totalCustomers)
{ 
 
    _performanceLogging.StartLogging("NHCustomerService.GetCustomers"); 
 
    string customerCode = customerTransaction.Customer.CustomerCode;
    string companyName = customerTransaction.Customer.CompanyName;
    string contactName = customerTransaction.Customer.ContactName; 

    int pageSize = customerTransaction.PageSize;
    int pageNumber = customerTransaction.CurrentPageNumber; 
 
    ICriteria customerCriteria = Session.CreateCriteria(typeof(Customer))           
             .SetMaxResults(pageSize)
             .SetFirstResult((pageNumber - 1) * pageSize); 
 
    ICriteria customerCountCriteria = Session.CreateCriteria(typeof(Customer))
             .SetProjection(Projections.RowCount()); 
 
    if (customerCode != null && customerCode.Trim().Length>0)
    {
        customerCriteria.Add(Expression.Like("CustomerCode", customerCode + "%"));
        customerCountCriteria.Add(Expression.Like("CustomerCode", customerCode + "%"));
    }  
 

    if (companyName != null && companyName.Trim().Length > 0)
    {
        customerCriteria.Add(Expression.Like("CompanyName", companyName + "%"));
        customerCountCriteria.Add(Expression.Like("CompanyName", companyName + "%"));
    } 
 
    if (contactName != null && contactName.Trim().Length > 0)
    {
        customerCriteria.Add(Expression.Like("ContactName", contactName + "%"));
        customerCountCriteria.Add(Expression.Like("ContactName", contactName + "%"));           
    } 
 
    if (customerTransaction.SortExpression != null &&  
        customerTransaction.SortExpression.Trim().Length > 0)
    {
        if (customerTransaction.SortDirection == "DESC")
            customerCriteria.AddOrder(Order.Desc(customerTransaction.SortExpression));
        else
            customerCriteria.AddOrder(Order.Asc(customerTransaction.SortExpression));
    }
         
    var multiResults = Session.CreateMultiCriteria()
                     .Add(customerCriteria)
                     .Add(customerCountCriteria)
                     .List();  
                   
    IList customerList = (IList)multiResults[0];
    IList counts = (IList)multiResults[1]; 
 
    totalCustomers = (int)counts[0];  
 
    List<Customer> customers = new List<Customer>();
    foreach (Customer customer in customerList)
    { 
       customers.Add(customer);
    }
     
    _performanceLogging.EndLogging("NHCustomerService.GetCustomers"); 
 
    return customers; 
 
}
 
 

Paging with the Entity Framework:

 
/// <summary>
/// Get Customers - Dynamic Linq
/// </summary>
/// <param name="customerTransaction"></param>
/// <param name="totalCustomers"></param>
/// <returns></returns>
public List<Customer> GetCustomers(CustomerTransaction customerTransaction, 
out long totalCustomers)
{
    _performanceLogging.StartLogging("EFCustomerService.GetCustomers"); 

    string customerCode = customerTransaction.Customer.CustomerCode;
    string companyName = customerTransaction.Customer.CompanyName;
    string contactName = customerTransaction.Customer.ContactName;
      
    int pageSize = customerTransaction.PageSize;
    int pageNumber = customerTransaction.CurrentPageNumber; 

    var query = ORMDatabaseFactory.Customers.AsQueryable();
    if (customerCode != null && customerCode.Trim().Length > 0)
    {
        query = query.Where(p => p.CustomerCode.StartsWith(customerCode));
    }  

    if (companyName != null && companyName.Trim().Length > 0)
    {
        query = query.Where(p => p.CompanyName.StartsWith(companyName));            
    }  

    if (contactName != null && contactName.Trim().Length > 0)
    {
        query = query.Where(p => p.ContactName.StartsWith(contactName)); 
    }
     
    List<Customer> customerList = query.ToList();
    List<Customer> customers;  

    if (customerTransaction.SortExpression != null &&   
        customerTransaction.SortExpression.Trim().Length > 0)
    {
        if (customerTransaction.SortDirection == "DESC")
            customers = customerList.AsQueryable()
                .OrderBy(customerTransaction.SortExpression + " DESC")
                .Skip((pageNumber - 1) * pageSize)
                .Take(pageSize)                       
                .ToList();
        else
            customers = customerList.AsQueryable()
                .OrderBy(customerTransaction.SortExpression)
                .Skip((pageNumber - 1) * pageSize)
                .Take(pageSize)                       
                .ToList();
    }
    else
    {
        customers = customerList.AsQueryable()
            .OrderBy(p => p.CustomerID)
            .Skip((pageNumber - 1) * pageSize)
            .Take(pageSize)                      
            .ToList();                  
    }
     
    totalCustomers = customerList.Count;
                         
     _performanceLogging.EndLogging("EFCustomerService.GetCustomers");

    return customers; 

}  

As you can see above, both frameworks are able to dynamically build commands to perform a dynamic SQL operation and return a generic list of "paged" customer objects in ascending or descending order - including returning a total count of rows that are available in the database based on the SQL query selected.

NHibernate uses an ISession interface that allows you to use the CreateCriteria object to build a SQL request. NHibernate also allows you to submit mutiple SQL commands to the database at once by using the CreateMultiCriteria object for returning both a recordset of data and returning the row count.

Dynamic LINQ Library

The Entity Framework paging example above uses Dynamic LINQ to SQL. To get this to work, I had to download the Dynamic LINQ Library source code from Microsoft and save it in a C# class file called DynamicLibrary.cs and then reference the namespace System.Linq.Dynamic in my Application Service class. The Dynamic LINQ library implements the IQueryable interface to perform it's operations. This was needed because I needed to be able to pass literal string values into LINQ's Lambda expression syntax.

Instrumentation

In the context of computer programming, instrumentation refers to an ability to monitor or measure the level of a product's performance, to diagnose errors and writing trace information. Instrumentation is in the form of code instructions that monitor specific components in a system.

Through out this sample application I inserted “instrumentation” code to record the response time of each method. Later I can benchmark and compare the performance of each object relational mapper framework. The PerformanceLogging class used throughout this application will measure the performance of each method by executing it’s StartLogging and EndLogging methods. Basically the PerformanceLogging class uses the .NET Environment.TickCount property.

The TickCount is a 32-bit signed integer containing the amount of time in milliseconds that has passed since the last time the computer was started.

The PerformanceLogging class has functionality where logging can be turned off. A threshold can also be set as a high-water mark for logging the response time in the database. At some point in time when you are in production, you might want to only log slow response times after a given number of seconds has elapsed.

Conclusion

This sample application detailed using the Abstract Factory Design Pattern to help test drive object relational mapping tools such as NHibernate and The Entity Framework from Microsoft. Moving forward I will build upon this application by adding more complexity and additional functionality to exercise these frameworks. NHibernate and The Entity Framework are apples and oranges in the same fruit bowl. NHibernate is an open source project derived from the Java Hibernate project. The Entity Framework is a pure Microsoft product. Both ultimately accomplish the same goals, mapping database objects to domain objects - it's just that they do it in very different ways. In the world of Information Technology we face technology decisions all the time. Fortunately there are design techniques like the Abstract Factory Design Pattern to help us swap out components of our application if needed.

Technology

Microsoft Entity Framework Code-First CTP5
NHibernate 3.0
LINQ To SQL
Microsoft Visual Web Developer 2010 Express
Microsoft ASP.NET MVC 3.0
Microsoft .NET C# 4.0
Microsoft SQL Server 2008 R2 Express Edition

License

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

About the Author

Mark J. Caplin
Software Developer Caplin Systems
United States United States
Mark Caplin has specialized in Information Technology solutions for 25 years. Specializing in full life-cycle development projects for both enterprise-wide systems and Internet/Intranet based solutions.
 
For the past ten years or so, Mark has specialized in the Microsoft .NET framework using both C# and VB.NET as his tools of choice.
 
When not coding, Mark enjoys playing tennis, listening to U2 music, watching Miami Dolphins football and watching movies in Blu-Ray technology.
 
In between all this, his wife of over 20 years, feeds him well with some great home cooked meals.
 
...

Comments and Discussions

 
GeneralFixed some reference problems [modified] PinmemberVincent0262-Jul-12 4:46 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 2 Apr 2011
Article Copyright 2011 by Mark J. Caplin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid