Cascade merge on associated entities can insert too many rows through "Persistence by Reachability"

Details

Description

I think that the UnitOfWork needs to maintain a map of spl_object_hash($newEntity)->$managedEntity for entities that were persisted via reachability during a merge. doMerge should then only call persistNew if the original entity has not already been persisted (if it has already been persisted it should merge the managed entity from the map). The map should be maintained until a flush() or until the UnitOfWork is cleared. The reasoning is as follows.

Obviously in this particular case we should use the return value from the first merge() as the parameter of the second merge which would give correct behaviour.

However, now imagine one Doctor has many Patients and many Patients have one Doctor, all the associations have cascade merge enabled, and further assume that $d1 (Doctor id=1) is already in the database. We now attempt to create two patients and assign them to the existing doctor:

This actually results in 4 rows being added to the 'patients' table instead of 2, I think because $p1 and $p2 are getting persisted both as the root objects and then again from the patient->doctor->patients array. Since the cascade merging happens internally we can't replace the array contents with the managed return values without walking through the object graph (in which case there is no point in using cascade merge in the first place). Maintaining a map in UnitOfWork will allow doMerge to ensure it doesn't persist the same entities twice.

Benjamin Eberlei
added a comment - 11/Nov/10 6:36 AM I added it to "Working with Objects" and the descripton of Merge. Its not yet live on the site.
Using this current workaround has a performance impact, since more SELECT statements have to be issued against the database.

Dave Keen
added a comment - 11/Nov/10 8:30 AM Apologies for not being clear - only the 3rd patch (multipleaddmerge.diff) is relevant to the 'DUPLICATE KEY' error I am now talking about, but I'll put it in a nother ticket if you prefer.