Setzen Sie die ID explizit mit Doctrine, wenn Sie die Strategie „AUTO“ verwenden

100

Meine Entität verwendet diese Anmerkung für ihre ID:

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

Aus einer sauberen Datenbank importiere ich vorhandene Datensätze aus einer älteren Datenbank und versuche, dieselben IDs beizubehalten. Wenn ich dann neue Datensätze hinzufüge, möchte ich, dass MySQL die ID-Spalte wie gewohnt automatisch inkrementiert.

Leider scheint Doctrine2 die angegebene ID vollständig zu ignorieren.


Neue Lösung

Gemäß den folgenden Empfehlungen ist Folgendes die bevorzugte Lösung:

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

Alte Lösung

Da Doctrine zur Bestimmung der Generatorstrategie von ClassMetaData abweicht, muss sie nach der Verwaltung der Entität im EntityManager geändert werden:

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

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

$this->em->flush();

Ich habe dies gerade unter MySQL getestet und es hat wie erwartet funktioniert, was bedeutet, dass Entitäten mit einer benutzerdefinierten ID mit dieser ID gespeichert wurden, während diejenigen ohne angegebene ID die verwendeten lastGeneratedId() + 1.

Eric
quelle
Verwenden Sie die Doktrin, um die vorhandenen Datensätze zu importieren?
Rojoca
2
Eric, egal ... Ich verstehe, was du versuchst zu tun. Sie benötigen grundsätzlich einen @GeneratedValue (Strategie = "ItDepends") :)
Wil Moore III
1
Zu beachten ist, dass ID-Generatoren, die nicht "isPostInsertGenerator" == true sind, anscheinend bereits ausgeführt wurden. Sie können den Wert der ID nach dem Fortbestehen ändern, verlieren jedoch eine Sequenznummer.
Gview
15
Mit der neuen Lösung kann ich jetzt die ID in einem Doktrin-Fixture festlegen. Verwenden Sie jedoch $ metadata-> setIdGeneratorType (\ Doctrine \ ORM \ Mapping \ ClassMetadata :: GENERATOR_TYPE_NONE). Ermöglicht das Festlegen und Speichern der ID. (MySQL).
jmoz
2
Diese neue Lösung funktioniert in Symfony 3.0 nicht. Ich musste$metadata = $this->getEntityManager()->getClassMetaData(User::class); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);
piotrekkr

Antworten:

51

Obwohl Ihre Lösung mit MySQL einwandfrei funktioniert, konnte ich sie mit PostgreSQL nicht als sequenzbasiert einsetzen.

Ich muss diese Zeile hinzufügen, damit sie perfekt funktioniert:

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

Freundliche Grüße,

nicolasbui
quelle
Vielen Dank! Die Doktrin hat sich etwas verbessert, seit dies das erste Problem war. Daher habe ich Ihre Antwort akzeptiert und mein Originalticket entsprechend aktualisiert.
Eric
Vielen Dank und ich freue mich, ein bisschen zu helfen, wie ich kann :)
nicolasbui
2
Wird dies diesen Generator dauerhaft einstellen? Kann ich einen Datensatz mit erzwungener ID hinzufügen und dann Autoincrement-IDs verwenden lassen?
Pavel Dubinin
1
Ich kann bestätigen, dass dies mit Symfony 3.2 funktioniert. Was ich jedoch nicht erwartet hatte, war, dass der Generator nach der Ausführung eingestellt werden muss $em->persist($entity).
Bodo
29

Vielleicht hat sich die Lehre geändert, aber jetzt ist der richtige Weg:

$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
Alexey B.
quelle
1
Dies sind immer noch relevante Informationen und funktionieren für Doctrine 2.4.1, aber die zweite Zeile, wie von @gphilip erwähnt, sollte entfernt werden.
Mantas
Funktioniert nicht für Doctrine> 2.5, da ClassMetadataes sich um eine Schnittstelle handelt und daher keine Konstanten haben kann.
TiMESPLiNTER
Es gibt eine Klasse ClassMetadata
Alexey B.
@gphilip Die zweite Zeile ist wichtig, wenn Sie möchten mit Assoziationen funktioniert .
Taz
1
Kann durch Verwendung vereinfachen $metadata::GENERATOR_TYPE_NONE
fyrye
7

Wenn die Entität Teil einer Klassentabellenvererbung ist , müssen Sie den ID-Generator in den Klassenmetadaten für beide Entitäten (die Entität, die Sie beibehalten, und die Stammentität) ändern.

Weregoat
quelle
Ich denke, der Fall ist, dass Sie nur die Stammentität angeben müssen. Die Metadatenfabrik überprüft die Vererbung bei der Bestimmung der ID-Strategie.
Seth Battin
In der Tat, wenn ich es nur zu Root-Entität hinzufüge, funktioniert es einwandfrei. Wenn ich es zu beiden hinzufüge, erhalte ich SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint failsFehler. Downvoted
Ioleo
5

Neue Lösung funktioniert nur, wenn ALLE Entitäten vor dem Einfügen eine ID haben. Wenn eine Entität eine ID hat und eine andere nicht - schlägt eine neue Lösung fehl.

Ich benutze diese Funktion zum Importieren aller meiner Daten:

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

Lösung für Doctrine 2.5 und MySQL

Die "neue Lösung" funktioniert nicht mit Doctrine 2.5 und MySQL. Sie müssen verwenden:

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

Dies kann ich jedoch nur für MySQL bestätigen, da ich noch kein anderes DBMS ausprobiert habe.

Gräten
quelle
1

Ich habe eine Bibliothek erstellt , um zukünftige IDs für Doctrine-Entitäten festzulegen. Es wird zur ursprünglichen ID-Generierungsstrategie zurückgekehrt, wenn alle IDs in der Warteschlange verwendet werden, um die Auswirkungen zu minimieren. Es sollte ein einfaches Drop-In für Unit-Tests sein, damit Code wie dieser nicht wiederholt werden muss.

Villermen
quelle
1

Inspiriert von der Arbeit von Villermen habe ich die Bibliothek Tseho / Doctrine-Assigned-Identity erstellt , mit der Sie einer Doctrine-Entität manuell IDs zuweisen können, selbst wenn die Entität die Strategien AUTO, SEQUENCE, IDENTITY oder UUID verwendet.

Sie sollten es niemals in der Produktion verwenden aber es ist wirklich nützlich für Funktionstests.

Die Bibliothek erkennt automatisch die Entitäten mit einer zugewiesenen ID und ersetzt den Generator nur bei Bedarf. Die Bibliothek greift auf den ursprünglichen Generator zurück, wenn einer Instanz keine ID zugewiesen wurde.

Der Austausch des Generators erfolgt in einem Doctrine EventListener, ohne dass Sie zusätzlichen Code in Ihre Geräte einfügen müssen.

Tseho
quelle