Read uncomitted data from HasMany-relationship with NHibernate

708 views Asked by At

I have an NHibernate map which defines a HasMany-relationship on a type, i.e. a class has a list of another class.

I would like NHibernate to be able to read uncommitted data including the list resulting from the HasMany-relationship.

I have isolationlevel ReadUncomitted and I am able to write data and read it back before committing.

However, the list is always empty, unless I commit first.

Is there a way to make NHibernate populate objects with data from HasMany-relationships?

EDIT

It turns out any non-primitive types on a class are not populated by an uncommitted read unless they are actually added to the class.

For example, class Foo below has a list of Member which - in the database - are connected by Id. If I persist an instance of Foo and an instance of Member to the database using NHibernate and both are in fact related by the value of Id, then Foo will not have the expected instance of Member if I read it back uncommitted (i.e. before completing the transaction).

public class Member
{
     Guid Id{ get; set; }
}

public class Foo
{
    List<Member> MemberList{ get; set; }
}

// the mapping
public class FooMap
{
    HasMany(x => x.MemberList).KeyColumn("Id").PropertyRef("Id");
}

However, If I create an instance of Foo and an instance of Member and set the latter as a reference of the former and persist both to the database using NHibermate, then Foo will have the expected instance of Member when I read it back before completing the transaction.

If I complete the transaction then Foo will have the expected instance of Member on subsequent reads; as long as the transaction is completed it is irrelevant whether Member existed only as a database record with the correct FK to Foo or it was a reference to Foo.

Revised Question: It is possible have NHibernate populate complex members based on FK-relationships only during an uncommitted read?

2

There are 2 answers

2
Oskar Berggren On BEST ANSWER

There seems to be some general confusion here, so to clarify a few things:

  • The isolation level of a transaction only affects what changes from other transactions are visible. A transaction can always read back data it has modified itself.

  • If follows that the call to Commit() for the transaction has nothing to with whether or not the NHibernate session that owns the transaction can read back those changes or not. It always can.

  • However, NHibernate will not modify an already loaded instance. You, as the developer of the model, is responsible for making changes in loaded entities, making sure the model is still valid and consistent with your business rules, and then NHibernate will store the changes in the DB. It is not NHibernate's job to make a part of you model consistent with changes you made in another part.

  • Reading back before or after the call to Commit() doesn't matter, as NHibernate guarantees that a session will only contain at most one copy of each entity. So when you query for the object you just saved, you will just get back the same, already loaded, unmodified instance.

  • What matters is if you close the session and open a new session for your query. Then NHibernate will instantiate the object again, and it will reflect the current state of the database. The same effect can be had in the original session if you use Clear() or Evict() on the session (after having called Flush() or Commit()). These methods remove the already loaded instance from the session, so the next time you query for it, the session will create a new instance, which would then of course reflect the current state of the database, as visible from the current transaction.

8
h.alex On

When you instantiate a new object it naturally contains default values for all it's properties + whatever is set through the constructor.

This means you must query for your entity object in order to get any persistent data in it.

So, e.g. if we have these two classes

public class Customer
{
    public virtual Guid Id { get; set; }
    public virtual ICollection<Order> Orders { get; set; }
}

public class Order
{
    public virtual Guid Id { get; set; }

    public virtual string Description { get; set; }
    //todo add items
}

Then we would need such a method:

public Customer GetCustomerWithUncommitedOrders(Guid customerId)
{
    ITransaction t = null;
    Customer customer = null;

    try
    {
        t = session.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);


        customer = session.QueryOver<Customer>().Where(x => x.Id == customerId).Fetch(x => x.Orders).Eager.List();


        t.Commit();
    }
    catch (Exception ex)
    {
        //todo log

        if (t != null)
            t.Rollback();
    }
    finally
    {
        if (t != null)
            t.Dispose();
    }

    return customer;
}

We can then edit the customer, and save it in another (or the same, depending on "editing" duration) transaction. However, this probably brings up concurrency worries.