Jawnie ustaw Id za pomocą Doctrine podczas korzystania ze strategii „AUTO”

100

Moja jednostka używa tej adnotacji jako swojego identyfikatora:

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

Z czystej bazy danych importuję istniejące rekordy ze starszej bazy danych i staram się zachować te same identyfikatory. Następnie podczas dodawania nowych rekordów chcę, aby MySQL jak zwykle automatycznie zwiększał wartość kolumny ID.

Niestety, wygląda na to, że Doctrine2 całkowicie ignoruje podany identyfikator.


Nowe rozwiązanie

Zgodnie z poniższymi zaleceniami preferowanym rozwiązaniem jest:

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

Stare rozwiązanie

Ponieważ Doctrine opiera się na ClassMetaData w celu określenia strategii generatora, musi zostać zmodyfikowana po zarządzaniu encją w EntityManager:

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

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

$this->em->flush();

Właśnie przetestowałem to na MySQL i działało zgodnie z oczekiwaniami, co oznacza, że ​​jednostki z niestandardowym identyfikatorem były przechowywane z tym identyfikatorem, podczas gdy te bez określonego identyfikatora używały rozszerzenia lastGeneratedId() + 1.

Eric
źródło
Czy używasz doctrine do importowania istniejących rekordów?
rojoca
2
Eric, nieważne ... Rozumiem, co próbujesz zrobić. Zasadniczo potrzebujesz @GeneratedValue (strategy = "ItDepends") :)
Wil Moore III
1
Należy zwrócić uwagę na to, że wydaje się, że generatory identyfikatorów, które nie mają wartości „isPostInsertGenerator” == true, będą już działać. Możesz zmienić wartość identyfikatora po utrwaleniu, jednak utracisz numer sekwencyjny.
gview
15
Nowe rozwiązanie pozwala mi teraz ustawić id jako element doktryny. Jednak używając $ metadata-> setIdGeneratorType (\ Doctrine \ ORM \ Mapping \ ClassMetadata :: GENERATOR_TYPE_NONE); umożliwia ustawienie i zapisanie identyfikatora. (MySQL).
jmoz
2
To nowe rozwiązanie nie działa w Symfony 3.0. Musiałem skorzystać$metadata = $this->getEntityManager()->getClassMetaData(User::class); $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdGeneratorType(ClassMetadata::GENERATOR_TYPE_NONE);
piotrekkr

Odpowiedzi:

51

Chociaż twoje rozwiązanie działa dobrze z MySQL, nie udało mi się go uruchomić z PostgreSQL, ponieważ jest oparte na sekwencji.

Muszę dodać tę linię, aby działała idealnie:

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

Z poważaniem,

nicolasbui
źródło
Dzięki! Doktryna poprawiła się nieco od czasu, gdy był to pierwszy problem, więc zaakceptowałem twoją odpowiedź i odpowiednio zaktualizowałem mój oryginalny bilet.
Eric
Dziękuję i chętnie pomogę jak tylko potrafię :)
nicolasbui
2
czy to ustawi ten generator na stałe? Czy mogę dodać jeden rekord z wymuszonym identyfikatorem, a następnie pozwolić mu używać identyfikatorów automatycznego zwiększania?
Pavel Dubinin
1
Mogę potwierdzić, że działa to z Symfony 3.2. Nie spodziewałem się jednak, że generator musi zostać ustawiony po wykonaniu $em->persist($entity).
bodo
29

Być może zmieniła się doktryna, ale teraz jest właściwa:

$metadata->setIdGeneratorType(\Doctrine\ORM\Mapping\ClassMetadata::GENERATOR_TYPE_NONE);
Alexey B.
źródło
1
Jest to nadal istotna informacja i działa dla Doctrine 2.4.1, ale druga linia, o której wspomniał @gphilip, powinna zostać usunięta.
Mantas
Nie działa dla Doctrine> 2.5, ponieważ ClassMetadatajest interfejsem i dlatego nie może mieć żadnych stałych.
TIMESPLiNTER
Jest klasa ClassMetadata
Alexey B.
@gphilip Druga linia jest ważna, jeśli chcesz, aby działała ze skojarzeniami .
Taz
1
Można uprościć, używając$metadata::GENERATOR_TYPE_NONE
fyrye
7

W przypadku, gdy jednostka jest częścią dziedziczenia tabeli klas , musisz zmienić generator id w metadanych klasy dla obu jednostek (encja, którą utrwalasz, i jednostka główna)

Weregoat
źródło
Myślę, że przypadek jest taki, że wystarczy określić jednostkę główną. Metadatafactory sprawdza dziedziczenie podczas określania strategii id.
Seth Battin,
W rzeczywistości, kiedy dodam go tylko do jednostki głównej, działa bezbłędnie. Kiedy dodam to do obu, otrzymuję SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint failsbłędy. Downvoted
ioleo
5

Nowe rozwiązanie działa dobrze tylko wtedy, gdy WSZYSTKIE podmioty mają identyfikator przed wstawieniem. Gdy jeden podmiot ma identyfikator, a inny nie - nowe rozwiązanie zawodzi.

Używam tej funkcji do importu wszystkich moich danych:

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();
    }
}
Андрей Миндубаев
źródło
4

Rozwiązanie dla Doctrine 2.5 i MySQL

„Nowe rozwiązanie” nie działa z Doctrine 2.5 i MySQL. Musisz użyć:

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

Jednak mogę to tylko potwierdzić dla MySQL, ponieważ nie próbowałem jeszcze żadnego innego DBMS.

ość rybna
źródło
1

Utworzyłem bibliotekę do ustawiania przyszłych identyfikatorów dla encji Doctrine. Powraca do oryginalnej strategii generowania identyfikatorów, gdy wszystkie identyfikatory w kolejce są zużywane, aby zminimalizować wpływ. Powinien to być łatwy dodatek do testów jednostkowych, aby taki kod nie musiał być powtarzany.

Villermen
źródło
1

Zainspirowany pracą Villermena , stworzyłem bibliotekę tseho / doctrine-assign-identity, która pozwala na ręczne przypisywanie identyfikatorów do encji Doctrine, nawet jeśli encja korzysta ze strategii AUTO, SEQUENCE, IDENTITY lub UUID.

Nigdy nie powinieneś używać go w produkcji, ale jest naprawdę przydatny do testów funkcjonalnych.

Biblioteka automatycznie wykryje jednostki z przypisanym identyfikatorem i zastąpi generator tylko wtedy, gdy będzie to potrzebne. Biblioteka powróci do początkowego generatora, gdy instancja nie ma przypisanego identyfikatora.

Zastąpienie generatora następuje w Doctrine EventListener, nie ma potrzeby dodawania dodatkowego kodu do urządzeń.

Tseho
źródło