VirtualEntityService, populate(), Simple value error

Hi all

Here’s a cut-down version of a problem I’m having with the populate() function.

Firstly, the framework:
I have a PersonService that extends coldbox.system.orm.hibernate.VirtualEntityService.
PersonService works with the entity called ‘Person’.

‘Person’ sits on the database table T_PERSON.
T_PERSON has a column ALERTS_EMAIL_PECO_KY (number) nullable=yes

Then I have this code:

var person = PersonService.populate(
target=accreditation.attr(‘T_PERSON’),
memento={
TITLE_FREE_LB = event.getValue(‘TITLE_FREE_LB’, ‘’)
, LAST_NAME_UN_LB = event.getValue(‘LAST_NAME_UN_LB’, ‘’)
, FIRST_NAME_UN_LB = event.getValue(‘FIRST_NAME_UN_LB’, ‘’)
, ALERTS_EMAIL_PECO_KY = 2
}

, nullEmptyInclude=‘ALERTS_EMAIL_PECO_KY’
);

PersonService.save(person);

This populates the ALERTS_EMAIL_PECO_KY with 2. No problem.

However, if I change the value of ALERTS_EMAIL_PECO_KY to either an empty string or null or javacast null, I get the following error:

Error populating bean app.model.Person with argument ALERTS_EMAIL_PECO_KY of type class java.lang.String.
Simple values are booleans, numbers, strings, and date-time values. The value cannot be converted to a numeric because it is not a simple value.

Are empty strings and nulls not simple values??

And why is it demanding a numeric when nulls are allowed? The property in the model looks like this:
property name=“ALERTS_EMAIL_PECO_KY” column=“ALERTS_EMAIL_PECO_KY” type=“numeric” ormtype=“integer” default="";

I’ve probably missed something really simple but this is driving me nuts. Can anyone see what’s wrong?

Michael,

My suggestion: Remove your “default=‘’” attribute from your properties and add “ignoreEmpty=true” to your populate() arguments. This will let the DB handle the default nullification. On an update, if you’re nulling the column after it already has a value, you’ll need an explicit PersonService.setALERTS_EMAIL_PECO_KY(javacast(’null’,’’)), to set it as a null.

When you have a default attribute in your property and you nullify it, it then re-sets the property to the default mapped to the property. This results in an insert attempt with that default (in your case a string in a numeric column). I rarely use the a “default” attribute because of this, in any column which is nullable, and set the default in the DB column instead.

HTH,
Jon

Hi Jon

Thanks for the advice. So, I have modified the code thus:

The Model (removed the default="")
property name=“ALERTS_EMAIL_PECO_KY” column=“ALERTS_EMAIL_PECO_KY” type=“numeric” ormtype=“integer”;

var person = PersonService.populate(

target=accreditation.attr(‘T_PERSON’),
memento={
TITLE_FREE_LB = event.getValue(‘TITLE_FREE_LB’, ‘’)
, LAST_NAME_UN_LB = event.getValue(‘LAST_NAME_UN_LB’, ‘’)
, FIRST_NAME_UN_LB = event.getValue(‘FIRST_NAME_UN_LB’, ‘’)
, ALERTS_EMAIL_PECO_KY = javacast(‘null’, ‘’)
}
, ignoreEmpty=true
);
PersonService.save(person);

Which gives me a new error:
Element ALERTS_EMAIL_PECO_KY is undefined in a CFML structure referenced as part of an expression

Using a value of 2 instead of javacast(‘null’, ‘’) still works fine, so I assume the problem still lies in handling the null value.

Any thoughts…?

Right. Don’t javacast your nullable - your form values coming in in the event collection will be simple strings and numerics anyway. With ignoreEmpty=true, you will be able to pass an empty string and it will will nullify it for you (if it isn’t already set, in which case you’ll need to use the explicit setter). Unless you’re using Lucee with the full null support setting on, setting a struct key to null deletes the struct key - which is why it’s throwing the error.

Jon

Hi again

Okay, I changed the javacast(‘null’, ‘’) to simple empty strings ‘’.

Now I have no error (yay!). However, the value is not hitting the database (bah!). I can see in the console that I’m definitely posting an empty string, but the database value remains at 2 (from my previous test).

That’s because, once it’s set, you’ll need an explicit setter to re-set it (See my notes below).

What ignoreEmpty=true does is omit that column from the INSERT/UPDATE statement. It’s not a perfect solution, but I’ve found that when I need to be able to test a column for a null value, I’d rather have the explicit setter on update to ensure it’s exactly what I need it to be:

PersonService.populate(
target=accreditation.attr('T_PERSON'),  
memento={  
TITLE_FREE_LB = event.getValue('TITLE_FREE_LB', '')
, LAST_NAME_UN_LB = event.getValue('LAST_NAME_UN_LB', '')
, FIRST_NAME_UN_LB = event.getValue('FIRST_NAME_UN_LB', '')
, ALERTS_EMAIL_PECO_KY = event.getValue('ALERTS_EMAIL_PECO_KY', '')  
}
, ignoreEmpty=true
);

//Conditional to handle the explicit setter on an update
if(!len(event.getValue('ALERTS_EMAIL_PECO_KY', ''))){
    PersonService.setALERTS_EMAIL_PECO_KY(javacast("null",""));
}

PersonService.save(person);

The conditional is long and tedious, but it will ensure your column is exactly as it should be every time. If you need to test for a null value (ORM empty string vs numeric), then you need to be precise anyway, though.

On a side note, setting numeric columns in your DB to a default of 0 will save you some of this and ensure a numeric value is always returned instead of the empty string which an ORM entity returns on for a null column value.

Hi Jon

Thanks again for the advice. Still not out of the woods yet though.

I altered the code as suggested and I get a new error:

BaseORMService.MissingMethodException
Invalid method call: setALERTS_EMAIL_PECO_KY

The dynamic/static method you called does not exist

So I’m assuming that that’s because there truly isn’t a setALERTS_EMAIL_PECO_KY() method in the PersonService template, which is a service, not a persistent entity. The methods are in the persistent entity template ‘Person’. So does this mean I have to load the entity first, then do the setting? Seems a bit of a weird runaround…

Is “ALERTS_EMAIL_PECO_KY” mapped as a property in PersonService? If so, then add accessors=“true” as a component attribute for PersonService and the method should become available. On an ORM entity, the accessors value is always set to true.

No it isn’t.

PersonService is a service layer for interacting with the database. The properties are mapped in Person.

Am I missing something here…? :frowning:

However, I tried adding accessors=“true” to PersonService and got the same error.

It depends on how you have set your service properties to access Person from PersonService. Are you using the init() method in PersonService to configure the Virtual Entity Service instance? I assumed so as you were using populate() directly on PersonService:

public any function init(){
      super.init( "Person");
      return this;
  }

I’m assuming your PersonService is loaded prior to the update via the get(id) method, correct?

If so, then your accessor methods for Person should still be available as long as “ALERTS_EMAIL_PECO_KY” is a property in Person. If so, and you’re still receiving the error while using the setter, there’s something in the config that’s not correct. Have you tried an ORMReload() since you made the changes to the properties?

Are you using the init() method in PersonService to configure the Virtual Entity Service instance?
No. We have a small misunderstanding I think (my fault).

  • The code, ie. the call to PersonService.populate(), runs in the handler/person.cfc

  • handler/person.cfc has this property at the top:

  • property name=“PersonService” inject=“id:PersonService”;- And then (as mentioned in the original post) PersonService extends coldbox.system.orm.hibernate.VirtualEntityService.

  • And then VirtualEntityService extends coldbox.system.orm.hibernate.BaseORMService, which contains the populate() method.

Solved.

Thanks Jon for all your suggestions. Based on the info you gave me, here’s the final solution;

a) remove the ALERTS_EMAIL_PECO_KY entirely column from the PersonService.populate() call.
b) no need to add the attributes nullEmptyInclude or ignoreEmpty to PersonService.populate().
c) immediately after the call, add conditional statements to handle explicit setters on the Person object.
d) that’s all!

Final version therefore looks like this:

var person = PersonService.populate(
target=accreditation.attr(‘T_PERSON’),
memento={
TITLE_FREE_LB = event.getValue(‘TITLE_FREE_LB’, ‘’)
, LAST_NAME_UN_LB = event.getValue(‘LAST_NAME_UN_LB’, ‘’)
, FIRST_NAME_UN_LB = event.getValue(‘FIRST_NAME_UN_LB’, ‘’)
}
);

// Conditionals to handle the explicit setters for columns that might contain null
if(!len(event.getValue(‘ALERTS_EMAIL_PECO_KY’, ‘’))){
person.setALERTS_EMAIL_PECO_KY(javacast(‘null’, ‘’));
} else {
person.setALERTS_EMAIL_PECO_KY(event.getValue(‘ALERTS_EMAIL_PECO_KY’));
}

PersonService.save(person);