Photo from Chile

More Fun with ColdFusion ORM Struct-Based Collections

Ben Nadel asked a question in a comment to my last post about struct-based collections with ColdFusion ORM which I thought was quite interesting and deserved some investigation. To recap the previous post, it simply pointed out that in order to add an object to a struct-based collection, when calling the add method one must pass both the object to be added to the collection and the key to be used in the struct. This raised a great question from Ben, whom I quote:

Does the ORM system do any validation on the Key that you pass in? Does it affect the way the data is persisted to the database (or will the persistence use the value as defined by the CFProperty structKeyColumn)?

I'd never tested this myself, so I did just that, and what I found was somewhat surprising.

I interpreted the question as this: Because you define the property of the child object that is meant to be used as a key in the structure, what happens if you pass a value into the add method that is not the same as the value of the property of the child object? That's a bit of a mouthful, so let's look at an example. Consider the following Department entity:

view plain print about
1component persistent="true" output="false" entityname="Department" {
2    property name="DeptId" fieldtype="id" generator="native";
3    property name="Name";
4    property name="Users" fieldtype="one-to-many" type="struct"
5        cfc="User" fkcolumn="DeptId" singularname="User"
6        structkeycolumn="UserName" structkeytype="string" cascade="save-update";
7}

We don't even need to look at the User entity, as for the purposes of this test we just need to know that the User entity has a property called UserName, and we know that to be the case because the Users collection in the Department entity specifies a structkeycolumn of UserName. This tells us that for the structure that will be returned whenever we call getUsers() on a Department object, the keys in that structure will be the value of UserName for each individual User object. Let's take a look at some code which would create a User and add it to a Department:

view plain print about
1U = new User();
2U.setUserName("Bob");
3entitySave(U);
4D = entityLoadByPK("Department",1);
5D.addUser("Bob",U);
6entitySave(D);
7ormFlush();
8writeDump(D);

The results of Dumping the Department object look like this:

Now, let's try messing with ColdFusion by passing a key value into the add method that doesn't match the actual value of the UserName property:

view plain print about
1U = new User();
2U.setUserName("Bob");
3entitySave(U);
4D = entityLoadByPK("Department",1);
5D.addUser("Ben",U);
6entitySave(D);
7ormFlush();
8writeDump(D);

Now the results of Dumping the Department object look like this:

Notice that the UserName property of the embedded User object still contains "Bob", but the key to the structure is "Ben", which is the value we passed into the addUser() method. Perhaps that's not surprising, as that's exactly what we told ColdFusion to do, but it does seem invalid, doesn't it?

One thing to note here is that we're calling writeDump(D) after ormFlush(), which means that the data for our objects has been written to the database, but, the variable D is still pointing to the object that is in the current request, so it doesn't necessarily reflect the data that was written to the database. If we want to know what the object that was just written to the database looks like, we have to retrieve a fresh copy from the database. In order to do that we're going to close the ORM session after calling ormFlush(), and then retrieve a fresh copy of the Department object from the database using entityLoadByPK():

view plain print about
1U = new User();
2U.setUserName("Bob");
3entitySave(U);
4D = entityLoadByPK("Department",1);
5D.addUser("Ben",U);
6entitySave(D);
7ormFlush();
8ormCloseSession();
9D = entityLoadByPK("Department",1);
10writeDump(D);

Now the results of Dumping the Department object look like this:

Aha! Look at that. The value of the UserName property in the User object now does match the value of the key in the struct of Users, but, it has actually been changed to the value that we passed into our key. So the value of the UserName property was actually modified by calling D.addUser("Ben",U). That is not what I expected to happen, but I suppose it makes sense. The mismatch had to be resolved somehow, and I did specifically tell ColdFusion that I wanted the value to be "Ben", so it went ahead and changed the UserName property to be "Ben".

To try to understand why this was happening I took a look at the SQL that was generated for the addUser() call. It looks like this:

view plain print about
1update `User`
2set DeptId=?, UserName=?
3where UserId=?
4
5HIBERNATE DEBUG - binding '1' to parameter: 1
6HIBERNATE DEBUG - binding 'Ben' to parameter: 2
7HIBERNATE DEBUG - binding '1' to parameter: 3

So, whenever an add method is called on a struct-based collection it generates an update on the child object which sets both the parent (as is obviously needed) and the value of the structkeycolumn, which is what surprises me. I guess this is just how Hibernate is designed to work with struct-based collections, so I wouldn't call this a bug, but I would call it unexpected behaviour and something that we all need to be aware of.

Of course, I think it's perfectly valid to suggest that a developer should never write code like this, and if one does then they get what they deserve. If you write code that doesn't make sense then you shouldn't have an expectation that the result will make sense. As I often say to my son, "Ask a silly question...".

TweetBacks
Comments
Great, that was my Q as well. Just didn't get around to test it. Thanks! Adobe should have included this in their documentation...
# Posted By Henry Ho | 1/25/10 1:57 PM
Bob, awesome! Thanks for taking me seriously - I know my lack of ORM experience can be a bit trying :) These are very interesting results.
# Posted By Ben Nadel | 1/25/10 11:17 PM