【问题标题】:$push and $set same sub-document in an Array$push 和 $set 数组中的相同子文档
【发布时间】:2018-01-02 11:27:06
【问题描述】:

我正在尝试使用 mongoosejs 4.9.5 和 mongo 3.2.7 将状态历史记录在子文档数组中

文档结构示例:

  • 公司(架构)
    • 员工(架构):[ ]​​i>
    • 当前状态:字符串
    • 状态(架构):[]
      • 状态:字符串
      • 开始:日期
      • 结束:日期

当我更改员工状态时,我想更改 currentState,将新状态添加到 states 数组中,并更新最后一个状态以定义“结束”时间戳

// I get the last state position from a previous find request
var lastStateIndex = employee.stateHistory.length - 1; 
var changeStateDate = new Date();

// Prepare the update
var query = { _id: companyId, "employees._id": employeeId };
var update = { 
    $set: { 
        "employees.$.state": newState, 
        `employees.$.stateHistory.${lastStateIndex}.ends`: changeStateDate
    },
    $push: {
        "employees.$.stateHistory": {
            state: newState,
            starts: changeStateDate 
        }
    }
}
Company.findOneAndUpdate(query, update, { multi:false, new:true}, ... )

Mongo 返回以下错误

{"name":"MongoError","message":"Cannot update 'employees.0.stateHistory.0.ends' and 'employees.0.stateHistory' at the same time","ok":0,"errmsg":"Cannot update 'employees.0.stateHistory.0.ends' and 'employees.0.stateHistory' at the same time","code":16837}
  • 有什么建议可以避免为此目的运行两次更新吗?
  • 是否有任何解决方法可以避免存储“结束”日期,但能够在基于数组中下一项的“开始”之后计算它?

谢谢,

【问题讨论】:

  • 当然是"stateHistory" 部分。更新语句中的操作应用程序没有“顺序”。所以你不能修改相同的路径。您应该改用.bulkWrite() 并在两个单独的操作中在“同一路径”上发出$set$push。它们需要分开,但“批量”的作用是发送 one 请求和 one 响应,而不是每个响应的“多个”。
  • 谢谢尼尔,真的很感激!

标签: node.js mongodb mongoose


【解决方案1】:

我预计这已经在其他地方得到了回答,但似乎没有其他合理的回应存在。正如所评论的,您实际上不能在单个更新操作中执行此操作,因为操作在同一路径上“冲突”。但是.bulkWrite() 允许在单个请求和响应中应用“多个更新”。

Company.bulkWrite([
  { "updateOne": {
    "filter": { "_id": companyId, "employees._id": employeeId },
    "update": {
      "$set": { 
        "employees.$.state": newState, 
        [`employees.$.stateHistory.${lastStateIndex}.ends`]: changeStateDate
      } 
  }},
  { "updateOne": {
    "filter": { "_id": companyId, "employees._id": employeeId },
    "update": { 
      "$push": {
        "employees.$.stateHistory": {
          "state": newState,
          "starts": changeStateDate 
        }
      }
    }
  }}
])

现在.bulkWrite() 当然不会像.findOneAndUpdate() 那样返回“修改后的文档”。因此,如果您需要实际返回文档,则需要添加到 Promise 链中:

Company.bulkWrite([
  { "updateOne": {
    "filter": { "_id": companyId, "employees._id": employeeId },
    "update": {
      "$set": { 
        "employees.$.state": newState, 
        [`employees.$.stateHistory.${lastStateIndex}.ends`]: changeStateDate
      } 
  }},
  { "updateOne": {
    "filter": { "_id": companyId, "employees._id": employeeId },
    "update": { 
      "$push": {
        "employees.$.stateHistory": {
          "state": newState,
          "starts": changeStateDate 
        }
      }
    }
  }}
]).then( result => {
  // maybe inspect the result
  return Company.findById(companyId);
})

当然要注意,在应用.bulkWrite() 和执行.findById() 之间,可以对文档进行另一次修改是“可能的”。但这就是您正在进行的操作的成本。

通常最好考虑您是否真的需要返回的文档。在大多数情况下,您只是已经有了您应该知道的信息和任何“更新”,因为您正在“发布它们”,如果您想要“真正的反应”,那么您应该通过套接字监听数据上的其他更改事件而是。

注意您可以简单地“链接”“多个”.findOneAndUpdate() 调用,但这确实是来自服务器的“多个”调用和响应,而不是 一个强> 使用.bulkWrite()。因此,不这样做真的没有任何好处。

【讨论】:

    猜你喜欢
    • 2020-09-08
    • 2018-05-28
    • 1970-01-01
    • 1970-01-01
    • 2013-03-02
    • 2014-03-02
    • 2018-04-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多