Search code examples
nhibernatequeryover

Nhibernate query after change collection


User have many Roles:

public class User
{
    private ISet<Role> _roles = new HashSet<Role>();
    public virtual ISet<Role> Roles { get { return _roles; } }
}

I add role to user (user1.Roles.Add(role1)).

Why first of these two queries does not return user1 as second?

session.QueryOver<User>()
    .JoinQueryOver<Role>(u => u.Roles)
    .Where(x => x.Id == role1.Id)
    .List();    

session.QueryOver<User>().List()
    .Where(u => u.Roles.Contains(role1));

Solution

  • The answer of this magical behaviour is hidden in Session pattern and its .Flush() operation.

    The ISession (see Chapter 2. Architecture):

    ISession (NHibernate.ISession)

    A single-threaded, short-lived object representing a conversation between the application and the persistent store. Wraps an ADO.NET connection. Factory for ITransaction. Holds a mandatory (first-level) cache of persistent objects, used when navigating the object graph or looking up objects by identifier.

    The interesting here is the first-level cache ... by identifier. What happened is that the User was updated inside of the ISession. Only in this session, because it was not synchronized with DB... it was not flushed

    The 9.6. Flush is (in a nutshell) a way how NHibernate

    From time to time the ISession will execute the SQL statements needed to synchronize the ADO.NET connection's state with the state of objects held in memory. This process, flush, occurs by default at the following points... (see more in the link)

    So, what we know now is: Session is keeping updated record of a User. DB does not, because session.Flush() was not called. We run the second query which looks like this:

    session.QueryOver<User>()
    // forces to load all rows from DB
    // these rows are converted into User instances by their ID
    // and while our updated User in memory already is... it will be used
    // instead of persisted one
    .List()
    // now we do in memory searching
    // i.e. check the User instances, which of them does have role1
    // and even our newly updated (just in memory) user is found
    .Where(u => u.Roles.Contains(role1));
    

    The second query, does really filtering on a DB side... but the synchronization was not done... no .Flush() ... User is not found.

    So, please, do read the soruces mentioned above to get more detailed understanding about: session, first-level cache and flushing

    Do call:

    session.Flush() // after adding Role1 to User
    

    immediately when adding Role to User... and both queries will return the same results