Doctrine ODM OneToOne Bi-Directional Reference using repositoryMethod
Asked Answered
H

2

8

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.

Hickey answered 8/1, 2015 at 14:33 Comment(0)
P
1

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.

Precede answered 18/1, 2015 at 0:4 Comment(1)
Thank you for your suggestion. I believe you are right that such a call is wrong. Unfortunately in practice, it doen't make any difference. I updated my post with your improvements though.Hickey
E
1

This is because of Logger (loggableCursor), it duplicates queries in the log file. For example, you call ...find()->limit(1)->getQuery() it loggs each call but actually there is a single query request.

More info: https://github.com/doctrine/mongodb-odm/issues/471#issuecomment-63999514

ODM issue: https://github.com/doctrine/mongodb/issues/151

Ellington answered 20/1, 2015 at 8:53 Comment(5)
Thank you. I hadn't seen this issues before. Based on the issues mentioned above the additional queries would only get logged but not actually run, right? However, when I turn on logging in MongoDB itself I can confirm that all queries are actually send to the database.Hickey
Do you mean "find article" and "find article_meta" or all the queries in log?Ellington
All three find queries.Hickey
Try to use repository method: $this->findOneBy(['groupcode' => $metadata->getGroupcode()]);Ellington
That doesn't make any difference. I start to believe that it is just a shortcomming in Doctrine ODM.Hickey

© 2022 - 2024 — McMap. All rights reserved.