当使用“AUTO”策略时,使用Doctrine明确设置Id

116

我的实体使用这个注解作为它的ID:

/**
 * @orm:Id
 * @orm:Column(type="integer")
 * @orm:GeneratedValue(strategy="AUTO")
 */
protected $id;

我从一个干净的数据库中导入旧数据库中的现有记录,并尝试保持相同的ID。然后,在添加新记录时,我希望MySQL像往常一样自动递增ID列。

不幸的是,看起来Doctrine2完全忽略了指定的ID。


新解决方案

根据下面的建议,以下是首选解决方案:

$this->em->persist($entity);

$metadata = $this->em->getClassMetaData(get_class($entity));
$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
$metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());

旧方案

因为Doctrine基于ClassMetaData来确定生成器策略,所以必须在EntityManager中管理实体后进行修改:

$this->em->persist($entity);

$metadata = $this->em->getClassMetaData(get_class($entity));
$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);

$this->em->flush();

我刚在MySQL上测试了这个,结果符合预期,也就是说,带有自定义ID的实体将以该ID存储,而没有指定ID的实体将使用lastGeneratedId() + 1


2
Eric,没事了...我知道你想做什么。你基本上需要一个@GeneratedValue(strategy="ItDepends") :) - Wil Moore III
1
关于这个问题,需要注意的一点是,似乎没有"isPostInsertGenerator" == true的ID生成器已经运行过了。您可以在持久化之后更改ID的值,但是将会丢失序列号。 - gview
16
新的解决方案现在允许我在Doctrine Fixture中设置id,但是使用如下代码:$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);可以允许设置并保存id。(适用于MySQL) - jmoz
请注意,实体中的 id 不能手动设置为 0。您必须使用更大的数字。当指定为 0 时,生成器仍将使用 AUTO 策略。在 doctrine 2.4 中进行了测试。 - Szymon Sadło
2
那个新的解决方案在Symfony 3.0中不起作用。我不得不使用 $metadata = $this->getEntityManager()->getClassMetaData(User::class); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE); - piotrekkr
显示剩余9条评论
7个回答

56

尽管你的解决方案可以在MySQL中正常工作,但我无法使其在PostgreSQL上工作,因为它是基于序列的。

我必须添加这行代码才能使其完美运行:

$metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());


谢谢!自从这个问题首次出现以来,Doctrine已经有所改进,因此我已经接受了您的答案并相应地更新了我的原始票据。 - Eric
谢谢,我很高兴能尽我所能地帮助一点 :) - nicolasbui
3
这会永久设置生成器吗?我可以添加一个强制ID的记录,然后让它使用自增ID吗? - Pavel Dubinin
1
我可以确认这适用于Symfony 3.2。然而,我没有预料到的是,在执行$em->persist($entity)之后必须设置生成器。 - bodo

32

或许教义已经改变,但现在正确的方式是:

$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);

1
这仍然是相关信息,并适用于Doctrine 2.4.1,但应该删除@gphilip提到的第二行。 - Mantas
由于ClassMetadata是一个接口,因此不能有任何常量,所以无法用于Doctrine >2.5。 - TiMESPLiNTER
有一个类ClassMetadata - Alexey B.
@gphilip 如果你想要它与关联数组一起工作,第二行很重要。(https://dev59.com/b-o6XIcBkEYKwwoYFQDR) - Taz
1
可以通过使用 $metadata::GENERATOR_TYPE_NONE 进行简化。 - Will B.

7

如果该实体是类表继承的一部分,则需要更改类元数据中两个实体(您正在持久化的实体和根实体)的id-generator


我认为情况是你只需要指定根实体。当确定ID策略时,元数据工厂会检查继承。 - Seth Battin
事实上,当我只将其添加到根实体时,它可以完美运行。但是当我将其添加到两个实体时,我会收到“SQLSTATE [23000]:完整性约束违规:1452无法添加或更新子行:外键约束失败”错误。被投票降级。 - ioleo

6
新解决方案只有在所有实体在插入之前都具有ID时才能正常工作。当一个实体具有ID而另一个没有时,新解决方案将失败。
我使用此函数导入所有数据:
function createEntity(\Doctrine\ORM\EntityManager $em, $entity, $id = null)
{
    $className = get_class($entity);
    if ($id) {
        $idRef = new \ReflectionProperty($className, "id");
        $idRef->setAccessible(true);
        $idRef->setValue($entity, $id);

        $metadata = $em->getClassMetadata($className);
        /** @var \Doctrine\ORM\Mapping\ClassMetadataInfo $metadata */
        $generator = $metadata->idGenerator;
        $generatorType = $metadata->generatorType;

        $metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator());
        $metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);

        $unitOfWork = $em->getUnitOfWork();
        $persistersRef = new \ReflectionProperty($unitOfWork, "persisters");
        $persistersRef->setAccessible(true);
        $persisters = $persistersRef->getValue($unitOfWork);
        unset($persisters[$className]);
        $persistersRef->setValue($unitOfWork, $persisters);

        $em->persist($entity);
        $em->flush();

        $idRef->setAccessible(false);
        $metadata->setIdGenerator($generator);
        $metadata->setIdGeneratorType($generatorType);

        $persisters = $persistersRef->getValue($unitOfWork);
        unset($persisters[$className]);
        $persistersRef->setValue($unitOfWork, $persisters);
        $persistersRef->setAccessible(false);
    } else {
        $em->persist($entity);
        $em->flush();
    }
}

感谢您提供这个有用的代码片段(我在我的测试数据中使用了它,以确保使用与生产表相同的ID)。 - Adrien G

4

Doctrine 2.5和MySQL的解决方案

"新的解决方案"不适用于Doctrine 2.5和MySQL。您需要使用:

$metadata = $this->getEntityManager()->getClassMetaData(Entity::class);
$metadata->setIdGenerator(new AssignedGenerator());
$metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_‌​NONE);

但是我只能确认对于MySQL,因为我还没有尝试过其他的DBMS。


使用部分: use Doctrine\ORM\Id\AssignedGenerator; use Doctrine\ORM\Mapping\ClassMetadata;并请重新检查ClassMetadata :: GENERATOR_TYPE_NONE,因为在IDE中显示TYPE_和NONE之间有奇怪的符号。 - Oleh Diachenko

1
Villermen工作的启发,我创建了库tseho/doctrine-assigned-identity,它允许您手动为Doctrine实体分配ID,即使实体使用AUTO、SEQUENCE、IDENTITY或UUID策略。

绝不应在生产中使用它,但对于功能测试非常有用。

该库将自动检测具有已分配ID的实体,并仅在需要时替换生成器。当实例没有分配ID时,库将回退到初始生成器。

生成器的替换发生在Doctrine EventListener中,无需在夹具中添加任何其他代码。


1
我已经创建了一个库来为Doctrine实体设置未来的ID。当所有排队的ID都被消耗完时,它会恢复到原始的ID生成策略,以最小化影响。它应该是一个易于使用的单元测试插件,这样就不必重复编写像这样的代码。

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