2017-02-08 11 views
2

私は「Foo」と「Bar」と呼ぶ2つの実体の間に1対多の関係を持つDoctrineアプリケーションを開発しています。 「バー」は関係の所有側であり、この文脈では奇妙に見えるかもしれない。私は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(); 

は、しかし、これが2回目の反復でひどく失敗何らかの理由で第二フラッシュは()が発生しますので、:すべてのバーが処理され、フラッシュ、そのようにされた後、私は紅潮し、取り外すことにより、この問題を解決しようとしていますオブジェクトグラフの最初のバーを表示し、それが永続化されていないと不満を表明します(アンマネージドです)。エラーメッセージは次のとおりです。「エンティティの永続操作をカスケードするように構成されていない関係「Foo#bars」によって新しいエンティティが見つかりました

すべてのエンティティではなく特定の$バーのみをフラッシュすると、正常に完了しますが、後で引数なしで$ 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」エンティティを1つずつフェッチして、他に何も参照していないことを確認します。これらの線に沿って何か:

$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()と呼ばれていない、または「バー」のオブジェクトをロードし、後でそれを再利用することを計画している場合のみ動作。

関連する問題