Doctrine 2 - ORM
  1. Doctrine 2 - ORM
  2. DDC-729

When merging many to many entities back into the repository all associations are deleted on the next flush

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Critical Critical
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: 2.0-BETA4
    • Component/s: ORM
    • Security Level: All
    • Labels:
      None

      Description

      When merging a DETACHED entity into the repository with a ManyToMany association, the entries in the join table are deleted on the next flush.

      Many Movies have many Artists:

      class Movie {
      	
      	/** @Id @Column(type="integer") @GeneratedValue(strategy="IDENTITY") */
      	public $id;
      	
      	/** @Column(length=50, type="string") */
      	public $title;
      	
      	/** 
      	 * @ManyToMany(targetEntity="Artist")
      	 */
      	public $artists;
      	
      	public function __construct() {
      		$this->artists = new ArrayCollection();
      	}
      	
      }
      
      class Artist {
      	
      	/** @Id @Column(type="integer") @GeneratedValue(strategy="IDENTITY") */
      	public $id;
      	
      	/** @Column(length=50, type="string") */
      	public $name;
      	
      	/** @ManyToMany(targetEntity="Movie", mappedBy="artists") */
      	public $movies;
      	
      	public function __construct() {
      		$this->movies = new ArrayCollection();
      	}
      	
      }
      

      Assume that the database contains:
      Movie: id=1, title="Movie 1"
      Artist: id=1, name="Artist 1"

      and that there is a entry (1, 1) in movie_artist so that there is a many-many relationship between Movie 1 and Artist 1.

      I then run the following code to merge the existing associations into the entity manager:

      $m1 = new \vo\Movie();
      $m1->id = 1;
      $m1->title = "Movie 1";
      
      $a1 = new \vo\Artist();
      $a1->id = 1;
      $a1->name = "Artist 1";
      
      $m1->artists->add($a1); $a1->movies->add($m1);
      
      $m1 = $em->merge($m1);
      $m1->artists->set(0, $em->merge($a1));
      $a1->movies->set(0, $em->merge($m1));
      

      At this point $m1 should contains merged entities reflecting the same as what is in the database.

      If I now run:

      $em->flush()
      

      the association is deleted from movie_artist and the SQL log shows DELETE FROM Movie_Artist WHERE Movie_id = '1' as having been run.

      Debugging $m1 both before and after the flush shows the expected values, and $em->getUnitOfWork()->computeChangeSets() is empty before the flush.

        Activity

        Hide
        Benjamin Eberlei added a comment -

        Can you explain why you merge $m1 twice?

        Show
        Benjamin Eberlei added a comment - Can you explain why you merge $m1 twice?
        Hide
        Dave Keen added a comment -

        Its a representation of how the algorithm I am using works - it assumes that once an entity is managed merge() will just return the already managed entity.

        Anyway, to be sure I just ran another test that only calls merge() once, and it has the same behaviour.

        Show
        Dave Keen added a comment - Its a representation of how the algorithm I am using works - it assumes that once an entity is managed merge() will just return the already managed entity. Anyway, to be sure I just ran another test that only calls merge() once, and it has the same behaviour.
        Hide
        Benjamin Eberlei added a comment -

        Fixed

        Show
        Benjamin Eberlei added a comment - Fixed
        Hide
        Dave Keen added a comment -

        A slightly different many to many merge bug is occurring - I am re-opening this ticket for it because a) I expect it is somewhat related and b) the example to recreate follows on from the example in this bug.

        Now assume the database has one more artist:

        Movie: id=1, title="Movie 1"
        Artist: id=1, name="Artist 1"
        Artist: id=2, name="Artist 2"

        and that there is still only an entry (1, 1) in movie_artist so that there is a many-many relationship between Movie 1 and Artist 1.

        I then run the following code to merge the existing entity AND add a new association between artist 2 and movie 1:

        $m1 = new \vo\Movie();
        $m1->id = 1;
        $m1->title = "Movie 1";
        
        $a1 = new \vo\Artist();
        $a1->id = 1;
        $a1->name = "Artist 1";
        
        $a2 = new \vo\Artist();
        $a2->id = 2;
        $a2->name = "Artist 2";
        
        $m1->artists->add($a1); $a1->movies->add($m1);
        $m1->artists->add($a2); $a2->movies->add($m1);
        
        $m1 = $em->merge($m1);
        
        $m1->artists->set(0, $em->merge($a1));
        $a1->movies->set(0, $em->merge($m1));
        
        $m1->artists->set(1, $em->merge($a2));
        $a2->movies->set(1, $em->merge($m1));
        

        If I now run:

        $em->flush();
        

        Instead of getting a (1, 2) entry added to movie_artist as expected, the change sets are empty and I get the following error:

        Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1-2' for key 'PRIMARY'' in D:\Projects\ORM\doctrine2\lib\vendor\doctrine-dbal\lib\Doctrine\DBAL\Connection.php on line 646
        ( ! ) PDOException: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '1-2' for key 'PRIMARY' in D:\Projects\ORM\doctrine2\lib\vendor\doctrine-dbal\lib\Doctrine\DBAL\Connection.php on line 646
        Call Stack

        1. Time Memory Function Location
          1 0.0006 400744 {main}

          ( ) ..\index.php:0
          2 0.0933 4375416 Doctrine\ORM\EntityManager->flush( ) ..\index.php:62
          3 0.0934 4375416 Doctrine\ORM\UnitOfWork->commit( ) ..\EntityManager.php:320

        Show
        Dave Keen added a comment - A slightly different many to many merge bug is occurring - I am re-opening this ticket for it because a) I expect it is somewhat related and b) the example to recreate follows on from the example in this bug. Now assume the database has one more artist: Movie: id=1, title="Movie 1" Artist: id=1, name="Artist 1" Artist: id=2, name="Artist 2" and that there is still only an entry (1, 1) in movie_artist so that there is a many-many relationship between Movie 1 and Artist 1. I then run the following code to merge the existing entity AND add a new association between artist 2 and movie 1: $m1 = new \vo\Movie(); $m1->id = 1; $m1->title = "Movie 1" ; $a1 = new \vo\Artist(); $a1->id = 1; $a1->name = "Artist 1" ; $a2 = new \vo\Artist(); $a2->id = 2; $a2->name = "Artist 2" ; $m1->artists->add($a1); $a1->movies->add($m1); $m1->artists->add($a2); $a2->movies->add($m1); $m1 = $em->merge($m1); $m1->artists->set(0, $em->merge($a1)); $a1->movies->set(0, $em->merge($m1)); $m1->artists->set(1, $em->merge($a2)); $a2->movies->set(1, $em->merge($m1)); If I now run: $em->flush(); Instead of getting a (1, 2) entry added to movie_artist as expected, the change sets are empty and I get the following error: Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE [23000] : Integrity constraint violation: 1062 Duplicate entry '1-2' for key 'PRIMARY'' in D:\Projects\ORM\doctrine2\lib\vendor\doctrine-dbal\lib\Doctrine\DBAL\Connection.php on line 646 ( ! ) PDOException: SQLSTATE [23000] : Integrity constraint violation: 1062 Duplicate entry '1-2' for key 'PRIMARY' in D:\Projects\ORM\doctrine2\lib\vendor\doctrine-dbal\lib\Doctrine\DBAL\Connection.php on line 646 Call Stack Time Memory Function Location 1 0.0006 400744 {main} ( ) ..\index.php:0 2 0.0933 4375416 Doctrine\ORM\EntityManager->flush( ) ..\index.php:62 3 0.0934 4375416 Doctrine\ORM\UnitOfWork->commit( ) ..\EntityManager.php:320
        Hide
        Benjamin Eberlei added a comment -

        There is no CASCADE=MERGE on this relation or?

        Show
        Benjamin Eberlei added a comment - There is no CASCADE=MERGE on this relation or?
        Hide
        Dave Keen added a comment -

        No - I think I am simulating what cascade merge would do though.

        Show
        Dave Keen added a comment - No - I think I am simulating what cascade merge would do though.
        Hide
        Benjamin Eberlei added a comment -

        I cannot reproduce this with any case.

        Additionally your algorithm is REALLY slow compared to doing this natively in the UnitOfWork. The merge operation does lots of stuff even if the entity is already merged before.

        Show
        Benjamin Eberlei added a comment - I cannot reproduce this with any case. Additionally your algorithm is REALLY slow compared to doing this natively in the UnitOfWork. The merge operation does lots of stuff even if the entity is already merged before.
        Hide
        Benjamin Eberlei added a comment -

        Added 3 more tests that verify the correct behavior. Please add a failing test-case for this and open up a new ticket.

        Show
        Benjamin Eberlei added a comment - Added 3 more tests that verify the correct behavior. Please add a failing test-case for this and open up a new ticket.
        Hide
        Dave Keen added a comment -

        Thanks for pointing out the inefficiency in the algorithm - you are completely right and as recommended I have changed the Flextrine codebase to use UnitOfWork merge (by internally changing the metadata to enable cascade). However, there still seems to be a bug mergng many to many - I will open it up in a new ticket. Cheers

        Show
        Dave Keen added a comment - Thanks for pointing out the inefficiency in the algorithm - you are completely right and as recommended I have changed the Flextrine codebase to use UnitOfWork merge (by internally changing the metadata to enable cascade). However, there still seems to be a bug mergng many to many - I will open it up in a new ticket. Cheers

          People

          • Assignee:
            Benjamin Eberlei
            Reporter:
            Dave Keen
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: