Cascading saves - objects are not saved until next flush?

10 views
Skip to first unread message

Paul Batum

unread,
Jul 16, 2008, 11:38:37 AM7/16/08
to nhusers
Hi all,

I have observed the following behaviour:

When I call save on a transient object, nhibernate will immediately
execute the appropriate insert statement so that the object can be
assigned a valid ID. This happens without any explicit call to flush.

However when I call save on an object that is persistent, and that
save causes a cascading save on a transient child object, the insert
statement is NOT immediately executed. Rather it is deferred until the
session is flushed.

Firstly, is this the expected behaviour?

Secondly, has anyone had to work around this problem? Do you put in
explicit flush calls before you perform an action that will require
appropriate ID's to be assigned (such as serialization to DTO's)?

Thirdly, is this problem usually avoided by using a different ID
generation algorithm, such as HiLo? Can anyone comment on the
different id generation algorithms? Do most people avoid using
SQLServer identity columns as the primary key?

Thanks,

Paul Batum

josh robb

unread,
Jul 17, 2008, 12:13:15 AM7/17/08
to nhu...@googlegroups.com
Hey Paul,

On Thu, Jul 17, 2008 at 1:38 AM, Paul Batum <paul....@gmail.com> wrote:
> Firstly, is this the expected behaviour?

This is standard NH behaviour. NH uses a UnitOfWork pattern - so
calling save only marks an object for persistence. It wont
(automatically) save the object to the DB. For objects which are
mapped as Identity - NH does execute an insert automatically - but
this is for the obvious reason of obtaining an PK.

Session (UoW) flushes happen when necessary - e.g. before a read so
that read data isn't stale - but in general - you need to manage the
duration of the Session (UoW) yourself - or call flush.

> Secondly, has anyone had to work around this problem? Do you put in
> explicit flush calls before you perform an action that will require
> appropriate ID's to be assigned (such as serialization to DTO's)?

Most people (AFAIK) use some abstraction on top of ISession so that
they can use something like:

using(UnitOfWork.Begin()) {
obj.Save();
other.Delete();
}

There are a number of these around.

> Thirdly, is this problem usually avoided by using a different ID
> generation algorithm, such as HiLo? Can anyone comment on the
> different id generation algorithms? Do most people avoid using
> SQLServer identity columns as the primary key?

Depends on your specific situation. HiLo has it's own problems
(generating IDs outside of NH - if you happen to need to do ETL or
share the DB for some reason).

You should also be aware that NH can and will save modified entities
without any call save if it detects they're dirty during a session
flush.

e.g.

var myentity = session.Get(1);
myentity.Name = "some new name";

....
// some other action causes a session.Flush(); could be a query or Get
- writes the changed myentity to the DB!
...

This is because during a flush NH compares the state of any entities
in it's UoW with the dehydrated (i.e. row/column) state that it pulled
from the DB. If it notices that things have changed then it will
automatically write these changes back.

j.

Paul Batum

unread,
Jul 17, 2008, 8:52:26 AM7/17/08
to nhusers
Thanks for taking the time to write such a comprehensive answer Josh.
I think I have a pretty good understanding of how ISession behaves as
a UnitOfWork, its just the bit about immediately performing inserts to
obtain PK that I am trying to fully understand.

At what point in time does NHibernate 'follow' cascade links looking
for actions to perform? Only at flush? I guess I expected that calling
session.Save(parent) would cause a session.Save(child) call to
immediately occur if the parent-child relationship has cascades turned
on. Given the current behaviour, I guess that must not be the case.

Paul Batum

On Jul 17, 5:13 am, "josh robb" <josh.r...@gmail.com> wrote:
> Hey Paul,
>

James Kovacs

unread,
Jul 17, 2008, 12:55:49 PM7/17/08
to nhu...@googlegroups.com
"Save" in NH really means "make this entity persistent". When Flush() is called* (either explicitly by the programmer or implicitly as the result of some other action), NH builds an object graph of all reachable persistent entities. It compares this graph with the persistent state of those objects (plus the mappings for cascade rules and such) to determine the correct INSERT, UPDATE, and DELETE actions. NHibernate defers generation and execution of the SQL until the last possible moment - generally a tx.Commit() or session.Close(). NH will sometimes perform these actions earlier to ensure accurate query results. For example:

// inside a UnitOfWork
var customer = new Customer("John", "Doe");
session.Save(customer);  // no flush to the database - John Doe does not exist in the database
var customersNamedSmith = session.CreateQuery("from Customer c where c.LastName like 'Smith'").List<Customer>();  // John Doe is flushed to the database

The new customer is flushed to the database as a result of executing a query to ensure that the customer query returns correct results. The query could have been a count of the total customers or a list of customers without addresses or some other query that would have returned John Doe. Note that if the query had been for Products, NH is smart enough to realize that a query against Products doesn't require Customers to be consistent and would not flush Customers needlessly.

* I'm writing this from memory having done some work on the NH2.0 internals regarding Flush(). So more knowledgeable individuals should feel free to correct any errors or omissions. :)

HTH,
James
--
James Kovacs, B.Sc., M.Sc., MCSD, MCT
Microsoft MVP - C# Architecture
http://www.jameskovacs.com
jko...@post.harvard.edu
403-397-3177 (mobile)

Ken Egozi

unread,
Jul 17, 2008, 1:08:03 PM7/17/08
to nhu...@googlegroups.com

Paul Batum

unread,
Jul 18, 2008, 8:29:41 AM7/18/08
to nhusers
Hi James,

I'm struggling to reconcile your answer with the behavior I've
observed. When working with domain objects that have a SQLServer
integer identity column for their primary key, when I call save on a
transient object, the insert statement is triggered -immediately-.
Also, if that transient object has a list of children with
cascade=All, and that list contains a transient child object, the
insert statement for that child object will also be triggered
immediately. Furthermore, those insert statements are not actually
part of a flush - if a change was made to a persistent object before
the transient objects were saved, the update statement for that object
does not appear along with the insert statements.

If I instead take a persistent object, add a transient child to the
list, and then call save on the persistent object, the insert
statement for the child is NOT triggered immediately. Rather it is
deferred to the next flush.

The reason why I raised this issue is that I am trying to apply the
DDD concept of aggregates by only saving the root object and relying
on the cascades to save objects inside the aggregate. After the root
has been saved, I am mapping it and its children to DTO
representations which required a PK to be assigned. I had to put in an
explicit flush call after the save to ensure that the DTO would
contain the correct ID. Its not a real problem to make the flush call,
but I just wanted to understand the details of the behavior as best as
possible.

Paul Batum
> jkov...@post.harvard.edu
> 403-397-3177 (mobile)

Ken Egozi

unread,
Jul 18, 2008, 8:43:06 AM7/18/08
to nhu...@googlegroups.com
When working with domain objects that have a SQLServer integer identity column for their primary key, when I call save on a transient object, the insert statement is triggered -immediately-.

as James has put it, NH tries to defer the save as long as it can.
however, the use of DB-generated identity forces NH to go to the db right away to satisfy the Id.  imo that's just another reason to move to Guid.Comb for PKs ...

James Kovacs

unread,
Jul 18, 2008, 2:20:32 PM7/18/08
to nhu...@googlegroups.com
I agree with Ken. The point is that NH tries to defer the save until NH deems that it is necessary. I haven't looked at why calling session.Save(transient) causes an INSERT, but session.Save(persistent) does not in the case of child objects. If you need to be sure that a native PK is set so that you can serialize DTOs, then call session.Flush(). Or call tx.Commit()/session.Close() and serialize the DTOs outside the scope of the transaction/session.

Like Ken, I tend to use guid.comb for my PKs if I have a choice. Guids can be application generated by multiple independent nodes and the guid.comb algorithm has much better insert performance than GUIDs created with the other algorithms.

As for UPDATEs not happening after the INSERT within the same session, this sounds like a bug. If you can provide a unit test demonstrating the problem, I'm sure the NH Team would be happy to investigate.


James
--
James Kovacs, B.Sc., M.Sc., MCSD, MCT
Microsoft MVP - C# Architecture
http://www.jameskovacs.com
jko...@post.harvard.edu
403-397-3177 (mobile)

Ayende Rahien

unread,
Jul 18, 2008, 2:41:17 PM7/18/08
to nhu...@googlegroups.com
Save(transient) will call the DB if your id generator is set to IDENTITY.

Paul Batum

unread,
Jul 21, 2008, 4:42:12 AM7/21/08
to nhusers
Hi James, Ken

No problem to call flush in this scenario. I just wanted to discuss
the issues to make sure I had a clear understanding of the expected
behavior.
You've piqued my curiosity regarding guid.comb. I did a quick search
and it looks like there is a nice fat thread from earlier this year on
identity in NHibernate so I will have a read.

Cheers!

On Jul 18, 7:20 pm, "James Kovacs" <jkov...@post.harvard.edu> wrote:
> I agree with Ken. The point is that NH tries to defer the save until NH
> deems that it is necessary. I haven't looked at why calling
> session.Save(transient) causes an INSERT, but session.Save(persistent) does
> not in the case of child objects. If you need to be sure that a native PK is
> set so that you can serialize DTOs, then call session.Flush(). Or call
> tx.Commit()/session.Close() and serialize the DTOs outside the scope of the
> transaction/session.
>
> Like Ken, I tend to use guid.comb for my PKs if I have a choice. Guids can
> be application generated by multiple independent nodes and the guid.comb
> algorithm has much better insert performance than GUIDs created with the
> other algorithms.
>
> As for UPDATEs not happening after the INSERT within the same session, this
> sounds like a bug. If you can provide a unit test demonstrating the problem,
> I'm sure the NH Team would be happy to investigate.
>
> James
> --
> James Kovacs, B.Sc., M.Sc., MCSD, MCT
> Microsoft MVP - C# Architecturehttp://www.jameskovacs.com
> jkov...@post.harvard.edu
> 403-397-3177 (mobile)
>
> On Fri, Jul 18, 2008 at 6:43 AM, Ken Egozi <egoz...@gmail.com> wrote:
> > When working with domain objects that have a SQLServer integer identity
> >> column for their primary key, when I call save on a transient object, the
> >> insert statement is triggered -immediately-.
>
> > as James has put it, NH tries to defer the save as long as it can.
> > however, the use of DB-generated identity forces NH to go to the db right
> > away to satisfy the Id.  imo that's just another reason to move to Guid.Comb
> > for PKs ...
>

Ken Egozi

unread,
Jul 21, 2008, 4:50:51 AM7/21/08
to nhu...@googlegroups.com
for more info on Guid.Comb,  I *think* the Jimmi Nilssen (from the DDD in C# book) is the guy to be accredited for that, and in the book he gives a good explanation on that.

James Kovacs

unread,
Jul 21, 2008, 7:10:31 AM7/21/08
to nhu...@googlegroups.com
I would also recommend reading Jimmy Nilsson's original article in which he introduces the guid.comb algorithm.

http://www.informit.com/articles/article.aspx?p=25862


James
--
James Kovacs, B.Sc., M.Sc., MCSD, MCT
Microsoft MVP - C# Architecture
http://www.jameskovacs.com
jko...@post.harvard.edu
403-397-3177 (mobile)

Simone Busoli

unread,
Jul 24, 2008, 5:27:20 AM7/24/08
to nhu...@googlegroups.com
Hi Paul,

the behavior you describe seems inconsistent to me. I have verified it right now and I don't understand why NH goes to the DB for the parent while it doesn't for the child. Have you had the chance to investigate it further?

Fabio Maulo

unread,
Jul 24, 2008, 10:27:39 AM7/24/08
to nhu...@googlegroups.com
2008/7/24 Simone Busoli <simone...@gmail.com>:

Hi Paul,

the behavior you describe seems inconsistent to me. I have verified it right now and I don't understand why NH goes to the DB for the parent while it doesn't for the child. Have you had the chance to investigate it further?

What NH do depend on the strategy you are using for your POID generator.
<native> don't mean the better strategy, <native> mean: the <native> strategy of the RDBMS.
For MSSQL the <native> is identity.

If you are using identity the only way NH have to know the POID is "throw INSERT INTO".
When ?
When NH or you need to know the ID.

Conclusion:
identity is absolutely NOT the better strategy to work with ORM.

Bye.
--
Fabio Maulo

Simone Busoli

unread,
Jul 24, 2008, 10:41:09 AM7/24/08
to nhu...@googlegroups.com
But you didn't explain why it does it when persisting transient root objects but not for child transient objects of parent persistent objects.

Fabio Maulo

unread,
Jul 24, 2008, 12:20:14 PM7/24/08
to nhu...@googlegroups.com
This is the explain :

"When NH or you need to know the ID."

NH need ID of parent but not the ID of child.
Active the SQL logger and try to write :
int savedId = parent.Childs[0].Id;

Take a look what happen when you need to know the ID of a child.

2008/7/24 Simone Busoli <simone...@gmail.com>:

But you didn't explain why it does it when persisting transient root objects but not for child transient objects of parent persistent objects.

--
Fabio Maulo

Simone Busoli

unread,
Jul 24, 2008, 6:05:59 PM7/24/08
to nhu...@googlegroups.com
This doesn't work for me. I'm not talking about collections, I'm talking about properties of a complex type. Here's the test case that fails (on the last assertion):

[TestFixture]
public class NHibernateTests
{
    [Test]
    public void Id_of_parent_and_child()
    {
        var cfg = new Configuration
          {
              Properties = new Dictionary<string, string>
               {
                   {Environment.ConnectionDriver, "NHibernate.Driver.SQLite20Driver"},
                   {Environment.Dialect, "NHibernate.Dialect.SQLiteDialect"},
                   {Environment.ConnectionProvider,"NHibernate.Connection.DriverConnectionProvider"},
                   {Environment.ConnectionString, ("Data Source=:memory:;Version=3;New=True;")},
                   {Environment.ShowSql, "true"},
                   {Environment.ReleaseConnections, "on_close"}
               }
          };

        cfg.AddAssembly(typeof (Contact).Assembly);
        var sessionFactory = cfg.BuildSessionFactory();

        using (var session = sessionFactory.OpenSession())
        {
            new SchemaExport(cfg).Execute(false, true, false, false, session.Connection, null);

            var message = new Message {Content = "Ciao"};

            session.Save(message);

            Assert.That(message.ID, Is.GreaterThan(0));

            message.Receiver = new Contact {Name = "Simone"};

            session.Update(message);

            Assert.That(message.Receiver.ID, Is.GreaterThan(0));
        }
    }
}

Message is this one:

public class Message
{
    public virtual int ID { get; private set; }

    public virtual DateTime DeliveryDate { get; set; }

    public virtual string Content { get; set; }

    public virtual Contact Sender { get; set; }

    public virtual Contact Receiver { get; set; }
}

Contact this one:

public class Contact
{
    public virtual int ID { get; private set; }
    public virtual string Name { get; set; }
}

And here's the mappings:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="AssemblyName" namespace="NamespaceName">
  <class name="Message">
    <id name="ID">
      <generator class="native"></generator>
    </id>
    <property name="DeliveryDate" not-null="true"></property>
    <property name="Content" not-null="true" type="StringClob"></property>
    <many-to-one name="Sender" class="Contact"></many-to-one>
    <many-to-one name="Receiver" class="Contact"></many-to-one>
  </class>
</hibernate-mapping>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="AssemblyName" namespace="NamespaceName">
  <class name="Contact">
    <id name="ID">
      <generator class="native"></generator>
    </id>
    <property name="Name" not-null="true"></property>
  </class>
</hibernate-mapping>

Fabio Maulo

unread,
Jul 24, 2008, 11:47:16 PM7/24/08
to nhu...@googlegroups.com
You don't having any kind of cascade actions on association so you can't retrieve the ID since you don't persist the transient entities (Sender and Receiver must be persistent).
In fact your test is incomplete because it don't have a a Fush or a Commit.... add it to your test and NH let you know what you are doing wrong. ;)

Bye.
Fabio Maulo.

P.S. My example was about parent-child relationship because you talk about it: "persisting transient root objects but not for child transient objects of parent persistent objects"

Simone Busoli

unread,
Jul 25, 2008, 4:02:45 AM7/25/08
to nhu...@googlegroups.com
No way. I added the cascade = all attribute to the association with the Receiver, but same behavior. What I don't understand is why for the parent object it goes to the db to get the ID - without any explicit flush nor commit -, thus persisting it, but for the child it doesn't.

Paul Batum

unread,
Jul 25, 2008, 8:18:24 AM7/25/08
to nhusers
Hi Simone,

I didn't investigate it much further. My interpretation of the answers
and observed behavior was that cascades only occur during a flush, and
when NHibernate executes an insert statement to assign a PK, that is
not part of a flush. I must admit I found this a little counter
intuitive, which is why I asked the question in the first place.

In my case, this behavior was pretty easy to deal with. I am using the
repository pattern so it was straightforward to insert an explicit
Flush after every Save.

Paul Batum

On Jul 25, 9:02 am, "Simone Busoli" <simone.bus...@gmail.com> wrote:
> No way. I added the cascade = all attribute to the association with the
> Receiver, but same behavior. What I don't understand is why for the parent
> object it goes to the db to get the ID - without any explicit flush nor
> commit -, thus persisting it, but for the child it doesn't.
>

Simone Busoli

unread,
Jul 25, 2008, 8:21:17 AM7/25/08
to nhu...@googlegroups.com
Yes, no big deal for me too, but I still can't get why for the parent object it goes to the db to get the id while for the child it doesn't.

Fabio Maulo

unread,
Jul 25, 2008, 9:56:54 AM7/25/08
to nhu...@googlegroups.com
2008/7/25 Simone Busoli <simone...@gmail.com>

Yes, no big deal for me too, but I still can't get why for the parent object it goes to the db to get the id while for the child it doesn't.

using the mapping you send

            session.Save(message);

             message.Receiver = new Contact {Name = "Simone"};
            session.Save(message.Receiver);
            Assert.That(message.Receiver.ID, Is.GreaterThan(0));

Do the Save the "child" and you have the same behavior.
There is no parent child relationship and an entity can't have the POID since it is transient.
--
Fabio Maulo

P.S. Was the last chance.

Simone Busoli

unread,
Jul 25, 2008, 10:00:43 AM7/25/08
to nhu...@googlegroups.com

P.S. Was the last chance.


for?

Paul Batum

unread,
Jul 26, 2008, 8:00:10 AM7/26/08
to nhusers
I'm sorry Fabio but I could not follow the point you were trying to
make there. The question that Simone was eluding to is why cascades
only occur on flush. I suspect to get a real answer we would have to
ask the hibernate designers?

On Jul 25, 2:56 pm, "Fabio Maulo" <fabioma...@gmail.com> wrote:
> 2008/7/25 Simone Busoli <simone.bus...@gmail.com>
>
> > Yes, no big deal for me too, but I still can't get why for the parent
> > object it goes to the db to get the id while for the child it doesn't.
>
> using the mapping you send
>
> session.Save(message);
> message.Receiver = new Contact {Name = "Simone"};
> session.Save(message.Receiver);
> Assert.That(message.Receiver.ID <http://message.receiver.id/>,

Fabio Maulo

unread,
Jul 26, 2008, 11:44:08 AM7/26/08
to nhu...@googlegroups.com
2008/7/26 Paul Batum <paul....@gmail.com>

I'm sorry Fabio but I could not follow the point you were trying to
make there. The question that Simone was eluding to is why cascades
only occur on flush.

There is no more deaf than who do not want to listen.
NH, as very good persistence layer, delay the access to DB as late as possible.

cascade ? where you saw "cascade" in the mapping ?
change the mapping with this
        <many-to-one name="Sender" class="Contact" cascade="all"/>
        <many-to-one name="Receiver" class="Contact" cascade="all"/>
Then force NH to go to db (enclose the test in transaction please)

                var message = new Message {Content = "Ciao"};
                session.Save(message);
                Assert.That(message.ID, Is.GreaterThan(0));
               message.Receiver = new Contact {Name = "Simone"};
                session.Persist(message);
                Assert.That(message.Receiver.ID, Is.GreaterThan(0));

Then let me know what happen.

I suspect to get a real answer we would have to
ask the hibernate designers?

Or read some good books about persistent layer designer.

Bye.
Fabio Maulo

Paul Batum

unread,
Jul 27, 2008, 2:58:11 PM7/27/08
to nhusers
Please Fabio, there is no need to be rude. I very much do wish to
listen. Otherwise I would not be asking on this mailing list in the
first place.

I tried the code you suggested, and I can confirm that cascades do
fire when calling ISession.Persist. I have never seen the Persist
method used before. I tried to do some searches on the semantic
differences between Save and Persist but I didn't find much. The
Hibernate documentation doesn't seem to make much of a distinction
between the two. Are there any good resources that explain the
differences?

Paul Batum

On Jul 26, 4:44 pm, "Fabio Maulo" <fabioma...@gmail.com> wrote:
> 2008/7/26 Paul Batum <paul.ba...@gmail.com>
>
>
>
> > I'm sorry Fabio but I could not follow the point you were trying to
> > make there. The question that Simone was eluding to is why cascades
> > only occur on flush.
>
> There is no more deaf than who do not want to listen.
> NH, as very good persistence layer, delay the access to DB as late as
> possible.
>
> cascade ? where you saw "cascade" in the mapping ?
> change the mapping with this
>         <many-to-one name="Sender" class="Contact" cascade="all"/>
>         <many-to-one name="Receiver" class="Contact" cascade="all"/>
> Then force NH to go to db (enclose the test in transaction please)
>                 var message = new Message {Content = "Ciao"};
>                 session.Save(message);
>                 Assert.That(message.ID, Is.GreaterThan(0));
>                message.Receiver = new Contact {Name = "Simone"};
>                 *session.Persist(message);*

Fabio Maulo

unread,
Jul 27, 2008, 5:15:43 PM7/27/08
to nhu...@googlegroups.com
2008/7/27 Paul Batum <paul....@gmail.com>

Please Fabio, there is no need to be rude.
I'm sorry for "rude"... was my mistake of the last phrase.
 
Are there any good resources that explain the
differences?

Not so far... I'm updating our doc reference but really... I prefer write C#

--
Fabio Maulo

Markus Zywitza

unread,
Jul 28, 2008, 4:07:16 AM7/28/08
to nhu...@googlegroups.com
My memory hook for this is the following:

I can only *save* a transient object, it is *unsaved* prior to the call.
When I have a persistent object, it is already *saved*, so *save* does nothing.

When there are changes, all changes will be *flushed* or *persisted*
when the UnitOfWork is completed.

In this spirit, I call save or evict to toggle between transient and
persisted state, but I don't notifiy NH of changes to persistent or
transient entities.

-Markus

Paul Batum

unread,
Jul 29, 2008, 11:47:53 AM7/29/08
to nhusers
On Jul 27, 10:15 pm, "Fabio Maulo" <fabioma...@gmail.com> wrote:
> I'm sorry for "rude"... was my mistake of the last phrase.

No worries Fabio.

> Not so far... I'm updating our doc reference but really... I prefer write C#

Haha, you are not alone in that regard.

Can you tell me why test 1 passes but test 2 fails? Assume wrapped in
a transaction...

[Test]
public void ExpectCascadeOnTransient()
{
var message = new Message { Content = "Ciao" };
message.Receiver = new Contact { Name = "Simone" };
session.Save(message);
Assert.That(message.Receiver.ID, Is.GreaterThan(0)); //
Passes OK
}

[Test]
public void ExpectCascadeOnPersistent()
{
var message = new Message { Content = "Ciao" };
session.Save(message);
message.Receiver = new Contact { Name = "Simone" };
session.Save(message);
Assert.That(message.Receiver.ID, Is.GreaterThan(0)); //
FAILS
}

Dario Quintana

unread,
Jul 29, 2008, 12:00:53 PM7/29/08
to nhu...@googlegroups.com
Hi

Maybe is the lack of the Flush();


On Tue, Jul 29, 2008 at 12:47 PM, Paul Batum <paul....@gmail.com> wrote:

Can you tell me why test 1 passes but test 2 fails? Assume wrapped in
a transaction...

       [Test]
       public void ExpectCascadeOnTransient()
       {
           var message = new Message { Content = "Ciao" };
           message.Receiver = new Contact { Name = "Simone" };
           session.Save(message);
           Assert.That(message.Receiver.ID, Is.GreaterThan(0)); //
Passes OK
       }

       [Test]
       public void ExpectCascadeOnPersistent()
       {
           var message = new Message { Content = "Ciao" };
           session.Save(message);
           message.Receiver = new Contact { Name = "Simone" };
           session.Save(message);
           Assert.That(message.Receiver.ID, Is.GreaterThan(0)); //
FAILS
       }

--
Dario Quintana
http://darioquintana.com.ar

Markus Zywitza

unread,
Jul 29, 2008, 2:44:40 PM7/29/08
to nhu...@googlegroups.com
On Tue, Jul 29, 2008 at 17:47, Paul Batum <paul....@gmail.com> wrote:
> [Test]
> public void ExpectCascadeOnPersistent()
> {
> var message = new Message { Content = "Ciao" };
> session.Save(message);
> message.Receiver = new Contact { Name = "Simone" };
> session.Save(message);
> Assert.That(message.Receiver.ID, Is.GreaterThan(0)); //
> FAILS
> }
Once called Save(), message is persistant. If you call Save() on it
again nothing happens. When the Session is flushed, changes are
detected and the unsaved object is persisted. But I'm sure, I wrote
just that 3 or 4 posts before...

-Markus

Paul Batum

unread,
Jul 30, 2008, 4:44:44 AM7/30/08
to nhusers
Dario: I am not calling flush in either test. Test 1 passes without a
flush.

Markus: Perhaps I asked the wrong question. Why does test 1 pass? Why
does NHibernate choose to apply the cascades IMMEDIATELY (no wait for
flush) when I call save on a transient object? Why doesn't it simply
generate the insert statement for the parent, assign the ID, and defer
executing the insert statements for the children until the next flush
(like it does in the case of the persistent object)? If it used this
approach, the cascading would follow a simple rule "cascades on
flush". Now presumably there is a good reason. I'm not saying its
wrong. I'm not complaining its broken. I'm simply saying that the
cascades behave differently based on the transient/persistent state of
the parent object and I would like to understand -why-.

On Jul 29, 7:44 pm, "Markus Zywitza" <markus.zywi...@gmail.com> wrote:

Paul Batum

unread,
Jul 30, 2008, 4:53:32 AM7/30/08
to nhusers
Whoops, forgot to ask this:

On Jul 28, 9:07 am, "Markus Zywitza" <markus.zywi...@gmail.com> wrote:
> In this spirit, I call save or evict to toggle between transient and
> persisted state, but I don't notifiy NH of changes to persistent or
> transient entities.

Does Evict really toggle to a transient state? If you evict a
persistent object, don't you now have a -disconnected- persistent
object?
I thought Save and Delete were the 'toggle' ?

Markus Zywitza

unread,
Jul 30, 2008, 5:01:38 AM7/30/08
to nhu...@googlegroups.com
Delete removes the entity from the database. This is presumably not
what you want...

-Markus

Markus Zywitza

unread,
Jul 30, 2008, 5:17:24 AM7/30/08
to nhu...@googlegroups.com
NH cascades whenever it flushes. If you use a native PK, NH decides
that it has to flush immediately after saving. The reasoning for this
is IMO questionable, because when I use a NH key generation strategy,
NH won't assign a value before flushing as well.
However, there is a complete flush upon saving a transient object with
native PK and thus NH cascades as defined in the mapping file.

-Markus

On 7/30/08, Paul Batum <paul....@gmail.com> wrote:
>

Paul Batum

unread,
Jul 30, 2008, 6:24:08 AM7/30/08
to nhusers
Hi Markus,

My observations are to the contrary: When NH performs an immediate
insert, this is NOT a flush. Here is the demonstration test:

[Test]
public void SaveCausesAFlushWhenNativeIdentityIsUsed()
{
var paul = new ServiceUser();
using (ITransaction tx =
CurrentSession.BeginTransaction())
{
CurrentSession.Save(paul);
tx.Commit();
}

using (ITransaction tx =
CurrentSession.BeginTransaction())
{
paul.DateOfBirth = DateTime.Today;
var john = new ServiceUser();
CurrentSession.Save(john);
//CurrentSession.Flush(); <--- Test fails without this
flush.
CurrentSession.Refresh(paul);
Assert.AreNotEqual(0, john.ID);
Assert.AreEqual(DateTime.Today,
paul.DateOfBirth);
}
}

An update statement setting my birthday to Today will never be
executed. Thus the refresh will revert the date of birth back to the
default value. If I uncomment the flush call, then the test passes
because refresh does nothing.

On Jul 30, 10:17 am, "Markus Zywitza" <markus.zywi...@gmail.com>
wrote:
> NH cascades whenever it flushes. If you use a native PK, NH decides
> that it has to flush immediately after saving. The reasoning for this
> is IMO questionable, because when I use a NH key generation strategy,
> NH won't assign a value before flushing as well.
> However, there is a complete flush upon saving a transient object with
> native PK and thus NH cascades as defined in the mapping file.
>
> -Markus
>

Ayende Rahien

unread,
Jul 30, 2008, 12:18:11 PM7/30/08
to nhu...@googlegroups.com
yes, you have a trasient object

Paul Batum

unread,
Jul 30, 2008, 5:07:13 PM7/30/08
to nhusers
I think I must have got some of my terminology confused. Based on what
Markus and Ayende said:

var p = new Person() // p is transient
session.Save(p) // p is persistent
session.Evict(p) // p is transient

What terminology do you use to differentiate between the state of p at
line one and at line three?

On Jul 30, 5:18 pm, "Ayende Rahien" <aye...@ayende.com> wrote:
> yes, you have a trasient object
>

Dario Quintana

unread,
Jul 30, 2008, 5:33:10 PM7/30/08
to nhu...@googlegroups.com
Little fix:

session.Evict(p) // p is detached, has an Id, but it's not associated to persistence context.

Best regards


On Wed, Jul 30, 2008 at 6:07 PM, Paul Batum <paul....@gmail.com> wrote:

I think I must have got some of my terminology confused. Based on what
Markus and Ayende said:

var p = new Person() // p is transient
session.Save(p) // p is persistent
session.Evict(p) // p is transient

What terminology do you use to differentiate between the state of p at
line one and at line three?


Ayende Rahien

unread,
Jul 30, 2008, 6:01:25 PM7/30/08
to nhu...@googlegroups.com
new Person { Id = 5 }; <== how do you define this?

Dario Quintana

unread,
Jul 30, 2008, 6:29:47 PM7/30/08
to nhu...@googlegroups.com
Detached, because you're assigning an Id and is a db concept: Maybe that entity exists in the Database, and you want to assign values and merge with the persistence one.


On Wed, Jul 30, 2008 at 7:01 PM, Ayende Rahien <aye...@ayende.com> wrote:
new Person { Id = 5 }; <== how do you define this?

Fabio Maulo

unread,
Jul 30, 2008, 7:38:16 PM7/30/08
to nhu...@googlegroups.com
2008/7/30 Ayende Rahien <aye...@ayende.com>

new Person { Id = 5 }; <== how do you define this?

well well...
new Person{Id = 5} is transient but for NH it depend on some other things like unsaved-value and version.

--
Fabio Maulo

Markus Zywitza

unread,
Jul 31, 2008, 2:48:00 AM7/31/08
to nhu...@googlegroups.com
I have looked into NH sources to figure ot the state. The comments of
ISession state that an object gets transient by calling Delete(), so
according to NH source docs, Paul's assumption about Save() and
Delete() was right.

The question is: Is there any "official" state model about NH object
states? I am currently writing docs on Castle ActiveRecord and started
an article about the persistant object lifecycle. ISession says there
only two states: transient and persistant. However, there is also a
StaleObjectStateException, so there must be at least the third state
of stale. I identified some more, but hitherto couldn't back them up
by the NH source. Perhaps someone has a hint where to look in the
sources for it.

-Markus

Paul Batum

unread,
Jul 31, 2008, 3:56:10 AM7/31/08
to nhusers
I found the blog post that gave me my weird ideas about transient
terminology:
http://intellect.dk/post/Detached-objects-in-nHibernate-and-Lazy-loading.aspx

Anyone care to comment on the correctness of this post?

Hmm, it looks like I accidently hijacked my own thread :/
Markus, do you have any thoughts regarding my example of how the
process of nhibernate going to the DB to assign an id is not a flush?
As far as I can tell it invalidates the "cascades only on flush"
argument.

On Jul 31, 7:48 am, "Markus Zywitza" <markus.zywi...@gmail.com> wrote:
> I have looked into NH sources to figure ot the state. The comments of
> ISession state that an object gets transient by calling Delete(), so
> according to NH source docs, Paul's assumption about Save() and
> Delete() was right.
>
> The question is: Is there any "official" state model about NH object
> states? I am currently writing docs on Castle ActiveRecord and started
> an article about the persistant object lifecycle. ISession says there
> only two states: transient and persistant. However, there is also a
> StaleObjectStateException, so there must be at least the third state
> of stale. I identified some more, but hitherto couldn't back them up
> by the NH source. Perhaps someone has a hint where to look in the
> sources for it.
>
> -Markus
>

Paul Batum

unread,
Jul 31, 2008, 4:05:53 AM7/31/08
to nhusers
On Jul 30, 11:01 pm, "Ayende Rahien" <aye...@ayende.com> wrote:
> new Person { Id = 5 }; <== how do you define this?

This question complicates things even further! Now the object has an
ID but its not persisted yet. I would argue that the question of
whether the object has been assigned an ID is not so relevant in terms
of lifecycle terminology - what DOES matter is whether that object has
a corresponding row in the db. i.e. Has it been persisted?

I think the 'does it have an id or not' question isn't really relevant
because this can be abstracted. Perhaps my object uses a native key,
perhaps it uses a guid.comb...it doesn't matter. The lifecycle is the
same (although it IS relevant to the behavior of cascades...).

Markus Zywitza

unread,
Jul 31, 2008, 8:18:50 AM7/31/08
to nhu...@googlegroups.com
On 7/31/08, Paul Batum <paul....@gmail.com> wrote:
> Markus, do you have any thoughts regarding my example of how the
> process of nhibernate going to the DB to assign an id is not a flush?
> As far as I can tell it invalidates the "cascades only on flush"
> argument.
Please allow some days for reading NH source. I'll do anyway for my
lifecycle document, but I don't know when exactly I will come to that
part.

As a rule of thumb, just don't use native PKs. Then you will have no
db activity besides flushes.

-Markus

Reply all
Reply to author
Forward
0 new messages