cascade doesn't save transient objects

42 views
Skip to first unread message

Bob Silverberg

unread,
Dec 11, 2009, 4:59:02 PM12/11/09
to cf-or...@googlegroups.com
I'm looking for some confirmation of this from other developers, if possible. I recall that I tested it awhile ago, and just ran some more tests, and it does appear that cascade does not save children if they are transient objects.  What do I mean by this?  For example, here are two cfcs:

Parent.cfc:

component persistent="true" 

{

property name="ParentId" fieldtype="id" generator="increment" type="numeric";

property name="ParentName";


property name="Children" fieldtype="one-to-many" cfc="Child" fkcolumn="ParentId" cascade="all" type="struct" singularname="Child" structkeycolumn="ChildName" structkeytype="string" inverse="true";


}


Child.cfc:

component persistent="true"

{

property name="ChildId" fieldtype="id" generator="increment" type="numeric";

property name="ChildName";


property name="Parent" fieldtype="many-to-one" cfc="Parent" fkcolumn="ParentId";


}


If I run the following code:

<cfscript>

Parent = new Parent();

Parent.setParentName("Bob");

Child = new Child();

Child.setChildName("Daniel");

Child.setParent(Parent);

EntitySave(Parent);

ormFlush();

</cfscript>


It will save my Parent object, but not the Child object.  The same goes for using Parent.addChild() vs. Child.setParent().  If I add an EntitySave for the Child, like so:

<cfscript>

Parent = new Parent();

Parent.setParentName("Bob");

Child = new Child();

Child.setChildName("Daniel");

Child.setParent(Parent);

EntitySave(Parent);

EntitySave(Child);

ormFlush();

</cfscript>


Then it does save both objects.  I'm wondering if this is expected behaviour (I'm guessing it is), and if there is any way to automatically cascade saving of transient objects.

Thanks,
Bob

--
Bob Silverberg
www.silverwareconsulting.com

Brian Kotek

unread,
Dec 11, 2009, 5:29:04 PM12/11/09
to cf-or...@googlegroups.com
It might be the fact that you've set Child as the owner of the relationship by using inverse. You probably want to make the Parent the owner, by moving the inverse attribute to the many-to-one in Child?

--

You received this message because you are subscribed to the Google Groups "cf-orm-dev" group.
To post to this group, send email to cf-or...@googlegroups.com.
To unsubscribe from this group, send email to cf-orm-dev+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/cf-orm-dev?hl=en.

Mark Mandel

unread,
Dec 11, 2009, 5:34:41 PM12/11/09
to cf-or...@googlegroups.com
You know what this probably is.

If you call child.setParent(parent), it doesn't implicitly add the child to the parent object (at least that was how it worked in Java land), so there is nothing to 'cascade' to in your 'Children' relationship, as there is nothing in it.

If you want to also add the child to the parent when setParent is called, you will need to overwrite the setter for setParent

so:

public function setParent(Parent parent)
{
   parent.addChild(this);
   variables.parent = arguments.parent;
}

And that should work.

Mark

--

You received this message because you are subscribed to the Google Groups "cf-orm-dev" group.
To post to this group, send email to cf-or...@googlegroups.com.
To unsubscribe from this group, send email to cf-orm-dev+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/cf-orm-dev?hl=en.



--
E: mark....@gmail.com
T: http://www.twitter.com/neurotic
W: www.compoundtheory.com

Brian Kotek

unread,
Dec 11, 2009, 5:39:36 PM12/11/09
to cf-or...@googlegroups.com
Yep, if Bob DOES mean for Child to be the owning side, then yes, Mark is right. One of the two sides needs to manage the relationship if it is bidirectional, as this one is.

Bob Silverberg

unread,
Dec 15, 2009, 5:32:42 PM12/15/09
to cf-or...@googlegroups.com
Thanks guys.  I thought I'd tested it with setting both sides of the relationship and that it hadn't worked, but going back and trying again it does work now.

Cheers,
Bob

Dutch Rapley

unread,
Dec 15, 2009, 8:17:27 PM12/15/09
to cf-or...@googlegroups.com
From the documentation:

INVERSE
According to the docs, it looks like inverse should be set in the Parent object, as that's where the "many" in the relationship is declared. Bob's original example is correct.

"As a general rule, in a bidirectional relation, one side must set inverse to true. For one-to-many or many-to-one relation, inverse should be set on the many side of the relation. For example, in ARTIST-ART relation, inverse should be set to true on the 'art' property in ARTIST. "

CASCADE & BIDIRECTIONAL RELATIONSHIPS
When you have a bidirectional relationship, cascade doesn't work, you have to do

parent.setChild(child);
child.setParent(parent);

but you only have to do an enitySave() on the parent

entitySave(parent);
ormFlush();

You don't have to call entitySave(child). Mark's example works, but it relies on the fact that you're writing a custom method, not that there's anything wrong with that.

The tidbit of information describing this is hidden under "Generated methods for relationships between CFCs"

"add<relationship_property_name>()

This method is generated for one-to-many and many-to-many relationships. The method adds the given object to the association collection (array or struct) of the component. For a bidirectional relationship, this method does not set the association on the other end."

Hence, you have to setChild()/setParent() on both sides. You have to look closely, as it's *very* easy to miss.


-Dutch

Dutch Rapley

unread,
Dec 16, 2009, 8:59:15 AM12/16/09
to cf-or...@googlegroups.com
I am going through the docs again, and found this in under "Define Relationships"

*************************************************
To set the association between objects, you need to set the references appropriately. For example, in case of Person-Address relation, where one person as one address, you need to associate Address to person as:

person.setAddress(address);

At this point, person object knows about the Address object but the address object does not know the person object. So, this is a unidirectional relation between Person-Address. To make this bidirectional, you need to associate Person to Address as:

address.setPerson(person);
*************************************************

With 60+ pages of documentation on the subject, I suppose you're bound to miss some minor details on the first couple of passes.

-Dutch
--
Dutch Rapley
www.dutchrapley.com

Bob Silverberg

unread,
Dec 16, 2009, 9:29:42 AM12/16/09
to cf-or...@googlegroups.com
Here's a question:

If you have a bi-directional relationship, is there ever a time when you would only want to set one side of the relationship?  Is there a use case for doing that, or would you always end up doing:

Child.setParent(Parent);
Parent.setChild(Child);

If there's no reason to ever set just one side, then would this be a good enhancement request for cf orm?  ColdFusion is already creating methods for us for setXXX(), addXXX() and removeXXX() when we create a relationship via a property, so would it be a good idea to have those methods that are created be smart enough to satisfy both sides of the relationship if it is a bi-directional relationship?

Without that we're going to end up having to override all of our setters, adders and removers on these relationships ourselves.

Cheers,
Bob

--

You received this message because you are subscribed to the Google Groups "cf-orm-dev" group.
To post to this group, send email to cf-or...@googlegroups.com.
To unsubscribe from this group, send email to cf-orm-dev+...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/cf-orm-dev?hl=en.

Brian Kotek

unread,
Dec 16, 2009, 10:17:48 AM12/16/09
to cf-or...@googlegroups.com
On Wed, Dec 16, 2009 at 9:29 AM, Bob Silverberg <bob.sil...@gmail.com> wrote:
Here's a question:

If you have a bi-directional relationship, is there ever a time when you would only want to set one side of the relationship?  Is there a use case for doing that, or would you always end up doing:

Child.setParent(Parent);
Parent.setChild(Child);


No, I can't see a reason why you would want to, say, attach a User to a Company, but NOT attach the Company to the User. If it only goes one way, it should be unidirectional.

 
If there's no reason to ever set just one side, then would this be a good enhancement request for cf orm?  ColdFusion is already creating methods for us for setXXX(), addXXX() and removeXXX() when we create a relationship via a property, so would it be a good idea to have those methods that are created be smart enough to satisfy both sides of the relationship if it is a bi-directional relationship?
 
Without that we're going to end up having to override all of our setters, adders and removers on these relationships ourselves.


Well, you don't HAVE to, you can explicitly let both sides yourself. But it would actually be more dangerous for CF to try to add association managment logic for the other side of the relationship. First of all, that would mean that something outside of the object is, in essence, "magically" changing the behavior of the object. Second, it can often be the case where the association management method/logic is more than just simply setting the value. Third, it means that if you DID override the setter, you'd better remember to ALSO add the association management logic yourself, or something that was working before will suddenly break. Next, what about REMOVING the relationship? Do we want CF to also take it upon itself to DELETE the other side if one side is removed? Basically, I think all of this would make things much worse than simply adding the setter yourself so that you control what is happening.

As an aside, one really probably doesn't want TOO many bidirectional relationships in their model. Initially, some people just try to make everything bidirectional "in case they ever need it", but this makes queries more difficult for Hibernate to generate, and almost certainly leads to heavy cyclical relationships all over the place. I mean, use bidirectional where it makes sense, but don't make everything bidirectional just because you can.



 

Bob Silverberg

unread,
Dec 16, 2009, 10:41:22 AM12/16/09
to cf-or...@googlegroups.com
Thanks Brian.  Good points.

Dutch Rapley

unread,
Dec 16, 2009, 11:04:12 AM12/16/09
to cf-or...@googlegroups.com
The key thing to remember here is that cascades, if using them, doesn't set your relationships between objects for you. The purpose of cascading is determine how associated objects are persisted.

Errors were originally thrown b/c one object didn't know about its relationship to the other when the cascaded save took place. You still have to make the objects on each side of the relationship aware that they are related to each other.

You can even have parent objects persisted when saving a child:

entitySave(child); by adding cascade="save-update" to your many-to-one relationship on the child object


Overall, Brian made some really good points. Hibernate in CF shouldn't really get any special treatment. Moving between CF and Java should be fairly seamless.

-Dutch

--
Dutch Rapley
www.dutchrapley.com
Reply all
Reply to author
Forward
0 new messages