文件

Mongoose 文件代表 MongoDB 中儲存的文件的一對一映射。每個文件都是其模型的實例。

文件 vs 模型

文件模型是 Mongoose 中不同的類別。模型類別是文件類別的子類別。當您使用模型建構子時,您會建立一個新的文件。

const MyModel = mongoose.model('Test', new Schema({ name: String }));
const doc = new MyModel();

doc instanceof MyModel; // true
doc instanceof mongoose.Model; // true
doc instanceof mongoose.Document; // true

在 Mongoose 中,「文件」通常是指模型的實例。您不應該在未透過模型的情況下建立文件類別的實例。

擷取

當您使用模型函式(例如 findOne())從 MongoDB 載入文件時,您會得到一個 Mongoose 文件。

const doc = await MyModel.findOne();

doc instanceof MyModel; // true
doc instanceof mongoose.Model; // true
doc instanceof mongoose.Document; // true

使用 save() 更新

Mongoose 文件會追蹤變更。您可以使用純 JavaScript 指定來修改文件,Mongoose 會將其轉換為 MongoDB 更新運算子

doc.name = 'foo';

// Mongoose sends an `updateOne({ _id: doc._id }, { $set: { name: 'foo' } })`
// to MongoDB.
await doc.save();

save() 方法會回傳 Promise。如果 save() 成功,則 Promise 會解析為已儲存的文件。

doc.save().then(savedDoc => {
  savedDoc === doc; // true
});

如果找不到具有對應 _id 的文件,Mongoose 將報告 DocumentNotFoundError

const doc = await MyModel.findOne();

// Delete the document so Mongoose won't be able to save changes
await MyModel.deleteOne({ _id: doc._id });

doc.name = 'foo';
await doc.save(); // Throws DocumentNotFoundError

設定巢狀屬性

Mongoose 文件具有 set() 函式,您可以使用該函式安全地設定深層巢狀屬性。

const schema = new Schema({
  nested: {
    subdoc: new Schema({
      name: String
    })
  }
});
const TestModel = mongoose.model('Test', schema);

const doc = new TestModel();
doc.set('nested.subdoc.name', 'John Smith');
doc.nested.subdoc.name; // 'John Smith'

Mongoose 文件也具有 get() 函式,可讓您安全地讀取深層巢狀屬性。get() 可讓您避免必須明確檢查 nullish 值,類似於 JavaScript 的選用鏈結運算子 ?.

const doc2 = new TestModel();

doc2.get('nested.subdoc.name'); // undefined
doc2.nested?.subdoc?.name; // undefined

doc2.set('nested.subdoc.name', 'Will Smith');
doc2.get('nested.subdoc.name'); // 'Will Smith'

您可以在 Mongoose 文件中使用選用鏈結 ?. 和 nullish 合併 ??。但是,使用 nullish 合併指定 ??= 建立 Mongoose 文件的巢狀路徑時請小心。

// The following works fine
const doc3 = new TestModel();
doc3.nested.subdoc ??= {};
doc3.nested.subdoc.name = 'John Smythe';

// The following does **NOT** work.
// Do not use the following pattern with Mongoose documents.
const doc4 = new TestModel();
(doc4.nested.subdoc ??= {}).name = 'Charlie Smith';
doc.nested.subdoc; // Empty object
doc.nested.subdoc.name; // undefined.

使用查詢更新

save() 函式通常是用於使用 Mongoose 更新文件的正確方法。使用 save(),您可以獲得完整的驗證中介軟體

對於 save() 不夠彈性的情況,Mongoose 可讓您建立自己的 MongoDB 更新,並具有轉換、中介軟體有限的驗證

// Update all documents in the `mymodels` collection
await MyModel.updateMany({}, { $set: { name: 'foo' } });

請注意,update()updateMany()findOneAndUpdate()不會執行 save() 中介軟體。如果您需要儲存中介軟體和完整驗證,請先查詢文件,然後 save() 它。

驗證

文件會在儲存之前進行轉換和驗證。Mongoose 會先將值轉換為指定的類型,然後驗證它們。在內部,Mongoose 會在儲存之前呼叫文件的 validate() 方法

const schema = new Schema({ name: String, age: { type: Number, min: 0 } });
const Person = mongoose.model('Person', schema);

const p = new Person({ name: 'foo', age: 'bar' });
// Cast to Number failed for value "bar" at path "age"
await p.validate();

const p2 = new Person({ name: 'foo', age: -1 });
// Path `age` (-1) is less than minimum allowed value (0).
await p2.validate();

Mongoose 也支援使用 runValidators 選項在更新時進行有限的驗證。Mongoose 會預設轉換查詢函式的參數(例如 findOne()updateOne())。但是,Mongoose 預設對查詢函式參數執行驗證。您需要設定 runValidators: true,Mongoose 才會進行驗證。

// Cast to number failed for value "bar" at path "age"
await Person.updateOne({}, { age: 'bar' });

// Path `age` (-1) is less than minimum allowed value (0).
await Person.updateOne({}, { age: -1 }, { runValidators: true });

請閱讀驗證指南以了解更多詳細資訊。

覆寫

有 2 種不同的方法可以覆寫文件(取代文件中的所有索引鍵)。一種方法是使用 Document#overwrite() 函式,然後再使用 save()

const doc = await Person.findOne({ _id });

// Sets `name` and unsets all other properties
doc.overwrite({ name: 'Jean-Luc Picard' });
await doc.save();

另一種方法是使用 Model.replaceOne()

// Sets `name` and unsets all other properties
await Person.replaceOne({ _id }, { name: 'Jean-Luc Picard' });

接下來

既然我們已經介紹了文件,讓我們來看看子文件