SaveOrUpdate Vs Update and Save in NHibernate - nhibernate

What is the difference between SaveOrUpdate and Save/Update in NHibernate. Why wouldnt you just always use SaveOrUpdate? Also, what is the point of SaveOrUpdateCopy?

Chapter 9 covers all of this better than I can:
http://nhibernate.info/doc/nh/en/index.html
But cliff notes:
Save() takes a new object without an identifier and attaches it to the session. The object will be INSERT'd.
Update() takes an existing object that has an identifier but is not in the session and attaches it to the session. The object will be UPDATE'd.
SaveOrUpdate() looks at the identifier and decides what is necessary in the above.
SaveOrUpdateCopy() is special in that say you have two objects with the same identifier -- one in the session and one not. If you try and update the one not in the session an exception is thrown normally (you are now trying to attach two objects that represent the same persistent object to the session). SaveOrUpdateCopy() copies the non-session object state to the session object state.
I'm not sure how you are going to use NH, but for a lot of cases all you need is Save(). The session is doing ALL of the work necessary to know what has to be updated and simply Flush() or a Commit() does everything you need.

Related

Fluent NHibernate Disable Cache

I have problem with cache in Fluent NHibernate. I want to disable it for query by ID e.g.
session.Get<Person>(10);
Do you have any ideas ?
Are you referring to the first-level (session) cache?
You can refresh the state of an entity from the database by using Refresh, that is:
// Will get the state from the first-level cache if already present in the session:
var entity = Session.Get<EntityType>(entityId);
// Line below will update the entity with the current state from the database:
Session.Refresh(entity);
If you already hold the entity, call directly session.Refresh(person) on it instead of getting it again.
You may also evict it with session.Evict(person), causing it to no more be in the session, and no more tracked for changes either. Then discard it and eventually get it again later if you need.
Otherwise, this is unusual to consider it is a trouble getting it from the session cache. This is frequently a sign of bad session usage, such as using a same session across many user interactions (anti-pattern).
You can still do what Fredy proposes. Or call session.Clear() before getting for clearing the session cache (and losing all pending changes by the way).
Instead of a Person object that is mapped you could create a DTO for Person and do a QueryOver().
The PersonDTO object wont be cached in Nhibernates first-lvl-cache.

Why there is the need of detaching and merging entities in a ORM?

The question is about Doctrine but I think that can be extended to many ORM's.
Detach:
An entity is detached from an EntityManager and thus no longer managed
by invoking the EntityManager#detach($entity) method on it or by
cascading the detach operation to it. Changes made to the detached
entity, if any (including removal of the entity), will not be
synchronized to the database after the entity has been detached.
Merge:
Merging entities refers to the merging of (usually detached) entities
into the context of an EntityManager so that they become managed
again. To merge the state of an entity into an EntityManager use the
EntityManager#merge($entity) method. The state of the passed entity
will be merged into a managed copy of this entity and this copy will
subsequently be returned.
I understand (almost) how this works, but the question is: why one would need detaching/merging entitiies? Can you give me an example/scenario when these two operations can be used/needed?
When should I Detaching an entity?
Detaching an entity from the an EM (EntityManager) is widely used when you deal with more than one EM and avoid concurrency conflicts, for example:
$user= $em->find('models\User', 1);
$user->setName('Foo');
// You can not remove this user,
// because it still attached to the first Entity Manager
$em2->remove($user);
$em2->flush();
You can not take control of $user object by $em2 because its session belongs to $em that initially load the $user from database. Them how to solve the problem above? You need to detaching the object from the original $em first:
$user= $em->find('models\User', 1);
$user->setName('Foo');
$em->detach($user);
$em2->remove($user);
$em2->flush();
When should I use merging function?
Basically when you want to update an entity:
$user= $em->find('models\User', 1);
$user->setName('Foo');
$em->merge($user);
$em->flush();
The EM will make a compare between the $user in database vs the $user in memory. Once the EM recognize the changed fields, it only updates them and keeps the old ones.
The flush method triggers a commit and the user name will updated in the database
You would need to detach an entity when dealing with issues of concurrency.
Suppose you are using an asynchronous API that makes callbacks to your project. When you issue the API call along with callback instruction, you might still be managing the entity that is affected by the callback, and therefore overwrite the changes made by the callback.
You can also detach entity when you have pernamently data in your database, but in your code you modify this entities depending on the user account.
For example browser game which have some characters and some attacks to fight. AttackOne used by "UserFoo" (lvl 90) will be modified by better bonuses than used by "UserBarr" (lvl 20), but in our database AttackOne all the time is the same attack

How does one gracefully merge object graphs after NHibernate StaleObjectStateException?

We are trying to combine objects after a StaleObjectStateException has been thrown to save a merged copy.
Here's our environmental situation:
List item
Multi-user system
WPF Desktop application, SQL Server 2008 database
NHibernate 3.1.0.4000, FluentNHibernate 1.2.0.712
Global, long-running NHibernate sessions [for the moment. We understand session-per-presenter is the recommended pattern, but do not have time in our project schedule to convert at present.]
Top-down saves and property navigation (that is to say we save the top-level object (herein called Parent) in our domain graph)
.Cascade.AllDeleteOrphan() used in most cases.
Users exclusively own some objects in the domain graph, but share ownership of the Parent.
Navigation properties on Children objects do not exist.
All classes have numeric ID and numeric Version fields.
Use case:
User 1 starts application and opens Parent.
User 2 starts application and opens Parent.
User 2 adds a child (herein C2).
User 2 saves Parent.
User 1 adds a child (herein C1).
User 1 saves Parent.
User 1 receives a StaleObjectStateException (and rightly so)
We want to gracefully handle the exception.
Because the users share ownership of the parent, User 1 should be able to save successfully, and save the Parent with both his new child, and User 2's child.
When SOSE is thrown, according to Ayende (http://msdn.microsoft.com/en-us/magazine/ee819139.aspx):
your session and its loaded entities are toast, because with NHibernate, an exception thrown
from a session moves that session into an undefined state. You can no longer use that session
or any loaded entities
C1 has already been assigned an ID and Version # by the now-not-useful session. (I wish it had not been.)
How do we combine the use of ISession.Merge() and ISession.Refresh() to get a newly saved Parent that has both C1 and C2 ?
We have tried a number of arcane permutations, none of which fully work.
Usually, either a "row was updated or deleted by another transaction (or unsaved-value mapping was incorrect" or an actual ID collision at the ODBC level.
Our theory, at the moment:
Reset version numbers on C1 (to prevent "unsaved-value mapping was incorrect")
Get a new session
newSession.Refresh(C1);
newParent = newSession.QueryOver[...]
newParent.Add(C1);
newSession.SaveOrUpdate(newParent)
However, all the documentation suggests that newSession.Merge is supposed to be sufficient.
Other posts used as research:
Fluent NHibernate Newbie: Row was updated or deleted by another transaction
Is there an alternative to ISession.Merge() that doesn't throw when using optimistic locking?
StaleObjectstateException row was updated or deleted by
How I can tell NHibernate to save only changed properties
Hibernate (JPA): how to handle StaleObjectStateException when several object has been modified and commited (java, but relevant, i think)
Because the users share ownership of the parent, User 1 should be able to save successfully, and save the Parent with both his new child, and User 2's child.
Why don't you just disable optimistic locking on the child collection? Then anyone can add childs and it won't increase the version of the parent.
Otherwise, here is the solution my current project uses for all recoverable exceptions a session could throw (e.g. connection to DB lost, foreign key violated, ...):
Before calling session.Flush() the session is serialized to a MemoryStream.
If session.Flush() or transaction.Commit() throws an exception that is recoverable, the original session is disposed and the saved one is deserialized.
The calling screen then gets the information that the session was recovered after an exception and calls the same queries again that were called when the screen was opened the first time. And because all the modified entities are still in the recovered session the user now has the state of just before he pressed save.

How should NHibernate update properties mapped as Version

Using fluent NHibernate I have a property on a class mapped using Version
Version(x => x.Version);
When I save the object, the Version property gets incremented in the database as I would expect, but the value of the property on the object only seems to change sometimes.
using (var tx = session.BeginTransaction())
{
session.Merge(item);
tx.Commit();
item.Version; // Sometimes this is still 1, when I expect it to be 2.
}
The problem is then that if it remains as 1 and I make more changes and save again I get a StaleObjectStateException.
What's weird is that sometimes it works fine and the item.Version value does get correctly incremented, but I can't figure out the difference between the cases where it does and the cases where it doesn't.
I've tried searching but can't seem to find any documentation on this. Can anyone explain what NHibernates expected behaviour is with the Version mapping?
[NHibernate version 2.1.2]
From the ISession.Merge documentation:
Copy the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance. If the given instance is unsaved, save a copy of and return it as a newly persistent instance. The given instance does not become associated with the session.
So, it will not modify item.
(I might add I have never used Merge in my apps. You might want to review how you are dealing with attached and detached entities)
Did you try
item = session.Merge(item);
tx.Commit();
?
You need to flush the session before the updated version will propagate up to your entities. Unless you flush the session, you are responsible for keeping the entities up to date yourself.
You should TYPICALLY let the session flush on its own when its closed. However, in some instances where you rely on database updates that happen via nhibernate and not settings you make to the entity itself, you might need to flush the session yourself after a commit. In this case be aware that when you flush the session ANY entities that are dirty will be committed. This may not be desirable so be sure that the scope is very limited.

Sharing Non-Persistent Objects Between Contexts in Core Data?

I was wondering if there is a way to share an NSManagedObject between two or more NSManagedObjectContext objects running in the same thread.
I have the following problem: I have one main context shared through all my code in the application and several different contexts that are created for each remote fetch request that I issue. (I created a custom class that fetches remotely and inserts all the objects found in the server in his own NSManagedObjectContext). Those fetch requests may run simultaneously since they use NSURLConnection objects that may end at different times. If the same remote object gets fetched by different connections, I will end up with duplicates at the moment of saving and merging the context with the main one. (That is, objects that have the same remote ID but a different objectID).
One possible solution would be to save (and so persist) every object as soon as it is created but I can't do that because it may have some relationships that may still have not been filled and won't validate during the save operation.
I'm really looking forward to a method that allows you to share the same non-persistent instance of an object between context. If anybody has encountered this issue and came up with a solution, I would be pleased to know!
Context cannot communicate between each other save through their stores. However, you can insert a managed object with a nil managed object context and it will be independent (albeit without relationships) of any context. You could pass that independent managed object around however you wished and insert it into a context when you needed to persist it. This is dangerous but possible.
However, if you're not running each connection on a separate thread then you don't gain anything by having multiple context. Each connection object will activate its delegate in sequence on the main thread. In this case, your easiest solution would be to use the same delegate for all the connections and let the delegate handle the insertions into a single context. To prevent duplication, just do a fetch on the remoteID and see if you get back an extant object before inserting a new object for that remoteID.
I don't think what you want to do is possible. I mean if you want to share changes between different contexts, you got to use notifications and merge it whenever did save or did change occur. But in your case, I'd say just use 1 context and save in the end. Or a less elegant way: save all the remote ids temporary in your app and check before inserting new ones. In this case, you can continue use multiple contexts and save after each didfinishloading.