A Short Introduction to NHibernate


What we are about to discuss here is a product that deals with Metadata Mapping [Fowler PoEAA], an O/R Mapper, which is called NHibernate.

As I said, NHibernate is a port from the very popular O/R Mapper in Java-land called Hibernate, originally created by Gavin King [Bauer/King HiA]. The port is based on version 2.1 of Hibernate, which is considered a pretty old version, but the port doesn't strictly just move the code base for that particular Hibernate version. Instead, features from later versions of Hibernate as well as other features have been added here and there.

NHibernate (and Hibernate) is open source. As I write, NHibernate is released as version 1.0. You can download it from here: [NHibernate].

The proof of the pudding is in the eating, so let's see how NHibernate is used.

Preparations

Let's assume you have a Domain Model and you now want to write some consumer code against the Domain Model.

Note

One more requirement is that you want to make the Domain Model persistent. Therefore, you also have a database product, but don't think about the database schema for the moment.


First, you need to set a reference in the consumer to the NHibernate framework (namely nhibernate.dll). That wasn't too hard.

Then you need to configure the usage of NHibernate in the consumer of the Domain Model. You can either do that in code or in a .config file. Typically a .config file is used, so let's assume that. Some things that are configured can be what the database product in question is, the connection string to the database, and logging. You will find lots of examples of .config files to copy and paste from at the NHibernate site, so I won't go into detail here, but I'll just take it for granted that you have a suitable .config file in place.

What you then try to do just once per application execution is to set up a SessionFactory. The reason for not creating several is that the cost is fairly high. The SessionFactory will analyze all metadata and build up memory-based structures for that. As you might guess from its name, the SessionFactory is then used for instantiating new instances that implement ISession.

ISession is the string to pull in order to get work done against the database and is also the interface that you will interact with all the time from the consumer perspective.

Next, it's nice if you can use a helper for the ISession management. This can be done with different levels of sophistication, but here a simple attempt would be good enough for our purpose. If we assume that our Domain Model is called ADDDP.Ordering.DomainModel, a simplified helper called NHConfig could look like this:

public class NHConfig {     private static ISessionFactory _sessionFactory;     static NHConfig()     {         Configuration config = new Configuration();         config.AddAssembly("ADDDP.Ordering.DomainModel");         _sessionFactory = config.BuildSessionFactory();      }      public static ISessionFactory GetSessionFactory()      {          return _sessionFactory;      } }


Making more assumptions, we are now working with a rich client application; therefore, we think it's enough to have a single ISession instance. At least, let's start that way. Then the following code might be suitable for the start of the application, or perhaps for a single form:

_session = NHConfig.GetSessionFactory().OpenSession(); _session.Disconnect();


So when you want to get an ISession to act on, you can use the following little snippet over and over again:

_session.Reconnect(); //Do stuff... _session.Disconnect();


And finally, when the application terminates (or the form is closed, depending upon your chosen strategy), you close the ISession like this:

_session.Close();


Note

One ISession per form or one per application are just two of several possible strategies.


OK, that was all good fun, but it was completely useless considering what we accomplished in the database. Opening and closing ISessions is not enough, and we need to persist changes to the database as well. For that to be possible, we need to make some more preparations.

Some Mapping Metadata

In the preparations so far, the Domain Model itself hasn't been affected at all. However, we do need to do somethingnot to affect it, but rather to complement it. We need to add mapping information to show the relationship between the Domain Model and the database. Typically, this mapping information is created in a separate XML file per Entity [Evans DDD], and those files are stored in the project directory for the Domain Model.

So far, we haven't created a schema in the database, so we are pretty free to work as we like. The only thing we have is the Domain Model. We can use tools for creating the mapping information and/or the database schema, but the only thing that is important for this introduction is showing what a mapping file could look like, given a certain Domain Model Entity and database table. Let's pick a simple Entity from our Domain Model. I think Customer fits the requirements pretty well. You find the Entity and its Value Objects [Evans DDD] in Figure 9-1.

Figure 9-1. Customer Entity and Address and ReferencePerson Value Objects


In the database, both the Customer and the Address are probably stored in a single table. The DDL for those two classes could look like this:

create table Customers (   Id UNIQUEIDENTIFIER not null,    CustomerNumber INT not null,    Name VARCHAR(100) not null,    Street VARCHAR(50) not null,    PostalCode VARCHAR(10) not null,    Town VARCHAR(50) not null,    Country VARCHAR(50) not null,    primary key (Id) )


Note

It felt strange writing that piece regarding the Town and Country at least. I would probably factor them out in a real situation, but that's not important for this discussion.

The Reference Persons table is missing from the DDL above, but you can probably deduce it from Figure 9-1.


Then the missing piece, which is the interesting piece here, is the mapping file.

Note

Again, please note that I never said you had to create the database table before the mapping file. The order of how you work is up to you.

If I can choose, I prefer to start with the Domain Model, then write the mapping files, and from that automatically generate the database from the mapping information. To accomplish that, you can use the following snippet:

Configuration config = new Configuration(); config.AddAssembly("ADDDP.Ordering.DomainModel"); SchemaExport se = new SchemaExport(config); se.Execute(true, true, false, true);


In my experience, I often want to add some more constraints to the database, on top of what can be described in the mapping file, and I do that by writing some custom code. A bit raw, but it solves the problem.

With that in place, I can regenerate the development database as often as I like, even before every test execution (if it's not becoming too slow).


Let's take it piece by piece. First, you need to describe the document, something like this for Customer.hbm.xml:

<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.0"     namespace="ADDDP.Ordering.DomainModel"     assembly="ADDDP.Ordering.DomainModel">


I provided the namespace and assembly tags here, so the rest of the mapping information can be expressed less verbosely because that information won't have to be repeated.

Note

If you want the metadata to be within the assembly, don't forget to set the property of the XML file to be an Embedded Resource.


Then you describe the name of the class for the Entity and the name of the table. For example, like this:

<class name="Customer" table="Customers">


Then you describe the Identity Field [Fowler PoEAA], not only the name in the class and the table (in the upcoming example the property name and the column name are the same, so the column tag isn't needed), but also the strategy that is used for creating a new value. It could look like this:

<id name="Id" access="field.camelcase-underscore"     unsaved-value="00000000-0000-0000-0000-000000000000" >     <generator  /> </id>


In this specific example, a guid is generated by NHibernate when a new instance is associated with the ISession.

Then the simple properties are mapped, such as Name, like this:

<property name="Name" type="AnsiString" length="100"     not-null="true" />


Normally NHibernate can understand what type to use for the properties by investigating the class by reflection, but in the case of strings, I provide a specific type to get VARCHAR instead of NVARCHAR if I automatically generate the schema from the mapping information. The length is also used for generating DDL from the mapping information, but because I like moving along that route, I take the extra work of adding that information.

Note

Information like this is also very useful for your custom validation.


NHibernate also makes it possible to map not only properties, but fields as well. As a matter of fact, Customer.Name isn't a property (which NHibernate considers the default and therefore doesn't have to be expressed) but a public field, so I need to add an access-tag like this to get it all correctly:

<property name="Name" access="field" type="AnsiString"     length="100" not-null="true" />


And it works for all accessor types, even private, so you are pretty free to choose a strategy. For example, you often expose something slightly different in your property get to what is stored, or you do some interception on get/set. In these cases, you need to map, for example, the private field instead.

Mapping a private field could look like this:

<property name="_customerNumber"     access="field" not-null="true" />


Assuming that you do map private fields, it's a good idea to use naming rules. This is so your query code doesn't have to be written to use the name of the private fields (for example, _customerNumber), but rather ordinary, nice property names (like CustomerNumber). It could look like this:

<property name="CustomerNumber"     access="field.camelcase-underscore" not-null="true" />


Note

Sure, there are pitfalls here (as always), but I usually find it good enough to map private fields instead of introducing separate private properties purely for the aspect of persistence. If it works, fine. If not, I can always change it when/if the need arises.

This is the topic of a heated debate, though. Many prefer to map to persistency-specific private properties instead.


Finally, (well, it could be done before the simple properties just as wellit just happened to be like this here) we need to describe how the used Value Object is mapped to the single Customers table. It could look like this in the mapping file for Customer (the Address class won't have any mapping information of its own because it's a Value Object and not an Entity):

<component name="Address" access="field">     <property name="Street" access="field.camelcase-underscore"         type="AnsiString" length="50" not-null="true" />     <property name="PostalCode"         access="field.camelcase-underscore"         type="AnsiString" length="10" not-null="true" />     <property name="Town" access="field.camelcase-underscore"         type="AnsiString" length="50" not-null="true" />     <property name="Country" access="field.camelcase-underscore"         type="AnsiString" length="50" not-null="true" /> </component>


And a list of Value Objects such as ReferencePerson could look like this:

<bag name="ReferencePersons" access="field.camelcase-underscore"     cascade="all">     <key column="OrderId" />     <composite-element >         <property name="FirstName"             access="field.camelcase-underscore" not-null="true"/>         <property name="LastName"             access="field.camelcase-underscore" not-null="true"/>     </composite-element> </bag>


Let's take another example. In earlier chapters I sketched that OrderLine should be a Value Object. If so, the mapping in the Order regarding OrderLine might look similar to what was just shown.

But it's not too much of a twist to later on find out that the OrderLine might have a list of its own, such as a list of notes. If so, that's a good reason for transforming OrderLine into an Entity instead. That is, it's transformed to an Entity for technical and infrastructural reasons rather than conceptual. We have to be pragmatic. If so, the mapping information in Order changes to this instead (and OrderLine will have mapping information of its own):

<bag name="OrderLines" access="field.camelcase_underscore"     cascade="all">     <key column="OrderId" />     <one-to-many  /> </bag>


For the sake of the example, let's also assume that we want the OrderLine to have a field pointing back to the Order (despite what I have said earlier about trying to use bidirectionality sparingly). So in the OrderLine XML file that was added when OrderLine was transformed to an Entity, there is now a many-to-one section like this:

<many-to-one     name="Order"     access="field.camelcase-underscore"          column="OrderId" />


An important point here is that you are on your own regarding the bidirectionality. So in the AddOrderLine() method of Order, it could now look like this:

//Order public void AddOrderLine(OrderLine ol) {     _orderLines.Add(ol);     ol.Order = this; }


Finally, this also means that OrderLine will have an Identity Field of its own, whether it's used or not in the Domain Model.

If you fill in the open sections in the files, we are ready to make the consumer code much more interesting.

A Tiny API Example

Let's now take a look at some small code snippets showing how consumer code could look when NHibernate is put to work carrying out some Create, Read, Update, and Delete (CRUD) operations. CRUD is often a large part of the persistence-related work of an application. The first of the snippets to look at is the C in CRUD.

CRUD-C: Create

To create a new instance (or row) in the database, you just instantiate a new Customer, set its properties, and call the methods you want. When you're ready to save it, you use the snippet we talked about before for reconnecting an ISession. Then you associate the instance with the ISession, and you then call Flush() on the ISession to store all changes.

All together, it could look like this:

//A consumer Customer c = new Customer(); c.Name = "Volvo"; _session.Reconnect(); _session.Save(c); _session.Flush(); _session.Disconnect();


In this case, I was specific and told the ISession that it should lead to an INSERT (because I called Save()). I could have called SaveOrUpdate() instead, and then NHibernate would have decided on its own whether it should be an INSERT or an UPDATE. The information used in that case is the value of the Identity Field, and it's compared to what you indicated for unsaved-value in the mapping file ("00000000-0000-0000-0000-000000000000" in the case of Guids). If the Identity Field matches the unsaved-value, it's time for an INSERT; otherwise, it would be an UPDATE.

It is also important pointing out that the INSERT is delayed and won't happen when you say Save(), but rather when you say Flush(). The reason for this is that you should be able to make many changes in the consumer and then get them all persisted together and as late as possible.

Did we succeed in writing to the database? The easiest way to check that is to read the instance back, so let's do that.

CRUD-R (One): Read One

The second example is to read one instance (row in the database) by its Identity Field. From now on, let's assume that we use the ordinary reconnect/disconnect snippet and just focus on the specific code instead.

As usual, you talk to the ISession instance. You call Load()/Get() and say what type you are looking for and its Identity Field value. It could go like this:

Customer c = (Customer)_session.Load(typeof(Customer), theId);


But beware. This didn't necessarily prove anything if you didn't close the ISession since the last Flush(), because the ISession will provide you with the instance from the Identity Map [Fowler PoEAA] when you call Load()/Get() instead of going to the database. What you can do to force a database jump (instead of opening a new ISession) is to call Evict() on the instance by saying that you don't want the Identity Map to keep track of the instance any longer. It could look like this:

_session.Evict(customer);


That was good, but what if you don't know the identity value? Perhaps you only know part of the name of the customer and want to fetch all instances that match that name pattern. This takes us over to the next example, that of read many.

CRUD-R (Many): Read Many

There are two specific languages for querying in NHibernate. But we are going to use Hibernate Query Language (HQL) for this example. It's pretty similar to SQL, yet different. In order to fetch all customers with a name that starts with "Vol", the code could look like this:

//A consumer string hql = "select from Customer where Name like 'Vol%'"; IList result = _session.CreateQuery(hql).List();


So in the result, you will get a list of customers with names that start with "Vol", just as you expected.

Normally, you wouldn't write code as static as this and would use parameterization instead. But again, I just want to show very simple code here in order to give you a quick feeling of how it could be done.

CRUD-U: Update

We found lots of instances with names starting with "Vol". We saw that we needed to make some changes to one of them. We made the changes to the properties of that instance, and we are now ready to persist the changes. Again, we can use SaveOrUpdate(), but here we know that it's a question of an UPDATE, so we use Update() like this instead (continuing from the previous snippet):

Customer c2 = (Customer) result[0]; c2.Name = "Ford"; _session.Update(c2);  //Can be skipped _session.Flush();


As a matter of fact, because we found the instance via a read, we didn't have to make the Update() call at all. The instance was already associated with the ISession and so it would have been taken care of anyway at Flush() time.

CRUD-D: Delete

One of the found instances was wrong, so it should be deleted. That could be done like this:

//A consumer Customer c3 = (Customer) result[1]; _session.Delete(c3); _session.Flush();


As you probably guessed, Delete() is delayed until Flush().

That was pretty straightforward, wasn't it?

Transactions

Before we leave the API example, I think it's important to have a look at how transactions can be controlled when NHibernate is used, because transactions are used a great deal.

If you are used to other ways of manual transaction control, it's easy to understand how it's done with NHibernate as well. What you do is grab an ITransaction instance, which you can then use to make either a Commit() or Rollback() (Commit() will by default automatically do a Flush()). The complete snippet could look like this:

//A consumer ITransaction tx = _session.BeginTransaction(); try {     tx.Commit(); } catch (Exception ex) {     tx.Rollback();     ...


Note

Suddenly I used exception handling in the code snippet. The reason is that Rollback() makes most sense then. Of course, exception handling should be used for the other snippets as well, but here in the book it would mostly be a distraction.


I think that finishes an extremely short introduction. See the NHibernate site [NHibernate] for much more information on how to get started.

We now move on to positioning the chosen example of a Persistence Framework with the structure that was presented in Chapter 8. First, let's take a look at the overall requirements.




Applying Domain-Driven Design and Patterns(c) With Examples in C# and  .NET
Applying Domain-Driven Design and Patterns: With Examples in C# and .NET
ISBN: 0321268202
EAN: 2147483647
Year: 2006
Pages: 179
Authors: Jimmy Nilsson

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net