Re: EOCopyable Problems with toMany relationships
Re: EOCopyable Problems with toMany relationships
- Subject: Re: EOCopyable Problems with toMany relationships
- From: David Avendasora <email@hidden>
- Date: Wed, 28 Mar 2012 11:49:16 +0800
Okay,
It seems like it is a problem with Wonder's InverseRelationshipUpdater's handling of adding objects to to-many relationships.
From detailed examination - I stepped through the code line-by-line and recorded what objects and relationships exist at each point of the copy process - it appears that when Wonder intercepts includeObjectIntoPropertyWithKey it does fine at setting both sides of the new relationship but it interferes with or somehow causes the _removal_ of one side of the old relationship.
For example:
Using EOCopyable I copy a ProgramYear EO (let's call it "source") to make an identical one (let's call it "destination"), EOCopyable also copies the CountryForProgramYear EOs that are related via a to-many relationship called "countriesForProgramYear"
After it makes a duplicate of a CountryForProgramYear object EOCopyable then attempts to add the duplicate to the "destination" ProgramYear's countriesForProgramYear relationship using:
destinationProgramYear.addObjectToBothSidesOfRelationshipWithKey(duplicateCountryForProgramYear, "countriesForProgramYear")
Here's how the EOs look after that call (with Wonder's InverseRelationshipUpdater turned ON):
sourceProgramYear.countriesForProgramYear() = (originalCountryForProgramYear, *duplicateCountryForProgramYear*)
destinationProgramYear.countriesForProgramYear() = (*duplicateCountryForProgramYear*)
duplicateCountryForProgram.programYear = destination
Yes, that's correct. BOTH the sourceProgramYear and destinationProgramYear have pointers to the duplicateCountryForProgramYear object. The Temporary EOGlobalIDs are _identical_.
The good news is that both sides of the relationship do get updated (they should have anyway, we did call addObjectToBothSidesOfRelationshipWithKey after all). The bad news is that the sourceProgramYear still has a link to the duplicate.
To be clear, this doesn't seem to be something that is specific to EOCopyable! Any time you move an object that is in a to-many relationship from one EO to another EO by calling destination.addObjectToBothSidesOfRelationshipWithKey(Object,"toManyRelationship"), the source EO will still have links to the object after it is moved.
If I turn off Wonder's InverseRelationshipUpdater and call destination.addObjectToBothSidesOfRelationshipWithKey(Object,"toManyRelationship"), then everything works as expected and the source EO will be properly updated to no longer have a link to the object that was moved.
The other work-around is what I discovered below, which is to not ever call destination.addObjectToBothSidesOfRelationshipWithKey(object,"toManyRelationship") for a to-many relationship. Get the inverse, to-one relationship and call the addObject method on that. Such as: object.addObjectToBothSidesOfRelationshipWithKey(destination,"inverseToOneRelationship")
Dave
On Mar 27, 2012, at 2:49 AM, Chuck Hill wrote:
>
> On 2012-03-25, at 2:16 PM, David Avendasora wrote:
>
>> Hi all, especially you, Chuck.
>
> Oh Gawd! It is that Avendasora guy. Again!
>
>
>> <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.
>
> I am certain that EOF feels the same way. :-P
>
>
>> </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)
>
> As soon as I hear "extra object" I immediately think "Owns Destination and Propagate Primary Key" as EOF likes to helpfully auto create objects. But one of them is not empty with no attribute values, is it?
>
>
>> 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)?!
>
> It looks like a shallow copy of ProgramYear.
>
>
>> 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
>
> Are you sure that this is only creating one CountryForProgramYear? My guess is that it is creating one and EOF is creating a second. Somehow.
>
>
>> - 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.
>
> Though that suggests that something else is wrong unless it is addObjectToBothSidesOfRelationshipWithKey that is somehow creating the second new object. Trying breaking in the CountryForProgramYear and see where each is created from.
>
>
>
>> Here's the details:
>>
>> WO 5.4.3, WOnder, Head of http://github.com/amagavi/wonder
>>
>> 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
>
> Are they modeled exactly the same?
>
>
>> 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?
>
> It should, but your EO or superclasses maybe subtly altering what it does.
>
>
>> 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.
>
> I still think it sounds like something in your model that is causing the copying to not work like you intend. I am not saying the model is wrong, but you maybe have something setup that I never tested with.
>
>
> Chuck
>
>
> --
> Chuck Hill Senior Consultant / VP Development
>
> Practical WebObjects - for developers who want to increase their overall knowledge of WebObjects or who are trying to solve specific problems.
> http://www.global-village.net/gvc/practical_webobjects
>
>
>
>
>
>
>
>
—————————————————————————————
WebObjects - so easy that even Dave Avendasora can do it!™
—————————————————————————————
David Avendasora
Senior Software Abuser
Kaiten, Inc.
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Webobjects-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden