如何根据 object 的键(而不是整个 object)更新列表中的 object?
How do I update an object within a list based on an object's key (and not the whole object)?
问题:
如何根据 object 中的特定键更新列表中的 object?
描述:
假设我的 mongodb 中有以下 object:
{
"keysteps":[
{"timecode":"01:00:00", "title": "Chapter 1"}
]
}
并说我想用关键步骤列表(又名:"chapters")更新 "keysteps",我将如何确保如果时间码已经存在,我将更新object 而不是创建一个新的?
如果我用这些关键步骤更新我的 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"
}
]
它没有更新关键步骤,而是在关键步骤列表中添加了一个新的 object。 如果我发送相同的时间码 object(相同的时间码和标题),它就不会创建一个新的。
我做了什么:
这是我用来更新的 Symfony 控制器的片段:
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 服务的片段:
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-library:
请大家指点一下title/question怎么写,我觉得好像说的不对
如您所见,$addToSet
运算符不适合在此处使用。这是设计使然,因为 "set" 属于数组中的 "objects",如果您以任何方式在键中给出不同的值组合,那么这是一个 "new" 对象并且是已添加。
所以正确的处理方法是使用 "two" 操作:
显然您不希望为多个操作写入并等待数据库的响应,因此您使用 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
。如果您的输入中可以包含多个数组项,那么您将循环这些项并创建 "pair" 操作,按索引拉出数组值。
所以这里的 0
索引用法只是索引变量的占位符,您将使用它来驱动它以支持输入中的多个数组项。您还可以只为 "operations" 生成 "internal" 文档,而不是实际发出 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;
}
与 Neil 的回答类似,先删除重复内容,然后推送新章节可能值得:
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();
}
我的理由是它应该与带有关键字段 'timecode' 的任意 sub-documents 一起使用,因此例如希望您向子文档添加一个 'description' 字段而不是更改标题,它仍然有效。
用批量写入包装它是个好主意,但不是必需的。它不是事务的替代品,所以如果处理并发更新很关键,应该使用乐观锁之类的东西。
问题:
如何根据 object 中的特定键更新列表中的 object?
描述:
假设我的 mongodb 中有以下 object:
{
"keysteps":[
{"timecode":"01:00:00", "title": "Chapter 1"}
]
}
并说我想用关键步骤列表(又名:"chapters")更新 "keysteps",我将如何确保如果时间码已经存在,我将更新object 而不是创建一个新的?
如果我用这些关键步骤更新我的 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"
}
]
它没有更新关键步骤,而是在关键步骤列表中添加了一个新的 object。 如果我发送相同的时间码 object(相同的时间码和标题),它就不会创建一个新的。
我做了什么:
这是我用来更新的 Symfony 控制器的片段:
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 服务的片段:
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-library:
请大家指点一下title/question怎么写,我觉得好像说的不对
如您所见,$addToSet
运算符不适合在此处使用。这是设计使然,因为 "set" 属于数组中的 "objects",如果您以任何方式在键中给出不同的值组合,那么这是一个 "new" 对象并且是已添加。
所以正确的处理方法是使用 "two" 操作:
显然您不希望为多个操作写入并等待数据库的响应,因此您使用 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
。如果您的输入中可以包含多个数组项,那么您将循环这些项并创建 "pair" 操作,按索引拉出数组值。
所以这里的 0
索引用法只是索引变量的占位符,您将使用它来驱动它以支持输入中的多个数组项。您还可以只为 "operations" 生成 "internal" 文档,而不是实际发出 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;
}
与 Neil 的回答类似,先删除重复内容,然后推送新章节可能值得:
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();
}
我的理由是它应该与带有关键字段 'timecode' 的任意 sub-documents 一起使用,因此例如希望您向子文档添加一个 'description' 字段而不是更改标题,它仍然有效。
用批量写入包装它是个好主意,但不是必需的。它不是事务的替代品,所以如果处理并发更新很关键,应该使用乐观锁之类的东西。