Many To One Cascade Delete Issue

2,194 views
Skip to first unread message

Michael Charalambous

unread,
May 1, 2012, 10:05:51 AM5/1/12
to nhu...@googlegroups.com
Hi
I am confused about how NHibernate treats orphan objects and would like some clarification on this subject. Please see below for my simple test case. When I run this test case I expect the child object "sequence" to be deleted from the DB but this isn't the case, instead I am left with an orphan object. The documentation on many-to-one, http://www.nhforge.org/doc/nh/en/index.html#mapping-declaration-manytoone, states that the cascade option:

     "Specifies which operations should be cascaded from the parent object to the associated object"

To be honest, I'm not sure if this means that my delete scenario is supported or if the cascade option only comes into play when the parent object is deleted.

So to summarise my questions are:
  1. Does NHibernate support many-to-one deletes as in the unit test, and if so what is wrong with my test case.
  2. If NHibernate does not support this scenario what is the logic behind not supporting it.
  3. If NHibernate does not support this scenario is there a work around so child objects can be deleted by their parents from within the domain model and be deleted within the DB.

Thanks in advance

Michael


NB. I have also tried using one-to-one and described by Ayende (http://ayende.com/blog/3960/nhibernate-mapping-one-to-one) with the same results.

--------------------------------Class declarations---------------------------------------------

public class Host
{
    public virtual int id { get; set; }
    public virtual Sequence sequence { get; set; }
}

public class Sequence
{
    public virtual int id { get; set; }
}

--------------------------------XML declarations---------------------------------------------

<class name="Host">
    <id name="id">
        <generator class="native" />
    </id>
    <many-to-one name ="sequence" cascade="all" />
</class>

<class name="Sequence">
    <id name="id">
        <generator class="native" />
    </id>
</class>

--------------------------------Unit Test---------------------------------------------

[TestFixture]
public class Fixture : BugTestCase
{
    [Test]
    public void CanDeleteChild()
    {
        Host host = new Host();
        Sequence sequence = new Sequence(); ;
        host.sequence = sequence;

        using (var s = OpenSession())
        {
            using (s.BeginTransaction())
            {
                s.SaveOrUpdate(host);
                s.Transaction.Commit();
            }
            s.Close();
        }

        int sequence_id = sequence.id;

        using (var s = OpenSession())
        {
            using (s.BeginTransaction())
            {
                sequence.host = null;
                sequence = null;
                host.sequence = null;
                
                s.SaveOrUpdate(host);
                s.Transaction.Commit();
            }
            s.Close();
        }

        using (var s = OpenSession())
        {
            Assert.IsNull(s.Get<Sequence>(sequence_id));
            sessions.Close();
        }    
    }
}

Jordan

unread,
May 1, 2012, 11:32:04 AM5/1/12
to nhusers
Hi Michael,

I believe you need to do:

<many-to-one name ="sequence" cascade="all-delete-orphan" />

regards,
Jordan.


On May 1, 3:05 pm, Michael Charalambous
<michael.charalamb...@gmail.com> wrote:
> Hi
> I am confused about how NHibernate treats orphan objects and would like
> some clarification on this subject. Please see below for my simple test
> case. When I run this test case I expect the child object "sequence" to be
> deleted from the DB but this isn't the case, instead I am left with an
> orphan object. The documentation on many-to-one,http://www.nhforge.org/doc/nh/en/index.html#mapping-declaration-manyt...,
> states that the cascade option:
>
>      "Specifies which operations should be cascaded from the parent object
> to the associated object"
>
> To be honest, I'm not sure if this means that my delete scenario is
> supported or if the cascade option only comes into play when the parent
> object is deleted.
>
> So to summarise my questions are:
>
>    1. Does NHibernate support many-to-one deletes as in the unit test, and
>    if so what is wrong with my test case.
>    2. If NHibernate does not support this scenario what is the logic behind
>    not supporting it.
>    3. If NHibernate does not support this scenario is there a work around

Michael Charalambous

unread,
May 1, 2012, 12:00:39 PM5/1/12
to nhu...@googlegroups.com
Hi Jordan

Thank you for you response. I have tried this and it does not fix the issue. From the docs the only valid cascade options for many-to-one are

cascade="all|none|save-update|delete"

Thanks

Michael

Jordan

unread,
May 1, 2012, 12:44:38 PM5/1/12
to nhusers
Sorry Michael, my bad, I didn't read your post properly at all.

I believe, as you suggest, that this behaviour only works when the
parent object is deleted, hence the all-delete-orphan option is only
available that way round.

regards,
Jordan


On May 1, 5:00 pm, Michael Charalambous

Michael Charalambous

unread,
May 2, 2012, 5:53:41 AM5/2/12
to nhu...@googlegroups.com
Hi Jordan

Thank you for you response, however I would also really like to know the logic behind not supporting this functionality and what is the recommended work-around for it.

I have quite a bit of googling on this issue and haven't found any information on why NHibernate behaves this way, just that it does. I really like to understand why things work a particular way so I really want an explanation of why it behaves this way.

Thanks
Michael

Jordan

unread,
May 2, 2012, 6:39:25 AM5/2/12
to nhusers
Hi Michael,

I'm not a hibernate developer so I can only give you my opinion. But
from my point of view the functionality you are asking for doesn't
make any sense. "Sequence" is the parent in this relationship, and
shouldn't ever be automatically deleted just because it has no
children.

regards,
Jordan.


On May 2, 10:53 am, Michael Charalambous

Ramon Smits

unread,
May 2, 2012, 6:54:32 AM5/2/12
to nhu...@googlegroups.com

It seems that Sequence is part of Host. In that case, it is not a many-to-one relationship but a one-to-one. 

I am missing the mapping from Sequente to Host. I expected to see a Host property on the Sequence class.

Maximilian Raditya

unread,
May 2, 2012, 8:30:59 AM5/2/12
to nhu...@googlegroups.com
Michael,
 
I think the question should really be: why are you modelling thing that way? Couldn't you model it in another way?
 
In one-to-many or many-to-one relationship, the "one" part is the parent and the "many" part is the children. What you're asking is actually if you deleted the child, you wanted the parent to cascadedly deleted. And that is just making any sense to me, as Jordan pointed out.
 
Looking at your code, I'm not sure which one the parent is and which one child is.
So now, I'm asking you: could you tell me which one the parent is and which one the child is?
Or are they actually the same entity splitted in multiple table, which implies one-to-one relationship as Ramon pointed out before?
 


 
--
You received this message because you are subscribed to the Google Groups "nhusers" group.
To view this discussion on the web visit https://groups.google.com/d/msg/nhusers/-/72iffKkJ9_MJ.

To post to this group, send email to nhu...@googlegroups.com.
To unsubscribe from this group, send email to nhusers+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/nhusers?hl=en.



--
Regards,

Maximilian Haru Raditya

Maximilian Raditya

unread,
May 2, 2012, 8:32:48 AM5/2/12
to nhu...@googlegroups.com
EDIT:
> And that is just making any sense to me, as Jordan pointed out.
 
And that is just NOT making any sense to me, as Jordan pointed out.

Michael Charalambous

unread,
May 2, 2012, 8:33:11 AM5/2/12
to nhu...@googlegroups.com
Jordan, thanks for pointing this out, for some reason I got it into my head that the parent should be the one where the many-to-one mapping is, which when I think about it, doesn't make much sense. Ramon is correct though, I did mean the Host to be the parent of the relationship.

Ramon, sorry I removed the Sequence to Host many to one to simplify the test case. Please see below for the corrected mapping for the many-to-one unit test. The unit test still fails with the change btw.

---------------------------------Mapping Declaration---------------------------------------

<class name="Host">
    <id name="id" column="host_id">

        <generator class="native" />
    </id>
    <many-to-one name ="sequence" column="sequence_id" cascade="all" />
</class>

<class name="Sequence">
    <id name="id" column="sequence_id">

        <generator class="native" />
    </id>
    <many-to-one name="host" class="Host" column="host_id" cascade="all"/>
</class>

-------------------------------------------------------------------------------------------

If I change the many-to-one to one-to-one, as per below, the unit test still fails.

--------------------------------Mapping Declaration---------------------------------------------

    <class name="Host">
        <id name="id" column="host_id">

            <generator class="native" />
        </id>        
        <one-to-one name ="sequence" cascade="all" />
    </class>
    
    <class name="Sequence">
        <id name="id" column="sequence_id">

            <generator class="native" />
        </id>
        <many-to-one name="host" class="Host" cascade="all"/>
    </class>
---------------------------------------------------------------------------------------------
    
In fact the only way I have been able to get the unit test to pass was to add a one-to-many list of sequences to the host and remove the sequence from that list, see below.

--------------------------------Class declarations---------------------------------------------

public class Host
{
    public virtual int id { get; set; }
    public virtual Sequence sequence { get; set; }
    public virtual ISet<Sequence> sequences { get; set; }

}

public class Sequence
{
    public virtual int id { get; set; }
    public virtual Host host { get; set; }
}

--------------------------------Mapping declarations---------------------------------------------

<class name="Host">
    <id name="id" column="host_id">

        <generator class="native" />
    </id>
    <many-to-one name ="sequence" column="sequence_id" cascade="all" />
    <set name="sequences" inverse="true" cascade="all,delete-orphan">
        <key column="host_id" />
        <one-to-many class="Sequence" />        
    </set>
</class>

<class name="Sequence">
    <id name="id" column="sequence_id">

        <generator class="native" />
    </id>
    <many-to-one name="host" class="Host" column="host_id"/>

</class>

--------------------------------Unit Test---------------------------------------------

[TestFixture]
public class Fixture : BugTestCase
{
    [Test]
    public void CanDeleteChild()
    {

        int host_id = 0;
        int sequence_id = 0;


        {
            Host host = new Host();
            Sequence sequence = new Sequence(); ;
            host.sequence = sequence;
            sequence.host = host;


            using (var s = OpenSession())
            {
                using (s.BeginTransaction())
                {
                    s.SaveOrUpdate(host);
                    s.Transaction.Commit();
                }
                s.Close();
            }

            host_id = host.id;

            sequence_id = sequence.id;
        }

        using (var s = OpenSession())
        {
            using (s.BeginTransaction())
            {
                var host = s.Get<Host>(host_id);                        
                host.sequences.Remove(host.sequence);

                host.sequence = null;
                s.SaveOrUpdate(host);
                s.Transaction.Commit();
            }
            s.Close();
        }

        using (var s = OpenSession())
        {
            Assert.IsNull(s.Get<Sequence>(sequence_id));
            sessions.Close();
        }

    }
}
-------------------------------------------------------------------------------------------

Thanks
Michael

Michael Charalambous

unread,
May 2, 2012, 9:04:57 AM5/2/12
to nhu...@googlegroups.com
Hi Maximillian

Basically I am trying to model a relationship where a parent, a host, has a single child property, a sequence, and when this child property is removed from its parent, i.e. host.sequence = null, then this results in
the child being removed from the DB.

I know this can be done by using a collection but I would really like to be able to do it for a single element.

Sorry if my earlier posts were not clear on this.

Thanks
Michael

Ramon Smits

unread,
May 2, 2012, 9:22:36 AM5/2/12
to nhu...@googlegroups.com

I am trying to understand your model

You have two relations from host to sequence:

Host -> Sequence (Host.Sequence) [many-to-one]
Sequence -> Host (Host.Sequences) [


1. I expect it to work correctly when you remove the property and mapping for Host.Sequence.

2. Your problem seems that you have multiple properties sharing the same child entity Sequence.

What is your requirement for this model as it seemes a bit flawed for an aggregate?

I have created a similar model once but there the child entity was not part of the aggrete. There I had this:

Language
Document.Languages
Document.DefaultLanguage

This required the language entity to be managed apart from the document. My guess is that you are in the same situation.


Would the following work?

var host = new Host();
var sequence = new Sequence();

host.Sequence = sequence;
host.SequencesAdd(sequence);

session.Save(sequence);
session.Save(host);


var host = session.Get<Host>(id);
var sequence = host.Sequence;
host.SequenceRemove();
host.Sequence = null;

session.Delete(sequence);



Michael Charalambous

unread,
May 2, 2012, 9:47:34 AM5/2/12
to nhu...@googlegroups.com
Hi Ramon

I think due to my inexperience with NHibernate the mapping I gave in my first example is pretty much wrong.

In principle I would like to having the following mapping

Host -> Sequence (Host.Sequence) [one-to-one]   : host.sequence points to one sequence.
Sequence -> Host (Host.Sequences) [one-to-one] : sequence.host points to one host.

So that when I set host.sequence = null the sequence is deleted. Note, host and sequence are held in separate tables.

As I haven't been able to get this working I have ended up using the collection approach as you outlined.

It would be nice if I could get the one-to-one approach working though :-)

Thanks
Michael

Ramon Smits

unread,
May 2, 2012, 11:37:28 AM5/2/12
to nhu...@googlegroups.com
Host -> Sequence (Host.Sequence) [one-to-one]   : host.sequence points to one sequence.
Sequence -> Host (Host.Sequences) [one-to-one] : sequence.host points to one host.

I understand, problem is that only one of the tables has a foreignkey relation to the other. This is th many-to-one side where the other side is mapped as one-to-one.

Examples for such a mapping are:


 
So that when I set host.sequence = null the sequence is deleted. Note, host and sequence are held in separate tables.

Always make sure that you set both sides of the relation to null. Don't use objects as tables. You do this by making one of the classes responsible for managing the relation.

In your example I would somehow expect methods like the following pseudo code:

Host::SequenceLink(Sequence sequence)
{
    Sequence = sequence;
    sequence.Host = this;
}

Host::Sequence SequenceUnlink()
{
    var s = Sequence;
    s.Host = null;
    Sequence = null;
    return s;
}

This would also simplity management of the remaining Sequence entity:


var host = session.Get<Host>(id);
session.Delete(host.SequenceUnlink());


AFAIK, orphened childs only work for collections but I must say that I understand your problem and wondering why it does not work especially when you consides Host/Sequence to be an aggregate with Host as the root object.

What you could do if use the set approach internally and use something like:
protected ICollection Sequences { get;set } // Managed by NHibernate mapping

public Sequence Sequence
{
set
{
if(Sequences.Length>0)
{
var s = Sequences[0];
s.Host = null;
Sequences.Clear();
}
if(value!=null)
{
value.Host = this;
Sequences.Add(value);
}
}
get
{
if(Sequences.Length==0) return null;
return Sequences[0];
}
}


But that would be a very nasty hack which I think should be avoided.

--
Ramon

Ramon Smits

unread,
May 2, 2012, 12:06:28 PM5/2/12
to nhu...@googlegroups.com

I just read another article regarding one-to-one:


I did not tried to code yet but maybe deletes do work for one-to-one.

--
Ramon
--
Ramon

Michael Charalambous

unread,
May 2, 2012, 12:51:19 PM5/2/12
to nhu...@googlegroups.com
Hi Ramon

Its a strange one. I currently have a working solution using the list of sequence(s) approach but it is a bit of a hack.

When removing the sequence I do the following
 
host.Sequence.Host = null;
host.Sequence = null;


but this doesn't seem to make a difference.

Thank you for all the links. I've read through the information and tried the different approaches they describe but with no luck.

I'm going to do some further research tonight and have a look at the NHibernate source. Maybe I will strike it lucky!

Thanks
Michael

Jordan

unread,
May 3, 2012, 4:36:35 AM5/3/12
to nhusers
Hi Michael,

I'm starting to vaguely remember that this used to be a known missing
feature/bug with nhibernate:

http://groups.google.com/group/nhusers/browse_frm/thread/a48ba8c83114e64c/0f705226cf338d14

regards,
Jordan


On May 2, 5:51 pm, Michael Charalambous

Michael Charalambous

unread,
May 3, 2012, 8:01:21 AM5/3/12
to nhu...@googlegroups.com
Hi Jordan

It also looks like this feature was unsupport in Hibernate for quite a while until it was implemented in 2010:

https://hibernate.onjira.com/browse/HHH-4726

It was also discussed here for NHibernate

https://forum.hibernate.org/viewtopic.php?t=961144&highlight=onetoone+cascade

and I have found an open ticket about this

https://nhibernate.jira.com/browse/NH-1262

The last update says:

Oskar Berggren added a comment -
There is no plan. Anyone willing to work on porting the feature should do so, and submit pull requests on github (preferably formatted for easy reviewing).

I think I might have a go at implementing it, but don't have much time at the moment :-)

Thanks
Michael








Thanks
Michael
Reply all
Reply to author
Forward
0 new messages