views:

603

answers:

3

When using Rhino Commons UnitOfWork (in a UnitOfWorkApplication for ASP-MVC), I like to use the Rhino Repository static class to save entities like this:

Repository<Car>.Save(new Car(Id = 1));

I find that I can then get the entity out immediately after that call using:

Car car = Repository<Car>.Get(1);

This works fine. However, when I use the NHibernate Linq provider on Rhino UnitOfWork like this:

var allCars = (from rep in UnitOfWork.CurrentSession.Linq<Car>()
               select rep).ToList();

I get an empty list. It seems I have to call UnitOfWork.Current.Flush() before I can get the car out like this. I don't understand why, given that behind the scenes I assume that both retrieval methods are querying the same session / unit of work. Does this mean that you should call UnitOfWork.Current.Flush() after every save to the database? Shouldn't NHibernate be able to work out when to flush itself? Or am I misunderstanding something?

+1  A: 

When you call Repository.Save, you are notifying the session held by the repository to track that object and synchronize the changes to the database at the next flush. Until you flush the session, no changes are made to the database. The object does become part of the session cache though, and so will be returned by the Get(1).

When you run a query to populate a collection, the session queries the database to get the results, unless it has already cached those results. Since you have not updated the database yet, the Car you added to the session will not be part of the result set. (<-- Possibly incorrect) If I'm reading the documentation correctly, both query results and Save()ed entities should be added to the session (first-level) cache. That doesn't necessarily mean that the querystatement.List() queries the cache after adding the DB results... I'm having a hard time wrapping my head around exactly what's going on.

As an aside, I believe you can set the session to autoflush but I'd have to check the documentation.

UPDATE:

I think I might see what's going on here. The default session FlushMode is Auto but Rhino's UnitOfWork.Start() creates a session with a FlushMode set to Commit, which means the session will not auto-flush unless you explicitly call Flush() or commit the transaction. With a FlushMode of Auto, NHibernate will (sometimes?) flush the session before querying to prevent stale data from being returned. If I'm right, your DB transaction looks something like:

SELECT * FROM Car

INSERT INTO Car (...) VALUES (...)

When it Auto flushes seems a bit ambiguous from the documentation/blogs I have read... the most common answer is that it with FlushMode = Auto it flushes "sometimes" although guarantees that Session.Find will never return stale data. Since NHibernate Linq actually just creates a Criteria query, it might not trigger the auto flush (maybe this has been fixed now... it's hard to know).

So it seems to me that in your case you want to flush after your save because you immediately want to retrieve the results of your save. In a smaller unit of work where you were only updating entities, a single Commit() would be fine. Maybe UnitOfWork.CurrentSession.FlushMode = FlushMode.Auto; would do the trick but the fact that the UOW Factory explicitly sets the mode to Commit seems to be encouraging you to really think about your UOW boundaries.

Stuart Childs
But UnitOfWork.CurrentSession should be returning the same session used by Repository (as I understand it from the Rhino source code) so the Linq query should be able to 'see' the car in the session cache. If I use UnitOfWork.CurrentSession.Get<SalesRep>(1), I get the car.
James Allen
It might have to do with the fact that UnitOfWork.CurrentSession is by default set to flush only on commit. See edit.
Stuart Childs
Good spot. I tried setting the flush mode using UnitOfWork.CurrentSession.FlushMode but it does not seem to change. I like your idea that it is the Linq provider going wrong, maybe opening its own session?
James Allen
A: 

Thanks to Stuart Childs, I suspect he is right that the issue may be with the NHibernate Linq provider. I'm not sure what it does behind the scenes but it might use a different session, if it does then it makes sense that I have to flush the repsoitory save before the Linq qery will 'see' it. Time to look through the source code but I've been told that it will melt my head trying to understand it!

James Allen
+2  A: 

Okay it seems that although a Get call to the Repository can use the session cache, so can 'see' the saved car in the cache:

Car car = Repository<Car>.Get(1); // This works because it uses the same session

A linq query DOES NOT use the session cache:

var allCars = (from rep in UnitOfWork.CurrentSession.Linq<Car>()               
select rep).ToList(); // Does not work, even if it is in the same code block and even though it uses the same session

So best practice is any database changes (save, update, delete, insert) should be followed by:

UnitOfWork.Session.Flush(),

or wrapped in a:

With.Transaction(delegate{
   // code here
})

or decorate your method with [Transaction] and use ATM. This will ensure subsequent linq queries will be looking at up-to-date data.

James Allen