Answer a question

I want to update (replace) a subdocument within an array of all the documents, that have this specific embedded subdocument.

My sample content object is:

{
    "_id" : ObjectId("51f289e5345f9d10090022ef"),
    "title" : "This is a content",
    "descriptors" : [ 
        {
            "_id" : ObjectId("51f289e5345f9d10090022f4"),
            "name" : "This is a descriptor",
            "type" : "This is a property"
        },
        {
            "_id" : ObjectId("51f289e5345f9d10090022f0"),
            "name" : "This is another descriptor",
            "type" : "This is another property"
        }
    ]
}

I want to find the object with a specific descriptor._id. I want to replace the one subdocument with another one (respectivley with an updated version of the old object, but not necessarily with the same properties).

While this works well in RoboMongo / shell...

db.Content.update(
{
    'descriptors._id': ObjectId("51f289e5345f9d10090022f4")
},
{
    $set: {
        'descriptors.$': {
            "_id" : ObjectId("51f289e5345f9d10090022f4"),
            "name" : "This is the updated descriptor",
            "category" : "This is a new property"
        }
    }
},
{
    'multi': true
})

...and with the plain php methods...

$descriptor = array();
$descriptor['_id'] = new \MongoID('51f289e5345f9d10090022f4');
$descriptor['name'] = 'This is the updated descriptor';
$descriptor['category'] = 'This is a new property';

$mongo = new \Mongo('mongodb://localhost:27017');
$database = $mongo->selectDB('MyDatabase');

$output = $database->selectCollection('Content')->update(
    array('descriptors._id' => $descriptor['_id']),
    array('$set' => array('descriptors.$' => $descriptor)),
    array("multiple" => true)
);

...it doesn't work with Doctrine MongoDB ODM...

$descriptor = array();
$descriptor['_id'] = new \MongoID('51f289e5345f9d10090022f4');
$descriptor['name'] = 'This is the updated descriptor';
$descriptor['category'] = 'This is a new property';

$query = $dm->createQueryBuilder('Content')
->update()->multiple(true)
->field('descriptors._id')->equals($descriptor['_id'])
->field('descriptors.$')->set($descriptor)
->getQuery()->execute();

...because it fails with the following error:

Notice: Undefined offset: 2 in C:\MyProject\vendor\doctrine\mongodb-odm\lib\Doctrine\ODM\MongoDB\Persisters\DocumentPersister.php line 998

So I assume, that Doctrine MongoDB ODM needs three parts in the dot-notation. The not so nice solution would be to iterate over the properties of the subdocument and set them manually.

$descriptor = array();
$descriptor['_id'] = new \MongoID('51f289e5345f9d10090022f4');
$descriptor['name'] = 'This is the updated descriptor';
$descriptor['category'] = 'This is a new property';

$query = $dm->createQueryBuilder('Content')
->update()->multiple(true)
->field('descriptors._id')->equals($descriptor['_id']);
foreach ($descriptor as $key => $value) {
    $query->field('descriptors.$.'.$key)->set($value);
}
$query->getQuery()->execute();

But this will only update existing and add new properties, but won't remove old/unnecessary properties from the subdocument.

Any ideas how to solve the problem:

  • with a simple query
  • while using Doctrine MongoDB ODM
  • without looping over the subdocument-array in php

I'm using:

  • Mongo-Server: 2.4.5
  • PHP: 5.4.16
  • PHP-Mongo-Driver: 1.4.1

Composer:

"php": ">=5.3.3",
"symfony/symfony": "2.3.*",
"doctrine/orm": ">=2.2.3,<2.4-dev",
"doctrine/doctrine-bundle": "1.2.*",
"doctrine/mongodb-odm": "1.0.*@dev",
"doctrine/mongodb-odm-bundle": "3.0.*@dev"

Answers

This was a bug in ODM and should be fixed in PR #661. Please take a look and ensure that the revised test satisfies your use case.

Until the next beta of ODM is tagged, this will only reside in the master branch; however, that should match your 1.0.*@dev version requirement.

Logo

MongoDB社区为您提供最前沿的新闻资讯和知识内容

更多推荐