【问题标题】:How do I update an object within a list based on an object's key (and not the whole object)?如何根据对象的键(而不是整个对象)更新列表中的对象?
【发布时间】:2017-05-23 07:48:00
【问题描述】:

问题:

如何根据对象中的特定键更新列表中的对象?

说明:

假设我的 mongodb 中有以下对象:

{
    "keysteps":[
        {"timecode":"01:00:00", "title": "Chapter 1"}
    ]
}

说我想用一个关键步骤列表(又名:“章节”)更新“关键步骤”,我将如何确保如果时间码已经存在,我会更新对象而不是创建一个新的一个?

如果我用这些关键步骤更新我的 mongodb:

{
    "keysteps":[
        {"timecode":"01:00:00", "title": "Chapter 1 - edited"}
    ]
}

这是我得到的输出:

keysteps": [
      {
        "timecode": "01:00:00",
        "title": "Chapter 1"
      },
      {
        "timecode": "01:00:00",
        "title": "Chapter 1 - edited"
      }
    ]

它没有更新 keysteps,而是在 keysteps 列表中添加了一个新对象。 如果我发送了相同的时间码对象(相同的时间码和标题),它就不会创建一个新对象。

我做了什么:

这是我用来更新的 Symfony 控制器的 sn-p:

class VideoToolsController extends Controller
{
    //More code

    public function keystepsUpdateAction(Request $request, int $video_id){
        //If the video id already exists, throw an error

        $keystepsS = $this->get('video_tools.keysteps');
        $exists = $keystepsS->exists($video_id);

        //Return immediately if keysteps doesn't exist.
        if(!$exists)
            return JsonResponseService::getErrorBadRequest("Keystep does not exist.");

        $keysteps = json_decode($request->getContent(), true);
        //die(var_dump($data));
        $form = $this->createForm(KeystepsType::class);
        $form->submit($keysteps);
        //return new JsonResponse($data);
        //Return bad request if the form is not valid.
        if(!$form->isValid())
            return JsonResponseService::getErrorBadRequest("Keysteps are not valid, try again.");

        try {
            $res = $keystepsS->update($video_id, $keysteps['keysteps']);
            return JsonResponseService::getSuccess($res, "Updated keysteps.");

        } catch(Exception $exception){
            return JsonResponseService::getErrorBadRequest($exception->getMessage(), $keysteps);
        }
    }
    //More code
}

这是我用来更新的 Php 服务中的一个 sn-p:

class KeystepsService {

    //More code

    //TODO: Make timecode UNIQUE
    public function update($video_id, $keysteps) : int {

        $updatedOne = $this->mongoCollection->updateOne(
            ['video_id' => $video_id ],
            ['$addToSet' => [ "keysteps" => [ '$each' => $keysteps ]]]
        );

        return $updatedOne->getModifiedCount();
    }

    //More code
}

mongodb/mongo-php-库:

mongodb/mongo-php-library

请随意给我一些关于如何措辞标题/问题的建议,我觉得好像这不是重点。

【问题讨论】:

    标签: php json mongodb symfony mongodb-php


    【解决方案1】:

    正如您所发现的,$addToSet 运算符在这里不适合使用。这是设计使然,因为“集合”是数组中的“对象”,如果您以任何方式在键中给出不同的值组合,那么这是一个“新”对象并被添加。

    所以处理这个问题的正确方法是使用“两个”操作:

    1. 尝试通过键在数组中找到它确实存在的项目,然后用$set 更新它
    2. 如果键不存在,则尝试$push 将项添加到数组中。

    显然,您不想为多个操作写入并等待来自数据库的响应,因此您可以使用bulkWrite() 方法同时发送两者:

    $this->mongoCollection->bulkWrite([
      [ 'updateOne' => [ 
        [ 'video_id' => $video_id, 'keysteps.timecode' => $keysteps[0]['timecode'] ],
        [ '$set' => [ 'keysteps.$.title' => $keysteps[0]['title'] ] ]
      ]],
      [ 'updateOne' => [
        [ 'video_id' => $video_id, 'keysteps.timecode' => [ '$ne' => $keysteps[0]['timecode'] ] ],
        [ '$push' => [ 'keysteps' => $keysteps[0] ]
      ]]
    ])
    

    请注意,虽然仍然保留您的数组对象结构,但由于这两个步骤操作的性质,此处故意不使用 $each。如果您的输入中可以包含多个数组项,那么您将循环这些项并创建“一对”操作,按索引拉出数组值。

    因此,此处的0 索引使用只是索引变量的占位符,您将使用它来驱动它以支持输入中的多个数组项。您也只需为“操作”生成“内部”文档,而不是多次实际发出bulkWrite()。这个方法的重点毕竟是只调用一次。

    编辑工作解决方案:

    public function update($video_id, $keysteps) : int {
    
        $modifiedData = 0;
        foreach($keysteps as $keystep){
            $bulkUpdate =  $this->mongoCollection->bulkWrite([
                [ 'updateOne' => [
                    [ 'video_id' => $video_id, 'keysteps.timecode' => $keystep['timecode'] ],
                    [ '$set' => [ 'keysteps.$.title' => $keystep['title'] ] ]
                ]],
                [ 'updateOne' => [
                    [ 'video_id' => $video_id, 'keysteps.timecode' => [ '$ne' => $keystep['timecode'] ] ],
                    [ '$push' => [ 'keysteps' => $keystep ]
                    ]]
                ]]);
            $modifiedData += $bulkUpdate->getModifiedCount();
        }
    
        return $modifiedData;
    }
    

    【讨论】:

    • @rottenoats 我希望语法没问题,我的 PHP 有点生疏,但我希望至少总体意图能够通过
    • 工作就像一个魅力。非常感谢。
    【解决方案2】:

    与尼尔的回答类似,可能值得先提取副本,然后推送新章节:

    public function update($video_id, $keysteps) : int {
        $timecodes = array_map(function($chapter){return $chapter['timecode'];}, $keysteps);
    
        $updatedOne = $this->mongoCollection->updateOne(
            ['video_id' => $video_id ],
            ['$pull' => [ 'keysteps' => [ 'timecode' => ['$in' => $timecodes]]]]
        );
    
        $updatedOne = $this->mongoCollection->updateOne(
            ['video_id' => $video_id ],
            ['$addToSet' => [ 'keysteps' => [ '$each' => $keysteps]]]
        );
    
        return $updatedOne->getModifiedCount();
    }
    

    我的理由是它应该适用于具有关键字段“时间码”的任意子文档,因此例如希望您向子文档添加一个“描述”字段而不是更改标题,它仍然有效。

    用批量写入包装它是个好主意,但不是必需的。它不是事务的替代品,所以如果处理并发更新很关键,应该使用乐观锁之类的东西。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-01-06
      • 1970-01-01
      • 2019-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-01
      • 1970-01-01
      相关资源
      最近更新 更多