Ayende @ Rahien

It's a girl

NHibernate -

A natural id is a way to refer to a unique field of an object as a substitute of the real entity identifier. A good (and typical) example of that would be with the User entity. We have the user name and the user id, both are unique, but the user id is usually something that is generated by our application and has no relation to the a human being. In other words, user #123814 doesn’t mean anything to me, while user ‘ayende’ has a meaning to us.

There are many reasons for choosing this approach, but the most common one is that we want a small primary key, since it is duplicated to all related tables (especially important for things like user, which is associated with just about everything).

So far, it doesn’t seem really interesting, right? Why am I boring you with talking about this?

Well, it is all about the second level cache and optimizing access to it. Let us take a look at how we set it up:

<class name="User"
		 table="Users">
	<cache usage="nonstrict-read-write"/>
	
	<id name="Id">
		<generator class="identity"/>
	</id>
	
	<natural-id mutable="false">
		<property name="Username"/>
	</natural-id>
</class>

And now we are going to query it:

using (var s = sf.OpenSession())
using (var tx = s.BeginTransaction())
{
	s.CreateCriteria<User>()
		.Add(Restrictions.NaturalId()
			.Set("Username", "Ayende"))
		.SetCacheable(true)
		.UniqueResult();

	tx.Commit();
}

<natrual-id/> is used almost exclusively for documentation purposes. That means that internally the query above will be translated to:

using (var s = sf.OpenSession())
using (var tx = s.BeginTransaction())
{
	s.CreateCriteria<User>()
		.Add(Restrictions.Eq("Username", "Ayende"))
		.SetCacheable(true)
		.UniqueResult();

	tx.Commit();
}

Well, almost. There is one subtle different between the two. When querying on <natural-id/>, we also bypass the usual expiration check in the second level cache implemented by NHibernate.

That means that even if we changed the underlying table, NHibernate will not automatically expire the results in the cache for this query, it will still consider it valid, because it assumes that like primary keys, there isn’t really a reason to perform an expiration check on natural ids.

Comments

Dave Harms
06/23/2009 03:10 PM by
Dave Harms

Good to know, thanks. Any thoughts on how to handle a situation where the natural id may change? For instance, I don't allow my web site users to change their own username, but I will on request change it for them. This doesn't happen often - just a few times per year.

Do I just call sessionFactory.evict() after changing the natural id?

Carlos Cubas
06/23/2009 04:02 PM by
Carlos Cubas

I wonder if this feature could be used to improve on the flush's insert-update-delete order of operation. Knowing the entity contains a non-mutable natural id gives enough context to know that the flush should do a delete-insert-update instead for this entity.

Scott White
06/23/2009 05:24 PM by
Scott White

Will this be used to generate Equals/GetHashCode overrides in the proxies?

Ayende Rahien
06/23/2009 06:08 PM by
Ayende Rahien

Dave,

I wouldn't worry about that too much, the only effect it will have is that for the duration of the cache, you'll be able to find the user by both usernames.

Ayende Rahien
06/23/2009 06:12 PM by
Ayende Rahien

Carlos,

I am not following you

Carlos Cubas
06/23/2009 06:37 PM by
Carlos Cubas

This mostly has to do with an issue I've deal with in the past.

Consider the Entity :

User

-Id //PK

-Name //Natural ID (Unique Constraint on DB)

An error will occur if I:

Load("Carlos")

Delete("Carlos")

SaveOrUpdate("Carlos")

Flush()

As per NHibernate flush documentation, the inserts will happen first, tripping the unique constraint on Name on the already existing row on the table.

I was wondering if with the introduction of a feature such as <natural-id whether there was enough context to know that User:Carlos, should instead be handled in a Delete-Insert-Update order during the flush.

I hope this cleared it up.

Carlos Cubas
06/23/2009 06:39 PM by
Carlos Cubas

I should clarify that the SaveOrUpdate takes a new instance(new User("Carlos")) as follows.

SaveOrUpdate(new User("Carlos"));

Ayende Rahien
06/23/2009 07:06 PM by
Ayende Rahien

Oh, no, it will do nothing.

If you really care about this scenario, you have to manually place a Flush in the middle

Carlos Fortes
06/25/2009 07:04 AM by
Carlos Fortes

I would like use (custom) id generators with natural-id. It would be posible?

Ayende Rahien
06/25/2009 07:12 AM by
Ayende Rahien

What is the usage scenario you have in mind?

Carlos Fortes
06/25/2009 07:46 AM by
Carlos Fortes

For example, "year/correlativeNumber" like administrative file number. It is an ID that we can calculate like an ID, and it is a natural ID becouse user will see that ID and will use it in the bussines process.

Ayende Rahien
06/25/2009 07:51 AM by
Ayende Rahien

Isn't that why you have field initializer for?

Carlos Fortes
06/25/2009 08:18 AM by
Carlos Fortes

Yes, but what if i want use a database sequence in order to obtain the correlative number? I can create a class for that, but i see more more natural use an id generator becouse it use database access and we use nhibernate for that work.

Carlos Fortes
06/25/2009 10:59 AM by
Carlos Fortes

Yes and no. That is the use of generators for any property and for any purpose (not only generate unique id for <natural). But yes that the idea.

 Carlos Fortes
06/25/2009 11:24 AM by
Carlos Fortes

I was thinking in:

<id
<generator

<natural-id
<property

or better (use <natural-id in the same manner that <id)

<natural-id
<generator

Natural-id is the real id, but we use other (invented) id ONLY for convenience.

Carlos Fortes
06/25/2009 11:29 AM by
Carlos Fortes

Uff sorry for the last comment.

I was thinking in:

class name="AdministrativeFile"

         table="AdministrativeFiles"


    id name="Id">

    /id>

   natural-id mutable="false"

    property name="FileNumber" generator="CustomGeneratorInheritedFromHilo"/

/natural-id

or better (use <natural-id in the same manner that <id) natural-id name="FileNumber"

           generator class="CustomGeneratorInheritedFromHilo"/

/natural-id

/class

Natural-id is the real id, but we use other (invented) id ONLY for convenience.

Sebastijan Pistotnik
07/01/2009 12:16 AM by
Sebastijan Pistotnik

Scenario:

Let us assume that on class User we use soft deletable feature, so there is another property IsDeleted added. To create an unique constraint in a database, we must add additional field to Username and that is for example DateCreated field, since othewise soft deleting will not work properly. Can natural-id be composed of two properties (Username And DateCreated); composite class with equals and hash?

Sebastijan Pistotnik
07/01/2009 12:27 AM by
Sebastijan Pistotnik

Ups...sorry for the question...I found it...it seems that you can add as many properties as you need...I just wonder now what would Restrictions.NaturalId() return in this case...

Comments have been closed on this topic.