Symfony 在持久化之后和刷新数据之前触发Uniquentity

Symfony 在持久化之后和刷新数据之前触发Uniquentity,symfony,doctrine,Symfony,Doctrine,我正在通过doctrine entity Manager导入一种csv数据,但是,我有一个循环,它执行文档级别提到的批处理。 这是我的用户类,它在电子邮件字段上有一个唯一的约束: /** * @ORM\Entity * @UniqueEntity("email") */ class User { /** * @ORM\Column(name="email", type="string", length=255,

我正在通过doctrine entity Manager导入一种csv数据,但是,我有一个循环,它执行文档级别提到的批处理。

这是我的用户类,它在电子邮件字段上有一个唯一的约束:

/**
 * @ORM\Entity
 * @UniqueEntity("email")
 */
class User
{
    /**
     * @ORM\Column(name="email", type="string", length=255, unique=true)
     * @Assert\Email
     */
    protected $email;
}
不幸的是,如果我的数据中有多行具有相同的电子邮件地址,则不会触发
uniquentity
验证,因为用户是持久化的,但不会刷新到数据库中

  • 解决方案1:避免批处理,并在每次迭代时进行刷新,这是非常违规的,可能会引发连接关闭原则或某种内存泄漏
  • 解决方案2:创建一个自定义约束,其灵感来源于
    uniquentity(“email”)
    ,然后检查每个项目的电子邮件地址,看是否有用户已经使用相同的邮件
问题是,如果用户已经存在于数据库中,并且我们调用
em->persist()
,我在
$entityManager->getUnitOfWork()->getScheduledEntityInsertions()
$entityManager->getUnitOfWork()->getScheduledEntityUpdates()
中都找不到任何持久化对象

只有在新的插入过程中,对象才会保留在函数的响应中
->getScheduleIdentityInsertions()

如果有人知道如何恢复
$entityManager->persist()
步骤后保存的实体,我将不胜感激。
或者仅仅是第三种解决方案,它允许我触发对电子邮件唯一性的验证,即使在批处理上下文中也是如此。

除非您愿意将整个电子邮件加载到内存缓存中,并使用REDIS或direct Array来处理,否则我看不到第二种方法,只能使用DB唯一性约束(即刷新)

考虑到您对内存过载的恐惧,您可以通过调用clear方法将持久化实体从托管池中分离出来。因此,您的代码如下所示:

foreach($data as $itrationNumber => $item) {
    /** @var User|null $user **/
    $user = $this->em->getRepository(User::class)->findOnBy(['email' => $item['email']]);
    $user = ($user) ? $user : new User();
    $user->setName('test');
    if ($this->validator->validate($user)->count() === 0) {
        $this->persist($user);
        $this->em->flush();

    }
    
        $this->em->clear();

}
我个人将此解决方案用于批量处理巨大的CSV流,并从中获得了灵感


另外,我会考虑DB事务,但可能需要更多技巧。

您应该保留第二点信息,以查看是否已处理电子邮件地址:

$emails = [];
foreach($data as $itrationNumber => $item) {
    if (isset($emails[$item['email']])) {
        continue;
    }

    /** @var User|null $user **/
    $user = $this->em->getRepository(User::class)->findOnBy(['email' => $item['email']]);
    $user = ($user) ? $user : new User();
    $user->setName('test');
    if ($this->validator->validate($user)->count() === 0) {
        $this->persist($user);
        $emails[$item['email']] = true;
    }
    
    if ($itratioNumber% 100 === 0) {
         $this->em->flush();
    }
}

此代码段没有规范化电子邮件地址,这应该首先完成(例如strtolower和删除+foo gmail本地部分)。

您是否尝试使用
em->contains($user)
?你可能想得太多了。只要在保存新实体时维护一个电子邮件列表,并在添加新实体之前检查该列表。@Cerad是否有合适的方法将该列表传递给我的验证器,我想我将使用会话!为什么需要会话数据?你必须为每一个会重新开始的请求刷新。或者我只是在想。
$emails = [];
foreach($data as $itrationNumber => $item) {
    if (isset($emails[$item['email']])) {
        continue;
    }

    /** @var User|null $user **/
    $user = $this->em->getRepository(User::class)->findOnBy(['email' => $item['email']]);
    $user = ($user) ? $user : new User();
    $user->setName('test');
    if ($this->validator->validate($user)->count() === 0) {
        $this->persist($user);
        $emails[$item['email']] = true;
    }
    
    if ($itratioNumber% 100 === 0) {
         $this->em->flush();
    }
}