Der EntityManager ist geschlossen


85
[Doctrine\ORM\ORMException]   
The EntityManager is closed.  

Nachdem ich beim Einfügen von Daten eine DBAL-Ausnahme erhalten habe, wird EntityManager geschlossen und ich kann die Verbindung nicht wieder herstellen.

Ich habe es so versucht, aber es wurde keine Verbindung hergestellt.

$this->em->close();
$this->set('doctrine.orm.entity_manager', null);
$this->set('doctrine.orm.default_entity_manager', null);
$this->get('doctrine')->resetEntityManager();
$this->em = $this->get('doctrine')->getEntityManager();

Hat jemand eine Idee, wie man sich wieder verbindet?


Warum schließt der Entity Manager?
Jay Sheth

2
@JaySheth Der Entity Manager kann nach einer DBAL-Ausnahme geschlossen werden oder wenn Sie vor einem Flush einen EntityManager-> clear () ausführen. Ich habe einige Leute gesehen, die DBAL-Ausnahmen verwendet haben, um den Ausführungsfluss zu verzweigen, und dann einen Fehler beim Schließen von EntityManager erhalten haben. Wenn Sie diesen Fehler erhalten, stimmt etwas im Ausführungsablauf in Ihrem Programm nicht.
ILikeTacos

5
@AlanChavez - Ich erhalte diesen Fehler, weil ich Doctrine verwende, um ein Semaphor-Flag in eine Tabelle zu schreiben, auf die mehrere Threads gleichzeitig zugreifen. MySQL wird einen Fehler in einem der beiden konkurrierenden Threads verursachen, die versuchen, das Semaphor zu erstellen, da die Schlüsselbeschränkung bedeutet, dass nur einer von ihnen erfolgreich sein kann. IMO gibt es einen Fehler in Doctrine, der es Ihnen nicht ermöglicht, erwartete MySQL-Fehler sicher zu behandeln . Warum sollte die gesamte MySQL-Verbindung getrennt werden, da eine INSERT-Anweisung einen Konflikt aufweist?
StampyCode

2
Dieser Fehler wird auch angezeigt, wenn Sie versuchen, Ausnahmen in einer Datenbank zu protokollieren, app.exception_listeneraber die Ausnahme (z. B. eine Einschränkungsverletzung) die Verbindung geschlossen hat.
Lg102

Antworten:


24

Dies ist ein sehr kniffliges Problem, da es zumindest für Symfony 2.0 und Doctrine 2.1 in keiner Weise möglich ist, den EntityManager nach dem Schließen erneut zu öffnen.

Die einzige Möglichkeit, dieses Problem zu lösen, besteht darin, eine eigene DBAL-Verbindungsklasse zu erstellen, die Doctrine-Klasse zu verpacken und eine Ausnahmebehandlung bereitzustellen (z. B. mehrmals zu wiederholen, bevor die Ausnahme an den EntityManager gesendet wird). Es ist ein bisschen hackig und ich befürchte, dass es in Transaktionsumgebungen zu Inkonsistenzen führen kann (dh ich bin mir nicht sicher, was passiert, wenn sich die fehlgeschlagene Abfrage mitten in einer Transaktion befindet).

Eine Beispielkonfiguration für diesen Weg ist:

doctrine:
  dbal:
    default_connection: default
    connections:
      default:
        driver:   %database_driver%
        host:     %database_host%
        user:     %database_user%
        password: %database_password%
        charset:  %database_charset%
        wrapper_class: Your\DBAL\ReopeningConnectionWrapper

Der Unterricht sollte mehr oder weniger so beginnen:

namespace Your\DBAL;

class ReopeningConnectionWrapper extends Doctrine\DBAL\Connection {
  // ...
}

Eine sehr ärgerliche Sache ist, dass Sie jede Verbindungsmethode überschreiben müssen, die Ihren Wrapper für die Ausnahmebehandlung bereitstellt. Die Verwendung von Verschlüssen kann dort Schmerzen lindern.


71

Meine Lösung.

Bevor Sie etwas tun, überprüfen Sie:

if (!$this->entityManager->isOpen()) {
    $this->entityManager = $this->entityManager->create(
        $this->entityManager->getConnection(),
        $this->entityManager->getConfiguration()
    );
}

Alle Entitäten werden gespeichert. Aber es ist praktisch für bestimmte Klassen oder einige Fälle. Wenn Sie einige Dienste mit injiziertem Entitymanager haben, wird dieser weiterhin geschlossen.


Dies ist viel besser, wenn der di-Container selbst nicht verfügbar ist. Vielen Dank.
Hari KT

1
Möglicherweise möchten Sie auch $ this-> entityManager-> getEventManager () im 3. Parameter übergeben.
Medhat Gayed

34

Symfony 2.0 :

$em = $this->getDoctrine()->resetEntityManager();

Symfony 2.1+ :

$em = $this->getDoctrine()->resetManager();

6
WARNUNG: resetEntityManager ist seit Symfony 2.1 veraltet. Verwenden Sie resetManagerstattdessen
Francesco Casula

Setzt dies auch die Arbeitseinheit zurück?
Grippe

@flu Angesichts der Tatsache, dass die EntityManager-Klasse die UnitOfWork-Klasse verwaltet, vermute ich, dass dies der Fall ist. Ich habe dies jedoch nicht getestet und kann mich daher nicht sicher sein.
Ryall

26

So löste ich die Doktrin "Der EntityManager ist geschlossen." Problem. Grundsätzlich führt Doctrine jedes Mal, wenn eine Ausnahme vorliegt (dh ein doppelter Schlüssel) oder wenn keine Daten für eine obligatorische Spalte angegeben werden, dazu, dass Doctrine den Entity Manager schließt. Wenn Sie dennoch mit der Datenbank interagieren möchten, müssen Sie den Entity Manager zurücksetzen, indem Sie die resetManager()von JGrinon erwähnte Methode aufrufen .

In meiner Anwendung habe ich mehrere RabbitMQ-Konsumenten ausgeführt, die alle dasselbe taten: Überprüfen, ob eine Entität in der Datenbank vorhanden war, wenn ja, geben Sie sie zurück, wenn nicht, erstellen Sie sie und geben Sie sie dann zurück. In den wenigen Millisekunden zwischen der Überprüfung, ob diese Entität bereits vorhanden war, und ihrer Erstellung hat ein anderer Verbraucher dasselbe getan und die fehlende Entität erstellt, sodass der andere Verbraucher eine doppelte Schlüsselausnahme ( Race-Bedingung ) aufweist.

Dies führte zu einem Software-Design-Problem. Grundsätzlich habe ich versucht, alle Entitäten in einer Transaktion zu erstellen. Dies mag sich für die meisten natürlich anfühlen, war aber in meinem Fall definitiv konzeptionell falsch. Betrachten Sie das folgende Problem: Ich musste eine Fußballspiel-Entität speichern, die diese Abhängigkeiten hatte.

  • eine Gruppe (zB Gruppe A, Gruppe B ...)
  • eine Runde (zB Halbfinale ...)
  • ein Veranstaltungsort (dh ein Stadion, in dem das Spiel stattfindet)
  • einen Spielstatus (zB Halbzeit, Vollzeit)
  • die beiden Mannschaften spielen das Spiel
  • das Spiel selbst

Warum sollte die Erstellung des Veranstaltungsortes in derselben Transaktion wie das Spiel erfolgen? Es kann sein, dass ich gerade einen neuen Veranstaltungsort erhalten habe, der nicht in meiner Datenbank enthalten ist, sodass ich ihn zuerst erstellen muss. Es kann aber auch sein, dass an diesem Veranstaltungsort ein weiteres Spiel stattfindet, sodass ein anderer Verbraucher wahrscheinlich gleichzeitig versucht, es zu erstellen. Ich musste also zuerst alle Abhängigkeiten in separaten Transaktionen erstellen, um sicherzustellen, dass ich den Entitätsmanager in einer doppelten Schlüsselausnahme zurücksetzte. Ich würde sagen, dass alle Entitäten dort neben dem Match als "gemeinsam genutzt" definiert werden könnten, da sie möglicherweise Teil anderer Transaktionen bei anderen Verbrauchern sein könnten. Etwas, das dort nicht "geteilt" wird, ist das Match selbst, das wahrscheinlich nicht von zwei Verbrauchern gleichzeitig erstellt wird.

All dies führte auch zu einem anderen Problem. Wenn Sie den Entity Manager zurücksetzen, sind alle Objekte, die Sie vor dem Zurücksetzen abgerufen haben, für Doctrine völlig neu. Doctrine wird also nicht versuchen, ein UPDATE auf ihnen auszuführen , sondern ein INSERT ! Stellen Sie daher sicher, dass Sie alle Ihre Abhängigkeiten in logisch korrekten Transaktionen erstellen und dann alle Ihre Objekte aus der Datenbank zurückrufen, bevor Sie sie auf die Zielentität setzen. Betrachten Sie den folgenden Code als Beispiel:

$group = $this->createGroupIfDoesNotExist($groupData);

$match->setGroup($group); // this is NOT OK!

$venue = $this->createVenueIfDoesNotExist($venueData);

$round = $this->createRoundIfDoesNotExist($roundData);

/**
 * If the venue creation generates a duplicate key exception
 * we are forced to reset the entity manager in order to proceed
 * with the round creation and so we'll loose the group reference.
 * Meaning that Doctrine will try to persist the group as new even
 * if it's already there in the database.
 */

So denke ich, sollte es gemacht werden.

$group = $this->createGroupIfDoesNotExist($groupData); // first transaction, reset if duplicated
$venue = $this->createVenueIfDoesNotExist($venueData); // second transaction, reset if duplicated
$round = $this->createRoundIfDoesNotExist($roundData); // third transaction, reset if duplicated

// we fetch all the entities back directly from the database
$group = $this->getGroup($groupData);
$venue = $this->getVenue($venueData);
$round = $this->getGroup($roundData);

// we finally set them now that no exceptions are going to happen
$match->setGroup($group);
$match->setVenue($venue);
$match->setRound($round);

// match and teams relation...
$matchTeamHome = new MatchTeam();
$matchTeamHome->setMatch($match);
$matchTeamHome->setTeam($teamHome);

$matchTeamAway = new MatchTeam();
$matchTeamAway->setMatch($match);
$matchTeamAway->setTeam($teamAway);

$match->addMatchTeam($matchTeamHome);
$match->addMatchTeam($matchTeamAway);

// last transaction!
$em->persist($match);
$em->persist($matchTeamHome);
$em->persist($matchTeamAway);
$em->flush();

Ich hoffe, es hilft :)


Fantastische Erklärung. Ich habe etwas Ähnliches gefunden und dachte, es wäre schön, zu Ihrer Antwort beizutragen. Vielen Dank.
Anjana Silva

17

Sie können Ihre EM so zurücksetzen

// reset the EM and all aias
$container = $this->container;
$container->set('doctrine.orm.entity_manager', null);
$container->set('doctrine.orm.default_entity_manager', null);
// get a fresh EM
$em = $this->getDoctrine()->getManager();

9

In Symfony 4.2+ müssen Sie das Paket verwenden:

composer require symfony/proxy-manager-bridge

andernfalls erhalten Sie die Ausnahme:

Resetting a non-lazy manager service is not supported. Declare the "doctrine.orm.default_entity_manager" service as lazy.  

Dann können Sie den entityManager folgendermaßen zurücksetzen:

services.yaml:

App\Foo:
    - '@doctrine.orm.entity_manager'
    - '@doctrine'

Foo.php:

use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\EntityManagerInterface;


 try {
    $this->entityManager->persist($entity);
    $this->entityManager->flush();
} catch (DBALException $e) {
    if (!$this->entityManager->isOpen()) {
        $this->entityManager = $this->doctrine->resetManager();
    }
}

4

In der Steuerung.

Ausnahme schließt den Entity Manager. Dies macht Probleme beim Masseneinsatz. Um fortzufahren, müssen Sie es neu definieren.

/** 
* @var  \Doctrine\ORM\EntityManager
*/
$em = $this->getDoctrine()->getManager();

foreach($to_insert AS $data)
{
    if(!$em->isOpen())
    {
        $this->getDoctrine()->resetManager();
        $em = $this->getDoctrine()->getManager();
    }

  $entity = new \Entity();
  $entity->setUniqueNumber($data['number']);
  $em->persist($entity);

  try
  {
    $em->flush();
    $counter++;
  }
  catch(\Doctrine\DBAL\DBALException $e)
  {
    if($e->getPrevious()->getCode() != '23000')
    {   
      /**
      * if its not the error code for a duplicate key 
      * value then rethrow the exception
      */
      throw $e;
    }
    else
    {
      $duplication++;
    }               
  }                      
}


1

Ich habe festgestellt, dass dieses Problem in einem Stapelimportbefehl aufgetreten ist, weil eine Try / Catch-Schleife einen SQL-Fehler (mit em->flush()) abgefangen hat, gegen den ich nichts unternommen habe. In meinem Fall lag es daran, dass ich versucht habe, einen Datensatz mit einer nicht nullbaren Eigenschaft einzufügen, die als null belassen wurde.

Normalerweise würde dies dazu führen, dass eine kritische Ausnahme auftritt und der Befehl oder Controller angehalten wird, aber ich habe stattdessen nur dieses Problem protokolliert und weitermache. Der SQL-Fehler hatte dazu geführt, dass der Entitätsmanager geschlossen wurde.

Überprüfen Sie Ihre dev.logDatei auf solche dummen SQL-Fehler, da dies Ihre Schuld sein könnte. :) :)


1

Beim Testen der Änderungen in Symfony 4.3.2 trat das gleiche Problem auf

Ich habe die Protokollstufe auf INFO gesenkt

Und lief den Test erneut

Und das Protokollierte zeigte dies:

console.ERROR: Error thrown while running command "doctrine:schema:create". Message: "[Semantical Error] The annotation "@ORM\Id" in property App\Entity\Common::$id was never imported. Did you maybe forget to add a "use" statement for this annotation?" {"exception":"[object] (Doctrine\\Common\\Annotations\\AnnotationException(code: 0): [Semantical Error] The annotation \"@ORM\\Id\" in property App\\Entity\\Common::$id was never imported. Did you maybe forget to add a \"use\" statement for this annotation? at C:\\xampp\\htdocs\\dirty7s\\vendor\\doctrine\\annotations\\lib\\Doctrine\\Common\\Annotations\\AnnotationException.php:54)","command":"doctrine:schema:create","message":"[Semantical Error] The annotation \"@ORM\\Id\" in property App\\Entity\\Common::$id was never imported. Did you maybe forget to add a \"use\" statement for this annotation?"} []

Dies bedeutet, dass ein Fehler im Code Folgendes verursacht:

Doctrine\ORM\ORMException: The EntityManager is closed.

Es ist daher eine gute Idee, das Protokoll zu überprüfen


Könnten Sie zusätzliche Informationen darüber geben, wie die erste mit der zweiten zusammenhängt?
George Novik

1

Symfony v4.1.6

Lehre v2.9.0

Verarbeiten Sie das Einfügen von Duplikaten in ein Repository

  1. Erhalten Sie Zugriff auf eine Registrierung in Ihrem Repo


    //begin of repo
    
    /** @var RegistryInterface */
    protected $registry;
    
    public function __construct(RegistryInterface $registry)
    {
        $this->registry = $registry;
        parent::__construct($registry, YourEntity::class);
    }

  1. Wickeln Sie riskanten Code in die Transaktion ein und setzen Sie den Manager im Ausnahmefall zurück


    //in repo method
    $em = $this->getEntityManager();
    
    $em->beginTransaction();
    try {
        $em->persist($yourEntityThatCanBeDuplicate);
        $em->flush();
        $em->commit();
    
    } catch (\Throwable $e) {
        //Rollback all nested transactions
        while ($em->getConnection()->getTransactionNestingLevel() > 0) {
            $em->rollback();
        }
        
        //Reset the default em
        if (!$em->isOpen()) {
            $this->registry->resetManager();
        }
    }


0

Ich hatte dieses Problem. So habe ich es behoben.

Die Verbindung scheint zu schließen, während versucht wird, zu spülen oder zu bestehen. Der Versuch, es wieder zu öffnen, ist eine schlechte Wahl, da neue Probleme entstehen. Ich habe versucht zu verstehen, warum die Verbindung geschlossen wurde, und festgestellt, dass ich vor dem Fortbestehen zu viele Änderungen vorgenommen habe.

persist () hat das Problem früher gelöst.


0

Versuchen Sie es mit:

$em->getConnection()->[setNestTransactionsWithSavepoints][1](true);

bevor Sie eine Transaktion starten.

Bei der Connection::rollbackMethode wird nach nestTransactionsWithSavepointsEigenschaften gesucht .


3
Können Sie das näher erläutern?
Paul.ago

0

Dies ist ein wirklich altes Problem, aber ich hatte gerade ein ähnliches Problem. Ich habe so etwas gemacht:

// entity
$entityOne = $this->em->find(Parent::class, 1);

// do something on other entites (SomeEntityClass)
$this->em->persist($entity);
$this->em->flush();
$this->em->clear();

// and at end I was trying to save changes to first one by
$this->em->persist($entityOne);
$this->em->flush();
$this->em->clear();

Das Problem war, dass alle Entitäten einschließlich der ersten eindeutig getrennt wurden und ein Fehler ausgelöst wurde. Der EntityManager ist geschlossen.

In meinem Fall bestand die Lösung darin , nur einen bestimmten Entitätstyp zu klären und $entityOnenoch unter EM zu bleiben:

$this->em->clear(SomeEntityClass::class);

0

Gleiches Problem, gelöst mit einem einfachen Code-Refactoring. Das Problem tritt manchmal auf, wenn ein erforderliches Feld null ist. Versuchen Sie vor dem Anithing, Ihren Code umzugestalten. Ein besserer Workflow kann das Problem lösen.


-1

Ich hatte den gleichen Fehler bei der Verwendung von Symfony 5 / Doctrine 2. Eines meiner Felder wurde mit einem von MySQL reservierten Wort "order" benannt, was eine DBALException verursachte. Wenn Sie ein reserviertes Wort verwenden möchten, müssen Sie seinen Namen mit Back-Ticks umgehen. In Anmerkungsform:

@ORM\Column(name="`order`", type="integer", nullable=false)

-2
// first need to reset current manager
$em->resetManager();
// and then get new
$em = $this->getContainer()->get("doctrine");
// or in this way, depending of your environment:
$em = $this->getDoctrine();

-2

Ich hatte das gleiche Problem. Nachdem ich mir einige Stellen hier angesehen habe, habe ich damit umgegangen.

//function in some model/utility
function someFunction($em){
    try{
        //code which may throw exception and lead to closing of entity manager
    }
    catch(Exception $e){
        //handle exception
        return false;
    }
    return true;
}

//in controller assuming entity manager is in $this->em 
$result = someFunction($this->em);
if(!$result){
    $this->getDoctrine()->resetEntityManager();
    $this->em = $this->getDoctrine()->getManager();
}

Hoffe das hilft jemandem!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.