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      (Webobjects-dev@lists.apple.com)
Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/webobjects-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to