Ayende @ Rahien

My name is Oren Eini
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:


+972 52-548-6969

, @ Q c

Posts: 6,026 | Comments: 44,842

filter by tags archive

NHibernate - <natural-id/>

time to read 3 min | 415 words

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"
	<cache usage="nonstrict-read-write"/>
	<id name="Id">
		<generator class="identity"/>
	<natural-id mutable="false">
		<property name="Username"/>

And now we are going to query it:

using (var s = sf.OpenSession())
using (var tx = s.BeginTransaction())
			.Set("Username", "Ayende"))


<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())
		.Add(Restrictions.Eq("Username", "Ayende"))


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.


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

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

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

Ayende Rahien


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


I am not following you

Carlos Cubas

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

Consider the Entity :


-Id //PK

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

An error will occur if I:





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

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

SaveOrUpdate(new User("Carlos"));

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

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

Ayende Rahien

What is the usage scenario you have in mind?

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

Isn't that why you have field initializer for?

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

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

I was thinking in:



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


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

Carlos Fortes

Uff sorry for the last comment.

I was thinking in:

class name="AdministrativeFile"


    id name="Id">


   natural-id mutable="false"

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


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

           generator class="CustomGeneratorInheritedFromHilo"/



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

Sebastijan Pistotnik


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

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...

Comment preview

Comments have been closed on this topic.


No future posts left, oh my!


  1. Technical observations from my wife (3):
    13 Nov 2015 - Production issues
  2. Production postmortem (13):
    13 Nov 2015 - The case of the “it is slow on that machine (only)”
  3. Speaking (5):
    09 Nov 2015 - Community talk in Kiev, Ukraine–What does it take to be a good developer
  4. Find the bug (5):
    11 Sep 2015 - The concurrent memory buster
  5. Buffer allocation strategies (3):
    09 Sep 2015 - Bad usage patterns
View all series


Main feed Feed Stats
Comments feed   Comments Feed Stats