Сохранение вложенных сущностей в доктрине: как повторно использовать существующие сущности - PullRequest
0 голосов
/ 03 апреля 2019

Если объект A содержит несколько объектов B и имеет каскад: persist, как повторно использовать существующие объекты B при сохранении?

Объект B имеет один первичный ключ, целое число и идентификатор родительского элемента A. Единственные данные, которые он содержит, это первичный ключ.

Пример: A имеет 2 объекта B, идентифицированных по их идентификаторам, 14 и 23.

A.Bs = [{id=14, AId=A.id}, {id=23, AId=A.Id}]

Теперь, если я изменю этот управляемый объект, добавим в A объект B с id = 56.

A.Bs = [{id=14, AId=A.id}, {id=23, AId=A.Id}, {id=56}]

Отношения

Сущность A

/**
 * @var B[]|ArrayCollection
 *
 * @ORM\OneToMany(targetEntity="B", mappedBy="A", cascade={"persist", "remove"}, orphanRemoval=true)
 * @Assert\Valid
 */
private $Bs;

Сущность B

/**
 * @var A
 *
 * @ORM\ManyToOne(targetEntity="A", inversedBy="Bs")
 * @ORM\JoinColumn(name="A_id", referencedColumnName="A_id")
 * @Assert\NotNull()
 */
private $A;

Если я пытаюсь сохранить, я получаю Integrity constraint violation, потому что Doctrine пытается сохранить существующие сущности с идентификаторами 14 и 23.

Я понимаю, что это ожидаемое поведение, но как я могу заставить его сохранять новые сущности и повторно использовать существующие?


Подробнее:

Если я получу существующую сущность A с $em->find($id) и напрямую использую persist и flush, я получу UniqueConstraintException, потому что она пытается сохранить уже сохраненную сущность B.

Пример кода:

/** @var A $existingEntityA */
$existingEntityA = $this->getEntity($id);
$this->serializerFactory->getComplexEntityDeserializer()->deserialize(json_encode($editedEntityADataJson), A::class, 'json', ['object_to_populate' => $existingEntityA]);

$this->entityValidator->validateEntity($existingEntityA);

$this->_em->flush();

Пример ошибки: нарушение ограничения целостности: 1062 Повторяющаяся запись '777111' для ключа 'ПЕРВИЧНАЯ'

1 Ответ

2 голосов
/ 03 апреля 2019

Если я правильно понимаю ваш пример - вы делаете что-то вроде этого:

$b = new B();
$b->setId(56);
$a->getB()->add($b);

и у вас есть строка с первичным ключом 56 в таблицу базы данных, которая представлена ​​B?

Если мои предположения верны - это неправильный путь.Причина в том, что Doctrine хранит внутри себя так называемую «карту идентичности», которая отслеживает все сущности, которые либо извлекаются из базы данных, либо сохраняются путем вызова EntityManager::persist().Каждый объект, который запланирован для фиксации, но недоступен в карте идентификации, считается «новым» и запланирован для вставки.Если строка с таким же первичным ключом уже доступна в базе данных - вы получаете UniqueConstraintException.

Doctrine не обрабатывает случай «позвольте мне посмотреть, есть ли объект с таким первичным ключом в базе данных» сам по себе, потому что это значительно снизит производительность и не требуется в большинстве случаев.Каждый такой тест приведет к запросу к базе данных, представьте, если у вас будет тысячи таких объектов.Так как Doctrine не знает бизнес-логики вашего приложения - он потратит еще больше ресурсов на попытки угадать оптимальную стратегию, поэтому она намеренно не входит в сферу применения.

Правильный путь для вас - получить вашу сущность путемсам перед добавлением в коллекцию:

$newB = $em->find(B::class, 56);
if ($newB) {
  $a->getB()->add($newB);
}

В этом случае новый объект будет иметь статус «управляемый» и будет корректно обрабатываться Doctrine во время принятия.

...