I'm working on a relatively simple ColdFusion ORM application. It is focused around a core entity type called Content. This entity type is rather large, around 50 or so properties. As you can imagine, some of the properties are simple, some are many-to-one, and some are many-to-many. I ran into some very frustrating issues though and I thought I'd share them.
Invoke
The first issue I ran into was a bit of code I used to set a set of simple values. I've got a bunch of values that don't (really) need any validation and can simply be copied from a structure directly into the entity. So I created a simple list and iterated over it to set my values.
Simple, right? But note the use of invoke. This is a ColdFusion 10 addition that lets you call dynamic methods in CFCs. I noticed that none of my data was actually persisting. Why?
Turns out - in order for my calls to setX (where X is a entity method) to work, I have to pass the value as a structure. Here is the modification:
Why? I've got no freaking clue. To be fair, the docs show passing a structure of arguments, but they don't explicitly state that you must do this. Even worse, an error was never thrown when I passed the argument as a simple value instead. I hate errors that are ignored.
Error Reporting
The second issue was much, much worse. Not that it was difficult to fix once I knew what the issue was, but the problem was in how the issue was reported.
As I mentioned above, my entity has simple properties, many-to-one properties, and a many-to-many. I began by coding in the simple properties (using the technique I described above). I then did my many-to-one. I then did the many-to-many.
I noticed that my join table was not populating. I didn't get an error though. What makes this more frustrating was that I could dump my entity before the save operation and clearly see my related data in the entity. Yet I'd run the save, it would insert a new record into the core table, and just... do nothing else. Again - no damn error. Anywhere.
So I backed up a bit. First I decided - let's turn off the auto flush feature and use transactions. I didn't think that would help per se, I just decided to give it a try. All of a sudden I got something - an error:
coldfusion.orm.PersistentTemplateProxy cannot be cast to java.util.Collection
Ok... so.... first off. Why would ColdFusion persist the entity, have a problem, and just not report it until I started handling the flush myself with a transaction? I can't imagine any reason why that would make sense.
Here's where things got even more interesting. I commented out the code handling the many-to-many and I still got the error!
On a whim, I decided to comment out the code handling 3 of my many-to-one properties. All of a sudden, that fixed the issue. I then uncommented out the many-to-many and it still worked fine (and persisted data to the join table). So obviously my issue was in the many-to-one blocks.
When you work with relationships, you're supposed to ensure you set both sides of a relationship. You can sometimes get by without, but you really shouldn't. In order to make this simpler, you can simply use some custom code in the core entity and have it do both sides of the relationship. So for example, here is one of those methods:
Simple, right? In my content entity I call setSegment. The method handles the logic of translating an ID to an entity, setting it, and doing the reverse side. But something in here was wrong.
At no time, though, was I told what was wrong. Outside of the error message I pasted above, I was lost.
Finally I started commenting the lines in that method and got it down to this:
segment.setContent(this);
And then it hit me. On the content side, it has one segment. On the segment side, it has many content entities.
So right away I've learned that the built-in methods are apparently not validating for the cases when an array is required.
Ugh.
Archived Comments
I pretty much stopped using ORM in Coldfusion for such reasons. I found it frustratingly difficult to figure out what an error message was telling me, or lack thereof. I got a few things working, but it just took an incredible amount of time. As soon as you get into more complex relationships it gets really harry.
We don't require database vendor independence so I just created my own way of doing things with queries.
I'm with Jim, though for slightly different reasons.
While plain Hibernate is a pure godsend in a Java environment, the ORM integration in CF can be such a pain at times that I'm also moving away from it. And don't even get me started on how useful easy access to Envers can be.
Me too :),
I started building a Legder Application with ORM but have to come back to CFC and Queries due to its undoubtedly complexity
I agree the CF ORM integration can be difficult to work with in the beginning, but once you get the hang of it, the productivity gain is amazing.
When it comes to debugging, please remember that the actual SQL queries are not run by Hibernate until the Hibernate session is flushed. ColdFusion flushes the Hibernate session on request end by default, after the HTTP response is sent back to the client (usually a browser). I think this is why Ray did not see any error message at first. During debugging it can be helpful to flush the ORM session manually using OrmFlush(). Also, setting this.ormsetting.logsql to true in Application.cfc can be a lifesaver.
Oh fascinating Martijn. So would it have been logged *anywhere* then? To be clear, I had turned on logsql. It showed nothing. Well, it showed inserts, but no errors.
Well,
i was just considering to move to CF ORM, from plain queries and dataMgr, but this article and comments made me turn back, so thanks very much!!
Well, I wouldn't avoid ORM. It just can be a bit tricky at times. Plus I'm a bit rusty. It has been - possibly - a good whole year since I used it.
Neither tricky nor rusty, sir. Surely we are not the only peoples that does not like, or even hate, cf orm: there are many many others.
To log the full queries and errors for ORM, see the following:
http://www.rupeshk.org/blog...
To be clear Jim, I *was* doing this. It didn't help. (Well, outside of confirming it wasn't doing the inserts.)
Hi Ray,
In Railo the ORM exceptions are logged in a file called orm.log. I can image it's the same for Adobe ColdFusion. Could you check that?
For me it usually gives me useful information like:
"Cannot delete or update a parent row: a foreign key constraint fails ....."
Hi Ray,
Just FYI, this should also work: invoke(content, "set#li#", [data[li]]);
A benefit of the new argument array support is that unnamed arguments maintain their order, even when the function signature defines arguments but the argument array length exceeds the number of defined arguments.
Just like w/ superfluous struct keys, the entity's dynamic setter will ignore any superfluous array elements.
Thanks!,
-Aaron
Cool, thanks for sharing that Aaron.
I got away from CF ORM and datamgr all together. Been a happy cfwheels user for two years and I've never looked back. Cfwheels has it's own ORM, and you should check out how easy you can handle nested properties http://cfwheels.org/docs/1-...
Hi Ray,
You're very welcome. That was a very small piece of info compared to what I've learned from your blog over the years. So I'm glad that it'll possibly help.
Thanks!,
-Aaron
I just ran into this issue ("coldfusion.orm.PersistentTemplateProxy cannot be cast to java.lang.String") because I was attempting to set an entity into a string column (duh). Just wanted to add that to this record for other people and probably future me. :)
Adam, there's actually a Hibernate feature along those lines: http://256.com/gray/docs/mi...