Establecer Id explícitamente con Doctrine cuando se usa la estrategia "AUTO"

100

Mi entidad usa esta anotación para su ID:

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

De una base de datos limpia, estoy importando registros existentes de una base de datos más antigua y trato de mantener las mismas ID. Luego, al agregar nuevos registros, quiero que MySQL aumente automáticamente la columna de ID como de costumbre.

Desafortunadamente, parece que Doctrine2 ignora por completo el ID especificado.


Nueva solucion

Según las recomendaciones a continuación, la siguiente es la solución preferida:

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

Solución antigua

Debido a que Doctrine gira fuera de ClassMetaData para determinar la estrategia del generador, debe modificarse después de administrar la entidad en EntityManager:

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

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

$this->em->flush();

Acabo de probar esto en MySQL y funcionó como se esperaba, lo que significa que las Entidades con una ID personalizada se almacenaron con esa ID, mientras que aquellas sin una ID especificada usaron el lastGeneratedId() + 1.

Eric
fuente
¿Está utilizando doctrina para importar los registros existentes?
rojoca
2
Eric, no importa ... Veo lo que intentas hacer. Básicamente necesitas un @GeneratedValue (estrategia = "ItDepends") :)
Wil Moore III
1
Una cosa a tener en cuenta sobre esto, es que parece que los generadores de Id que no son "isPostInsertGenerator" == true, ya se habrán ejecutado. Puede cambiar el valor de la ID después de persistir, sin embargo, perderá un número de secuencia.
gview
15
La nueva solución ahora me permite establecer la identificación en un accesorio de doctrina. Sin embargo, usando $ metadata-> setIdGeneratorType (\ Doctrine \ ORM \ Mapping \ ClassMetadata :: GENERATOR_TYPE_NONE); permite configurar y guardar la identificación. (MySQL).
jmoz
2
Esa nueva solución no funciona en Symfony 3.0. Tuve que usar$metadata = $this->getEntityManager()->getClassMetaData(User::class); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);
piotrekkr

Respuestas:

51

Aunque su solución funciona bien con MySQL, no logré que funcione con PostgreSQL ya que se basa en secuencias.

Tengo que agregar esta línea para que funcione perfectamente:

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

Atentamente,

nicolasbui
fuente
¡Gracias! Doctrine ha mejorado un poco desde que esto fue un problema por primera vez, así que acepté tu respuesta y actualicé mi ticket original en consecuencia.
Eric
Gracias y estoy feliz de ayudar un poco como pueda :)
nicolasbui
2
¿Esto establecerá este generador de forma permanente? ¿Puedo agregar un registro con ID forzado y luego dejar que use ID de autoincremento?
Pavel Dubinin
1
Puedo confirmar que esto funciona con Symfony 3.2. Sin embargo, lo que no esperaba era que el generador tuviera que configurarse después de la ejecución $em->persist($entity).
bodo
29

Quizás lo que cambió la doctrina pero ahora de la manera correcta es:

$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
Alexey B.
fuente
1
Esta sigue siendo información relevante y funciona para Doctrine 2.4.1, pero la segunda línea mencionada por @gphilip debería eliminarse.
Mantas
No funciona para Doctrine> 2.5 porque ClassMetadataes una interfaz y por lo tanto no puede tener constantes.
TIMESPLiNTER
Hay una clase ClassMetadata
Alexey B.
@gphilip La segunda línea es importante si desea que funcione con asociaciones .
Taz
1
Puede simplificar usando$metadata::GENERATOR_TYPE_NONE
fyrye
7

En caso de que la entidad sea parte de la herencia de una tabla de clases , debe cambiar el generador de id en los metadatos de la clase para ambas entidades (la entidad que persiste y la entidad raíz)

Hombre cabra
fuente
Creo que el caso es que solo necesitas especificar la entidad raíz. La fábrica de metadatos comprueba la herencia al determinar la estrategia de identificación.
Seth Battin
De hecho, cuando lo agrego solo a la entidad raíz, funciona perfectamente. Cuando lo agrego a ambos, obtengo SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint failserrores. Voto en contra
ioleo
5

La nueva solución funciona bien solo cuando TODAS las entidades tienen una identificación antes de la inserción. Cuando una entidad tiene ID y otra no, la nueva solución está fallando.

Utilizo esta función para importar todos mis datos:

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();
    }
}
Андрей Миндубаев
fuente
4

Solución para Doctrine 2.5 y MySQL

La "Nueva solución" no funciona con Doctrine 2.5 y MySQL. Tienes que usar:

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

Sin embargo, solo puedo confirmar eso para MySQL, porque todavía no he probado ningún otro DBMS.

espina
fuente
1

He creado una biblioteca para establecer identificaciones futuras para las entidades de Doctrine. Vuelve a la estrategia de generación de ID original cuando se consumen todas las ID en cola para minimizar el impacto. Debería ser fácil para las pruebas unitarias para que un código como este no tenga que repetirse.

Villermen
fuente
1

Inspirándome en el trabajo de Villermen , creé la biblioteca tseho / doctrine-assign-identity que te permite asignar identificaciones manualmente a una entidad de Doctrine, incluso cuando la entidad usa las estrategias AUTO, SEQUENCE, IDENTITY o UUID.

Nunca debe usarlo en producción, pero es realmente útil para pruebas funcionales.

La biblioteca detectará automáticamente las entidades con una identificación asignada y reemplazará el generador solo cuando sea necesario. La biblioteca recurrirá al generador inicial cuando una instancia no tenga una identificación asignada.

El reemplazo del generador ocurre en un EventListener de Doctrine, sin necesidad de agregar ningún código adicional en sus dispositivos.

Tseho
fuente