Symfony使用唯一约束条件插入或更新Doctrine实体

4

问题

我有一个业务场景,管理员需要导入一个中等规模的参与者列表,最多10,000条记录。实体在4个字段(姓名、姓氏、电子邮件和事件ID)上具有唯一约束条件。更新或插入新记录到数据库的最佳方法是什么?

可能的情况是,列表只被导入一次,然后更多的参与者被附加到Excel文件中,也许一些额外的非唯一字段在现有参与者上被改变,然后文件再次被导入。

我尝试过的方法

  1. 最初的想法是使用Doctrine的 merge() 函数,但这会导致约束违规,我的猜测是因为我的对象不包含一个id

  2. 第二种方法是在每次迭代时刷新并捕获异常,但这会导致实体管理器关闭。我尝试重置它,但无效。可能可以尝试持久化不抛出异常的实体,并在引发异常的实体上进行另一次迭代,但这意味着发送10,000个查询

  3. 另一个想法是选择所有现有参与者,将它们放入数组集合中,并在每次迭代时调用 exists()filter(),但这会导致过滤函数在每个集合元素上被调用,所以最终会有10,000 x 10,000 = 100,000,000次检查,性能非常差

  4. 另一个想法是获取现有参与者并连接唯一字段,将它们放入哈希映射中,理论上这可能会起作用

  5. 我的最后一个想法是尝试以100的块插入所有内容。如果插入成功,我们移动到另一个100的块。如果块抛出约束违规,我们将块分成10个小块,并尝试做同样的事情。只是不知道这在最坏情况下的表现如何


我的代码

实体:

class Person implements Comparable
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="surname", type="string", length=255)
     */
    private $surname;

    /**
     * @var string
     *
     * @ORM\Column(name="email", type="string", length=255)
     */
    private $email;

    /**
     * @var object
     *
     * @ORM\Column(name="extras", type="object")
     */
    private $extras;

    /**
     * @ORM\ManyToOne(targetEntity="Event")
     * @ORM\JoinColumn(name="event_id", referencedColumnName="id", nullable=false)
     */
    private $event;

<...>

    public function compareTo($other)
    {
        if(get_class($other) == Person::class)
        {
            if($other->getName() == $this->name
                && $other->getSurname() == $this->surname
                && $other->getEmail() == $this->email
                && $other->getEvent() == $this->event)
            {
                return true;
            }
        }

        return false;
    }
}

控制器:

    public function postTicketsAction(Request $request, Event $event)
    {
<...>
        if($form->isValid())
        {
<...>
            while($iterator->valid())
            {
<...>
                $person = new Person();
                $person
                    ->setEvent($event)
                    ->setName($name)
                    ->setSurname($surname)
                    ->setEmail($email)
                    ->setExtras($extras)
                ;

                $em->merge($person);
<...>
            }

            dump($failedPeople);
            $em->flush();
        }
    }
1个回答

0
在呼吸新鲜空气后,我决定使用字段标识作为一种检查现有记录的方法。
由于我们想要检查唯一性,我们将生成一个字符串,其中包含串联的唯一字段。但是我们还需要一个对象引用来合并到数据库中。这可以通过自定义存储库查询来解决。
由于这是一个数据库查询,所以速度非常快。

PersonRepository.php

<...>
public function getSlugs(Event $event)
    {
        $qb = $this->getEntityManager()->getRepository('AppBundle:Person')->createQueryBuilder('p');

        $qb->select('CONCAT(p.name, \'-\', p.surname, \'-\', p.email, \'-\', IDENTITY(p.event)) slug, p obj')
            ->where($qb->expr()->eq('p.event', $event->getId()))
        ;

        $res = $qb->getQuery()->getResult();

        return [
            'slugs' => array_column($res, 'slug'),
            'objects' => array_column($res, 'obj')
        ];
    }
<...>

接下来我们将其实现到控制器中

PersonController.php

public function postPeopleAction(Request $request, Event $event)
{
<...>
    // Handle file upload and iterator creation
<...>
    $existingPeople = $em->getRepository('AppBundle:Person')->getSlugs($event);

    while($iterator->valid())
    {
        $row = $iterator->current()->getRowIndex();

        $name = $sheet->getCell($requiredColumns['name'] . $row)->getValue();
        $surname = $sheet->getCell($requiredColumns['surname'] . $row)->getValue();
        $email = $sheet->getCell($requiredColumns['email'] . $row)->getValue();

        $search = "$name-$surname-$email-$eventId";
        $indexOfPerson = array_search($search, $existingPeople['slugs']);

        // person already exists
        if($indexOfPerson > -1)
        {
            $person = $existingPeople['objects'][$indexOfPerson];
            $person->setExtras($extras);
            $em->merge($person);
        }
        // this is a new person
        else
        {
            $person = new Person();
            $person
                ->setEvent($event)
                ->setName($name)
                ->setSurname($surname)
                ->setEmail($email)
                ->setExtras($extras)
            ;

            $em->persist($person);
        }

        $iterator->next();
    }

    $em->flush();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接