NHibernate - <natural-id/>
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
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?
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.
Will this be used to generate Equals/GetHashCode overrides in the proxies?
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.
Carlos,
I am not following you
Scott,
No, it will not.
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.
I should clarify that the SaveOrUpdate takes a new instance(new User("Carlos")) as follows.
SaveOrUpdate(new User("Carlos"));
Oh, no, it will do nothing.
If you really care about this scenario, you have to manually place a Flush in the middle
I would like use (custom) id generators with natural-id. It would be posible?
What is the usage scenario you have in mind?
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.
Isn't that why you have field initializer for?
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.
You seems to be looking for this:
http://nhjira.koah.net/browse/NH-1852
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.
I was thinking in:
<class
table="AdministrativeFiles">
<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.
Uff sorry for the last comment.
I was thinking in:
or better (use <natural-id in the same manner that <id) natural-id name="FileNumber"
/natural-id
/class
Natural-id is the real id, but we use other (invented) id ONLY for convenience.
Yes, as I said
NH-1852
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?
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