Orm: EntityManager wants to persist an existing relationship value

Created on 7 Apr 2018  路  9Comments  路  Source: doctrine/orm

So this is going to be a lot, but here is a cut down version of what I'm working with. I have three entities (technically four) at play here. Appointment, Conversion (AppointmentConversion) and Profile. Here is the setup for each entity:

/**
 * @ORM\Entity
 */
class Appointment
{
    /**
     * @var Conversion
     *
     * @ORM\ManyToOne(targetEntity="AppointmentConversion", cascade={"persist"}, inversedBy="appointment")
     * @ORM\JoinColumn(name="conversionId", referencedColumnName="id")
     */
    private $conversion;
}

```php
/**

  • @ORM\Entity()
    /
    class AppointmentConversion extends Conversion
    {
    /
    *

    • @var Appointment

      *

    • @ORM\OneToOne(targetEntity="Appointment", mappedBy="conversion", fetch="EAGER")

      */

      private $appointment;

      }

```php
/**
 * @ORM\Entity
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="conversionTypeId", type="integer")
 * @ORM\DiscriminatorMap({
 *  2 = "AppointmentConversion",
 *  1 = "Conversion"
 * })
 */
class Conversion
{
    /**
     * @var Profile
     *
     * @ORM\ManyToOne(targetEntity="Profile")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="profileId", referencedColumnName="id")
     * })
     */
    private $profile;
}
/**
 * @ORM\Entity
 */
class Profile
{
}

For what I'm trying to do, I need the appointment, the conversion, and the profile. So I'm doing the following:

public function getAppointmentForConversion(string $publicId): Appointment
    {
        return $this->getEntityManager()->createQueryBuilder()
            ->select('a, c, p')
            ->from($this->getEntityName(), 'a')
            ->join('a.conversion', 'c')
            ->join('c.profile', 'p')
            ->addCriteria(
                Criteria::create()->where(Criteria::expr()->eq('a.publicId', $publicId))
            )
            ->getQuery()
            ->getSingleResult();
    }

I use the profile object to get a value for a third party API call, but otherwise I'm only updated the Appointment and Conversion (AppointmentConversion) and persisting both entities and then at the end flushing. Whenever I call flush I get the following message:

A new entity was found through the relationship 'Conversion#profile' that was not configured to cascade persist operations for entity: Profile@000000005854d8b6000000004f05f655. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example @ManyToOne(..,cascade={\"persist\"}). If you cannot find out which entity causes the problem implement 'Profile#__toString()' to get a clue.

If I persist this object it tries to insert as a brand new profile when it already exists. I am fairly new to Doctrine so try as I may and xdebugging every which way, I cannot for the life of me figure out why the entity manager whats to persist the profile object when I just need to make updates to the Appointment and Conversion (AppointmentConversion)

Edit:

For more context I'm doing something like this:

$appointment = getAppointmentForConversion('someId');
$conversion = $appointment->getConversion();
$profile = $conversion->getProfile();
// At this point all three entities are rows that already exist in the database.

$this->someThirdPartyApiCall($profile); // Only need the profile to make a third party API call

$conversion->setFoo('new val');
$appointment->setBar('also new val');

$em->persist($conversion);
$em->persist($appointment);
$em->flush();

Most helpful comment

@zquintana Sure thing, just left it open incase there was anything else to follow up on.

For anyone else who runs into this, on top of Zachary's suggestions, don't forget to make sure all of your classes are getting the same instance of the EntityManager

All 9 comments

It's hard to see what's going on without knowing the rest of the process that is updating the object. That said, it looks like the problem you have is that the profile object you are setting on the Conversation entity isn't persisted before it's flushed. This can happen if you are creating a new Profile object and setting it on the Conversation without persisting it with the entity manager. There are a few other ways I've seen this can happen, but again those are fringe cases that without knowing more about your application would only add noise to solving the issue.

@zquintana I updated my description with an example of what I'm roughly doing and putting it here as well. But in this case I'm not making a new profile object, the profile object already exists and is fetch joined by the DQL.

$appointment = getAppointmentForConversion('someId');
$conversion = $appointment->getConversion();
$profile = $conversion->getProfile();
// At this point all three entities are rows that already exist in the database.

$this->someThirdPartyApiCall($profile); // Only need the profile to make a third party API call

$conversion->setFoo('new val');
$appointment->setBar('also new val');

$em->persist($conversion);
$em->persist($appointment);
$em->flush();

Seems odd, by sound of the error I still think that the profile object is getting attached some how to the conversation before it is persisted. Are you using lazy getter in conversation getProfile that creates a Profile when it doesn't exist? Also have you checked the query that is just to make sure it's creating the query you're expecting? Is this in Symfony project? If it is, I'd suggest using the profiler to see the query generated and the validate the schema. You can also validate your schema with the built in doctrine tool, have you tried that to make sure everything is mapped correctly from Doctrines stand point?

@zquintana So the project object is coming "pre-attached" to the conversion based on the following line from the above DQL:

->join('a.conversion', 'c')
->join('c.profile', 'p')

So no lazy getting, anything lazy that'd happen is if I made Doctrine use a reference proxy to lazy load the profile first. But since I'm explicitly fetch-joining it, that shouldn't be the case.

When I checked the query on Friday it was loading all the correct data (not gonna paste it cause it's a long long long select statement)

This is a Laravel project, but I have query logging enabled at the moment. When you say You can also validate your schema with the built in doctrine tool, have you tried that to make sure everything is mapped correctly from Doctrines stand point?, which tool are you referencing?

If you've setup with the laravel doctrine package you should have the artisan command doctrine:schema:validate. That's the tool that validates your mapping data with Doctrine.

I see that the join is suppose to select the profile and load it's relation, but the error you're getting implies that the profile attached to the conversation is not managed. I've used Doctrine for a while and the only times I've seen that happen is when the object is being set accidentally to the property. Generally when a query is made via the entity manager any mapped entities are added to the unit of work right after they're mapped to entities. So if you're getting that error and the you are receiving an entity, it's likely that another process you have in place is overwriting that object with another unmanaged entity. To hunt it down, check the rest of the code that sets the profile object on the conversation. I'd look at everything else if you're confident that the query is executing and returning the expected result set.

@zquintana So I used the validate command, had a few unrelated fixed but cleaned some things up. Thanks for the tip!

I think I may have figure out what is going on here. We have multiple databases, and as such we are having to utilize multiple EntityManagers. My repository with the custom query was getting the right entity manager, but my service class was getting the default manager that was missing the context of all the entities that were just fetched.

Second I fixed that, boom everything worked. That was.. fun to figure out. But I highly appreciate the help here

@hskrasek, yep no problem. Glad you were able to figure it out. Sounded like it something outside scope of the context you originally provided. Happy to hear your figured it out. Can you close your issue to keep this clean?

@zquintana Sure thing, just left it open incase there was anything else to follow up on.

For anyone else who runs into this, on top of Zachary's suggestions, don't forget to make sure all of your classes are getting the same instance of the EntityManager

@hskrasek I've that problem. I'm using an EM for read and other for write. I foudn this error by migrating from doctrine 2.5 to 2.5.11, so maybe, it's a problem of defaults confguration? do you know more about?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

doctrinebot picture doctrinebot  路  4Comments

doctrinebot picture doctrinebot  路  3Comments

doctrinebot picture doctrinebot  路  3Comments

weaverryan picture weaverryan  路  3Comments

kcassam picture kcassam  路  3Comments