【问题标题】:Trigger UniqueEntity after persisting and before flushing data在持久化数据之后和刷新数据之前触发 UniqueEntity
【发布时间】:2020-09-28 18:39:40
【问题描述】:

我正在通过学​​说实体管理器导入一种 csv 数据,但是,我有一个执行批处理的循环,正如在文档级别提到的那样。 https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/batch-processing.html#bulk-inserts

$validationErrors = [];
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');
    $errors = $this->validator->validate($user);
    if ($errors->count() === 0) {
        $this->persist($user);
    } else {
       $validationErrors[] = $errors;
    }
    
    if ($itratioNumber% 100 === 0) {
         $this->em->flush();
    }
}

return $validationErrors;

这是我的 User 类,它对电子邮件字段有一个独特的约束:

/**
 * @ORM\Entity
 * @UniqueEntity("email")
 */
class User
{
    /**
     * @ORM\Column(name="email", type="string", length=255, unique=true)
     * @Assert\Email
     */
    protected $email;
}

不幸的是,如果我的数据中有不止一行具有相同的电子邮件地址,UniqueEntity 验证将不会被触发,原因很简单,因为用户被持久化但没有被刷新到数据库中。

  • 解决方案 1:避免批处理并在每次迭代时执行刷新,这非常违反并且可能引发连接关闭原则或某种内存泄漏。
  • 解决方案 2:创建一个自定义约束,其灵感来自 UniqueEntity("email"),然后检查每个项目的电子邮件地址,如果有用户已经使用相同的邮件。

如果用户已经存在于数据库中,并且我们调用em-> persist(),我在$entityManager->getUnitOfWork()->getScheduledEntityInsertions()$entityManager->getUnitOfWork()->getScheduledEntityUpdates() 中都找不到任何持久对象。

仅在新插入期间,我洞对象在函数->getScheduledEntityInsertions()的响应中持续存在

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

【问题讨论】:

  • 您是否尝试过使用em->contains($user)?参考:stackoverflow.com/questions/17613684/…
  • 你可能想多了。只需在保留新实体时维护一份电子邮件列表,并在添加新实体之前检查该列表。
  • @Cerad 是否有适当的方法将该列表传递给我的验证者,我想我会使用会话!
  • 为什么需要会话数据?您确实必须为每个请求刷新,这将重新开始。或者也许我只是在考虑。

标签: symfony doctrine


【解决方案1】:

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

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

此代码 sn-p 不会规范化电子邮件地址,这应该首先完成(例如 strtolower 并且可能删除 +foo gmail 本地部分)。

【讨论】:

    【解决方案2】:

    除非您愿意将整个电子邮件加载到内存缓存中并使用 REDIS 或直接数组来解决这个问题,否则我看不到第二种方法,而是使用唯一性的 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 流,并从 doctrine documentation section 中获得灵感。

    P.S 我会考虑 DB 事务,但可能需要更多技巧。

    【讨论】:

      猜你喜欢
      • 2015-08-23
      • 1970-01-01
      • 2011-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多