MongoDB
 sql >> Cơ Sở Dữ Liệu >  >> NoSQL >> MongoDB

Cách tạo mục nếu không tồn tại và trả về lỗi nếu tồn tại

Như đã lưu ý trong phần bình luận trước đó, bạn có hai cách tiếp cận cơ bản để tìm ra thứ gì đó đã được "tạo ra" hay không. Đây là:

  • Trả lại rawResult trong phản hồi và kiểm tra updatedExisting thuộc tính cho bạn biết đó có phải là "nâng cấp" hay không

  • Đặt new: false để kết quả "không có tài liệu" thực sự được trả về khi nó thực sự là "nâng cấp"

Như một danh sách để chứng minh:

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()
  }


})()

Và đầu ra:

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"
}

Vì vậy, trường hợp đầu tiên thực sự xem xét mã này:

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

Hầu hết các tùy chọn là tiêu chuẩn ở đây là "tất cả" "upsert" các hành động sẽ dẫn đến nội dung trường được sử dụng để "khớp" (tức là username ) là "luôn luôn" được tạo trong tài liệu mới, vì vậy bạn không cần phải $set lĩnh vực đó. Để không thực sự "sửa đổi" các trường khác trong các yêu cầu tiếp theo, bạn có thể sử dụng $setOnInsert , chỉ thêm các thuộc tính này trong một "upsert" hành động mà không tìm thấy kết quả phù hợp nào.

Đây là new: true tiêu chuẩn được sử dụng để trả về tài liệu "đã sửa đổi" từ hành động, nhưng sự khác biệt là ở rawResult như được hiển thị trong phản hồi được trả lại:

{
  "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
    }
  }
}

Thay vì một "tài liệu mongoose", bạn nhận được phản hồi "thô" thực tế từ trình điều khiển. Nội dung tài liệu thực tế nằm dưới "value" nhưng đó là thuộc tính "lastErrorObject" chúng tôi quan tâm đến.

Ở đây chúng tôi thấy thuộc tính updatedExisting: false . Điều này cho thấy rằng "không có kết quả phù hợp" đã thực sự được tìm thấy, do đó một tài liệu mới đã được "tạo ra". Vì vậy, bạn có thể sử dụng điều này để xác định rằng quá trình sáng tạo đã thực sự xảy ra.

Khi bạn phát hành lại các tùy chọn truy vấn tương tự, kết quả sẽ khác:

{
  "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 giá trị bây giờ là true và điều này là do đã có một tài liệu khớp với tên người dùng username: 'Bill' trong câu lệnh truy vấn. Điều này cho bạn biết tài liệu đã ở đó, vì vậy bạn có thể phân nhánh logic của mình để trả về "Lỗi" hoặc bất kỳ phản hồi nào bạn muốn.

Trong trường hợp khác, có thể mong muốn "không" trả lại phản hồi "thô" và thay vào đó sử dụng "tài liệu mongoose" đã trả về. Trong trường hợp này, chúng tôi thay đổi giá trị thành new: false không có rawResult tùy chọn.

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

Hầu hết các điều tương tự đều áp dụng ngoại trừ việc bây giờ hành động là nguyên bản trạng thái của tài liệu được trả về trái ngược với trạng thái "đã sửa đổi" của tài liệu "sau" hành động. Do đó, khi không có tài liệu nào thực sự khớp với câu lệnh "truy vấn", kết quả trả về là null :

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

Điều này cho bạn biết tài liệu đã được "tạo" và có thể cho rằng bạn đã biết nội dung của tài liệu sẽ như thế nào kể từ khi bạn gửi dữ liệu đó cùng với câu lệnh (lý tưởng là trong $setOnInsert ). Hiện tại, bạn đã biết những gì cần trả lại "nên" bạn yêu cầu để thực sự trả lại nội dung tài liệu.

Ngược lại, tài liệu "tìm thấy" trả về "trạng thái ban đầu" hiển thị tài liệu "trước khi" nó được sửa đổi:

{
  "_id": "5adfc8696878cfc4992e7639",
  "username": "Ted",
  "__v": 0,
  "password": "password"
}

Do đó, bất kỳ phản hồi nào "không phải là null "do đó, là một dấu hiệu cho thấy tài liệu đã có sẵn và một lần nữa, bạn có thể phân nhánh logic của mình tùy thuộc vào những gì thực sự nhận được trong phản hồi.

Vì vậy, đó là hai cách tiếp cận cơ bản cho những gì bạn đang yêu cầu, và chúng chắc chắn nhất "có tác dụng"! Và cũng giống như được chứng minh và có thể tái tạo với các câu lệnh tương tự ở đây.

Phụ lục - Dự trữ Khóa trùng lặp cho các mật khẩu xấu

Có một cách tiếp cận hợp lệ hơn cũng được gợi ý trong danh sách đầy đủ, về cơ bản chỉ là .insert() (hoặc .create() từ các mô hình mongoose) dữ liệu mới và có lỗi "khóa trùng lặp" nơi thực sự gặp phải thuộc tính "duy nhất" theo chỉ mục. Đó là một cách tiếp cận hợp lệ nhưng có một trường hợp sử dụng cụ thể trong "xác thực người dùng", đây là một phần xử lý logic tiện dụng và đó là "xác thực mật khẩu".

Vì vậy, đó là một mẫu khá phổ biến để truy xuất thông tin người dùng bằng usernamepassword sự kết hợp. Trong trường hợp "upert", sự kết hợp này chứng minh là "duy nhất" và do đó, một "insert" được cố gắng thực hiện nếu không tìm thấy kết quả phù hợp nào. Đây chính là điều làm cho việc so khớp mật khẩu trở thành một triển khai hữu ích ở đây.

Hãy xem xét những điều sau:

    // 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 }
    );

Trong lần thử đầu tiên, chúng tôi thực sự không có username cho "Fred" , do đó, "upert" sẽ xảy ra và tất cả những thứ khác như đã được mô tả ở trên xảy ra để xác định xem đó là một tác phẩm hay một tài liệu được tìm thấy.

Câu lệnh theo sau sử dụng cùng một username nhưng cung cấp một mật khẩu khác với những gì được ghi lại. Ở đây MongoDB cố gắng "tạo" tài liệu mới vì nó không khớp trên tổ hợp mà vì username được mong đợi là "unique" bạn nhận được "Lỗi khóa trùng lặp":

{ MongoError: E11000 duplicate key error collection: thereornot.users index: username_1 dup key: { : "Fred" }

Vì vậy, những gì bạn nên nhận ra là bây giờ bạn nhận được ba điều kiện để đánh giá cho "miễn phí". Đang:

  • "Nâng cấp" được ghi lại bởi updatedExisting: false hoặc null kết quả tùy thuộc vào phương pháp.
  • Bạn biết tài liệu (bằng cách kết hợp) "tồn tại" qua updatedExisting: true hoặc nơi tài liệu trả về là "không phải null ".
  • Nếu password được cung cấp không phù hợp với những gì đã tồn tại cho username thì bạn sẽ nhận được "lỗi khóa trùng lặp" mà bạn có thể bẫy và phản hồi theo đó, thông báo cho người dùng để trả lời rằng "mật khẩu không chính xác".

Tất cả điều đó từ một yêu cầu.

Đó là lý do chính để sử dụng "uperts" thay vì chỉ ném chèn vào một bộ sưu tập, vì bạn có thể nhận được các nhánh logic khác nhau mà không cần đưa ra yêu cầu bổ sung đối với cơ sở dữ liệu để xác định "điều kiện" nào trong số những điều kiện đó phải là phản hồi thực tế.



  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Trận chiến của các cơ sở dữ liệu NoSQL - So sánh MongoDB và Cassandra

  2. Lưu trữ tệp trong mongodb bằng node.js

  3. MongoDB nhận được số lượng riêng lẻ từ các tài liệu bằng các thao tác so sánh

  4. Kích thước tài liệu trong MongoDb

  5. Làm cách nào để tôi có thể thực hiện một truy vấn với mongoose từ một hàm sử dụng một tham số?