学说的嵌套实体级联存在:如何重用现有实体

时间:2019-04-03 12:02:07

标签: symfony doctrine-orm doctrine

如果实体A包含多个实体B并具有cascade:persist,那么在持久化时如何重用现有实体B?

B实体具有一个主键,一个整数和A父代的ID。它包含的唯一数据是主键。

示例: A有2个B实体,分别由其ID(分别为14和23)标识。

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

现在,如果我修改此受管实体,则将ID为56的B实体添加到A。

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尝试保留ID为14和23的现有实体。

我了解这是预期的行为,但是如何使它保留新实体并重用现有实体呢?


更多详细信息:

如果我获得一个具有$em->find($id)的现有实体A,并直接使用persist and 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键“ PRIMARY”的条目“ 777111”重复

1 个答案:

答案 0 :(得分:1)

如果我正确理解您的示例-您正在执行以下操作:

$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);
}

在这种情况下,新实体将在内部具有“托管”状态,并且在提交时将由教义进行正确处理。

相关问题