简体   繁体   中英

How do I get a new parent ID when cascade creating from a child in NHibernate?

I have a parent child relationship where the parent doesn't have a reference to the children - something like this :

class Child
{
    int Id { get; set;
    Parent Parent { get; set; }
    // + other stuff
}

class Parent
{
    int Id { get; set; }
    // + other stuff
}

In the database this just means that the Child table has a ParentID column. This column can be null - ie it's possible to have a Parent -less Child .

I'm using Fluent conventions to map the domain, but there's an override for this relationship so that we can update a Child and create a new Parent at the same time :

public class ChildOverride : IAutoMappingOverride<Child>
{
    public void Override(AutoMapping<Child> mapping)
    {
        mapping.References(x => x.Parent).Cascade.All();
    }
}

The problem arises when we add a new Parent to an existing Child and immediately need to read the ID of that Parent (while in a transaction), eg :

existingChild.CreateParent(parameters);
session.Save(existingChild);
Debug.WriteLine(existingChild.Parent.Id);

Which just prints 0 rather than giving me the Id of the new Parent - I had assumed Cascade.All() would...well, cascade. Is that wrong?

If I commit the transaction before accessing the Id, everything is fine, or if I save the new Parent explicitly that works too, eg

// This works
existingChild.CreateParent(parameters);
session.Save(existingChild);
transaction.Commit();
Debug.WriteLine(existingChild.Parent.Id);

// This also works
existingChild.CreateParent(parameters);
session.Save(existingChild);
session.Save(existingChild.Parent);
Debug.WriteLine(existingChild.Parent.Id);

Is there any way I can change my override so that saving the child object will allow the parent id to be immediately accessible? (Or is there something else I'm doing wrong?)

The good is, that all is working properly . And your mapping is correct . Because if this snippet is working...

// This works
existingChild.CreateParent(parameters);
session.Save(existingChild);
transaction.Commit();

... the concept is working as well. Why? What is all about? Why Commit() helped to solve that?

Well, because ISession is an abstraction , it is the virtual persistence storage. It does not execute any SQL Statements in the moment we call session.Save() . It just keeps all information (gathered during working with that session) and executing the SQL WRITE Statements only

  • if desperately must (the ID generated by DB is needed for farther stuff)
  • if the session decides (if allowed ... see below session mode Auto)
  • if explicitly asked for

But better and more precise will be to cite doc:

9.6. Flush

From time to time the ISession will execute the SQL statements needed to synchronize the ADO.NET connection's state with the state of objects held in memory. This process, flush, occurs by default at the following points

...

Except when you explicity Flush() , there are absolutely no guarantees about when the Session executes the ADO.NET calls , only the order in which they are executed. However, NHibernate does guarantee that the ISession.Find(..) methods will never return stale data; nor will they return the wrong data.

It is possible to change the default behavior so that flush occurs less frequently. The FlushMode class defines three different modes: only flush at commit time (and only when the NHibernate ITransaction API is used), flush automatically using the explained routine (will only work inside an explicit NHibernate ITransaction), or never flush unless Flush() is called explicitly. The last mode is useful for long running units of work, where an ISession is kept open and disconnected for a long time (see Section 11.4, “Optimistic concurrency control”).

And there is the answer. The FlushMode setting:

public enum FlushMode
{
    Unspecified = -1,
    Never = 0,
    Commit = 5,
    Auto = 10,
    Always = 20,
}

So, if the FlushMode is Commit - only transaction commit triggers the session.Flush()

But we can do it any time ourselves:

// This works
existingChild.CreateParent(parameters);
session.Save(existingChild);
session.Flush();

And now parent will have the ID - becuase session state was just converted into SQL WRITE operations...

You are using a ID generation strategy that only generates the ID when the object hits the DB. If you want to have parent ID before that, you'll need to take the ID generation into your hand, using assigned strategy.

It's a long topic that I don't want to repeat here. You can read about what you need to do here: Don't Let Hibernate Steal Your Identity

In the article, the ID type is UUID. You can easily implement a custom ID generator for int or long data types.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM