Данный EntityManager закрыт


[DoctrineORMORMException]   
The EntityManager is closed.  

после того, как я получаю исключение DBAL при вставке данных, EntityManager закрывается, и я не могу его повторно подключить.

Я пытался, как это, но он не получил соединение.

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

кто-нибудь идея, как восстановить?

12 55

12 ответов:

Это очень сложная проблема, так как, по крайней мере, для Symfony 2.0 и Doctrine 2.1, невозможно каким-либо образом повторно открыть EntityManager после его закрытия.

единственный способ, который я нашел, чтобы преодолеть эту проблему, - это создать свой собственный класс подключения DBAL, обернуть доктрину один и обеспечить обработку исключений (например, повторяя несколько раз, прежде чем выскакивать исключение в EntityManager). Это немного хаки, и я боюсь, что это может вызвать некоторую несогласованность в транзакциях среды (т. е. я не совсем уверен, что происходит, если запрос в середине транзакции).

пример конфигурации, чтобы пойти по этому пути:

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

класс должен начинаться примерно так:

namespace Your\DBAL;

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

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

мое решение.

прежде чем что-либо делать проверьте:

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

все объекты будут сохранены. Но это удобно для конкретного класса или некоторых случаев. Если у вас есть некоторые службы с введенным entitymanager, он все равно будет закрыт.

Symfony 2.0:

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

Symfony 2.1+:

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

вы можете сбросить EM так

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

вот как я решил учение " EntityManager закрыт." вопрос. В основном каждый раз, когда есть исключение (т. е. дубликат ключа), доктрина закрывает Entity Manager. Если вы все еще хотите взаимодействовать с базой данных, вы должны сбросить Entity Manger, вызвав resetManager() метод, как упоминалось JGrinon.

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

Это привело к проблеме разработки программного обеспечения. В основном то, что я пытался сделать, это создать все сущности в одной транзакции. Это может показаться естественным для большинства, но было определенно концептуально неправильно в моем случае. Рассмотрим следующую проблему: мне пришлось хранить объект футбольного матча, который имел эти зависимости.

  • группа A (т. е. Группа A, Группа B...)
  • раунд (т. е. полуфинал)
  • место проведения (т. е. стадион, где проходит матч)
  • статус матча (т. е. половину времени, полный рабочий день)
  • две команды играют в матче
  • матч

Теперь, почему местом проведения создание должно быть в той же транзакции, что и матч? Возможно, я только что получил новое место, которого нет в моей базе данных, поэтому я должен сначала создать его. Но также может быть, что это место может провести еще один матч, поэтому другой потребитель, вероятно, попытается создать его одновременно. Поэтому мне нужно было сначала создать все зависимости в отдельных транзакциях, убедившись, что я сбрасываю диспетчер сущностей в исключении повторяющегося ключа. Я бы сказал, что все сущности в там рядом с матчем можно было бы определить как "общий", потому что они потенциально могут быть частью других транзакций в других потребителях. То, что не является "общим", есть сам матч, который вряд ли будет создан двумя потребителями одновременно. Поэтому в последние сделки я ожидаю увидеть только матч и отношение между двумя командами и матч. Все это также привело к другой проблеме. При сбросе диспетчера сущностей все объекты, которые были получены перед сбросом являются для учения совершенно новыми. Так что доктрина не будет пытаться запустить обновление на них, но вставить! Поэтому убедитесь, что вы создаете все свои зависимости в логически правильных транзакциях, а затем извлекаете все свои объекты из базы данных, прежде чем устанавливать их в целевую сущность. Рассмотрим следующий код в качестве примера:

$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.
 */

Так вот как я думаю, что это должно быть сделано.

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

надеюсь, это поможет:)

в контроллер.

исключение закрывает диспетчер сущностей. Это создает проблемы для массовой вставки. Чтобы продолжить, нужно переопределить его.

/** 
* @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++;
    }               
  }                      
}

попробуйте использовать:

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

перед началом операции.

On Connection::rollback метод проверки nestTransactionsWithSavepoints собственность.

У меня была эта проблема. Вот как я это исправил.

соединение, кажется, закрывается при попытке сбросить или сохранить. Попытка открыть его-плохой выбор, потому что создает новые проблемы. Я попытался понять, почему соединение было закрыто, и обнаружил, что я делаю слишком много изменений перед сохранением.

persist () ранее решил эту проблему.

Это действительно старая проблема, но у меня просто была подобная проблема. Я делал что-то вроде этого :

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

проблема была в том, что ясно отсоединить все объекты, включая первый и бросить ошибку EntityManager закрыт.

В моем случае решение было просто сделать ясно на отдельный тип сущности и оставить $entityOne все еще под EM:

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

для чего это стоит я обнаружил, что эта проблема происходит в команде пакетного импорта из-за цикла try/catch, ловящего ошибку SQL (с em->flush()), что я ничего не делала. В моем случае это было потому, что я пытался вставить запись с ненулевым свойством, оставленным как null.

Проверьте dev.log файл для любых глупых ошибок SQL, как это, как это может быть ваша вина. :)

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

я столкнулся с той же проблемой. После просмотра нескольких мест вот как я справился с этим.

//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();
}

надеюсь, это кому-то поможет!