Lambda Expressions with NHibernate.Linq

clock February 19, 2010 07:33 by author jamesstill

In the last two posts below I talked about creating a templated DataService class and also mused about whether UI data queries really were a cross-cutting concern. I'm agnostic about the question really; however, I do have one practical concern. I don't want to expose methods that take any old HQL query that the client sends my way. I'd like to have a controlled way of extending database queries down to the UI.

The answer is LINQ. Thanks to Rahien and others NHibernate.Linq 1.0 RTM is available. (Eventually this will be merged into the NHibernate mainline but for now it's a separate download.) Once you reference the assembly and put a reference to NHibernate.Linq the extension methods are available in your ISession instance:

   1:  // grab an ISession from ISessionFactory            
   2:  var query = from item in session.Linq()                 
   3:              select item;

So I'm going to extend my DataService class below to support LINQ queries along these lines. Kudos to Ryan Lanciaux who blogged on this before me. To support Linq queries we first must modify the IDataService interface to support lamdba expressions:
   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Linq.Expressions;
   5:  using System.Text;
   6:   
   7:  namespace ConcertMusic.DomainModel
   8:  {
   9:      public interface IDataService<T>
  10:      {
  11:          T Get(int id);
  12:          IList<T> Get(string hqlQueryString);
  13:          IList<T> Get();
  14:          void AddCriteria(Expression<Func<T, bool>> lambdaFunction);
  15:          T Save(T item);
  16:          bool Delete(T item);       
  17:      }
  18:  }

Now to implement the interface in the DataService class:

   1:  public class DataService<T> : IDataService<T>
   2:      {
   3:          private IList<Expression<Func<T, bool>>> _criteria; 
   4:   
   5:          public DataService() 
   6:          {
   7:             _criteria = new List<Expression<Func<T, bool>>>(); 
   8:          }
   9:   
  10:          public void AddCriteria(Expression<Func<T, bool>> lambdaFunction)
  11:          {
  12:              _criteria.Add(lambdaFunction);
  13:          } 
  14:   
  15:          public IList<T> Get()
  16:          {
  17:              using (ISession session = NHibernateHelper.OpenSession())
  18:              {
  19:                  var query = from item in session.Linq<T>()
  20:                              select item;
  21:   
  22:                  foreach (var criterion in _criteria)
  23:                  {
  24:                      query = query.Where<T>(criterion);
  25:                  }
  26:                  return query.ToList(); 
  27:              }         
  28:          }
  29:   
  30:          // other methods removed for clarity
  31:      }
  32:  }

That's pretty much it for the data access plumbing. Now the client code can fetch the IDataService instance and get back anything it wants using lambda expressions. Let's say I want to get every widget from the database with a name that starts with the letter "V". Here's my code:

   1:  IDataService<Widget> svc = DataServiceFactory<Widget>.Create();
   2:  svc.AddCriteria(item => item.Name.StartsWith("V"));
   3:  IList<Widget> list = svc.Get();

And these lambda expressions can be chained of course for very fine-grained search criteria. Suppose I want every widget with a name that starts with the letter "V", is round and blue, and has greater than 8 teeth:

   1:  IDataService<Widget> svc = DataServiceFactory<Widget>.Create();
   2:  svc.AddCriteria(item => item.Name.StartsWith("V"));
   3:  svc.AddCriteria(item => item.Color.Equals("Blue"));
   4:  svc.AddCriteria(item => item.Shape.Equals("Round"));
   5:  svc.AddCriteria(item => item.Teeth > 8));
   6:  IList<Widget> list = svc.Get();

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


UI Data Query A Cross Cutting Concern?

clock February 8, 2010 04:42 by author jamesstill

Rehein makes the interesting argument that there is no clean separation between the presentation, business, and data access layer. Instead, UI concerns often known only at runtime like paging, filtering, sorting, and so on make data querying a cross cutting concern. He therefore, recommends we stop creating UI interfaces like:

    GetInvoicesForUser(int userID)

This is too inflexible and we end up breaking or overloading these interface methods every time a new need comes up:

    GetInvoicesForUser(int userID, int pageNumber, string orderByColumn)

The goal is for any interface implementation to be open for extension but closed for modification. But by insisting upon a hard separation of concern between the UI and the business/facade interface we're making modifications left and right.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Refactor to DataService

clock February 1, 2010 15:23 by author jamesstill

In the post below on NHibernate 101 I said not to plumb data access code in the Operation Contract implementation. Now I'd like to refactor and get that out of there. Let's think about it a bit first. Do I really want to create a data layer class called "InstrumentService" with the CRUD in there? I could. But since this is a concert music application, I know I'm going to have several lookup tables in the system. In addition to musical instruments there will be musical genres, catalogs, recording labels, ensemble types, and so on and on.

So we want to make sure all of these data access classes implement an interface with the standard read, insert, update, and delete behavior. Let's do it. But let's be smart about it and take advantage of generics:

   1:  public interface IDataService<T>
   2:  {
   3:      IList<T> GetAll(string queryString);
   4:      T GetItem(int id);
   5:      T Save(T item);
   6:      bool Delete(int id, string tableName);
   7:      bool Delete(T item);
   8:  }

Having done that we can code a single DataService that implements the interface:

   1:  /// <summary>
   2:  /// Data service to fetch and save common lookup table items.
   3:  /// </summary>
   4:  public class DataService<T> : IDataService<T>
   5:  {
   6:      public DataService() { }
   7:   
   8:      public IList<T> GetAll(string queryString)
   9:      {
  10:          IList<T> list;
  11:          using (ISession session = NHibernateHelper.OpenSession())
  12:          {
  13:              IQuery query = session.CreateQuery(queryString);
  14:              list = query.List<T>();
  15:          }
  16:          return list;
  17:      }
  18:   
  19:   
  20:      public T GetItem(int id)
  21:      {
  22:          T item;
  23:          using (ISession session = NHibernateHelper.OpenSession())
  24:          {
  25:              item = session.Get<T>(id);
  26:          }
  27:          return item;
  28:      }
  29:   
  30:   
  31:      public T Save(T item)
  32:      {
  33:          using (ISession session = NHibernateHelper.OpenSession())
  34:          {
  35:              using (ITransaction transaction = session.BeginTransaction())
  36:              {
  37:                  session.SaveOrUpdate(item);
  38:                  transaction.Commit();
  39:              }
  40:          }
  41:          return item;
  42:      }
  43:   
  44:   
  45:      public bool Delete(int id, string tableName)
  46:      {
  47:          bool deleted = false;
  48:          using (ISession session = NHibernateHelper.OpenSession())
  49:          {
  50:              using (ITransaction transaction = session.BeginTransaction())
  51:              {
  52:                  string hqlString = string.Format("from {0} where ID = :ID", tableName);
  53:                  session.Delete(hqlString, id, NHibernateUtil.Int32);
  54:                  transaction.Commit();
  55:                  deleted = true;
  56:              }
  57:          }
  58:          return deleted;
  59:      }
  60:   
  61:      public bool Delete(T item)
  62:      {
  63:          bool deleted = false;
  64:          using (ISession session = NHibernateHelper.OpenSession())
  65:          {
  66:              using (ITransaction transaction = session.BeginTransaction())
  67:              {
  68:                  session.Delete(item);
  69:                  transaction.Commit();
  70:                  deleted = true;
  71:              }
  72:          }
  73:          return deleted;
  74:      }
  75:  }

Note that this implementation assumes that all of my lookup tables will have a surrogate primary key. There's just one last detail and that's creating a factory for these DataService instances. It's not really necessary. But I'm obsessed with web service methods that do as little as possible so I'm going to make their job as easy as possible. So here's the simple factory:

   1:  public class DataServiceFactory<T> where T : new()
   2:  {
   3:      public static DataService<T> Create()
   4:      {
   5:          return new DataService<T>();
   6:      }
   7:  }

That takes care of the NHiberate plumbing for all lookup tables in my system. Now if I want to fetch all musical instruments, or genres, or record labels from the system it's pretty easy:

   1:  public IList<Instrument> GetInstruments()
   2:  {
   3:      IDataService<Instrument> svc = DataServiceFactory<Instrument>.Create();
   4:      return svc.GetAll("FROM Instrument");
   5:  }
   6:   
   7:  public IList<Genre> GetGenres()
   8:  {
   9:      IDataService<Genre> svc = DataServiceFactory<Genre>.Create();
  10:      return svc.GetAll("FROM Genre");
  11:  }
  12:   
  13:  public IList<Catalog> GetCatalogs()
  14:  {
  15:      IDataService<Catalog> svc = DataServiceFactory<Catalog>.Create();
  16:      return svc.GetAll("FROM Catalog");
  17:  }

The one thing that still bugs me is the hard-coded "FROM [TableName]" that gets passed in. That could get refactored and put into the DataService base class. However, doing so would make an assumption that I'm not ready to make, namely, that my class name equals the table name. There is probably a way to decorate the POCO classes with attributes but then we're creeping back into domain entites that are not persistent ignorant. So it's a small price to pay.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


NHibernate 101: Silverlight 4 with NHibernate 2.n

clock February 1, 2010 05:44 by author jamesstill

In this intro I'll create a vanilla Silverlight 4 application, hosted in a WCF Service, and wired up to SQL Server 2008 with the open source NHibernate ORM tool. I'm not going to dive into the weeds here. I just want to show the basics.

Prerequisites:

* Visual Studio 2010 Beta 2
* Silverlight 4 Tools for VS 2010
* NHibernate 2.n
* SQL Server 2008

In Visual Studio create a new project using the Silverlight Application project template. When prompted, choose to host the project in a new web site with the default ASP.NET Web Application Project type. I'll call it "ConcertMusic.Web" because we're going to fetch a list of classical music instruments from a database table and display them in a SL DataGrid. Do not enable .NET RIA Services.

After the project template unfolds right-click the ConcertMusic.Web and create a WCF Service using the Silverlight-enabled WCF Service template. I'm not going to get into WCF here but you'll want to create a contract interface, configure the service behavior and bindings, and perhaps add a ClientAccessPolicy.xml file. Here's my service contract:

   1:  namespace ConcertMusic.Web
   2:  {
   3:      [ServiceContract(Namespace="http://ConcertMusic.Web/2010/07")]
   4:      public interface IConcertMusicService
   5:      {
   6:          [OperationContract]
   7:          IList<Instrument> GetInstruments();
   8:      }
   9:  }

Now let's configure NHiberate. Add references to the required NHibernate assemblies: NHibernate.dll, log4net.dll, Iesi.collections.dll, and Antlr3.Runtime.dll. (Optionally, include Castle or Spring assemblies if you want to support lazy loading. I'm not going to get into that here.)

In Web.config add the plumbing to configure NHibernate:

   1:  <configuration>
   2:   
   3:    <configSections>
   4:      <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
   5:    </configSections>
   6:   
   7:    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
   8:      <session-factory>
   9:        <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
  10:        <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
  11:        <property name="connection.connection_string">Data Source=SERVER_INSTANCE;Initial Catalog=DatabaseName;Integrated Security=SSPI</property>
  12:        <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
  13:        <property name="show_sql">false</property>
  14:        <property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
  15:      </session-factory>
  16:    </hibernate-configuration>

Ok so we've got the NHibernate assemblies referenced and we've configured it the project. Let's start at the database and work our way forward. First, we've got a table of musical instruments in the database:

   1:  CREATE TABLE [dbo].[Instrument](
   2:      [ID] [int] IDENTITY(1,1) NOT NULL,
   3:      [Name] [nvarchar](50) NULL,
   4:      [Description] [nvarchar](120) NULL,
   5:   CONSTRAINT [PK_Instrument] PRIMARY KEY CLUSTERED 
   6:  (
   7:      [ID] ASC
   8:  )WITH 
   9:  (PAD_INDEX  = OFF, 
  10:  STATISTICS_NORECOMPUTE  = OFF, 
  11:  IGNORE_DUP_KEY = OFF, 
  12:  ALLOW_ROW_LOCKS  = ON, 
  13:  ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
  14:  ) ON [PRIMARY]
  15:   
  16:  GO

We need to do two things to map the table to a plain old CLR object (POCO) in our application. We have to create the POCO class and then we have to create a mappping file that tells NHibernate how to create an instance of that class from the data store. Here's our simple POCO class:

   1:  [DataContract]
   2:  public class Instrument
   3:  {
   4:      public Instrument() { }
   5:   
   6:      [DataMember]
   7:      public virtual int ID { get; set; }
   8:   
   9:      [DataMember]
  10:      public virtual string Name { get; set; }
  11:   
  12:      [DataMember]
  13:      public virtual string Description { get; set; }
  14:  }

Notice that we're using auto-implemented properties introduced in C# 3.0. Also, the class is implemented as a DataContract with DataMember properties. This is necessary if you want to bind an IList collection of Instrument objects to a Silverlight control. If you don't use DataMember attributes, then Silverlight will attempt to bind to the IL-generated backing fields instead and you'll get skinny blank rows in your DataGrid.

Ok, now let's create the NHibernate mapping file. It can live in the same place as the POCO or you can put your mapping files in a separate folder to keep them all together. It's up to you. The naming convention is [ClassName].hbm.xml:

   1:  <?xml version="1.0" encoding="utf-8"?>
   2:  <hibernate-mapping assembly="ConcertMusic.Web" xmlns="urn:nhibernate-mapping-2.2">
   3:    <class name="ConcertMusic.DomainModel.Instrument, ConcertMusic.DomainModel" table="Instrument" lazy="false" >
   4:      <id name="ID" type="Int32" column="ID">
   5:        <generator class="identity" />
   6:      </id>
   7:      <property name="Name" column="Name" />
   8:      <property name="Description" column="Description" />
   9:    </class>
  10:  </hibernate-mapping>

I'm not going into a long explanation of this file. Read the NHibernate docs for a detailed specification. For now I'll just say that ID is described as a synthetic key (identity column), and Name and Description are mapped one-to-one to identical column names in the table. Pretty straightforward. Very important! Change the build action of this xml file to make it an embedded resource. It needs to be compiled into the assembly in order by NHibernate to find it at runtime.

NHibernate uses an ISessionFactory to talk to the database. Just like IDbConnections in ADO.NET, these are very expensive to set up so you don't want to set them up and tear them down with every transaction to the database. The best practice is to create them once and then hold onto them during the lifetime of the application. Here I'm using a Singleton pattern to wrap the factory in a helper class:

   1:  public class NHibernateHelper
   2:  {
   3:      public static readonly ISessionFactory SessionFactory;
   4:   
   5:      static NHibernateHelper()
   6:      {
   7:          Configuration cfg = new Configuration().AddAssembly("ConcertMusic.DomainModel");
   8:          SessionFactory = cfg.Configure().BuildSessionFactory();
   9:      }
  10:   
  11:      public static ISession OpenSession()
  12:      {
  13:          return SessionFactory.OpenSession();
  14:      }
  15:  }

In line 7 I'm adding the assembly ConcertMusic.DomainModel to the NHibernate configuration. This is to tell NHibernate where my [ClassName].hbm.xml mapping files live. I created a separate project for my POCO classes and NHibernate mapping files. Change this to Silverlight1.Web or whatever assembly name where your mapping files were added.

One last plumbing task to complete. We need to implement the OperationContract so that Silverlight can call and fetch a list of musical instruments. I keep my web methods very thin but for illustration purposes only I'll just code it right there:

   1:  public class ConcertMusicService : IConcertMusicService
   2:  {
   3:      public IList<Instrument> GetInstruments()
   4:      {
   5:          IList<Instrument> list;
   6:          using (ISession session = NHibernateHelper.OpenSession())
   7:          {
   8:              IQuery query = session.CreateQuery("FROM Instrument");
   9:              list = query.List<Instrument>();
  10:          }
  11:          return list;    
  12:      }
  13:  }

Do not do this in a production application. Get that data access plumbing out of your web method and into a data layer somewhere. That makes it easy to write unit tests that call the data layer too. But this is fine for our purposes. Run your unit tests and make sure it's all working. Then wire up the Silverlight MainPage.xaml to consume the data. Drag a DataGrid onto your MainPage.xaml. It should register the System.Windows.Controls.Data assembly and stub out a DataGrid for you:

   1:  <data:DataGrid x:Name="InstrumentDataGrid" AutoGenerateColumns="False" Height="400" Width="200">
   2:      <data:DataGrid.Columns>
   3:          <data:DataGridTextColumn Header="ID" Binding="{Binding ID}" />
   4:          <data:DataGridTextColumn Header="Name" Binding="{Binding Name}" />
   5:      </data:DataGrid.Columns>
   6:  </data:DataGrid>

Create a service reference to the WCF service in your Silverlight app. Then in the code behind make an async call to the web service and bind the results to the grid:

   1:  public partial class MainPage : UserControl
   2:  {
   3:      public MainPage()
   4:      {
   5:          InitializeComponent();
   6:          Loaded += new RoutedEventHandler(MainPage_Loaded);
   7:      }
   8:   
   9:      void MainPage_Loaded(object sender, RoutedEventArgs e)
  10:      {
  11:          ConcertMusicServiceClient service = new ConcertMusicServiceClient();
  12:          service.GetInstrumentsCompleted += 
  13:              new EventHandler<GetInstrumentsCompletedEventArgs>(Service_GetInstrumentsCompleted);
  14:          service.GetInstrumentsAsync();
  15:      }
  16:   
  17:      void Service_GetInstrumentsCompleted(object sender, GetInstrumentsCompletedEventArgs e)
  18:      {
  19:          InstrumentDataGrid.ItemsSource = e.Result;  
  20:      } 
  21:  }

That's all there is to it.

Currently rated 2.0 by 1 people

  • Currently 2/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


About

SquareWidget LLC is an Oregon-based software development company that specializes in handcrafted .NET software solutions.

Search

Archive

Categories


Sign in