Chapter 10. Read-only entities

Important

NHibernate's treatment of read-only entities may differ from what you may have encountered elsewhere. Incorrect usage may cause unexpected results.

When an entity is read-only:

In some ways, NHibernate treats read-only entities the same as entities that are not read-only:

Even if an entity is not read-only, its collection association can be affected if it contains a read-only entity.

For details about the affect of read-only entities on different property and association types, see Section 10.2, “Read-only affect on property type”.

For details about how to make entities read-only, see Section 10.1, “Making persistent entities read-only”

NHibernate does some optimizing for read-only entities:

10.1. Making persistent entities read-only

Only persistent entities can be made read-only. Transient and detached entities must be put in persistent state before they can be made read-only.

NHibernate provides the following ways to make persistent entities read-only:

10.1.1. Entities of immutable classes

When an entity instance of an immutable class is made persistent, NHibernate automatically makes it read-only.

An entity of an immutable class can created and deleted the same as an entity of a mutable class.

NHibernate treats a persistent entity of an immutable class the same way as a read-only persistent entity of a mutable class. The only exception is that NHibernate will not allow an entity of an immutable class to be changed so it is not read-only.

10.1.2. Loading persistent entities as read-only

Note

Entities of immutable classes are automatically loaded as read-only.

To change the default behavior so NHibernate loads entity instances of mutable classes into the session and automatically makes them read-only, call:

Session.DefaultReadOnly = true;

To change the default back so entities loaded by NHibernate are not made read-only, call:

Session.DefaultReadOnly = false;

You can determine the current setting by using the property:

Session.DefaultReadOnly;

If Session.DefaultReadOnly property returns true, entities loaded by the following are automatically made read-only:

Changing this default has no effect on:

  • persistent entities already in the session when the default was changed

  • persistent entities that are refreshed via Session.Refresh(); a refreshed persistent entity will only be read-only if it was read-only before refreshing

  • persistent entities added by the application via Session.Persist(), Session.Save(), Session.Update() and Session.SaveOrUpdate()

10.1.3. Loading read-only entities from an HQL query/criteria

Note

Entities of immutable classes are automatically loaded as read-only.

If Session.DefaultReadOnly returns false (the default) when an HQL query or criteria executes, then entities and proxies of mutable classes loaded by the query will not be read-only.

You can override this behavior so that entities and proxies loaded by an HQL query or criteria are automatically made read-only.

For an HQL query, call:

Query.SetReadOnly(true);

Query.SetReadOnly(true) must be called before Query.List(), Query.UniqueResult(), or Query.Enumerable()

For an HQL criteria, call:

Criteria.SetReadOnly(true);

Criteria.SetReadOnly(true) must be called before Criteria.List(), or Criteria.UniqueResult()

Entities and proxies that exist in the session before being returned by an HQL query or criteria are not affected.

Uninitialized persistent collections returned by the query are not affected. Later, when the collection is initialized, entities loaded into the session will be read-only if Session.DefaultReadOnly returns true.

Using Query.SetReadOnly(true) or Criteria.SetReadOnly(true) works well when a single HQL query or criteria loads all the entities and initializes all the proxies and collections that the application needs to be read-only.

When it is not possible to load and initialize all necessary entities in a single query or criteria, you can temporarily change the session default to load entities as read-only before the query is executed. Then you can explicitly initialize proxies and collections before restoring the session default.

using (ISession session = factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
    session.DefaultReadOnly = true;
    Contract contract = session
        .CreateQuery("from Contract where CustomerName = 'Sherman'")
        .UniqueResult<Contract>();
    NHibernate.Initialize(contract.Plan);
    NHibernate.Initialize(contract.Variations);
    NHibernate.Initialize(contract.Notes);
    session.DefaultReadOnly = false;
    ...
    tx.Commit();
}

If Session.DefaultReadOnly returns true, then you can use Query.SetReadOnly(false) and Criteria.SetReadOnly(false) to override this session setting and load entities that are not read-only.

10.1.4. Making a persistent entity read-only

Note

Persistent entities of immutable classes are automatically made read-only.

To make a persistent entity or proxy read-only, call:

Session.SetReadOnly(entityOrProxy, true)

To change a read-only entity or proxy of a mutable class so it is no longer read-only, call:

Session.SetReadOnly(entityOrProxy, false)

Important

When a read-only entity or proxy is changed so it is no longer read-only, NHibernate assumes that the current state of the read-only entity is consistent with its database representation. If this is not true, then any non-flushed changes made before or while the entity was read-only, will be ignored.

To throw away non-flushed changes and make the persistent entity consistent with its database representation, call:

Session.Refresh(entity);

To flush changes made before or while the entity was read-only and make the database representation consistent with the current state of the persistent entity:

// evict the read-only entity so it is detached
session.Evict(entity);

// make the detached entity (with the non-flushed changes) persistent
session.Update(entity);

// now entity is no longer read-only and its changes can be flushed
s.Flush();

10.2. Read-only affect on property type

The following table summarizes how different property types are affected by making an entity read-only.

Table 10.1. Affect of read-only entity on property types

Property/Association TypeChanges flushed to DB?
Simple

(Section 10.2.1, “Simple properties”)

no*

Unidirectional one-to-one

Unidirectional many-to-one

(Section 10.2.2.1, “Unidirectional one-to-one and many-to-one”)

no*

no*

Unidirectional one-to-many

Unidirectional many-to-many

(Section 10.2.2.2, “Unidirectional one-to-many and many-to-many”)

yes

yes

Bidirectional one-to-one

(Section 10.2.3.1, “Bidirectional one-to-one”)

only if the owning entity is not read-only*

Bidirectional one-to-many/many-to-one

inverse collection

non-inverse collection

(Section 10.2.3.2, “Bidirectional one-to-many/many-to-one”)

only added/removed entities that are not read-only*

yes

Bidirectional many-to-many

(Section 10.2.3.3, “Bidirectional many-to-many”)

yes

* Behavior is different when the entity having the property/association is read-only, compared to when it is not read-only.

10.2.1. Simple properties

When a persistent object is read-only, NHibernate does not dirty-check simple properties.

NHibernate will not synchronize simple property state changes to the database. If you have automatic versioning, NHibernate will not increment the version if any simple properties change.

using (ISession session = factory.OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
    // get a contract and make it read-only
    Contract contract = session.Get<Contract>(contractId);
    session.SetReadOnly(contract, true);

    // contract.CustomerName is "Sherman"
    contract.CustomerName = "Yogi";
    tx.Commit();

    tx = session.BeginTransaction();

    contract = session.Get<Contract>(contractId);
    // contract.CustomerName is still "Sherman"
    ...
    tx.Commit();
}

10.2.2. Unidirectional associations

10.2.2.1. Unidirectional one-to-one and many-to-one

NHibernate treats unidirectional one-to-one and many-to-one associations in the same way when the owning entity is read-only.

We use the term unidirectional single-ended association when referring to functionality that is common to unidirectional one-to-one and many-to-one associations.

NHibernate does not dirty-check unidirectional single-ended associations when the owning entity is read-only.

If you change a read-only entity's reference to a unidirectional single-ended association to null, or to refer to a different entity, that change will not be flushed to the database.

Note

If an entity is of an immutable class, then its references to unidirectional single-ended associations must be assigned when that entity is first created. Because the entity is automatically made read-only, these references can not be updated.

If automatic versioning is used, NHibernate will not increment the version due to local changes to unidirectional single-ended associations.

In the following examples, Contract has a unidirectional many-to-one association with Plan. Contract cascades save and update operations to the association.

The following shows that changing a read-only entity's many-to-one association reference to null has no effect on the entity's database representation.

// get a contract with an existing plan;
// make the contract read-only and set its plan to null
using (var tx = session.BeginTransaction())
{
    Contract contract = session.Get<Contract>(contractId);
    session.SetReadOnly(contract, true);
    contract.Plan = null;
    tx.Commit();
}

// get the same contract
using (var tx = session.BeginTransaction())
{
    Contract contract = session.Get<Contract>(contractId);

    // contract.Plan still refers to the original plan;

    tx.Commit();
}
session.Close();

The following shows that, even though an update to a read-only entity's many-to-one association has no affect on the entity's database representation, flush still cascades the save-update operation to the locally changed association.

// get a contract with an existing plan;
// make the contract read-only and change to a new plan
Contract contract;
Plan newPlan;
using (var tx = session.BeginTransaction())
{
    contract = session.Get<Contract>(contractId);
    session.SetReadOnly(contract, true);
    newPlan = new Plan("new plan");
    contract.Plan = newPlan;
    tx.Commit();
}

// get the same contract
using (var tx = session.BeginTransaction())
{
    contract = session.Get<Contract>(contractId);
    newPlan = session.Get<Plan>(newPlan.Id);

    // contract.Plan still refers to the original plan;
    // newPlan is non-null because it was persisted when
    // the previous transaction was committed;

    tx.Commit();
}
session.Close();

10.2.2.2. Unidirectional one-to-many and many-to-many

NHibernate treats unidirectional one-to-many and many-to-many associations owned by a read-only entity the same as when owned by an entity that is not read-only.

NHibernate dirty-checks unidirectional one-to-many and many-to-many associations;

The collection can contain entities that are read-only, as well as entities that are not read-only.

Entities can be added and removed from the collection; changes are flushed to the database.

If automatic versioning is used, NHibernate will update the version due to changes in the collection if they dirty the owning entity.

10.2.3. Bidirectional associations

10.2.3.1. Bidirectional one-to-one

If a read-only entity owns a bidirectional one-to-one association:

  • NHibernate does not dirty-check the association.

  • updates that change the association reference to null or to refer to a different entity will not be flushed to the database.

  • If automatic versioning is used, NHibernate will not increment the version due to local changes to the association.

Note

If an entity is of an immutable class, and it owns a bidirectional one-to-one association, then its reference must be assigned when that entity is first created. Because the entity is automatically made read-only, these references cannot be updated.

When the owner is not read-only, NHibernate treats an association with a read-only entity the same as when the association is with an entity that is not read-only.

10.2.3.2. Bidirectional one-to-many/many-to-one

A read-only entity has no impact on a bidirectional one-to-many/many-to-one association if:

  • the read-only entity is on the one-to-many side using an inverse collection;

  • the read-only entity is on the one-to-many side using a non-inverse collection;

  • the one-to-many side uses a non-inverse collection that contains the read-only entity

When the one-to-many side uses an inverse collection:

  • a read-only entity can only be added to the collection when it is created;

  • a read-only entity can only be removed from the collection by an orphan delete or by explicitly deleting the entity.

10.2.3.3. Bidirectional many-to-many

NHibernate treats bidirectional many-to-many associations owned by a read-only entity the same as when owned by an entity that is not read-only.

NHibernate dirty-checks bidirectional many-to-many associations.

The collection on either side of the association can contain entities that are read-only, as well as entities that are not read-only.

Entities are added and removed from both sides of the collection; changes are flushed to the database.

If automatic versioning is used, NHibernate will update the version due to changes in both sides of the collection if they dirty the entity owning the respective collections.