Sunday, November 8, 2009

LINQ to NHibernate Tutorial

Note: This post applies to NHibernate 2.1 with LINQ to NHibernate version 1.0.

Starting with LINQ to NHibernate is really simple; you just have to add a reference to NHibernate.Linq.dll to your host project (a test project, an ASP.NET app).

Here is a unit test to demonstrate a simple LINQ query.  This test uses the same entities defined in my previous NHibernate tutorial.

[Test]
public void SelectWithPredicate()
{
    using (var session = _factory.OpenSession())
    {
        //Act
        var customers = from cust in session.Linq<Customer>()
                        where cust.LastName.StartsWith("St")
                        select cust;
        //Assert
        Assert.AreEqual(1, customers.Count());
    }
}

The Linq() method is an extension method defined in NHibernate.Linq.dll. It provides the entry point to execute Linq queries on your NHibernate entities. When you run that test, NHibernate generates the following statement:

SELECT count(*) as y0_ FROM Customer this_
WHERE this_.LastName like @p0;@p0 = 'St%'

In an upcoming post I will explore the capabilities and limitations of LINQ to NHibernate. For now, here is the full test class I used to discover LINQ to NHibernate:

using System.Diagnostics;
using System.Linq;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Linq;
using NUnit.Framework;
using PocoLib;

namespace PocoLibTests
{
    [TestFixture]
    public class CustomerLINQTests
    {
        private ISessionFactory _factory;

        [SetUp]
        public void Setup()
        {
            Configuration configuration = new Configuration();
            configuration.Configure("PocoLib.cfg.xml");
            configuration.AddXmlFile("Customer.hbm.xml");

            _factory = configuration.BuildSessionFactory();

            SetupDatabase();
        }

        [TearDown]
        public void TearDown()
        {
            PurgeDatabase();
        }

        [Test]
        public void SelectAllCustomers()
        {
            using (var session = _factory.OpenSession())
            {
                //Act
                var customers = from cust in session.Linq<Customer>()
                                select cust;
                //Assert
                Assert.AreNotEqual(4, customers.Count());
            }
        }

        [Test]
        public void SelectWithPredicate()
        {
            using (var session = _factory.OpenSession())
            {
                //Act
                var customers = from cust in session.Linq<Customer>()
                                where cust.LastName.StartsWith("St")
                                select cust;
                //Assert
                Assert.AreEqual(1, customers.Count());
            }
        }

        private void SetupDatabase()
        {
            using (var session = _factory.OpenSession())
            {
                Customer c = new Customer();
                c.FirstName = "Mike";
                c.LastName = "Laroco";
                c.Address.Country = "USA";
                session.SaveOrUpdate(c);

                c = new Customer();
                c.FirstName = "Bubba";
                c.LastName = "Stuart";
                c.Address.Country = "USA";
                c.Address.State = "Florida";
                session.SaveOrUpdate(c);

                c = new Customer();
                c.FirstName = "Ricky";
                c.LastName = "Carmichael";
                c.Address.Country = "USA";
                c.Address.State = "Florida";
                session.SaveOrUpdate(c);

                c = new Customer();
                c.FirstName = "Jean-Sebastien";
                c.LastName = "Roy";
                c.Address.Country = "Canada";
                c.Address.State = "Quebec";
                session.SaveOrUpdate(c);

                session.Flush();
            }

            Debug.WriteLine("end prepare data");

        }

        private void PurgeDatabase()
        {
            using (var session = _factory.OpenSession())
            {
                session.Delete("from Customer");
                session.Flush();
            }
        }

    }
}

Thanks

Thursday, November 5, 2009

NHibernate Tutorial

Note: This post is based on NHibernate version 2.1.

This is a tutorial to get you started with NHibernate.

At the end of this tutorial you will have:

  • A class library project containing one entity
  • A NHibernate configuration file
  • A NHibernate mapping file to map your entities
  • A database running on SQL Server 2005 (changing to another version of SQL server is very simple)
  • A NUnit test project

1 - Create a new class library project named PocoLib in a new solution. This project will contain your entity classes.

2 - In the same solution, create a new class library project called PocoLibTests

3 - In the PocoLibTests project add references to

  • PocoLib project
  • nunit.framework.dll
  • nhibernate.dll
  • LinFu.DynamicProxy
  • NHibernate.ByteCode.LinFu

Note: You do not have to add any references to nhibernate dlls in the PocoLib project. Your entity classes will not have any knowledge of NHibernate; they are real POCO. If you eventually use your entities in a Web app or Web Services project (most likely), it will be that app that will need those same references (except for Nunit of course).

4 - In PocoLibTests, rename class1.cs to CustomerCRUDTests.cs to your project.

using NHibernate;
using NHibernate.Cfg;
using NUnit.Framework;
using PocoLib;

namespace PocoLibTests
{
    [TestFixture]
    public class CustomerCRUDTests
    {

        [Test]
        public void Insert()
        {
            Customer c = new Customer()
                { FirstName = "Bubba", LastName = "Stuart" };

            c.Address.Country = "USA";
            c.Address.State = "Florida";

            using (ISession session = _factory.OpenSession())
            {
                session.SaveOrUpdate(c);
                session.Flush();
            }
        }
    }
}

This test does not compile for now but that gives you an idea of what we are going to work with.

5 - In the PocoLib project rename class1.cs to Address.cs:

namespace PocoLib
{
    public class Address
    {
        private string _zipCode;
        private string _country;
        private string _state;
        private string _street;

        public string Street
        {
            get { return _street; }
            set { _street = value; }
        }

        public string State
        {
            get { return _state; }
            set { _state = value; }
        }

        public string Country
        {
            get { return _country; }
            set { _country = value; }
        }

        public string ZipCode
        {
            get { return _zipCode; }
            set { _zipCode = value; }
        }
    }
}

6 - In the PocoLib project add a new class named Customer.cs:

using System;
using System.Collections.Generic;

namespace PocoLib
{
    public class Customer
    {
        private int _iD;
        private DateTime? _birthDate;
        private string _lastName;
        private string _firstName;
        private Address _address = new Address();

        public int ID
        {
            get { return _iD; }
            set { _iD = value; }
        }

        public string FirstName
        {
            get { return _firstName; }
            set { _firstName = value; }
        }

        public string LastName
        {
            get { return _lastName; }
            set { _lastName = value; }
        }

        public DateTime? BirthDate
        {
            get { return _birthDate; }
            set { _birthDate = value; }
        }

        public Address Address
        {
            get { return _address; }
            set { _address = value; }
        }

    }
}

As you can see, the model is simple: a customer has a few properties and an Address.

Note: At this point, the test project still does not compile because the _factory is not defined yet. We will define it really soon.

7 - Create a new database with a Customer table

CREATE DATABASE PocoLib
GO

USE PocoLib
GO

CREATE TABLE [dbo].[Customer](
[CustomerID] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](50) NULL,
[LastName] [varchar](50) NOT NULL,
[BirthDate] [datetime] NULL,
[Street] [varchar](50) NULL,
[State] [varchar](50) NULL,
[Country] [varchar](50) NULL,
[ZipCode] [varchar](50) NULL)
GO

Notice how we are using two classes (Customer and Address) but a single Customer table. It is much cleaner to use two classes at the class design level. But since it is a one-to-one relationship, saving the address in the same table as customer makes total sense (it's simpler, faster and feel more natural).

8 - In the PocoLib project, add an xml file named PocoLib.cfg.xml. That will be your main NHibernate configuration file. It's arguable whether this file belongs to the PocoLib class library or to the host application (the test project in this case), but for now let's put it in PocoLib.

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.provider">
      NHibernate.Connection.DriverConnectionProvider
    </property>
    <property name="connection.driver_class">
      NHibernate.Driver.SqlClientDriver
    </property>
    <property name="connection.connection_string">
      Server=localhost;database=PocoLib;Integrated Security=SSPI;
    </property>
    <property name="dialect">
      NHibernate.Dialect.MsSql2005Dialect
    </property>
    <property name="show_sql">
      true
    </property>
    <property name='proxyfactory.factory_class'>
      NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu
    </property> 
  </session-factory>
</hibernate-configuration>

  • Change the connection string (line 10) if needed.
  • If you are not on SQL Server 2005, you can change the other settings. For instance, set the dialect setting to Nibernate.Dialect.MsSql2008Dialect if your on SQL Server 2008.

9 - In the PocoLib project, add an xml file named Customer.hbm.xml. That will be mapping file used to tell NHibernate where to persist and load our entities. (The same question about where this file should reside applies here too).

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

  <class name="Customer" lazy="false">
    <id name="ID" column="CustomerID">
      <generator class="native"/>
    </id>
    <property name="FirstName"/>
    <property name="LastName"/>
    <property name="BirthDate"/>
    <component name="Address">
      <property name="Street"/>
      <property name="State"/>
      <property name="Country"/>
      <property name="ZipCode"/>
    </component> 
  </class>
</hibernate-mapping>

I think the content of the file pretty much speaks for itself but here are a few things worth mentioning:

  • By default, NHibernate assumes that your database column names match your class property names.
  • The <id> element identifies the primary key column. The <generator class="native"> indicates that SQL Server will generate the primary key value; not NHibernate.
  • The Customer is defined as a <class> in the mapping. This is what NHibernate calls an entity.
  • The Address is declared inside the Customer as an<component> element. That basically means that the Address is a part of the Customer and it can't live without a customer. NHibernate components are also know as Value Object in Domain Driven Design.

10 - Copy the PocoLib.cfg.xml and Customer.hbm.xml to the test library \bin\debug\ folder.

11 - Add the missing _factory to the test class. Add this code at the top of the test class:

        private ISessionFactory _factory;

        [SetUp]
        public void Setup()
        {
            Configuration configuration = new Configuration();
            configuration.Configure("PocoLib.cfg.xml");
            configuration.AddXmlFile("Customer.hbm.xml");

            _factory = configuration.BuildSessionFactory();
        }

  • The first line create a Configuration object.
  • The second line loads the PocoLib.cfg.xml.
  • The 3rd line loads the mapping file for the Customer class.
  • The 4th line create a factory that we will use to create NHibernate Sessions (a connection)

13 - Run the Insert() test. Go look in the database and be amazed (or not). Notice how SQL statements are written to the output window (If you are using Test Driven at least). You can turn that on or off by changing the <property name="show_sql">true</property> in the .cfg.xml file.

12 - Here are the other CRUD tests. I'm sorry but I don't feel like writing all the Asserts; this is just a test project to experiment with the CRUD operations.

using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;
using NUnit.Framework;
using PocoLib;

namespace PocoLibTests
{
    [TestFixture]
    public class CustomerCRUDTests
    {
        private ISessionFactory _factory;

        [SetUp]
        public void Setup()
        {
            Configuration configuration = new Configuration();
            configuration.Configure("PocoLib.cfg.xml");
            configuration.AddXmlFile("Customer.hbm.xml");

            _factory = configuration.BuildSessionFactory();
        }

        [Test]
        public void Insert()
        {
            Customer c = new Customer()
               { FirstName = "Bubba", LastName = "Stuart" };

            c.Address.Country = "USA";
            c.Address.State = "Florida";

            using (ISession session = _factory.OpenSession())
            {
                session.SaveOrUpdate(c);
                session.Flush();
            }
        }

        [Test]
        public void Update()
        {
            // Arrange : Create customer in order to update it
            Customer c = new Customer() 
                { FirstName = "Bubba", LastName = "Stuart" };
            using (ISession session = _factory.OpenSession())
            {
                session.SaveOrUpdate(c);
                session.Flush();
            }
            int id = c.ID;

            //Act : update
            using (ISession session = _factory.OpenSession())
            {
                c = session.Get<Customer>(id);
                c.FirstName = "James";
                session.SaveOrUpdate(c);
                session.Flush();
            }
        }

        [Test]
        public void Delete()
        {
            // Arrange : Create customer in order to delete it
            Customer c = new Customer()
                { FirstName = "Bubba", LastName = "Stuart" };
            using (ISession session = _factory.OpenSession())
            {
                session.SaveOrUpdate(c);
                session.Flush();
            }
            int id = c.ID;

            //Act : Delete
            using (ISession session = _factory.OpenSession())
            {
                c = session.Get<Customer>(id);
                session.Delete(c);
                session.Flush();
            }
        }

        [Test]
        public void Query()
        {
            // Arrange : Create a customer in order to query it
            Customer c = new Customer() 
                 { FirstName = "Bubba", LastName = "Stuart" };
            using (ISession session = _factory.OpenSession())
            {
                session.SaveOrUpdate(c);
                session.Flush();
            }

            IList<Customer> list = null;

            //Act:
            using (ISession session = _factory.OpenSession())
            {
                list = session.CreateQuery(
                   "from Customer where LastName is not null")
                  
.List<Customer>();
            }

            //Assert:
            Assert.AreNotEqual(0, list.Count);

        }

    }
}

I hope this helps.

BTW: I started learning NHibernate just a few days ago. I'm wrote this post to serialize what I have learned so far (and hopefully help people at the same time). I'm sure this approach is not ideal. If you have comments on how I could improve this tutorial, just let me know.

Thanks

Sunday, November 1, 2009

Monotouch debugger now avaliable

This very short post is simply to announce that monotouch now has a debugger (breakpoints, watch) that works on the device and in the simulator.

Kudos to the monotouch team!

jQuery plug-in to fix the width=100% problem

While making the layout for a new app I’m writing, I discovered what I call the Box model 100% width bug.

The box model spec states that the resulting width of an element whose width is 100% will equal to:

calculated width = (inner width of parent) + (element's padding) + (element's margin)

That means that given this html:

<div id="container" style="width:400px;">
  <div id="div1" 
     style="background-color:Green; width:100%; margin-left:10px; padding-left:10px;">
     Lorem ipsum dolor sit amet, consectetur adipisicing elit...
  </div>
</container>

The div1 's total width will be 420px; not 400px as you would want.

A solution is to compensate by wrapping div1 in a container that has a padding-left equal to the element's (2 * margin) + (padding):

<div id="container" style="width:400px;">
  <div id="wrapper" style="padding-left:30px;">
    <div id="div1" 
     style="background-color:Green; width:100%; margin-left:10px; padding-left:10px;">
     Lorem ipsum dolor sit amet, consectetur adipisicing elit...
    </div>
  </div>
</container>

Why add an extra element instead of modifying the container div? Because in a real cases, the container div will probably be many levels above the element that you are trying to style. The wrapper div used to compensate should be the immediate parent of that element.

I wrote a jQuery plugin to automatically inject the wrapper div to elements. Here is how to use it:

$(function() {
    $("#div1").fixFullWidth();
})

The plug-in iterates  through at all elements selected by the jQuery selector and wraps each of them in a div that has the correct padding-left value.

Here is the plug-in source. BTW, I'm not a javascript expert so if you come up with a better implementation just let me know and I will update this post.

$.fn.fixFullWidth = function() {
    //create the endsWith function of the String type
    String.prototype.endsWith = function(str) {
        return (this.match(str + "$") == str);
    }

   this.each(function() {
        var elemMarginValue = $(this).css('margin-left');
        var elemMargin = 0;
        if (elemMarginValue.endsWith("px")) {
            elemMargin = parseInt( elemMarginValue.replace('px', ''));
        }
        var elemPaddingValue = $(this).css('padding-left');
        var elemPadding = 0;
        if (elemPaddingValue.endsWith("px")) {
            elemPadding = parseInt( elemPaddingValue.replace('px', ''));
        }
        var padding_left = (elemMargin * 2) + elemPadding;
        if (padding_left != 0) {
            $(this).wrap('<div class="fixFullWidth" style="padding-right:' + padding_left + 'px;"></div>');
        }
    });
    return this;
};

I hope this helps.