Tetapkan Id secara eksplisit dengan Doctrine saat menggunakan strategi "AUTO"

100

Entitas saya menggunakan anotasi ini untuk ID-nya:

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

Dari database yang bersih, saya mengimpor dalam catatan yang ada dari database yang lebih lama dan mencoba untuk menyimpan ID yang sama. Kemudian, saat menambahkan record baru, saya ingin MySQL menaikkan kolom ID secara otomatis seperti biasa.

Sayangnya, tampaknya Doctrine2 mengabaikan ID yang ditentukan.


Solusi Baru

Sesuai rekomendasi di bawah, berikut ini adalah solusi yang lebih disukai:

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

Solusi Lama

Karena Doctrine berporos dari ClassMetaData untuk menentukan strategi generator, itu harus dimodifikasi setelah mengelola entitas di EntityManager:

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

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

$this->em->flush();

Saya baru saja menguji ini di MySQL dan berfungsi seperti yang diharapkan, yang berarti Entitas dengan ID khusus disimpan dengan ID itu, sedangkan yang tanpa ID ditentukan menggunakan lastGeneratedId() + 1.

Eric
sumber
Apakah Anda menggunakan doktrin untuk mengimpor catatan yang ada?
rojoca
2
Eric, tidak masalah ... Saya melihat apa yang Anda coba lakukan. Anda pada dasarnya membutuhkan @GeneratedValue (strategy = "ItDepends") :)
Wil Moore III
1
Satu hal yang perlu diperhatikan tentang ini, adalah tampaknya generator Id yang tidak "isPostInsertGenerator" == true, akan sudah berjalan. Anda dapat mengubah nilai ID setelah bertahan, namun, Anda akan kehilangan nomor urut.
gview
15
Solusi baru sekarang memungkinkan saya untuk menetapkan id dalam perlengkapan doktrin. Namun, menggunakan $ metadata-> setIdGeneratorType (\ Doctrine \ ORM \ Mapping \ ClassMetadata :: GENERATOR_TYPE_NONE); memungkinkan id untuk diatur dan disimpan. (MySQL).
jmoz
2
Solusi baru itu tidak berfungsi di Symfony 3.0. Saya harus menggunakan$metadata = $this->getEntityManager()->getClassMetaData(User::class); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);
piotrekkr

Jawaban:

51

Meskipun solusi Anda berfungsi dengan baik dengan MySQL, saya gagal membuatnya berfungsi dengan PostgreSQL karena itu berbasis urutan.

Saya telah menambahkan baris ini agar berfungsi dengan sempurna:

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

Salam Hormat,

nicolasbui
sumber
Terima kasih! Doktrin telah sedikit meningkat sejak ini pertama kali menjadi masalah, jadi saya telah menerima jawaban Anda & memperbarui tiket asli saya.
Eric
Terima kasih dan saya akan dengan senang hati membantu
semampu
2
apakah generator ini akan disetel secara permanen? Dapatkah saya menambahkan satu catatan dengan ID paksa dan kemudian membiarkannya menggunakan id autoincrement?
Pavel Dubinin
1
Saya dapat memastikan bahwa ini berfungsi dengan Symfony 3.2. Namun yang tidak saya harapkan, adalah generator harus disetel setelah dijalankan $em->persist($entity).
bodo
29

Mungkin doktrin apa yang berubah tetapi sekarang cara yang benar adalah:

$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
Alexey B.
sumber
1
Ini masih merupakan informasi yang relevan dan berfungsi untuk Doctrine 2.4.1, tetapi baris kedua seperti yang disebutkan oleh @gphilip harus dihilangkan.
Mantas
Tidak berfungsi untuk Doctrine> 2.5 karena ClassMetadatamerupakan antarmuka dan karenanya tidak dapat memiliki konstanta.
TiMESPLiNTER
Ada kelas ClassMetadata
Alexey B.
@gphilip Baris kedua penting jika Anda ingin bekerja dengan asosiasi .
Taz
1
Dapat menyederhanakan dengan menggunakan$metadata::GENERATOR_TYPE_NONE
fyrye
7

Jika entitas adalah bagian dari warisan tabel kelas, Anda perlu mengubah pembuat-id di metadata kelas untuk kedua entitas (entitas yang Anda pertahankan dan entitas akar)

Kambing hitam
sumber
Saya pikir masalahnya adalah Anda hanya perlu menentukan entitas root. Pabrik metadata memeriksa pewarisan saat menentukan strategi id.
Seth Battin
Faktanya, ketika saya menambahkannya hanya ke entitas root, itu berfungsi dengan sempurna. Saat saya menambahkannya ke keduanya, saya mendapatkan SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint failskesalahan. Downvoted
ioleo
5

Solusi baru berfungsi dengan baik hanya jika SEMUA entitas memiliki id sebelum dimasukkan. Ketika satu entitas memiliki ID dan entitas lainnya tidak - solusi baru gagal.

Saya menggunakan fungsi ini untuk mengimpor semua data saya:

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

Solusi untuk Doctrine 2.5 dan MySQL

"Solusi baru" tidak berfungsi dengan Doctrine 2.5 dan MySQL. Anda harus menggunakan:

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

Namun saya hanya dapat mengkonfirmasi itu untuk MySQL, karena saya belum mencoba DBMS lainnya.

tulang ikan
sumber
1

Saya telah membuat perpustakaan untuk menetapkan ID masa depan untuk entitas Doktrin. Ini kembali ke strategi pembuatan ID asli ketika semua ID antrean digunakan untuk meminimalkan dampak. Ini harus menjadi drop-in yang mudah untuk pengujian unit sehingga kode seperti ini tidak perlu diulang.

Villermen
sumber
1

Terinspirasi oleh karya Villermen , saya membuat perpustakaan tseho / doktrin-ditugaskan-identitas yang memungkinkan Anda untuk menetapkan ID secara manual ke entitas Doktrin, bahkan ketika entitas menggunakan status AUTO, SEQUENCE, IDENTITY atau UUID.

Anda tidak boleh menggunakannya dalam produksi tetapi ini sangat berguna untuk pengujian fungsional.

Library akan mendeteksi secara otomatis entitas dengan id yang ditetapkan dan mengganti generator hanya jika diperlukan. Pustaka akan fallback pada generator awal ketika sebuah instance tidak memiliki id yang ditetapkan.

Penggantian generator terjadi di Doctrine EventListener, tidak perlu menambahkan kode tambahan apa pun di perlengkapan Anda.

Tseho
sumber