How can Doctrine ODM be used to create a one-to-one bi-directional reference that lazy loads while using a field other than the primary key for the reference?
I have two collections in MongoDB with documents, Article and ArticleMetaData. For every Article document, there is an ArticleMetaData and vise versa. (A OneToOne Bi-Directional relationship.) For legacy reasons, the two document types need to be in separate collections. Both collections are updated by external systems that have no knowledge about the Mongo IDs. They do however contain a shared field "groupcode" which can be used to match the right article to its meta data.
I try to configure Doctrine in such a way that I can get the meta data for an article object and an article from its meta data object but I want to keep them lazy loaded. (There is no need to query for the other end when I don't need it.)
The mappings look as follows:
Foo\BarBundle\Document\Article:
repositoryClass: Foo\BarBundle\Repository\ArticleRepository
changeTrackingPolicy: DEFERRED_EXPLICIT
collection: article
type: document
fields:
id:
id: true
groupcode:
type: int
index: true
unique:
order: asc
...
referenceOne:
metaData:
targetDocument: Foo\BarBundle\Document\ArticleMetaData
mappedBy: groupcode
repositoryMethod: findOneByArticle
Foo\BarBundle\Document\ArticleMetaData:
repositoryClass: Foo\BarBundle\Repository\ArticleMetaDataRepository
changeTrackingPolicy: DEFERRED_EXPLICIT
collection: article_meta
fields:
id:
id: true
groupcode:
type: int
index: true
unique:
order: asc
...
referenceOne:
article:
targetDocument: Foo\BarBundle\Document\Article
mappedBy: groupcode
repositoryMethod: findOneByMetaData
And the repository methods mentioned above:
// In the ArticleRepository
public function findOneByMetaData(ArticleMetaData $metadata)
{
$article = $this
->createQueryBuilder()
->field('groupcode')->equals($metadata->getGroupcode())
->getQuery()
->getSingleResult();
$article->setMetaData($metadata);
return $article;
}
// In the ArticleMetaDataRepository
public function findOneByArticle(Article $article)
{
$metaData = $this
->createQueryBuilder()
->field('groupcode')->equals($article->getGroupcode())
->getQuery()
->getSingleResult();
$metaData->setArticle($article);
return $metaData;
}
It all seems to work quite well. I can query an Article or ArticleMetaData and get the other side, only the problem is: it does not seem to lazy load. When I query for an Article:
$article = $documentManager
->getRepository('FooBarBundle:Article')
->findOneBy(['groupcode' => 123]);
A lot of queries are executed:
doctrine.INFO: MongoDB query: {"find":true,"query":{"groupcode":123},"fields":[],"db":"development","collection":"article"}
doctrine.INFO: MongoDB query: {"limit":true,"limitNum":1,"query":{"groupcode":123},"fields":[]}
doctrine.INFO: MongoDB query: {"limit":true,"limitNum":null,"query":{"groupcode":123},"fields":[]}
doctrine.INFO: MongoDB query: {"find":true,"query":{"groupcode":123},"fields":[],"db":"development","collection":"article_meta"}
doctrine.INFO: MongoDB query: {"limit":true,"limitNum":1,"query":{"groupcode":123},"fields":[]}
doctrine.INFO: MongoDB query: {"find":true,"query":{"groupcode":123},"fields":[],"db":"development","collection":"article"}
doctrine.INFO: MongoDB query: {"limit":true,"limitNum":1,"query":{"groupcode":123},"fields":[]}
What am I doing wrong? Is there I way I can accomplish a lazy loading one-to-one bi-directional reference having the above constraints?
Edit:
After reading Rob Holmes' answer I removed a test in the repository methods that could have caused the issue. Unfortunately the problem still remains and there are still 3 queries being executed where one (or two at most) suffice.
Doctrine ODM will already lazy load the referenced document, rather than prefetch it for you.
I believe your problem actually lies in your repository methods... For example, in the findOneByMetaData
function the first thing you are doing is calling $metadata->getArticle()
in doing this you are asking doctrine to load the article from the database, which due to your repositoryMethod is going to call findOneByMetaData
again. This is why you are seeing multiple queries.
Your findOneByMetaData
function should look more like this:
// In the ArticleRepository
public function findOneByMetaData(ArticleMetaData $metadata)
{
$article = $this->createQueryBuilder()
->field('groupcode')->equals($metadata->getGroupcode())
->getQuery()
->getSingleResult();
$article->setMetaData($metadata);
return $article;
}
Doctrine will take care of whether the article has been loaded yet, so there is no need to try and check for a null value. The same also applies to your findOneByArticle
function too.
Hope this make sense, and helps you resolve your issue.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With