如果实体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”重复
答案 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);
}
在这种情况下,新实体将在内部具有“托管”状态,并且在提交时将由教义进行正确处理。