Lazy load between referenced documents on MongoDB

2019-09-08 12:24发布

问题:

Helo,

firstly, please excuse my English, is not very good.

I am migrating the data container of a Symfony2 application to MongoDB, before that it run with MySQL.

I added the DoctrineMongoDBBundle and "almost everything" works perfectly.

I have some references between documents in which I would like to keep the "lazy load" pattern offered by the Doctrine ORM. I have read the official documentation for Doctrine ODM,

  • http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/bidirectional-references.html

and some examples that explain how to create relations and define documents to get "lazy load" behavior,

  • http://www.slideshare.net/jwage/doctrine-mongodb-object-document-mapper (slide 44)

but I can not make it work.

In my case, I have two documents, "travel" and "note" with a 1:N relationship I want to keep, something like this:

<?php

namespace MyApp\TravelBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Mapping\Annotation as Gedmo;

/**
 * Travel
 *
 * @ODM\Document(collection="travel")
 */
class Travel {

    /**
     * @var \MyApp\NoteBundle\Document\Note
     * 
     * @ODM\ReferenceMany(targetDocument="\MyApp\NoteBundle\Document\Note", mappedBy="travel", sort={"createdAt"="asc"} )
     */
    private $notes;

    // more properties ...

    public function __construct() {
        $this->notes = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Add notes
     *
     * @param \MyApp\NoteBundle\Document\Note $notes
     */
    public function addNote(\MyApp\NoteBundle\Document\Note $notes) {
        $this->notes[] = $notes;
    }

    /**
     * Remove notes
     *
     * @param \MyApp\NoteBundle\Document\Note $notes
     */
    public function removeNote(\MyApp\NoteBundle\Document\Note $notes) {
        $this->notes->removeElement($notes);
    }

    /**
     * Get notes
     *
     * @return Doctrine\Common\Collections\Collection $notes
     */
    public function getNotes() {
        return $this->notes;
    }

    // more methods ...

}
?>

<?php

namespace MyApp\NoteBundle\Document;

use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Mapping\Annotation as Gedmo;

/**
 * Note
 *
 * @ODM\Document(collection="note")
 */
class Note
{
    /**
     * @var \MyApp\TravelBundle\Document\Travel
     * 
     * @ODM\ReferenceOne(targetDocument="MyApp\TravelBundle\Document\Travel", inversedBy="notes")
     */
    private $travel;

    // more properties ...

    /**
     * Set travel
     *
     * @param \MyApp\TravelBundle\Document\Travel $travel
     * @return Note
     */
    public function setTravel(\MyApp\TravelBundle\Document\Travel $travel) {
        $this->travel = $travel;
        $travel->addNote($this);

        return $this;
    }

    // more methods ...

}
?>

When I add a note to a travel I understand that the result for the travel document should be:

{ "_id" : ObjectId( "5183aa63095a1a3921000000" ),
  "name" : "First travel",
  "isActive" : true,
  "createdAt" : Date( 1367583331000 ),
  "updatedAt" : Date( 1367583331000 ),
  "notes" : [{ "$ref" : "note",
    "$id" : ObjectId( "5183aa63095a1a3955000000" ),
    "$db" : "mydb" }]
 }

and for the note document should be:

{ "_id" : ObjectId( "5183aa63095a1a3955000000" ),
  "travel" : { "$ref" : "travel",
    "$id" : ObjectId( "5183aa63095a1a3921000000" ),
    "$db" : "mydb" },
  "note" : "First note",
  "createdAt" : Date( 1367583331000 ),
  "updatedAt" : Date( 1367583331000 ) }

but for now I only get a reference in the note document, whilst no reference appears in the travel document, and when I do a query in the travel document Doctrine do not load the related note documents:

<?php
.
.
$travel = $dm->getRepository('TravelBundle:Travel')->findCurrentTravel($user->getId());
$travel->getNotes(); // IS EMPTY :(
.
.
?>

The process I follow to add a note to a travel is as follows:

<?php

namespace MyApp\TravelBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class TravelController extends Controller {

    public function createNoteAction(Request $request) {
        $dm = $this->get('doctrine.odm.mongodb.document_manager');
        $travel = $dm->getRepository('TravelBundle:Travel')->findCurrentTravel($user->getId());
        $entity = new Note();
        $form = $this->createForm(newNoteType(), $entity);
        if ($request->isMethod('POST')) {
            $form->bind($request);
            if ($form->isValid()) {
                $entity->setTravel($travel);
                $dm>persist($travel);
                $dm>persist($entity);
                $dm>flush();
            }
        }
    }
}
?>

Any ideas or suggestions to get the method $travel->getNotes() can automatically retrieve referenced notes through the "lazy load".

Thank you very much beforehand for the contributions,

Zacarías

回答1:

What you want to achieve is done by simply removing the mappedBy attribute in the ReferenceOne of the $travel property:

@ODM\ReferenceMany(targetDocument="\MyApp\NoteBundle\Document\Note", sort={"createdAt"="asc"} )

In this way doctrine will store the Notes IDs in the $nodes array.


With "mappedBy" instead, Doctrine doesn't store the IDs of the Notes in the $notes array, but instead it will do a query like this to fetch the actual notes:

db.Notes.find({travel.$id: <travelId>});

Note that IMHO this one is the preferred approach, since in this way when you add/remove a note, you don't have to update the Travel doc. (however you'll have to add an index on the $travel field)

Also notice that with ReferenceMany, using the mappedBy is lazy: only when you try to cycle the $notes array it will actually execute the query, so it is also lightweight.

See the doc for more info.