Hi all, especially you, Chuck.
<preamble> So, I've been working on this project that has been around since the pre-Practical WebObjects days and it has several places where EOs (entire graphs of EOs, really) are being manually copied. Entity by entity, attribute-by-attribute, relationship-by-relationship. The code is relatively easy to understand and has worked quite well for years, but it's a lot of code and needs to be regularly updated to handle new model attributes and such.
Then I came along.
"Ooooh!" I said.
"This is soooo pre-2004! We should use EOCopyable," I said.
"It would make our code much simpler and less prone to break," I said.
"It's a lot fewer lines of code that we have to maintain because we won't have written it, Chuck did, years ago," I said.
"We don't have to worry about _his_ code not working!" I said.
I'm very clever this way.
Only EOCopyable bitch-slapped me back to reality by refusing to work correctly right out of the gate, and I couldn't very well claim it was Chuck's fault, because, well, this is WebObjects. _I_ make things not work. Not Chuck.
So here I've been all weekend (it's 4 in the morning, Monday morning now) and I think I've finally fixed it!
But as for the _why_ EOCopyable wasn't working … that I'm not quite sure of. EOF is a deep and murky ocean into which I wade with trepidation. </preamble>
I ran into problems using the EOCopyable interface and classes described in Practical WebObjects. When I try to deepCopy an EO, which then deepCopies the EO's toMany relationship, I will end up with an extra related object (the original related object, plus 2 copies) One of the copies retains links to the original EO and the other is linked to the duplicate EO.
When EOF tries to insert both, it trips a unique constraint because the original EO now has two identical related objects (identical except for their PK)
I have the following structure:
ProgramYear <->> CountryForProgramYear <<-> Country
each Entity has numerous attributes and other relationships as well.
What I want to do is to create a new ProgramYear for 2013, only it's pretty much exactly the same as the 2012 ProgramYear, including being related to the same countries and the attributes for each related country are the same as well. EOCopyable to the rescue!
In EOCopyable terms, I did the following:
1) I made ProgramYear implement EOCopyable 2) the ProgramYear#duplicate(NSMutableDictionary) method on ProgramYear calls the EOCopyable.DefaultImplimentation#duplicate(NSMutableDictionary, EOEnterpriseObject) 3) I made CountryForProgramYear implement EOCopyable 4) the CountryForProgramYear#duplicate(NSMutableDictionary) calls EOCopyable.Utility#shallowCopy(EOEnterpriseObject)
The problem is that I get constraint violations from the database because EOF is trying to insert two CountryForProgramYear records for one Country. One record for the new ProgramYear (the copy) and one for the _original_ ProgramYear.
"INSERT INTO "NSLIY"."COUNTRY_FOR_PROGRAM_YEAR"("ID", "SOME_FLAG_ID", "COUNTRY_ID", "PROGRAM_YEAR_ID", "DISPLAY_NAME") VALUES (1000032, 2, 1000001, 4002057, 'USA')" "INSERT INTO "NSLIY"."COUNTRY_FOR_PROGRAM_YEAR"("ID", "SOME_FLAG_ID", "COUNTRY_ID", "PROGRAM_YEAR_ID", "DISPLAY_NAME") VALUES (1000453, 2, 1000001, 4002011, 'USA')"
Why is it trying to insert a CountryForProgram record that is associated with the original ProgramYear (4002011)?! CountryForProgram has a unique constraint on the combination of COUNTRY_ID and PROGRAM_YEAR_ID and this insert is violating it because COUNTRY_FOR_PROGRAM_YEAR already has a record where COUNTRY_ID = 1000001 and PROGRAM_YEAR_ID = 4002011 - the original that I requested EOCopyable copy!
As I stepped though the debug, I noticed something interesting. In EOCopyable.java immediately after this call (line 679 & 680):
EOEnterpriseObject originalCopy = ((EOCopyable)original).copy(copiedObjects);
The duplicate CountryForProgram is associated with original ProgramYear - which is how it should be because this is an exact copy. But when the next line is called,
if ( ! destinationObjects.containsObject(originalCopy)) { destination.addObjectToBothSidesOfRelationshipWithKey(originalCopy, relationshipName); }
which adds the duplicated object to the destination's programYear relationship, change the relationship from pointing to the original object to pointing to the new ProgramYear object (destination), which it does, BUT (and here's the interesting part) the original ProgramYear#countriesForProgramYear() relationship still has two objects! One to the original CountryForProgram object AND one for the duplicate. When EOF tries to write both to the DB, it fails.
By simply changing the method to come at it from the other direction,
originalCopy.addObjectToBothSidesOfRelationshipWithKey(destination, relationship.inverseRelationship().name());
everything works as expected.
Here's the details:
ProgramYear.countryForPrograms relationship: { deleteRule = EODeleteRuleCascade; destination = CountryForProgramYear; isToMany = Y; joinSemantic = EOInnerJoin; joins = ({destinationAttribute = programYearID; sourceAttribute = ID; }); name = partnerOrgsForPIPY; ownsDestination = Y; },
CountryForProgram: attributesUsedForLocking = (id); className = "com.amagavi.ac.model.CountryForProgramYear"; classProperties = (displayName); externalName = "MULTI.COUNTR_FOR_PROGRAM_YEAR"; fetchSpecificationDictionary = {}; name = CountryForProgramYear; primaryKeyAttributes = (id);
CountryForProgramYear.programYear: { destination = ProgramYear; isMandatory = Y; isToMany = N; joinSemantic = EOInnerJoin; joins = ({destinationAttribute = ID; sourceAttribute = programYearID; }); name = programYear; }
I'm seeing this in many, but not all, to-many relationships and the kicker is that I see this exact same behavior on structures where the destination Entity of the toMany relationship has a compound PK.
It seems EOCopyable's calling of addObjectToBothSdesOfRelationshipWithKey doesn't actually work. But it should, right?
I swear I've used this before with MSSQL Server and Oracle without any problem. Now I'm using FrontBase and it's breaking, but I don't see how the DB engine / plugin would have any impact on what I'm seeing. It seems almost like it isn't updating both sides of the relationship and leaving the toMany in a bad state.
Dave
————————————————————————————— WebObjects - so easy that even Dave Avendasora can do it!™ ————————————————————————————— David Avendasora Senior Software Abuser Kaiten, Inc.
|