1

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?

hlintrup
  • 169
  • 1
  • 13

2 Answers2

1

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.

Oskar Berggren
  • 5,583
  • 1
  • 19
  • 36
  • This question is [linked with this](http://stackoverflow.com/questions/18128666/nhibernate-populate-list-with-uncommitted-data-related-only-by-fk-in-database) which @oskar has also answered. He suggests using Flush(), Evict() or Clear() to have NHibernate update the already loaded object. – hlintrup Aug 12 '13 at 08:27
  • 1
    Flush() was also suggested by @h.alex; however, only Evict(obj) or Clear() has an effect. – hlintrup Aug 12 '13 at 09:51
0

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.

h.alex
  • 902
  • 1
  • 8
  • 31
  • What I want is to be able to `SaveOrUpdate` a `Customer`-object with some `Orders` in it and read the entire thing back without committing. The issue right now is that `Id` will be read back correctly after `SaveOrUpdate` without a commit, but the list is empty. Do you have any suggestions for that behavior? – hlintrup Jul 30 '13 at 08:35
  • Can you post some code? well, after you saveorupdate() you can call flush() on session object. this should/could update the customer. – h.alex Jul 30 '13 at 08:51
  • `Flush` has no effect, only `Commit` does. The map looks like this: `HasMany(x => x.PspList).KeyColumn("PayExAgreementRef").Not.LazyLoad().Cascade.DeleteOrphan().Inverse();` If I use `(nolock)` in management studio I can see that all data is in db, but it is not resolved by NHibernate. – hlintrup Jul 30 '13 at 10:28
  • Have you tried querying for the Customer, after you call SaveOrUpdate()? At least you can use the updated Id:) And, you are using the read uncommitted scope for the transaction? – h.alex Jul 30 '13 at 10:42
  • I have a base class repository which utilizes `ISession.SaveOrUpdate` and I do get data back in all cases, except the list is always `null` unless I completely dispose of the `ISession` and use a new instance to read the data. I have a unit-of-work implementation which handles a `TransactionScope` set up with `IsolationLevel.ReadCommitted`. I simply can't get my head around it; thank you for taking the time to give suggestions – hlintrup Jul 30 '13 at 11:56
  • Can database schema affect NHibernates ability to read uncommitted data? – hlintrup Jul 30 '13 at 12:18
  • "I have a unit-of-work implementation which handles a TransactionScope set up with IsolationLevel.ReadCommitted" - missing an "Un" possibly? :) No, I do not think the Schema can have any effect, those are two different concerns. – h.alex Jul 30 '13 at 12:27
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/34448/discussion-between-hlintrup-and-h-alex) – hlintrup Jul 30 '13 at 12:45