2017-02-08 96 views
2

我正在开发一个Doctrine应用程序,我在两个实体之间有一对多的关系,我将其称为'Foo'和'Bar'。 '酒吧'是关系的拥有方,在这种情况下这可能看起来很奇怪。我需要一个接一个地处理属于foo的所有酒吧并刷新更改。但是,酒吧实体的体积非常大(每个几兆字节)。由于每个foo都可能有很多条,所以一个微不足道的循环会占用我所有的RAM。我试图通过冲洗和分离解决这个每栏已被处理后满脸通红,像这样:分离作为关系一部分的原则实体

foreach ($foo->getBars() as $bar) { 
    $bar->setSomething(...potentially large blobs...); 
    // ... dozens of more setters (and getters) 
    $em->flush(); 
    // or: $em->flush($bar); 
    $em->detach($bar); 
} 

// unrelated code 
$foo->setDoStuff(...) 
$em->flush(); 

但是失败严重的第二次迭代,因为某些原因,第二次冲洗()会遇到对象图中的第一个栏,并抱怨它没有被保留(它是非托管的)。错误消息是:“通过关系'Foo#bars'发现了一个新实体,该关系未配置为级联实体的持久化操作”

如果我只刷新特定的$ bar而不是所有实体,则循环成功完成,但后来无法再使用$ em-> flush(),因为它会遇到那些非托管实体。

分离$ foo可以解决这个问题,但不幸的是这是不可接受的,因为代码中有很多其他的$ foo引用可能需要修改并在稍后刷新。

我该如何解决这个问题?与clear()/ detach()相关的文档中的所有示例似乎都很平凡,并且不涉及关系。

理想情况下,有一种方法可以将对象恢复到非水合代理延迟加载状态,就像通过$ em-> getReference()获得的那样。垃圾收集器将释放之前由所有字段使用的内存。

+0

如果具体刷新不持续其他实体,你可以尝试使用级联?无论如何,如果内存是一个大问题,也许加载每个都不是最好的选择,你可以首先加载foo的条形码ID,并在循环中分别加载每个条形图并取消设置。 – mickadoo

+0

@mickadoo问题不在于事物不会被刷新。但是,您的评论的第二部分提示了一个很好的方向,我会进行调查。 – jlh

回答

0

我设法解决这个问题,我的具体用例,但它似乎是一个非常通用的,美丽的,到处工作的解决方案,这是不可能的。

要理解的重要一点是,Doctrine将如何加载实体并构建具有相互引用的对象的整个图形。为了正确分离任何实体,必须不是任何地方的任何引用。如果编程非常小心,可以这样做,但通常很难保证某些不相关的代码部分没有获得对要分离的实体的引用,或者更糟糕的是,没有将其附加到实体图上。 Doctrine将在每次刷新操作中遍历此图,并查找任何引用的实体,即使是分离的实体。

我上面代码中的实际问题是,$foo->getBars()初始化一个集合,最终将包含对所有“Bar”实体的引用。在每次冲洗时,Doctrine将在其身份映射中遇到$foo,并且$foo引用这个物化的'Bar'集合,该集合反过来引用“Bar”实体本身。因此它会尝试再次刷新它们。

我的解决方案是这样的:我完全避免使用集合并逐个获取“Bar”实体,以确保没有其他引用它们。类似的规定:

$sql = "SELECT b.id FROM Bar b WHERE b.foo = :foo"; 
$rows = $em->createQuery($sql)->setParameter('foo', $foo)->getScalarResult(); 
$repository = $this->em->getRepository('Bar'); 
foreach ($rows as $row) { 
    $bar = $repository->find($row['id']); 
    $bar->setSomething(...potentially large blobs...); 
    // ... dozens of more setters (and getters) 
    $em->flush($bar); 
    $em->detach($bar); 
} 

// unrelated code can now safely flush, because there are no references to any 'Bar' 
// in the entity graph 
$foo->setDoStuff(...) 
$em->flush(); 

但这仅仅在进行代码的任何其他部分之前已经叫$foo->getBars(),或加载“酒吧”的对象,并计划重新使用他们以后。

相关问题