【问题标题】:MongoDB - Mongoose - TypeError: save is not a functionMongoDB - Mongoose - TypeError:保存不是函数
【发布时间】:2019-02-20 19:20:58
【问题描述】:

我正在尝试通过首先使用 .findById 获取文档来更新 MongoDB 文档(使用 mongoose),然后使用新值更新该文档中的字段。我对此还是有点陌生​​,所以我使用了一个教程来弄清楚如何让它工作,然后我一直在更新我的代码以满足我的需要。这是教程:MEAN App Tutorial with Angular 4。原始代码定义了一个模式,但我的要求是一个通用的 MongoDB 接口,它将简单地接收发送给它的任何有效负载并将其发送到 MongoDB。原始教程是这样的:

exports.updateTodo = async function(todo){
    var id = todo.id

    try{
        //Find the old Todo Object by the Id

        var oldTodo = await ToDo.findById(id);
    }catch(e){
        throw Error("Error occured while Finding the Todo")
    }

    // If no old Todo Object exists return false
    if(!oldTodo){
        return false;
    }

    console.log(oldTodo)

    //Edit the Todo Object
    oldTodo.title = todo.title
    oldTodo.description = todo.description
    oldTodo.status = todo.status


    console.log(oldTodo)

    try{
        var savedTodo = await oldTodo.save()
        return savedTodo;
    }catch(e){
        throw Error("And Error occured while updating the Todo");
    }
}

但是,由于我不想要架构并希望允许任何内容通过,我不想将静态值分配给特定字段名称,例如标题、描述、状态等。所以,我想出了这个:

exports.updateData = async function(update){
    var id = update.id

    // Check the existence of the query parameters, If they don't exist then assign a default value
    var dbName = update.dbName ? update.dbName : 'test'
    var collection = update.collection ? update.collection : 'testing'; 

    const Test = mongoose.model(dbName, TestSchema, collection);

    try{
        //Find the existing Test object by the Id
        var existingData = await Test.findById(id);
    }catch(e){
        throw Error("Error occurred while finding the Test document - " + e)
    }

    // If no existing Test object exists return false
    if(!existingData){
        return false;
    }

    console.log("Existing document is " + existingData)

    //Edit the Test object
    existingData = JSON.parse(JSON.stringify(update))

    //This was another way to overwrite existing field values, but
    //performs a "shallow copy" so it's not desireable
    //existingData = Object.assign({}, existingData, update)

    //existingData.title = update.title
    //existingData.description = update.description
    //existingData.status = update.status

    console.log("New data is " + existingData)

    try{
        var savedOutput = await existingData.save()
        return savedOutput;
    }catch(e){
        throw Error("An error occurred while updating the Test document - " + e);
    }
}

我最初的问题是我在获取新值以覆盖旧值时遇到了很多问题。现在已经解决了,我收到“TypeError:existingData.save 不是函数”的错误。我在想数据类型发生了变化或其他什么,现在它不被接受。当我取消注释旧教程代码中的静态值时,它可以工作。我在加入对象之前和之后的控制台日志记录进一步支持了这一点,因为第一个打印实际数据,第二个打印 [object Object]。但是,我似乎无法弄清楚它在期待什么。任何帮助将不胜感激。

编辑:我想通了。显然 Mongoose 有自己的“模型”数据类型,如果你使用 JSON.stringify 之类的东西对底层数据做任何疯狂的事情,它就会改变。我使用 Object.prototype.constructor 来确定实际的对象类型,如下所示:

console.log("THIS IS BEFORE: " + existingData.constructor);
existingData = JSON.parse(JSON.stringify(update));
console.log("THIS IS AFTER: " + existingData.constructor);

我得到了这个:

THIS IS BEFORE: function model(doc, fields, skipId) {
  model.hooks.execPreSync('createModel', doc);
  if (!(this instanceof model)) {
    return new model(doc, fields, skipId);
  }
  Model.call(this, doc, fields, skipId);
}
THIS IS AFTER: function Object() { [native code] }

这向我展示了实际情况。我添加了这个来修复它:

existingData = new Test(JSON.parse(JSON.stringify(update)));

在相关说明中,此时我可能应该只使用本机 MongoDB 驱动程序,但它正在工作,所以我现在就把它放在我的待办事项列表中。

【问题讨论】:

  • 补充一点——如果您使用的是节点 8 或更高版本,请尝试使用 'let' 和 'const' 而不是 'var' - 请参阅:medium.com/javascript-scene/…
  • 你能把console.logs的输出包括进去吗?
  • 这真的很令人沮丧,但我想通了。所以显然 mongoose 的数据类型是 [object Model],当你使用 JSON.stringify 时,它会将其转换为 [object Object],这让 mongoose 很生气。我只需要将数据重新转换为猫鼬模型并修复它。虽然现在我想我应该举手并使用本机 MongoDB 驱动程序而不是 mongoose。我一直想将“借用”代码中的“vars”转换为“let”或“const”,但我一直忘记这样做。我将不得不阅读哪些更可取以及何时使用它们。感谢您的帮助!
  • 我建议使用 MonogoDB 驱动程序而不是 Mongoose,它更快,由 MonogoDB 直接支持,并且在我看来并不难使用 - 我将在明天提供一个示例。另外,您是否有任何理由在更新之前找到该文档?即您已经拥有文档 ID,因此您只需调用一次即可更新文档 - 目前您执行两次调用似乎很浪费资源。当您更新文档时,您打算替换整个文档还是只更新已更改的字段?
  • 太棒了——我就是这么想的

标签: javascript node.js mongodb mongoose ecmascript-6


【解决方案1】:

您现在已经找到了解决方案,但我建议您使用 MongoDB 驱动程序,这将使您的代码看起来与此类似,并使原始问题消失:

// MongoDB Settings
const MongoClient = require(`mongodb`).MongoClient;
const mongodb_uri = `mongodb+srv://${REPLACE_mongodb_username}:${REPLACE_mongodb_password}@url-here.gcp.mongodb.net/test`;
const db_name = `test`;
let db; // allows us to reuse the database connection once it is opened

// Open MongoDB Connection
const open_database_connection = async () => {
  try {
    client = await MongoClient.connect(mongodb_uri);
  } catch (err) { throw new Error(err); }
  db = client.db(db_name);
};


exports.updateData = async update => {

  // open database connection if it isn't already open
  try {
    if (!db) await open_database_connection();
  } catch (err) { throw new Error(err); }


  // update document
  let savedOutput;
  try {
    savedOutput = await db.collection(`testing`).updateOne( // .save() is being depreciated
      { // filter
        _id: update.id // the '_id' might need to be 'id' depending on how you have set your collection up, usually it is '_id'
      },
      $set: { // I've assumed that you are overwriting the fields you are updating hence the '$set' operator
        update // update here - this is assuming that the update object only contains fields that should be updated
      }

      // If you want to add a new document if the id isn't  found add the below line
      // ,{ upsert: true }

    );
  } catch (err) { throw new Error(`An error occurred while updating the Test document - ${err}`); }


  if (savedOutput.matchedCount !== 1) return false; // if you add in '{ upsert: true }' above, then remove this line as it will create a new document

  return savedOutput;
}

集合testing 需要在此代码之前创建,但这只是一次性的事情并且非常容易 - 如果您使用的是 MongoDB Atlas,那么您可以使用 MongoDB Compass / 进入您的在线管理员来创建没有一行代码的集合...

据我所知,您应该需要复制 update 对象。以上将数据库调用从 2 减少到 1,并允许您重用数据库连接,可能在应用程序中有助于加快速度的其他任何地方。也不要将您的 MongoDB 凭据直接存储在代码中。

【讨论】:

  • 这似乎可以独立运行,这太棒了。我很好奇这将如何分解为服务(我正在使用 Angular)。目前,我用于 Mongoose 方法的教程代码在 app.js 中发生了连接,并且所有 REST 调用都指向了 controller.js 文件中的控制器。该文件具有基本的 REST 调用,然后指向 service.js 文件中的代码,该文件具有用于执行查询、更新等的实际逻辑。这可能是我不理解的基本 Angular 概念,但我想您可能对此有所了解。
  • 恐怕我不是 Angular 用户,所以无能为力。通常最好重用连接,而不是为每个调用打开一个新连接,因此在此示例中完成的方式通常是一个相当耗费资源的操作。
猜你喜欢
  • 2020-12-02
  • 2020-08-08
  • 2021-07-18
  • 2015-10-28
  • 1970-01-01
  • 2017-07-27
  • 1970-01-01
  • 2016-11-22
  • 2011-07-01
相关资源
最近更新 更多