Явно установите идентификатор с помощью Doctrine при использовании стратегии «АВТО»

100

Моя сущность использует эту аннотацию для своего идентификатора:

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

Из чистой базы данных я импортирую существующие записи из более старой базы данных и пытаюсь сохранить те же идентификаторы. Затем при добавлении новых записей я хочу, чтобы MySQL как обычно автоматически увеличивал столбец идентификатора.

К сожалению, похоже, что Doctrine2 полностью игнорирует указанный идентификатор.


Новое решение

Согласно приведенным ниже рекомендациям, предпочтительным решением является следующее:

$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, и он работал, как ожидалось, то есть сущности с настраиваемым идентификатором сохранялись с этим идентификатором, в то время как объекты без указанного идентификатора использовали файл lastGeneratedId() + 1.

Эрик
источник
Вы используете доктрину для импорта существующих записей?
Rojoca
2
Эрик, забудь ... Я вижу, что ты пытаешься сделать. Вам в основном нужен @GeneratedValue (strategy = "ItDepends") :)
Вил Мур III,
1
Следует отметить, что похоже, что генераторы идентификаторов, которые не являются "isPostInsertGenerator" == true, уже будут запущены. Вы можете изменить значение идентификатора после сохранения, однако вы потеряете порядковый номер.
gview
15
Новое решение теперь позволяет мне установить идентификатор в приспособлении доктрины. Однако при использовании $ metadata-> setIdGeneratorType (\ Doctrine \ ORM \ Mapping \ ClassMetadata :: GENERATOR_TYPE_NONE); позволяет установить и сохранить идентификатор. (MySQL).
jmoz
2
Это новое решение не работает в Symfony 3.0. Пришлось использовать$metadata = $this->getEntityManager()->getClassMetaData(User::class); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);
piotrekkr

Ответы:

51

Хотя ваше решение отлично работает с MySQL, мне не удалось заставить его работать с PostgreSQL, поскольку оно основано на последовательности.

Я должен добавить эту строку, чтобы она работала идеально:

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

С уважением,

Николасбуи
источник
Спасибо! Doctrine немного улучшилась с тех пор, как это была первая проблема, поэтому я принял ваш ответ и соответствующим образом обновил свой исходный тикет.
Эрик
Спасибо, и я рад помочь немного, чем могу :)
nicolasbui
2
это установит этот генератор постоянно? Могу ли я добавить одну запись с принудительным идентификатором, а затем позволить ей использовать идентификаторы автоинкремента?
Павел Дубинин
1
Я могу подтвердить, что это работает с Symfony 3.2. Однако я не ожидал, что генератор нужно будет настроить после выполнения $em->persist($entity).
bodo
29

Возможно, какая доктрина изменилась, но теперь правильный путь:

$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
Алексей Б.
источник
1
Это по-прежнему актуальная информация и работает для Doctrine 2.4.1, но вторую строку, упомянутую @gphilip, следует удалить.
Mantas
Не работает для Doctrine> 2.5, потому что ClassMetadataэто интерфейс и поэтому не может иметь констант.
TiMESPLiNTER
Есть класс ClassMetadata
Алексей Б.
@gphilip Вторая строка важна, если вы хотите, чтобы она работала с ассоциациями .
Taz
1
Можно упростить с помощью$metadata::GENERATOR_TYPE_NONE
fyrye 07
7

В случае, если сущность является частью наследования таблицы классов, вам необходимо изменить генератор идентификаторов в метаданных класса для обеих сущностей (сущность, которую вы сохраняете, и корневую сущность).

Коза
источник
Я думаю, дело в том, что вам нужно указать только корневую сущность. Фабрика метаданных проверяет наследование при определении стратегии идентификатора.
Сет Баттин
Фактически, когда я добавляю его только в корневой объект, он работает безупречно. Когда я добавляю его к обоим, я получаю SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint failsошибки. Downvoted
ioleo
5

Новое решение работает нормально только тогда, когда ВСЕ объекты имеют идентификатор перед вставкой. Когда одна сущность имеет идентификатор, а другая - нет - новое решение не работает.

Я использую эту функцию для импорта всех моих данных:

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

Решение для 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, потому что я еще не пробовал никаких других СУБД.

рыбья кость
источник
1

Я создал библиотеку для установки будущих идентификаторов для сущностей Doctrine. Когда все идентификаторы в очереди израсходованы, он возвращается к исходной стратегии генерации идентификаторов, чтобы минимизировать влияние. Это должно быть легкое добавление для модульных тестов, чтобы не пришлось повторять подобный код.

Villermen
источник
1

Вдохновленный работой Виллермена , я создал библиотеку tseho / doctrine-assign-identity, которая позволяет вручную назначать идентификаторы сущности Doctrine, даже если сущность использует состояния AUTO, SEQUENCE, IDENTITY или UUID.

Вы никогда не должны использовать его в производстве, но он действительно полезен для функциональных тестов.

Библиотека автоматически обнаружит сущности с назначенным идентификатором и заменит генератор только при необходимости. Библиотека будет использовать исходный генератор, если экземпляру не назначен идентификатор.

Замена генератора происходит в Doctrine EventListener, нет необходимости добавлять какой-либо дополнительный код в ваши фикстуры.

Цехо
источник