Imposta esplicitamente Id con Doctrine quando si utilizza la strategia "AUTO"


100

La mia entità utilizza questa annotazione per il suo ID:

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

Da un database pulito, sto importando record esistenti da un database precedente e sto cercando di mantenere gli stessi ID. Quindi, quando si aggiungono nuovi record, voglio che MySQL incrementi automaticamente la colonna ID come al solito.

Sfortunatamente, sembra che Doctrine2 ignori completamente l'ID specificato.


Nuova soluzione

Secondo le raccomandazioni di seguito, la seguente è la soluzione preferita:

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

Vecchia soluzione

Poiché Doctrine si allontana da ClassMetaData per determinare la strategia del generatore, deve essere modificato dopo aver gestito l'entità in EntityManager:

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

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

$this->em->flush();

L'ho appena testato su MySQL e ha funzionato come previsto, il che significa che le entità con un ID personalizzato sono state memorizzate con quell'ID, mentre quelle senza un ID specificato hanno utilizzato l'estensione lastGeneratedId() + 1.


Stai usando dottrine per importare i record esistenti?
rojoca

2
Eric, non importa ... Capisco cosa stai cercando di fare. Fondamentalmente hai bisogno di un @GeneratedValue (strategy = "ItDepends") :)
Wil Moore III

1
Una cosa da notare su questo, è che sembra che i generatori di ID che non sono "isPostInsertGenerator" == true, saranno già eseguiti. Puoi modificare il valore dell'ID dopo la persistenza, tuttavia perderai un numero di sequenza.
gview

15
La nuova soluzione ora mi consente di impostare l'id in un dispositivo dottrine. Tuttavia, utilizzando $ metadata-> setIdGeneratorType (\ Doctrine \ ORM \ Mapping \ ClassMetadata :: GENERATOR_TYPE_NONE); consente di impostare e salvare l'id. (MySQL).
jmoz

2
Quella nuova soluzione non funziona in Symfony 3.0. Ho dovuto usare$metadata = $this->getEntityManager()->getClassMetaData(User::class); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);
piotrekkr

Risposte:


51

Sebbene la tua soluzione funzioni bene con MySQL, non sono riuscito a farlo funzionare con PostgreSQL poiché è basato su sequenze.

Devo aggiungere questa riga per farlo funzionare perfettamente:

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

I migliori saluti,


Grazie! Doctrine è migliorata un po 'da quando questo era il primo problema, quindi ho accettato la tua risposta e aggiornato di conseguenza il mio ticket originale.
Eric

Grazie e sono felice di aiutare un po 'come posso :)
nicolasbui

2
questo imposterà questo generatore in modo permanente? Posso aggiungere un record con ID forzato e poi lasciarlo usare gli ID di incremento automatico?
Pavel Dubinin

1
Posso confermare che funziona con Symfony 3.2. Quello che non mi aspettavo, tuttavia, era che il generatore dovesse essere impostato dopo l' esecuzione $em->persist($entity).
bodo

29

Forse quale dottrina è cambiata ma ora il modo giusto è:

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

1
Questa è ancora un'informazione rilevante e funziona per Doctrine 2.4.1, ma la seconda riga menzionata da @gphilip dovrebbe essere rimossa.
Mantas

Non funziona per Doctrine> 2.5 perché ClassMetadataè un'interfaccia e quindi non può avere costanti.
TiMESPLiNTER

C'è una classe ClassMetadata
Alexey B.

@gphilip La seconda riga è importante se vuoi che funzioni con le associazioni .
Taz

1
Può semplificare utilizzando$metadata::GENERATOR_TYPE_NONE
fyrye

7

Nel caso in cui l'entità faccia parte dell'ereditarietà di una tabella di classi, è necessario modificare il generatore di id nei metadati della classe per entrambe le entità (l'entità che si sta persistendo e l'entità radice)


Penso che il caso sia che devi solo specificare l'entità radice. La metadatafactory controlla l'ereditarietà quando determina la strategia id.
Seth Battin

In effetti, quando lo aggiungo solo all'entità di root, funziona perfettamente. Quando lo aggiungo a entrambi, ricevo SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint failserrori. Downvoted
ioleo

5

La nuova soluzione funziona bene solo quando TUTTE le entità hanno l'ID prima dell'inserimento. Quando un'entità ha l'ID e un'altra no, la nuova soluzione non funziona.

Uso questa funzione per importare tutti i miei dati:

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

4

Soluzione per Doctrine 2.5 e MySQL

La "Nuova soluzione" non funziona con Doctrine 2.5 e MySQL. Devi usare:

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

Tuttavia posso solo confermarlo per MySQL, perché non ho ancora provato nessun altro DBMS.


1

Ho creato una libreria per impostare gli ID futuri per le entità Doctrine. Torna alla strategia di generazione dell'ID originale quando tutti gli ID in coda vengono consumati per ridurre al minimo l'impatto. Dovrebbe essere un semplice drop-in per i test unitari in modo che il codice come questo non debba essere ripetuto.


1

Ispirato dal lavoro di Villermen , ho creato la libreria tseho / doctrine-assign-identity che ti consente di assegnare manualmente gli ID a un'entità Doctrine, anche quando l'entità utilizza le strategie AUTO, SEQUENCE, IDENTITY o UUID.

Non dovresti mai usarlo in produzione ma è davvero utile per i test funzionali.

La libreria rileverà automaticamente le entità con un ID assegnato e sostituirà il generatore solo quando necessario. La libreria eseguirà il fallback sul generatore iniziale quando un'istanza non ha un ID assegnato.

La sostituzione del generatore avviene in un EventListener di Doctrine, non è necessario aggiungere alcun codice aggiuntivo nei dispositivi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.