Introduction to NHibernate, pt. 6

Last time we looked at mapping value types and user defined types. This time I want to look at sessions. Understanding sessions is one of the challenges for any developers approaching ORMs for the first time, because they can represent a new way of thinking about interaction with the Db.

Normal
0

false
false
false

EN-GB
X-NONE
X-NONE

MicrosoftInternetExplorer4

st1\:*{behavior:url(#ieooui) }

<!–
/* Font Definitions */
@font-face
{font-family:Wingdings;
panose-1:5 0 0 0 0 0 0 0 0 0;
mso-font-charset:2;
mso-generic-font-family:auto;
mso-font-pitch:variable;
mso-font-signature:0 268435456 0 0 -2147483648 0;}
@font-face
{font-family:”Cambria Math”;
panose-1:2 4 5 3 5 4 6 3 2 4;
mso-font-charset:0;
mso-generic-font-family:roman;
mso-font-pitch:variable;
mso-font-signature:-1610611985 1107304683 0 0 159 0;}
@font-face
{font-family:Calibri;
panose-1:2 15 5 2 2 2 4 3 2 4;
mso-font-charset:0;
mso-generic-font-family:swiss;
mso-font-pitch:variable;
mso-font-signature:-1610611985 1073750139 0 0 159 0;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-update:auto;
mso-style-unhide:no;
mso-style-qformat:yes;
mso-style-parent:”";
mso-margin-top-alt:auto;
margin-right:0cm;
mso-margin-bottom-alt:auto;
margin-left:0cm;
mso-pagination:widow-orphan;
mso-outline-level:3;
font-size:10.0pt;
mso-bidi-font-size:12.0pt;
font-family:”Calibri”,”sans-serif”;
mso-fareast-font-family:”Times New Roman”;
mso-bidi-font-family:”Times New Roman”;
mso-bidi-font-weight:bold;}
h3
{mso-style-update:auto;
mso-style-unhide:no;
mso-style-qformat:yes;
mso-style-link:”Heading 3 Char”;
mso-style-next:Normal;
mso-margin-top-alt:auto;
margin-right:0cm;
mso-margin-bottom-alt:auto;
margin-left:0cm;
mso-pagination:widow-orphan;
page-break-after:avoid;
mso-outline-level:3;
font-size:11.0pt;
mso-bidi-font-size:13.0pt;
font-family:”Arial”,”sans-serif”;
mso-ansi-language:EN-US;
mso-fareast-language:EN-US;
mso-bidi-font-weight:normal;}
h4
{mso-style-update:auto;
mso-style-unhide:no;
mso-style-qformat:yes;
mso-style-link:”Heading 4 Char”;
mso-style-next:Normal;
mso-margin-top-alt:auto;
margin-right:0cm;
mso-margin-bottom-alt:auto;
margin-left:0cm;
mso-pagination:widow-orphan;
page-break-after:avoid;
mso-outline-level:4;
font-size:10.0pt;
mso-bidi-font-size:14.0pt;
font-family:”Arial”,”sans-serif”;
mso-bidi-font-family:”Times New Roman”;
mso-ansi-language:EN-US;
mso-fareast-language:EN-US;
mso-bidi-font-weight:normal;
text-decoration:underline;
text-underline:single;}
span.Heading3Char
{mso-style-name:”Heading 3 Char”;
mso-style-unhide:no;
mso-style-locked:yes;
mso-style-link:”Heading 3″;
mso-ansi-font-size:11.0pt;
mso-bidi-font-size:13.0pt;
font-family:”Arial”,”sans-serif”;
mso-ascii-font-family:Arial;
mso-hansi-font-family:Arial;
mso-bidi-font-family:Arial;
mso-ansi-language:EN-US;
mso-fareast-language:EN-US;
font-weight:bold;
mso-bidi-font-weight:normal;}
span.Heading4Char
{mso-style-name:”Heading 4 Char”;
mso-style-unhide:no;
mso-style-locked:yes;
mso-style-link:”Heading 4″;
mso-bidi-font-size:14.0pt;
font-family:”Arial”,”sans-serif”;
mso-ascii-font-family:Arial;
mso-hansi-font-family:Arial;
mso-ansi-language:EN-US;
mso-fareast-language:EN-US;
font-weight:bold;
mso-bidi-font-weight:normal;
text-decoration:underline;
text-underline:single;}
.MsoChpDefault
{mso-style-type:export-only;
mso-default-props:yes;
font-size:10.0pt;
mso-ansi-font-size:10.0pt;
mso-bidi-font-size:10.0pt;}
@page Section1
{size:612.0pt 792.0pt;
margin:72.0pt 72.0pt 72.0pt 72.0pt;
mso-header-margin:36.0pt;
mso-footer-margin:36.0pt;
mso-paper-source:0;}
div.Section1
{page:Section1;}
/* List Definitions */
@list l0
{mso-list-id:313804499;
mso-list-type:hybrid;
mso-list-template-ids:-1442514464 67698689 67698691 67698693 67698689 67698691 67698693 67698689 67698691 67698693;}
@list l0:level1
{mso-level-number-format:bullet;
mso-level-text:;
mso-level-tab-stop:none;
mso-level-number-position:left;
text-indent:-18.0pt;
font-family:Symbol;}
@list l0:level2
{mso-level-number-format:bullet;
mso-level-text:o;
mso-level-tab-stop:none;
mso-level-number-position:left;
text-indent:-18.0pt;
font-family:”Courier New”;}
@list l1
{mso-list-id:842478050;
mso-list-type:hybrid;
mso-list-template-ids:-509051268 67698689 67698691 67698693 67698689 67698691 67698693 67698689 67698691 67698693;}
@list l1:level1
{mso-level-number-format:bullet;
mso-level-text:;
mso-level-tab-stop:none;
mso-level-number-position:left;
text-indent:-18.0pt;
font-family:Symbol;}
@list l1:level2
{mso-level-number-format:bullet;
mso-level-text:o;
mso-level-tab-stop:none;
mso-level-number-position:left;
text-indent:-18.0pt;
font-family:”Courier New”;}
@list l1:level3
{mso-level-number-format:bullet;
mso-level-text:;
mso-level-tab-stop:none;
mso-level-number-position:left;
text-indent:-18.0pt;
font-family:Wingdings;}
–>

/* Style Definitions */
table.MsoNormalTable
{mso-style-name:”Table Normal”;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-qformat:yes;
mso-style-parent:”";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.0pt;
font-family:”Times New Roman”,”serif”;}

Working with Sessions

We need to use a session when we work with NHibernate. We
will talk later about managing entity lifetime, but the short version is that
you should avoid accessing an entity outside of the context of the session that
loaded it.

We obtain sessions from the session factory. The session
factory is an expensive resource, you want to create it once at application
startup. The factory gives you instances of sessions, which are cheap and you
should create and destroy as your business transactions require (see transient
and persistent entities below).

Entity Lifetime Management

It is important to understand the lifetime issues around
entities when working with an ORM. Clarity here helps prevent many of the
issues people have when working within an ORM.

Identity Map

The identity map is a cache of all of the entities loaded
from the Db during the session. We need an identity map because we want to
ensure that if we have made changes to an entity within the session i.e.
updated some of its data, new queries against the Db that returns a set of
entities keep our changes. As a corollary to this when working with an identity
map, we always get the same instance of an entity within a session, and they
can be compared by reference. Note that the identity map also functions as a
first-level cache, because it has our entities loaded. However, we will only go
to the first level cache in preference to querying the Db when using Get or Load to retrieve our entities. This is because only when we issue a query, through HQL for example, we do not know what entities will be returned, so it is only after the query is executed that we can look into the cache to se if we have that entity already, and return the copy there.

Unit of Work

NHibernate stores the old and new values of entities within
the identity map. The unit of work also stores new entities that have been
added with Save and are waiting to be flushed to the Db. NHibernate flushes
these changes to the Db both on demand and automatically. Automatic flushing happens when you perform
certain operations. For example if you are about to query NHIbernate will flush to make sure that your query results will be consistent with the modified data. You can
also explicitly flush a session either by calling Flush() on the session or by
using a transaction. The principle here is that you want to optimize when updates occur, instead of them happening piecemeal.

So when does NHibernate Flush?

  •  
    • When you explicitly call
      Flush.
    • When you commit a
      transaction.
    • Before some Find or
      Enumerable method calls
    • When an object using
      native ID generation is saved, the entity will be inserted
    • Whe  you close a Session

We can set the session FlushMode to restrict those to Commit
or Flush if we need to be explicit about when a flush occurs. One common reason for setting the FlushMode to Commit is the issue of what happens when an exception is thrown. It is common to have a using statement control your Session, so that it is closed when and flushed automatically. The problem becomes that if you throw an exception, the Dispose still runs as you exit the implicit try..finally block created by the using statement, flushing and pending writes. Given that an exception was thrown this might result in an incosistent or undesired state. Setting the session FlushMode to Commit and always using a transaction helps prevent this: the changes don’t get persisted until you commit. The side-affect to watch for is the issue that until your changes are flushed, queries will not report on any pending changes.

Transient and Persistent Entities

We need to distinguish between transient and persistent entities.
A persistent entity is one that the session knows about. The session knows
about an entity that has been loaded through it, using get, load, or one of the
query APIs. A transient entity is one that the session does not know about i.e.
has been created with new in the application. A transient entity is made
persistent by calling Save() on the session passing the new entity as a
parameter, or by adding it to the graph of an existing persistent entity. In
the latter case we will persist the new entity if we have set the cascade
options in the mapping to pickup children.

 We need to remember that an entity has its changes tracked
by the unit of work held by the session which loads it. In addition, if we have
lazy loaded associations on an entity, those will be loaded via a proxy that
calls out to the original session. So we should not access an entity outside
the scope of a session. The model in NHibernate is:

  • Open Session
    • Begin a transaction on
      the session

      • Retrieve any required
        entities
      • Save any transient
        entities or add to graph
      • Make any updates to the
        graph required
    • Commit the transaction
  • Close the session

If we have a detached entity, one that we loaded via a
transaction that is no longer extant (remember we don’t want any lazy loaded
associations on it), because we have loaded it within a different context, then
we can re-attach it to a session using Update(). Update tells the session that
the entity it thinks is transient, because it is not in its identity map, is
actually persistent. In that case NHibernate adds it to its list of persistent
entities.

NHibernate also supports SaveOrUpdate() which is useful if
are not sure if the entity is detached or transient. In this case it uses the
unsaved-value attribute of the primary key mapping to determine whether or not
we have a transient entity or a detached one.

A common early mistake is to assume that the Update method needs to be called to amend a persitent (i.e. existing object). In fact you do not need to explicilty ask for changes to persistent objects to be saved. NHibernate looks in the Unit of Work to figure out which objects are ‘dirty’ and flushes any changes to them.

Although there is support for detached entities, prefer to
work within the context of a session where possible as it prevents issues with
needing to fully materialize the object graph.

If we have a transient object which is in the reachable from the graph of a persistent object, then we do not need to explicitly save it. NHibernate will pick up the transient object and save it for us when it saves the persistent object provided that we have set the association between the persistent object and the transient one to cascade saves or updates. This works well with an aggregate strategy where your repository laods and saves aggregates (via the aggregate root) as you only ever need to save the root object to persist the graph.

CRUD

Insert, and Retrieve

 Inserting a new object is straightforward. Create it, and
make it persistent with Save. We use flush here to persist it instead of using
a transaction:

          Company
company = new Company();

         
company.Name = companyName;

          Customer customer = new Customer();

         
customer.FirstName = “Neil”;

         
customer.Surname = “Jordan”;

         
customer.DateOfBirth = new DateTime(1950, 2, 25);

         
customer.Company = company;

 

       
company.Customers.Add(customer);

        using
(ISession session = sessionFactory.OpenSession())

            {

               
session.Save(company);

               
session.Flush();

            }

 

Once we have saved our entity we can retrieve it. If we do
not know the id of the entity then we need to use one of NHibernate’s query
languages, HQL or ICriteria to find it.

HQL is a SQL like query language that works against the
domain model instead of the relational model. Queries have the form “from
Customer as cust where cust.FirstName = :name”. “:name” is a
named parameter, you can also use “?”, but then you have to refer to
parameters by position when setting them, so by name is usually more readable.

The Find method on session has been deprecated, so use the
CreateQuery on the Session to create an IQuery, set your parameters and
retrieve matching results.

            using
(ISession session = sessionFactory.OpenSession())

            {

                IQuery
query = session.CreateQuery(“from Company org where Name = :name”);

               
query.SetString(“name”, companyName);

               
IList<Company> companies = query.List<Company>();

                if
(companies.Count > 0)

                {

                   
Console.Out.WriteLine(companies[0].Name);

                   
foreach (Customer customer in companies[0].Customers)

                    {

                       
Console.Out.WriteLine();

                       
Console.Out.Write(“First Name: “);

                       
Console.Out.WriteLine(customer.FirstName);

                       
Console.Out.Write(“Surname: “);

                       
Console.Out.WriteLine(customer.Surname);

                    }

                }

                return
companies[0].Id;

            }

In addition NHibernate also provides the ICriteria API

If we know the Id we can use Get or Load. The primary
difference is that Get returns null if the entity cannot be found, whereas Load
throws an exception. However, also note that if the object exists as a proxy in
the session’s identity map then Load will return that proxy, Get will always
retrieve the entity.

            using
(ISession session = sessionFactory.OpenSession())

            {

               
Company company = session.Load<Company>(companyId);

               
Console.Out.WriteLine();

               
Console.Out.Write(“Company Name: “);

               
Console.Out.WriteLine(company.Name);

            }

 

Delete and Update

Updating an entity is straightforward, any entity that is in
the identity map that we then update will have those changes persisted when
NHibernate flushes to the Db.

            string
companyName = string.Empty;

            using (ISession session =
sessionFactory.OpenSession())

            {

               
Company company = session.Load<Company>(companyId);

               
companyName = company.Name;

 

               
ITransaction trans = session.BeginTransaction();

                Customer newCustomer = new
Customer();

               
newCustomer.FirstName = “Angela”;

               
newCustomer.Surname = “Carter”;

               
newCustomer.DateOfBirth = new DateTime(1940, 5, 7);

               
newCustomer.Company = company;

               
trans.Commit();

            }

 

Deleting an existing company is straightforward too, we
simply call the Delete method of the Session to remove it.

            using
(ISession session = sessionFactory.OpenSession())

            {

                session.Delete(“from Company
org where Id = :id”, companyId, NHibernate.NHibernateUtil.Int32);

               
session.Flush();

            }

 How long to keep the session around?

Sessions are lightweight resources and designed to
be ‘kept around’ as long as you need them.  For example, the session
only opens and closes a Db Connection when it flushes changes in the unit of
work to the Db. A session is also easy to create, so you should create them as
you need them.  By contrast the SessionFactory from which we get our sessions is a heavyweight object. It does all the work to generate code from the mappings, and generally you want to create this once, at application startup. The usual pattern is to create a session for each
business transaction, for which you may be collating changes in a unit of work.
On the web, it is common to use Http request affinity for a session, opening  a session as the request comes in and closing it once you have returned the response.


About Ian Cooper

Ian Cooper has over 18 years of experience delivering Microsoft platform solutions in government, healthcare, and finance. During that time he has worked for the DTi, Reuters, Sungard, Misys and Beazley delivering everything from bespoke enterpise solutions to 'shrink-wrapped' products to thousands of customers. Ian is a passionate exponent of the benefits of OO and Agile. He is test-infected and contagious. When he is not writing C# code he is also the and founder of the London .NET user group. http://www.dnug.org.uk
This entry was posted in NHibernate, ORM. Bookmark the permalink. Follow any comments here with the RSS feed for this post.