如果不存在则如何创建项目并返回错误(如果存在)

时间:2018-04-24 21:41:02

标签: node.js mongodb mongoose

我正在编写alexa技能,并希望检查MongoDB中是否存在用户。我的代码有效,但如果用户已经在数据库中,我不知道如何定义情况:(

每当我执行代码时,我得到: "你好安娜,你是新来的"

我的用户Anna已保存在MongoDB中

但我想区分我的用户何时已经在数据库中并对此作出反应。

聪明的人有没有解决我的问题的方法?

    var myName = "Anan1";
    var userID = this.event.session.user.userId;
    console.log(userID);

    self = this;
    User.findOneAndUpdate(
        {userId:  userID},
        {$set:{name:myName}},
        {upsert: true, new: false, runValidators: true},
        function(err, doc){
            if(err){
                console.log("eeoror");
            }

            console.log(doc);
            if (doc==null){
                self.emit(':ask',
                    "Hello "+ myName +"you are new here")
            }else {
                self.emit(':ask',
                    "Hello "+ myName +"you are not new here")
            }

        });

2 个答案:

答案 0 :(得分:2)

如前面评论中所述,您有两种基本方法可以确定某些事情是否已经创建了#34;或不。这些要么是:

  • 在回复中返回rawResult并检查updatedExisting属性,该属性会告诉您它是否为&#34; upsert&#34; <或者

  • 设置new: false以便&#34;没有文档&#34;当它实际上是&#34; upsert&#34;

  • 时,实际上会返回结果

作为展示的清单:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost/thereornot';

mongoose.set('debug', true);
mongoose.Promise = global.Promise;

const userSchema = new Schema({
  username: { type: String, unique: true },   // Just to prove a point really
  password: String
});

const User = mongoose.model('User', userSchema);

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri);

    await Promise.all(Object.entries(conn.models).map(([k,m]) => m.remove()));

    // Shows updatedExisting as false - Therefore "created"

    let bill1 = await User.findOneAndUpdate(
      { username: 'Bill' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: true, rawResult: true }
    );
    log(bill1);

    // Shows updatedExisting as true - Therefore "existing"

    let bill2 = await User.findOneAndUpdate(
      { username: 'Bill' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: true, rawResult: true }
    );
    log(bill2);

    // Test with something like:
    // if ( bill2.lastErrorObject.updatedExisting ) throw new Error("already there");


    // Return will be null on "created"
    let ted1 = await User.findOneAndUpdate(
      { username: 'Ted' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: false }
    );
    log(ted1);

    // Return will be an object where "existing" and found
    let ted2 = await User.findOneAndUpdate(
      { username: 'Ted' },
      { $setOnInsert: { password: 'password' } },
      { upsert: true, new: false }
    );
    log(ted2);

    // Test with something like:
    // if (ted2 !== null) throw new Error("already there");

    // Demonstrating "why" we reserve the "Duplicate" error
    let fred1 = await User.findOneAndUpdate(
      { username: 'Fred', password: 'password' },
      { $setOnInsert: { } },
      { upsert: true, new: false }
    );
    log(fred1);       // null - so okay

    let fred2 = await User.findOneAndUpdate(
      { username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password
      { $setOnInsert: { } },
      { upsert: true, new: false }
    );

    mongoose.disconnect();

  } catch(e) {
    console.error(e)
  } finally {
    process.exit()
  }


})()

输出:

Mongoose: users.remove({}, {})
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": false,
    "upserted": "5adfc8696878cfc4992e7634"
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}
Mongoose: users.findAndModify({ username: 'Bill' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: true, rawResult: true, remove: false, fields: {} })
{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": true
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null
Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
{
  "_id": "5adfc8696878cfc4992e7639",
  "username": "Ted",
  "__v": 0,
  "password": "password"
}

所以第一种情况实际上考虑了这段代码:

User.findOneAndUpdate(
  { username: 'Bill' },
  { $setOnInsert: { password: 'password' } },
  { upsert: true, new: true, rawResult: true }
)

大多数选项都是标准的&#34;所有&#34; "upsert"操作都会导致字段内容被用于匹配&#34; (即username&#34;始终&#34; 在新文档中创建,因此您不需要$set该字段。为了不实际修改&#34;您可以使用$setOnInsert后续请求中的其他字段,只会在找不到匹配项的"upsert"操作中添加这些属性。

此处标准new: true用于返回&#34;已修改的&#34;来自操作的文档,但区别在于rawResult,如返回的响应中所示:

{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": false,
    "upserted": "5adfc8696878cfc4992e7634"
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}

而不是&#34; mongoose文件&#34;你得到了实际的&#34; raw&#34;司机的回应。实际文档内容位于"value"属性下,但它是我们感兴趣的"lastErrorObject"

我们在这里看到了属性updatedExisting: false。这表明&#34;没有匹配&#34;实际上是找到了,因此创建了一个新文件&#34;。因此,您可以使用它来确定实际发生的创建。

再次发出相同的查询选项时,结果会有所不同:

{
  "lastErrorObject": {
    "n": 1,
    "updatedExisting": true             // <--- Now I'm true
  },
  "value": {
    "_id": "5adfc8696878cfc4992e7634",
    "username": "Bill",
    "__v": 0,
    "password": "password"
  },
  "ok": 1,
  "operationTime": "6548172736517111811",
  "$clusterTime": {
    "clusterTime": "6548172736517111811",
    "signature": {
      "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=",
      "keyId": 0
    }
  }
}

updatedExisting值现在为true,这是因为已有一个文档与查询语句中的username: 'Bill'匹配。这告诉您文档已经存在,因此您可以分支逻辑以返回&#34;错误&#34;或者你想要的任何回应。

在另一种情况下,可能需要&#34; not&#34;返回&#34; raw&#34;回复并使用返回的&#34; mongoose文档&#34;代替。在这种情况下,如果没有new: false选项,我们会将值更改为rawResult

User.findOneAndUpdate(
  { username: 'Ted' },
  { $setOnInsert: { password: 'password' } },
  { upsert: true, new: false }
)

大多数相同的事情适用,但现在行动是文件的原始状态,而不是&#34;修改过的&#34;文件的状态&#34;在&#34;之后那个行动。因此,当没有文档实际匹配&#34;查询&#34;语句,返回的结果是null

Mongoose: users.findAndModify({ username: 'Ted' }, [], { '$setOnInsert': { password: 'password', __v: 0 } }, { upsert: true, new: false, remove: false, fields: {} })
null           // <-- Got null in response :(

这告诉您文档是&#34;已创建&#34;并且可以说您已经知道文档的内容应该是什么,因为您使用该语句发送了该数据(理想情况下在{ {1}})。重点是,你已经知道该返回什么了#34;应该&#34;您需要实际返回文档内容。

相比之下,&#34;发现&#34;文件返回&#34;原始状态&#34;在&#34;之前显示文件&#34;它被修改了:

$setOnInsert

因此,任何回复都不是{ "_id": "5adfc8696878cfc4992e7639", "username": "Ted", "__v": 0, "password": "password" } &#34;因此表明该文档已经存在,并且您可以再次根据实际收到的响应来分支您的逻辑。

所以这些是你所要求的两种基本方法,而且他们肯定会工作&#34;!就像在这里用相同的陈述证明和重现一样。

附录 - 为错误密码保留重复密钥

还有一个更有效的方法在完整列表中暗示,主要是简单地null(或来自猫鼬模型的.insert())新数据并且具有&#34;重复键#34;错误扔在哪里&#34;唯一&#34;实际遇到属性索引。这是一种有效的方法,但在用户验证中有一个特定的用例&#34;这是一个方便的逻辑处理,那就是验证密码&#34;。

因此,通过.create()username组合检索用户信息是一种非常常见的模式。在&#34; upsert&#34;这种组合证明了&#34;独特的&#34;因此&#34;插入&#34;如果未找到匹配项,则尝试。这正是使密码匹配成为一个有用的实现的原因。

请考虑以下事项:

password

在第一次尝试时,我们 // Demonstrating "why" we reserve the "Duplicate" error let fred1 = await User.findOneAndUpdate( { username: 'Fred', password: 'password' }, { $setOnInsert: { } }, { upsert: true, new: false } ); log(fred1); // null - so okay let fred2 = await User.findOneAndUpdate( { username: 'Fred', password: 'badpassword' }, // <-- dup key for wrong password { $setOnInsert: { } }, { upsert: true, new: false } ); 实际上没有username,所以&#34; upsert&#34;会发生,并且上面已经描述过的所有其他事情都会确定它是创建文档还是找到的文档。

以下语句使用相同的"Fred"值,但为记录的内容提供不同的密码。 MongoDB试图在这里创建&#34;新文档,因为它与组合不匹配,但因为username预计为username,您会收到&#34;重复键错误&#34;:

"unique"

所以你应该意识到的是,你现在得到三个条件来评估&#34; free&#34;。为:

  • &#34; upsert&#34;由{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" } updatedExisting: false结果记录,具体取决于方法。
  • 您知道该文件(通过组合)&#34;存在&#34;通过null或文档返回的位置是&#34;而不是updatedExisting: true&#34;。
  • 如果提供的nullpassword已存在的内容不匹配,那么您将收到&#34;重复键错误&#34;您可以捕获并做出相应的响应,建议用户回答密码错误&#34;。

所有这些来自一个请求。

这是使用&#34; upserts&#34;的主要原因。而不是简单地在集合中抛出插入,因为您可以获得逻辑的不同分支,而无需向数据库发出额外请求以确定&#34;哪个&#34;那些条件应该是实际的反应。

答案 1 :(得分:0)

听起来你真正想要的是一个unique key constraint,而不是一个upsert。

可以使用架构字段选项在[mongoose]中设置唯一键:

const s = new Schema({ name: { type: String, unique: true }});

index方法:

Schema.path('name').index({ unique: true });

如果尝试创建已包含该密钥条目的文档,则会引发错误:

  

注意:违反约束会在保存时从MongoDB返回E11000错误,而不是Mongoose验证错误。