EntityManager已关闭

Uel*_*eli 74 orm entitymanager symfony doctrine-orm

[Doctrine\ORM\ORMException]   
The EntityManager is closed.  
Run Code Online (Sandbox Code Playgroud)

在插入数据后出现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();
Run Code Online (Sandbox Code Playgroud)

任何人都知道如何重新连接?

小智 61

我的解决方案

在做任何事之前检查:

if (!$this->entityManager->isOpen()) {
    $this->entityManager = $this->entityManager->create(
        $this->entityManager->getConnection(),
        $this->entityManager->getConfiguration()
    );
}
Run Code Online (Sandbox Code Playgroud)

将保存所有实体.但它对于特定类或某些情况很方便.如果你有一些注入了entitymanager的服务,它仍然会被关闭.

  • 您可能还想在第三个参数中传递 $this->entityManager->getEventManager() 。 (2认同)

小智 34

Symfony 2.0:

$em = $this->getDoctrine()->resetEntityManager();
Run Code Online (Sandbox Code Playgroud)

Symfony 2.1+:

$em = $this->getDoctrine()->resetManager();
Run Code Online (Sandbox Code Playgroud)

  • **警告:自`symfony 2.1以来,不推荐使用``resetEntityManager`.请改用"resetManager" (6认同)

Ald*_*nio 23

这是一个非常棘手的问题,因为至少对于Symfony 2.0和Doctrine 2.1,在关闭后不可能以任何方式重新打开EntityManager.

我发现克服这个问题的唯一方法是创建自己的DBAL Connection类,包装Doctrine并提供异常处理(例如,在将异常弹出到EntityManager之前重试几次).它有点hacky,我担心它会导致事务环境中的一些不一致(即,如果失败的查询处于事务中间,我不确定会发生什么).

这种方式的示例配置是:

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
Run Code Online (Sandbox Code Playgroud)

该课程应该或多或少地开始:

namespace Your\DBAL;

class ReopeningConnectionWrapper extends Doctrine\DBAL\Connection {
  // ...
}
Run Code Online (Sandbox Code Playgroud)

一个非常烦人的事情是你必须覆盖提供异常处理包装的Connection的每个方法.使用闭合可以缓解那里的一些痛苦.


Fra*_*ula 20

这就是我解释Doctrine "EntityManager关闭"的原因.问题.基本上每次出现异常(即重复密钥)时,Doctrine都会关闭实体管理器.如果您仍想与数据库进行交互,则必须通过调用JGrinonresetManager()提到的方法来重置实体管理器.

在我的应用程序中,我运行了多个RabbitMQ使用者,他们都在做同样的事情:检查数据库中是否存在实体,如果是,则返回它,如果不创建它然后返回它.在检查该实体是否已经存在并创建它之间的几毫秒内,另一个消费者碰巧做了同样的事情,并创建了丢失的实体,使另一个消费者招致了一个重复的密钥异常.

这导致了软件设计问题.基本上我想要做的是在一个事务中创建所有实体.这对大多数人来说可能是自然的,但在我的情况下肯定是概念错误的.考虑以下问题:我必须存储具有这些依赖关系的足球比赛实体.

  • 一组(即A组,B组......)
  • 一轮(即半决赛)
  • 场地(即比赛正在进行的体育场)
  • 比赛状态(即半场,全场)
  • 这两支球队都在比赛
  • 比赛本身

现在,为什么场地创建应该与匹配在同一个交易中?可能是因为我刚刚收到一个新的场所,它不在我的数据库中所以我必须先创建它.但也可能是该场地可能会举办另一场比赛,因此另一位消费者可能会尝试同时创建它.所以我要做的是首先在单独的事务中创建所有依赖项,确保我在重复键异常中重置实体管理器.我会说匹配旁边的所有实体都可以定义为"共享",因为它们可能是其他消费者中其他交易的一部分.在那里没有"共享"的东西是匹配本身,不可能同时由两个消费者创建.所以在上一次交易中,我希望看到比赛以及两支球队和比赛之间的关系.所有这些都导致了另一个问题.如果重置实体管理器,则在重置之前检索到的所有对象都是Doctrine全新的.所以Doctrine不会尝试对它们运行UPDATE而是INSERT!因此,请确保在逻辑上正确的事务中创建所有依赖项,然后在将它们设置为目标实体之前从数据库中检索所有对象.请考虑以下代码作为示例:

$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.
 */
Run Code Online (Sandbox Code Playgroud)

所以我认为应该这样做.

$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();
Run Code Online (Sandbox Code Playgroud)

我希望它有帮助:)


JGr*_*non 17

您可以重置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();
Run Code Online (Sandbox Code Playgroud)


Seb*_*eck 12

Symfony 4.2+ 中你必须使用这个包:

composer require symfony/proxy-manager-bridge
Run Code Online (Sandbox Code Playgroud)

否则你会得到例外:

Resetting a non-lazy manager service is not supported. Declare the "doctrine.orm.default_entity_manager" service as lazy.  
Run Code Online (Sandbox Code Playgroud)

你可以像这样重置 entityManager :

服务.yaml:

App\Foo:
    - '@doctrine.orm.entity_manager'
    - '@doctrine'
Run Code Online (Sandbox Code Playgroud)

.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();
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 6

我发现一篇关于这个问题的有趣文章

if (!$entityManager->isOpen()) {
  $entityManager = $entityManager->create(
    $entityManager->getConnection(), $entityManager->getConfiguration());
}
Run Code Online (Sandbox Code Playgroud)

原则 2 异常 EntityManager 已关闭


小智 5

Symfony v4.1.6

学说 v2.9.0

处理在存储库中插入重复项

  1. 访问您的仓库中的注册表


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

Run Code Online (Sandbox Code Playgroud)
  1. 将有风险的代码包装到事务中并在异常情况下重置管理器


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

Run Code Online (Sandbox Code Playgroud)