65.9K
CodeProject 正在变化。 阅读更多。
Home

使用抽象工厂设计模式测试驱动 NHibernate 3.0、LINQ 和 Entity Framework CTP 5

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (18投票s)

2011年3月27日

CPOL

12分钟阅读

viewsIcon

231400

downloadIcon

1723

使用 C# 开发 N 层应用程序。

下载 CodeProjectORM.zip - 2.75 MB


引言

无论您开发小型或大型应用程序,您总是需要处理数据。它甚至是一个应用程序的关键部分。问题在于这是一项乏味、重复性的工作,会消耗大量时间,我们更愿意将这些时间花在应用程序的其他部分。 为了解决这些问题,存在多种解决方案。 其中一种解决方案是对象关系映射工具 (O/RM)。 他们的目标是简化数据访问层的创建、自动化数据访问或生成数据访问代码。 正如生活中一样,我们在信息技术行业中经常需要在多种选择之间做出选择,有时在做出技术决策时必须在苹果和橙子之间做出选择。 这也适用于对象关系映射工具。 问题是:哪种 O/RM 工具是最佳解决方案? .NET 世界中最流行的两种 O/RM 工具包括 Microsoft 的 Entity Framework 和 NHibernate(Java 的 Hibernate 工具的 .NET 端口)。

示例应用

本文将演示一个示例应用程序,该应用程序将在 N 层应用程序中测试驱动 Microsoft 的 Entity Framework 和 NHibernate 作为可插拔选项。 为了允许这两个 O/RM 工具可插拔,此应用程序将实现抽象工厂设计模式。

SampleApplication.jpg

此示例应用程序的 Visual Studio 解决方案包含以下项目。

  • ORMWebApplicationMVC – 包含前端 MVC 3 Web 应用程序,其中包含视图和控制器。 控制器将调用应用程序服务层。

  • ORMApplicationServices – 包含应用程序业务规则和逻辑。

  • ORMDataServices – 包含 Entity Framework 和 NHibernate 的工厂类

  • ORMDataModels – 包含数据模型类。 此示例应用程序将包含一个用于维护客户信息的类。

  • ORMNHibernateMaps – 包含 Nhibernate XML 数据映射。

抽象工厂设计模式

设计模式是您在真实应用程序开发中一遍又一遍地发现的软件设计问题的重复出现的解决方案。此示例应用程序中使用的抽象工厂模式源自四人帮 (GoF) 模式出版物。

抽象工厂设计模式定义了一个接口,其中包含工厂方法,这些方法将客户端与实例化相关产品系列所需的具体(实际)类分离。 因此,客户端无需了解实际的具体实现。

ORMDataServices 项目实现了一个 OR/M 工厂的工厂。 实现抽象数据工厂首先要创建一个接口。 在应用程序中,将创建一个 IDataFactory 接口,该接口引用一个更具体的接口,称为 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; }    
    }

}

ICustomerDataService 接口将定义客户数据服务的签名。

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);
        
    }
    
}

如前所述,此应用程序定义了一个工厂的工厂。 定义了一个单独的离散工厂 - Entity Framework 和 NHibernate 各一个。 每个工厂都将实现 ICustomerDataServiceIDataFactory 接口。 实现这些接口将允许离散工厂通过“食物链”传递到调用客户端代码。

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(); }
        }   
    }
 
}

每个具体工厂都实现 ICustomerDataService 接口。 下面的示例是 NHibernate 工厂实现的代码片段。

 
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");
       } 
    }

}

食物链的最顶端是 DataAccess 类。 此类将根据 MVC Web 应用程序的 web.config 文件中设置的 DataFactory appsettings 来确定要使用哪个具体数据服务工厂。

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; }
        } 
 
    }  
 
}

上面的代码片段调用下面的 GetFactory 方法,该方法返回所需的具体工厂。

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();
        }
    } 
}

设置好数据工厂后,应用程序服务层现在可以使用客户数据服务,而无需实际了解从数据工厂返回的实际实现。

 
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; 
 
      }        
 
    } 
 
} 

在上面的代码片段中,构造函数实例化数据工厂并返回客户数据服务。 应用程序服务的方法没有直接引用 Entity Framework 或 NHibernate 的较低级别工厂方法。 应用程序代码本质上是通用的,这使得该应用程序可以插入任意数量的对象关系映射器。

 
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

© . All rights reserved.