So in the application I’m working on we have an entity, representing a patient, and also a one-to-many composition of various addresses of that patient. (Tracking of current/former/alternate/etc address is needed.) This composition is represented as a table onscreen, like you’d do with any similar construct.
In the patient entity we also have 3 separate one-to-one associations to address entities, representing the current, former, and alternate address. These are lookup fields onscreen, with their options DCs set to the same DC as the addresses table, so a user can only pick from addresses belonging to the current patient.
This all works fine, just as I’d expected… EXCEPT.
If you create a new patient, create a new address (or a couple), and assign one of said new addresses as one of the 1:1 associations (current/former/alt) … and then hit save, you get a “not null constraint” violation referencing the (mandatory) patient_id column of the address table. This only happens when everything is new. If you create the patient, create the addresses, save that, go back in and pick the current/etc…it works totally fine. It’s the act of selecting any of those associations that breaks it.
I CANNOT get it to happen in a bare test project; it works fine there. It DOES happen in a very bare test screen with a very bare test view in the same project, however.
I have no idea why this effect is happening; it kind of doesn’t make sense to me at all and I’ve been looking at this for hours now.
Even weirder is this bit - if you create two addresses… it works fine. It’s only if you create new patient, create new address, select said new address (on still-new patient) any of the current/former/etc, and then save.
Basically I’m wondering what could even be causing this strangeness.
UPDATE: I have it working (actually in this case; not working; failing) in a bare test project.
The thing I forgot is that PatientAddress extends a base Address entity which is set as a “MappedSuperclass.” Setting up a test project in this fashion does produce the effect mentioned in the original post.
Just create a new patient, add a new address (without saving said patient), set that newly created address as the current, then hit Ok/save. You’ll see the not-null violation.
If you create the patient and address, save it, go back in, set the current…it works fine.
Also if you create TWO addresses on a new patient and select the second as current… that works too without saving first.
testaddressweirdness.zip (79.0 KB)
This is the expected behavior.
PatientEdit screen, you have the main parent container,
patientDc. And there is an
addressesDc subordinate container associated with it.
A new patient record is created and has not yet been saved to the database.
Multiple new addresses are created simultaneously in the associated
addressesDc. They are also not yet saved in the database.
Now you select one of the new address entries as the value of the currentAddress attribute and use
addressesDc as the source of the options.
What happens when we try to call the screen cammit?
First, data is saved from
patientDc, and next the associated
addressesDc is cammited. It is not possible to save
addressesDc first because the entries in it refer to the ID of the entry in the main container.
This means that at the time of saving a record from
patientDc to the database, an error will occur because the
currentAddress attribute refers to a new address record that is not yet present in the database.
You can easily see this.
Create a new Patient record from the screen, create several new addresses, but do not fill in the
Make a cammit of the screen.
After that, you can successfully select one of the addresses for
currentAddress and save the full Patient record set.
Right - I even said that in the initial post.
However, if you create two addresses on a new record and set one of them as the current… it all works fine.
There’s something odd under the hood here and I’m sure there’s a way to make this work the way I need to.
Hm… Sorry, Yes, now I again carefully checked the script with the allocation of an address record without selection.
It’s really very strange that the error only occurs when the entry is highlighted in the address list.
It is certainly possible to clear the selection in the table before the write attempt, but it is very strange behavior…
I hadn’t even noticed the selection issue. I’ll check that out too. Very weird.
If the framework can save address records from the table with the proper (new) patient ID it should all work, since… they’re just entities in memory, the ID is there and I’m not sure where it’s getting “lost.”
Some strange interaction here I’m sure.
I just tested and the error happens whether or not anything is selected on the table. It’s simply a new patient, new address, pick said address as current, and try to save.
So…table selection has nothing to do with it.
Here is another version of the test project with the patient ID displayed everywhere, showing that it is known, matching, and set to the address entity and thus should all save correctly and not be null.
testaddressweirdness.zip (90.3 KB)
Heck, here’s yet another iteration - this one shows the value of currentAddress.patient.id in a field, as well as having the patient.id on the address table line! They’re all there, all matching, further proving the ID is known, matching, and is set to the relevant entity properties! I still can’t see where or why it’s nulling out right before persistence. A real head-scratcher, this is.
testaddressweirdness.zip (92.2 KB)
And it gets even a little weirder!
If you take off the “Mandatory” check on PatientAddress.patient (which of course then removes the not-null constraint on the DB)… then it all works correctly - no error, and it all goes into the DB correctly, with the correct ID (not a null) - so why, when the not-null/Mandatory is in effect, does something somewhere see a null there (and try to put it to the DB as a null)!?!?! It’s clearly not null since without the constraint it all goes in fine!
Seems like EclipseLink cannot order updates correctly in this case. When you create a Patient and Address and set this address as current in the Patient in one go, CUBA just forms a proper object graph and persists both entities in one persistence context (i.e. in a single transaction).
To save everything correctly in the database, EclipseLink should have made the following operations:
- insert Patient with empty current address
- insert Address with reference to Patient
- update Patient with the reference to address
But apparently it tries to do the following:
- insert Address with empty reference to Patient
- insert Patient with reference to current address
- update Address with the reference to Patient
and fails on the first step.
You already know the simplest workaround - just make the Address.patient field nullable to enable the first step. Or modify your screen logic to commit the screen in two steps: first Patient with the collection of addresses, then update the current address. For example, disable the current address field initially for new entity and enable it when the user clicks Save.
Yeah, I currently have logic in the controller that simply sets the current/former/alt address fields back to null if a user tries to set them (and also shows a notification saying “save first”) when EntityStates reports new. It works, but it’s not the best UX.
I could just remove the not-null constraint, since everything works fine in that case, but I feel weird about allowing that since that patient ID should never be allowed to be null… But then again, the application isn’t allowing a way to create it with a null (there’s no PatientAddress.edit/.browse; no way to create those outside the big patient screen)… so it’s a dilemma.