6

I have class Person mapped to database with NHibernate. I load objects from DB and send it to different clients. First client will modify Name and Country property. Second client will modify only Name property. Then both returns modified objects to server. When I save data from first client - then saved correctly, both - name and country updated. When I save data from second client - I have problem. It was override data from first client and save new name and initial value of country.

How I can tell NHibernate to save only Name value and not override Country value?

public class Person
{
    public string Name { get; set; }
    public string Country { get; set; }
}

public static List<Person> GetEntities()
{
    var factory = CreateSessionFactory();
    using (ISession session = factory.OpenSession())
    {
        return session.CreateCriteria<Person>().List<Person>();                
    }
}

public static void SaveEntities(List<Person> entities)
{
    var factory = CreateSessionFactory();
    using (ISession session = factory.OpenSession())
    {
         using (var t = session.BeginTransaction())
         {
             foreach (var person in entities)
             {
                 session.Merge(person);
             }

             t.Commit();
        }
    }
}

P.S: Sorry for my bad english

gagabu
  • 154
  • 1
  • 6

4 Answers4

15

Actually, you can tell NHibernate to specifically update the "dirty" fields using Dynamic Update.

More on: http://ayende.com/blog/3946/nhibernate-mapping-concurrency

rebelliard
  • 9,592
  • 6
  • 47
  • 80
  • 1
    I think that while other people are correctly urging the original poster to correctly understand concurrency pitfalls, this comes the closest to directly answering his question. – Matthew Lund Jun 07 '11 at 20:29
  • I read this article before post question. But I can't find there answer to my problem: how to only save the Name from second client. – gagabu Jun 08 '11 at 09:41
  • @Matthew: No, this is not an answer to the question. Dynamic update doesn't work with detached entities. The second client sends old data to the server. You can't know if the user set the name by purpose back to the old name. So it can't be solved by NH, required information is missing. – Stefan Steinegger Jun 10 '11 at 10:57
3

This is a concurrency problem. The second client doesn't know that the data has changed since they read it, so their changes overwrite the first client's changes. This can be handled in NHibernate by one of several methods, the most common of which is to use a version column.

This problem is easy to prevent, the bigger issue is providing good feedback to the user when it occurs.

Jamie Ide
  • 48,427
  • 16
  • 81
  • 117
1

The answer is: you can't. NH doesn't know that only the name changed.

You can avoid it by not allowing concurrent editing. For instance by NH's optimistic locking mechanism. The second client would get an StaleObjectStateException.

If the editing of the two clients is actually not at the same time (but both based on the same object state), you need to make sure that the second client gets the changes of the first before editing. For instance by retrieving the actual state before opening the editor or by sending a changed notification from the server.

If you want to keep the concurrent editing, you have quite some work to do. The client needs to provide the information what actually had changed. Then you need to copy only these values. This is hard work. Then you may still have the problem that the so merged values don't fit.

Stefan Steinegger
  • 63,782
  • 15
  • 129
  • 193
  • Thanks Stefan. I can send back from second client original state of object for detect changes in object and save only modified properties. – gagabu Jun 08 '11 at 10:57
  • 1
    It is important to note this answer is true only in the context of the question, where the OP works with detached entities. If someone works with entities loaded from the current session, NHibernate can update only changed properties, by mapping those entities with `dynamic-update` enabled on their `class` declaration, as [answered by rebelliard](/a/6271210/1178314). – Frédéric May 24 '17 at 10:09
0

As Jamie Ide correctly pointed out, what you're experiencing is a concurrency problem, not a mapping problem..

When you create your nhibernate mapping of your object, whatever data is present in those objects will be saved (or updated) in the database once the session is flushed. You can not designate individual fields to update, it's all or nothing.

Just another (unrelated) point, your sample code is instantiating a new session factory on each operation of your entity. This is generally a bad idea, since session factories are expensive to create. You're better off managing your session factory through a global context (singleton), create it once, and spawn sessions (which are lighter weight objects) when needed.

stantona
  • 3,260
  • 2
  • 24
  • 28