Development With A Dot

Blog on development in general, and specifically on .NET



My Friends

My Links

Permanent Posts

Portuguese Communities

July 2011 - Posts

NHibernate Pitfallls: Composite Keys

This is part of a series of posts about NHibernate Pitfalls. See the entire collection here.

At times, there may be a need for entities with composite identifiers, that is, entities that map to tables which have composite primary keys, composed of many columns. The columns that make up this primary key are usually foreign keys to another tables.

NHibernate fully supports this scenario, but you should make sure that you still define a single-property identifier for the entity. Some of NHibernate’s functionalities require so, although it is not strictly necessary if you don’t use them. These include loading by id (methods ISession.Get<T>() and ISession.Load<T>() and detaching an entity from a session (method ISession.Evict()). In particular, Evict() will fail saying that the session does not contain the entity, even though ISession.Contains() tells you so.

In order to have a single-property identifier, you should start by defining a new class, a component, not an entity, which may be defined inside your entity:

   1: [Serializable]
   2: public class UserProduct
   3: {
   4:     [Serializable]
   5:     public class UserProductId
   6:     {
   7:         public User User { get; set; }
   8:         public Product Product { get; set; }
  10:         public override Boolean Equals(Object obj)
  11:         {
  12:             if (obj as UserProduct == null)
  13:             {
  14:                 return(false);
  15:             }
  17:             if (Object.ReferenceEquals(this, obj) == true)
  18:             {
  19:                 return(true);
  20:             }
  22:             UserProductId other = obj as UserProductId;
  24:             if (Object.Equals(this.User, other.User) == false)
  25:             {
  26:                 return(false);
  27:             ]            
  29:             if (Object.Equals(this.Product, other.Product) == false)
  30:             {
  31:                 return(false);
  32:             }
  34:             return(true);
  35:         }
  37:         public override Int32 GetHashCode()
  38:         {
  39:             Int32 hash = 0;
  41:             hash += (this.User != null) ? this.User.GetHashCode() : 0;
  42:             hash += 1000 * (this.Product != null) ? this.Product.GetHashCode() : 0;
  44:             return(hash);
  45:         }
  46:     }
  48:     public UserProduct()
  49:     {
  50:         this.Id = new UserProductId();
  51:     }
  53:     public UserProductId Id { get; set; }
  55:     //...
  56: }

And then define the mapping for the entity (the new class does not need one):

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <hibernate-mapping default-lazy="false" namespace="Domain" assembly="Domain" xmlns="urn:nhibernate-mapping-2.2">
   3:     <class name="UserProduct" lazy="false" table="`USER_PRODUCT`">
   4:         <composite-id name="Id">
   5:             <key-many-to-one name="User" column="`USER_ID`" />
   6:             <key-many-to-one name="Product" column="`PRODUCT_ID`" />
   7:         </composite-id>
   8:         <!-- ... -->
   9:     </class>
  10: </hibernate>

Bookmark and Share

NHibernate Pitfalls: Large Strings

This is part of a series of posts about NHibernate Pitfalls. See the entire collection here.

If you want to map large string columns (SQL Server’s VARCHAR(max)/NVARCHAR(max), Oracle’s CLOB/NCLOB) to NHibernate properties, you must specify the StringClobType. This is because the default mapping type for string columns, StringType, does not support large strings (>4000 characters).

If you also want to generate the database from the model, you should also specify the native column type. For SQL Server:

   1: <property column="`LARGE_STRING`" name="LargeString" type="StringClob" sql-type="NVARCHAR(max)" />

And for Oracle:

   1: <property column="`LARGE_STRING`" name="LargeString" type="StringClob" sql-type="NCLOB" />

Of course, the .NET class remains the same, whatever the underlying NHibernate mapping type is:

   1: public virtual String LargeString { get; set; }

Bookmark and Share

NHibernate Pitfalls: Batching and Native Id Generators

This is part of a series of posts about NHibernate Pitfalls. See the entire collection here.

You may be aware of batching in NHibernate. In a nutshell, it allows, if properly configured, NHibernate to issue multiple INSERT statements in a single command sent to the database, instead of sending one command for each insertion.

It happens that you cannot use batching with native (SQL Server and MySQL’s IDENTITY and Oracle and PostgreSQL’s SEQUENCEs) id generators. Why? Because these id generators don’t know the new id to be inserted beforehand and so have to issue a SELECT in order to find it out, after each each new row is inserted.

In order to use this performance enhancement, you’ll have to turn to other id generation strategy.

Bookmark and Share

NHibernate Pitfalls: HQL, LINQ and Eager Loading

This is part of a series of posts about NHibernate Pitfalls. See the entire collection here.

With the HQL and LINQ query providers, NHibernate ignores the loading strategies specified in the mappings and treats every related entity (many to one, one to one) and collection (one to many, many to many) as lazy. If you want to eagerly load them, you have to tell NHibernate to do it explicitly.

In HQL, it is done using the fetch instruction, together with a join clause:

   1: var orders = session.CreateQuery("from Order o join fetch o.Details where = :id").SetParameter("id", 100).UniqueResult<Order>();
In LINQ, there is the Fetch, ThenFetch, FetchMany and ThenFetchMany operators:

   1: var orders = session.Query<Order>().Fetch(x => x.Details).Where(x => x.Id == 100).Single();

This is particularly important when using stateless sessions because these do not support lazy loading, so everything must be loaded at the same time.

The Get/Load, Criteria and QueryOver methods do not suffer from this problem.

Bookmark and Share

NHibernate Pitfalls: Table Per Concrete Type and Native Id Generators

This is part of a series of posts about NHibernate Pitfalls. See the entire collection here.

If you use the Table Per Concrete Type inheritance mapping strategy (<union-subclass/>) you must be aware that the ids you are going to use on each table must always be different, never equal; that is, there cannot be any records on each of the inheritance tables with the same id. The reason is quite easy to understand: if you query NHibernate by the common base class, it must perform a UNION of all the inheritance tables, and will only return the single record that has the desired id (if any). If two records have the same id, NHibernate will return an error:

   1: Animal animal = session.Get<Animal>(100);    //is it a Dog or a Cat?

The solutions are:

  • Using an id generator strategy that guarantees that ids are different all the time (the HiLo or GUID ones are good candidates);
  • Using the same sequence generator for all the inheritance tables (in DB engines that support sequences, such as Oracle and PostgreSQL).

You can never use SQL Server and MySQL IDENTITY for this.

Bookmark and Share

NHibernate Pitfalls: Stateless Sessions

This is part of a series of posts about NHibernate Pitfalls. See the entire collection here.

Stateless sessions are a good thing: they offer a simplified API for performing updates, deletes and inserts, which does not require that the entities are known by the session beforehand and they can also offer some performance benefits. However, this comes at a cost.

It happens that stateless sessions do not support some of NHibernate’s features that we’ve become accustomed to:

  • Interceptors: no way to use them;
  • Events: never fired;
  • Cascades: never happen;
  • Dirty checking: not performed;
  • Second-level cache: not used;
  • Lazy loading: not supported.

So, if you need any of these, don’t use stateless sessions.

Bookmark and Share

More Posts