By treating entities as read-only, we allow NHibernate to skip the memory and resource intensive dirty checking, which determines how and if an entity should be updated in the database. In Chapter 2, Models and Mappings, we learned how to configure the read-only behavior in a mapping. Here we'll see how the same thing can be accomplished programmatically, at runtime.
Complete the Getting Ready instructions at the beginning of Chapter 4, Queries.
ReadOnly
to the QueryRecipes
project.Recipe
in the folder:using NH4CookbookHelpers.Queries; using NH4CookbookHelpers.Queries.Model; using NHibernate; namespace QueryRecipes.ReadOnly { public class Recipe : QueryRecipe { private bool _readOnly=true; protected override void Run(ISessionFactory sessionFactory) { RunWithReadOnlySession(sessionFactory); RunWithQuery(sessionFactory); RunWithSetReadOnly(sessionFactory); } private void RunWithReadOnlySession(ISessionFactory sessionFactory) { using (var session = sessionFactory.OpenSession()) { session.DefaultReadOnly = _readOnly; using (var tx = session.BeginTransaction()) { var movie = session.Get<Movie>(1); movie.Director = "Updated in session"; tx.Commit(); } } } private void RunWithQuery(ISessionFactory sessionFactory) { using (var session = sessionFactory.OpenSession()) { using (var tx = session.BeginTransaction()) { var query = session.QueryOver<Movie>() .Where(x => x.Id == 1); if (_readOnly) { query.ReadOnly(); } var movie=query.SingleOrDefault(); movie.Director = "Updated in query"; tx.Commit(); } } } private void RunWithSetReadOnly(ISessionFactory sessionFactory) { using (var session = sessionFactory.OpenSession()) { using (var tx = session.BeginTransaction()) { var movie = session.Get<Movie>(1); session.SetReadOnly(movie, _readOnly); movie.Director = "Updated with SetReadOnly"; tx.Commit(); } } } } }
ReadOnly
recipe.We have already covered how to set a specific class as read-only in the mapping. In this recipe, we used three different techniques to configure the same thing at runtime.
In the RunWithReadOnlySession
method, we configure the session using the DefaultReadOnly
property. This means that any entity that is loaded from the database into the session, after the property and set to true
, will be marked as read-only. Toggling the value will never affect any entities already loaded.
We can also configure a specific query to load the entities with the read-only flag set. For an HQL query or a native SQL query, in other words a query implementing IQuery
, this is done using the SetReadOnly(bool)
method. The same method is also available on CriteriaQueries
.
SetReadOnly
will only affect the entities that are directly loaded by the query. Lazy loaded entities are not affected, but will respect the session's DefaultReadOnly
setting. It's therefore sometimes better to just set DefaultReadOnly
to true
before a query is run and reset it to false
once all the entities have been loaded.
It's also worth noting that specifying SetReadOnly
on a query will override the DefaultReadOnly
setting, making it possible to force query loaded entities to be writable (non read-only) even if DefaultReadOnly
is true.
Our recipe uses the QueryOver
syntax, which instead of SetReadOnly
has a ReadOnly
method, which can only set the value to true
. Currently, the LINQ provider doesn't support ReadOnly
, although such a feature is scheduled for a future release.
The most granular way to use read-only entities is to specify it on a specific entity. By calling session.SetReadOnly(entity, bool)
, we can cause a writable entity to become read-only or a read-only entity to become writable. In the latter case, NHibernate consider the current state of the entity to be the baseline. Any changes performed before setting the entity the writable will be ignored in the updates.
A read-only entity may not be quite as read-only as expected. For further details, read the How it works… section in Chapter 2, Models and Mappings.
One more way to avoid dirty checks and update is by setting session.FlushMode = FlushMode.Never
. We tell NHibernate that we never want any changes flushed to the database. Saving a new entity, with a database generated POID, will still call the database, but other than that no updates, deletes, or inserts will be performed, unless session.Flush()
is explicitly called.
18.119.106.237