我在Symfony项目中使用Doctrine来管理应用程序中的持久层。
最近,我在将实体的更改保存到数据库时遇到了一些问题。我遇到的问题是,当我更新实体并将其保存到数据库时,有时EntityManager
会将我的实体视为新对象,因此它不会执行更新操作,而是执行插入操作,从而在数据库中导致了一个独特的异常错误。
正如文档所述,更新对象时,您应该只执行以下步骤:
- 从Doctrine中提取对象
- 修改对象
- (可选)* 在实体管理器上调用
persist()
- (可选)* 在实体管理器上调用
- 在实体管理器上调用
flush()
注我在persist()
调用中添加了 (可选),因为正如文档所述,由于Doctrine已经在监视对象的更改,因此没有必要添加 (可选)
现在事情已经解释清楚了,下面是我在代码中所做的工作:
$myEntity = $this->myEntityRepository->byId($id);
// make some changes to the entity
$myEntity->setSomething('something');
$this->myEntityRepository->save($entity);
其中,我的存储库中的save()
操作如下所示:
$this->entityManager->persist($entity);
$this->entityManager->flush();
而byId()
运算:
return $this->entityManager->getRepository()->find($id);
正如我所说的,只有在持久化新实体时才应该调用持久化操作,但是由于Doctrine可以区分已经托管的实体和新实体,所以应该没有问题。如果我不调用persist()
方法,而不是执行和插入操作并导致唯一的违例,它实际上什么也不做,因为它不会检测到我的操作的任何更改。
我总是使用persist()
方法的原因是,我的存储库中的save()
操作既用于新实体,也用于对现有实体的更新。
正如我在another answer中所看到的,调用merge()
操作而不是persist()
应该可以解决问题,但我认为这是不正确的,因为我认为这只是一个“肮脏”的解决方案,加上Doctrine未来版本中的方法is being deprecated。
那么,我在这里遗漏了什么呢?为什么在运行上面的代码时,有时我会得到一个独特的错误呢?我在我的应用程序中只配置了一个连接和一个实体管理器。
我想补充的是,这个问题只出现在消费者(异步事件)中执行的代码中,而不是在API本身中,但每当我收到一个新事件时,都会创建一个新的数据库连接,以确保我不会遇到与以前某个事件中使用的实体管理器重叠的问题。
当谈到消费者时,我的意思是通过RabbitMQ发布一个事件(事件包含实体的ID),然后在一个独立于API的进程中消费它,直接使用实体管理器存储库获取实体。
我的猜测是,在我从存储库中获取实体的那一行(即,我使用find()
方法)和我将其保存到数据库中的那一行(即,我使用flush()
方法)之间,实体管理器以某种方式将实体从其UnitOfWork中删除,因此它将其视为新实体,而不是托管实体。
1条答案
按热度按时间aemubtdh1#
我最近在一些代码中发现的一个问题是实体在哪里被传递-通过RabbitMq消息和从头创建的实体,包括id --
$x = new Entity(); $x->setId($data['id');$x->setName($data['name'); # ... etc
)。然而,设置id并不会使实体被Doctrine“管理”--只会阅读原始记录(从DB),然后 * 更新它。如上所述持久化$x将创建一个新记录--并忽略设置的id。
我将确保(也许在记录上使用一些Assert,您正在获取的必须通过--或者至少现在因错误而终止),以便知道您总是在处理一个已知的实体(我还假设您的
->byId($id)
调用在内部只是一个->find($id)
)。