Andrew Clarke

systems architect, internet developer, team leader

Andrew Clarke

A workaround for "ids for this class must be manually assigned before calling save()"

January 12, 2011 · 4 Comments

2011-06-16 update:

I may have been too hasty in stating the ColdFusion has a bug here.  The solution might be as simple as putting fieldType="ID" ORMType="string" generator="GUID" in your entity primary key definition.  Oops.  I have a comment about this below.  This seems to work for me, but I've made quite a few changes to my code since I wrote this post, so it's hard to know for sure.  If anyone tests and this works or doesn't work, please post a comment.

Now, onto the original blog post...

ColdFusion 9 has an annoying bug in its Hibernate implementation when dealing with uniqueidentifier primary keys in Microsoft SQL Server.  Let's say you have the following table:

CREATE TABLE [dbo].[Product](

[ProductId] [uniqueidentifier] NOT NULL,

[Title] [varchar](50) NOT NULL,

[Description] [varchar](50) NOT NULL,

[IsActive] [bit] NOT NULL,

 CONSTRAINT [PK_Product] PRIMARY KEY CLUSTERED 

(

[ProductId] ASC

)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

) ON [PRIMARY];

 


If you want to add a new Product, you might use something like the following code:

p = entityNew("Product");

p.setTitle("My title");

p.setIsActive(1);

p.setDescription("My description");

entitySave(p);

 

The problem is, if you run this code, you'll get this error:

ids for this class must be manually assigned before calling save(): Product
Root cause :org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): Product

 

 

 

 

If you insert this same query directly from MSSQL's Query Analyzer, or I presume from a cfquery, it will insert fine as MSSQL will generate a GUID for productID.  Trying to create a product from ORM will generate that error though.

My workaround is to catch the error that comes back from the entitySave() function.  If it's the error we're after, we'll generate a GUID and redo the save.  If it's another error, we'll just rethrow it.

 

try {

entitySave(p);

}

catch (any e) {

// Look for an org.hibernate.id.IdentifierGenerationException.  This probably indicates a bug in ColdFusion's ORM implementation dealing with uniqueidentifier primary keys.

if (

isDefined("e.cause.type")

and (e.cause.type is "org.hibernate.id.IdentifierGenerationException"

and isDefined("e.message"

and findNoCase("ids for this class must be manually assigned before calling save()", e.message)

) {

transaction action="begin" isolation="serializable" {

queryService = new query(datasource="000_cr", sql="select newID() as newID");

q_getGUID = queryService.execute().getResult();

p.setProductID(q_getGUID.newID);

entitySave(p);

} // transaction

} else {

throw(e);

} // if

} // catch


Note that GUID stands for "globally unique ID".  You can't generate this yourself, as that won't guarantee to MSSQL that it's globally unique.  Therefore you have to request a GUID from MSSQL using MSSQL's newID() function.

 

I wrap that query and the entitySave() lines in a serializable transaction just to be safe.  I'm not sure about the details of the newID() function.  For example, as far as I know, it might be possible for the newID() function to return the same GUID twice in a row to two different processes, as long as it hasn't been used anywhere else.  If this happened then you'd have the same GUID in two places.  Not good.  The transaction should stop this from happening, if indeed it's even possible.

I've logged this bug at http://cfbugs.adobe.com/cfbugreport/flexbugui/cfbugtracker/main.html#bugId=85779, so let's hope it gets fixed at some point.

Tags: ColdFusion · ORM

4 responses so far ↓

  • 1 Kristie Butler // Jan 18, 2011 at 8:32 AM

    Hi - we are seeking a Coldfusion Developer for a 6+ month contract position with a top notch client based in Chapel Hill, NC. Please email me at kristieb@procomservices.com if you know of someone who may be interested.

    Thank you!
  • 2 Dan Schweitzer // Jun 15, 2011 at 2:49 PM

    Andrew,

    Did you ever find a real solution? I am just going to get the last id and + 1 to it but, Adobe needs to address this.
  • 3 Dan Schweitzer // Jun 16, 2011 at 8:38 AM

    I found this works pretty well and it only ads 2 lines of code....

    <cfscript>
    total = ormExecuteQuery("select count(id) from customers", true);
    total = total +1;

    customer = entityNew("customers");
    customer.setid(total);
    customer.setfName("Dan");
    customer.setlName("Swisso");
    entitySave(customer);
    </cfscript>
  • 4 Andrew Clarke // Jun 16, 2011 at 11:25 AM

    I've been mostly using MySQL lately, so I haven't revisted this blog post as early as I probably should have.

    Back in January I heard back from someone at Adobe on this issue. This is what I got back from him:

    In this case (datatype being uniqueidentifier and default value being newID() ) you can use the following configuration.

    <cfcomponent persistent="true" >
    <cfproperty name="id" fieldtype="id" ormtype="string" generator="guid" >
    </cfcomponent>

    <cfscript>
    p = entityNew("bug_85779");
    entitySave(p);
    ormFlush ();
    writeDump(p);
    </cfscript>

    Right now I'm testing with a "Question" entity with the primary key defined as this:

    property name="questionID" column="questionID" displayName="Question ID" fieldType="ID" generated="insert" generator="guid" ormtype="string" type="guid" length=36;

    It seems to be working without throwing any errors. Perhaps this blog post needs to be revised to state that in this case you will need to add fieldType="ID" ORMType="string" generator="GUID" in order to get this to work properly. That would be a much simpler solution...

Leave a Comment

Leave this field empty