doctrine / orm

Doctrine Object Relational Mapper (ORM)
https://www.doctrine-project.org/projects/orm.html
MIT License
9.91k stars 2.51k forks source link

BC break - While adding an entity of class %s with an ID hash of "3" to the identity map, another object of class %s was already present for the same #10890

Open perice opened 1 year ago

perice commented 1 year ago

Bug Report

Q A
BC Break yes
Version 2.16.0

Summary

Error message: While adding an entity of class %s with an ID hash of "%d" to the identity map, another object of class %s was already present for the same same ID. This exception is a safeguard against an internal inconsistency - IDs should uniquely map to entity object instances. This problem may occur if:

Otherwise, it might be an ORM-internal inconsistency, please report it. - 4524

Current behavior

Exception (see above).

How to reproduce

Not sure; normal operations find / persist / flush

Expected behavior

Entity stored without issues (like in previous versions).

mpdude commented 1 year ago

We need a code snippet to reproduce or at least more details about your use case and the methods you’re calling. We won’t be able to look into this with the information you provided.

mpdude commented 1 year ago

Oh, the exception also gives hints about what might be wrong. Does any of the given situations apply for you?

perice commented 1 year ago

Oh, the exception also gives hints about what might be wrong. Does any of the given situations apply for you?

No, I don't think so. I'm not reusing ID values (auto increment), I didn't truncate the database, and the object with that ID exists in the database.

This is the code that throws that exception ($feed is loaded before without Location):

$this->cache[$feed->getId()] = $feed->getLocation() instanceof Location ? $feed->getLocation()->getId() : null;

and that's the relation definition in the Feed entity:

#[ORM\ManyToOne(targetEntity: Location::class)]
#[ORM\JoinColumn(name: 'location_id', referencedColumnName: 'id')]
private $location;

Don't seem like anything special, and it works in previous versions. My current workaround is downgrading to 2.15.*

mpdude commented 1 year ago

We will need a running code snippet that shows the problem in order to investigate.

Have a look at the functional tests in the tests/Doctrine/Tests/ORM/Functional/Ticket directory, that should help you get going. You can define the entity classes to use in the test file itself.

PapyDanone commented 1 year ago

I'm also running into this issue when clearing the ObjectManager ($em->clear()) while looping through paginated results:

<?php

namespace App\Response;

use Pagerfanta\Adapter\AdapterInterface;
use Pagerfanta\Doctrine\ORM\QueryAdapter;
use Pagerfanta\Pagerfanta;
use Symfony\Component\HttpFoundation\StreamedResponse;

class StreamedCSVResponse extends StreamedResponse
{
    public function __construct(AdapterInterface $adapter, callable $rowCallback, string $filename, int $maxPerPage = 500, int $status = 200, array $headers = [])
    {
        $pagination = (new Pagerfanta($adapter))->setMaxPerPage($maxPerPage);
        $em = null;

        if ($adapter instanceof QueryAdapter) {
            $em = $adapter->getQuery()->getEntityManager();
        }

        parent::__construct(null, $status, $headers);

        $this->setCallback(function () use ($rowCallback, $pagination, $em) {
            $handle = fopen('php://output', 'w');
            $first = true;

            foreach (range(1, $pagination->getNbPages()) as $i) {

                $pagination->setCurrentPage($i);

                foreach ($pagination->getIterator() as $item) {

                    $formattedData = $rowCallback($item);

                    if (empty($formattedData)) {
                        continue;
                    }

                    // set header row
                    if ($first === true) {
                        fputcsv($handle, array_keys($formattedData));
                        $first = false;
                    }

                    // set body
                    fputcsv($handle, $formattedData);

                    $em?->clear();

                    unset($item);
                }
            }

            $em?->clear();

            fclose($handle);
        });

        $this->headers->set('Content-Disposition', sprintf('attachment; filename="%s"', $filename));

        if (!$this->headers->has('Content-Type')) {
            $this->headers->set('Content-Type', 'text/csv');
        }
    }
}

The same code works with doctrine/orm 2.15.3, which I also had to downgrade to.

If this doesn't help I can provide more code when I have time.

mpdude commented 7 months ago

Remove the $em->clear() calls. Does that make a difference?

alexandre-daubois commented 7 months ago

We ran into the same issue on some legacy code and it indeed fixed the problem on our side. Thanks @mpdude 👍