常見問題
Q. 當連線到 localhost
時,我收到錯誤訊息 connect ECONNREFUSED ::1:27017
。為什麼?
簡單的解決方案是將 localhost
替換為 127.0.0.1
。
發生此錯誤的原因是,預設情況下,Node.js 18 及更高版本優先使用 IPv6 位址而不是 IPv4 位址。而且,大多數 Linux 和 OSX 機器在 /etc/hosts
中預設都有一個 ::1 localhost
條目。這表示 Node.js 18 會假設 localhost
表示 IPv6 ::1
位址。而 MongoDB 預設不接受 IPv6 連線。
您也可以透過在您的 MongoDB 伺服器上啟用 IPv6 支援來解決此錯誤。
Q. 操作 ...
在 10000 毫秒後逾時。怎麼回事?
A. 此問題的核心在於未連線到 MongoDB。您可以在連線到 MongoDB 之前使用 Mongoose,但您必須在某個時間點進行連線。例如
await mongoose.createConnection(mongodbUri).asPromise();
const Test = mongoose.model('Test', schema);
await Test.findOne(); // Will throw "Operation timed out" error because didn't call `mongoose.connect()`
await mongoose.connect(mongodbUri);
const db = mongoose.createConnection();
const Test = db.model('Test', schema);
await Test.findOne(); // Will throw "Operation timed out" error because `db` isn't connected to MongoDB
Q. 我能夠在本機連線,但是當我嘗試連線到 MongoDB Atlas 時,我收到此錯誤。怎麼回事?
您必須確保您已在 mongodb 上將您的 IP 位址加入白名單,以允許 Mongoose 連線。您可以使用 0.0.0.0/0
來允許來自所有 IP 位址的存取。
Q. x.$__y 不是一個函式。怎麼回事?
A. 此問題是由於安裝了多個彼此不相容的 Mongoose 版本所導致的。執行 npm list | grep "mongoose"
來尋找並解決問題。如果您將綱要或模型儲存在單獨的 npm 套件中,請在您單獨的套件中將 Mongoose 列在 peerDependencies
中,而不是 dependencies
中。
Q. 我將綱要屬性宣告為 unique
,但是我仍然可以儲存重複項。怎麼回事?
A. Mongoose 本身不處理 unique
:{ name: { type: String, unique: true } }
只是建立 MongoDB 上 name
的唯一索引 的簡寫。例如,如果 MongoDB 在 name
上沒有唯一索引,則即使 unique
為 true,以下程式碼也不會出錯。
const schema = new mongoose.Schema({
name: { type: String, unique: true }
});
const Model = db.model('Test', schema);
// No error, unless index was already built
await Model.create([{ name: 'Val' }, { name: 'Val' }]);
但是,如果您等待索引使用 Model.on('index')
事件建置完成,則儲存重複項的嘗試將正確地產生錯誤。
const schema = new mongoose.Schema({
name: { type: String, unique: true }
});
const Model = db.model('Test', schema);
// Wait for model's indexes to finish. The `init()`
// function is idempotent, so don't worry about triggering an index rebuild.
await Model.init();
// Throws a duplicate key error
await Model.create([{ name: 'Val' }, { name: 'Val' }]);
MongoDB 會持久化索引,因此只有在您從全新的資料庫開始或執行 db.dropDatabase()
時,才需要重建索引。在生產環境中,您應該使用 MongoDB Shell 建立您的索引,而不是依賴 Mongoose 為您執行此操作。綱要的 unique
選項對於開發和文件很方便,但 Mongoose 不是 索引管理解決方案。
Q. 當我在綱要中具有巢狀屬性時,Mongoose 預設會新增空的物件。為什麼?
const schema = new mongoose.Schema({
nested: {
prop: String
}
});
const Model = db.model('Test', schema);
// The below prints `{ _id: /* ... */, nested: {} }`, mongoose assigns
// `nested` to an empty object `{}` by default.
console.log(new Model());
A. 這是一種效能最佳化。這些空的物件不會儲存到資料庫中,也不會在結果 toObject()
中,除非您關閉 minimize
選項,否則它們也不會顯示在 JSON.stringify()
輸出中。
此行為的原因是 Mongoose 的變更偵測和 getter/setter 基於 Object.defineProperty()
。為了支援巢狀屬性的變更偵測,而不會每次建立文件時都產生執行 Object.defineProperty()
的額外負擔,Mongoose 會在模型編譯時在 Model
原型上定義屬性。由於 Mongoose 需要為 nested.prop
定義 getter 和 setter,因此即使在底層 POJO 上未定義 nested
,nested
也必須始終定義為 Mongoose 文件上的物件。
Q. 我正在為 虛擬、中介軟體、getter/setter 或 方法 使用箭頭函式,而 this
的值是錯誤的。
A. 箭頭函式處理 this
關鍵字的方式與傳統函式不同。Mongoose getter/setter 依賴 this
來讓您存取您正在寫入的文件,但是此功能不適用於箭頭函式。除非您不打算在 getter/setter 中存取文件,否則請勿將箭頭函式用於 Mongoose getter/setter。
// Do **NOT** use arrow functions as shown below unless you're certain
// that's what you want. If you're reading this FAQ, odds are you should
// just be using a conventional function.
const schema = new mongoose.Schema({
propWithGetter: {
type: String,
get: v => {
// Will **not** be the doc, do **not** use arrow functions for getters/setters
console.log(this);
return v;
}
}
});
// `this` will **not** be the doc, do **not** use arrow functions for methods
schema.method.arrowMethod = () => this;
schema.virtual('virtualWithArrow').get(() => {
// `this` will **not** be the doc, do **not** use arrow functions for virtuals
console.log(this);
});
Q. 我有一個名為 type
的嵌入式屬性,如下所示
const holdingSchema = new Schema({
// You might expect `asset` to be an object that has 2 properties,
// but unfortunately `type` is special in mongoose so mongoose
// interprets this schema to mean that `asset` is a string
asset: {
type: String,
ticker: String
}
});
但是當我嘗試儲存具有 asset
物件的 Holding
時,Mongoose 給我一個 CastError 錯誤,告訴我它無法將物件強制轉換為字串。這是為什麼?
Holding.create({ asset: { type: 'stock', ticker: 'MDB' } }).catch(error => {
// Cast to String failed for value "{ type: 'stock', ticker: 'MDB' }" at path "asset"
console.error(error);
});
A. type
屬性在 Mongoose 中是特殊的,因此當您說 type: String
時,Mongoose 會將其解釋為類型宣告。在上面的綱要中,Mongoose 認為 asset
是一個字串,而不是一個物件。請改為執行此操作
const holdingSchema = new Schema({
// This is how you tell mongoose you mean `asset` is an object with
// a string property `type`, as opposed to telling mongoose that `asset`
// is a string.
asset: {
type: { type: String },
ticker: String
}
});
Q. 我正在填充陣列下的巢狀屬性,如下面的程式碼
new Schema({
arr: [{
child: { ref: 'OtherModel', type: Schema.Types.ObjectId }
}]
});
.populate({ path: 'arr.child', options: { sort: 'name' } })
不會依 arr.child.name
排序?
A. 請參閱 此 GitHub 問題。這是一個已知問題,但很難修復。
Q. 我模型上的所有函式呼叫都掛起,我做錯了什麼?
A. 預設情況下,Mongoose 會緩衝您的函式呼叫,直到它可以連線到 MongoDB 為止。有關更多資訊,請閱讀連線文件的緩衝區部分。
Q. 如何啟用除錯?
A. 設定 debug
選項
// all executed methods log output to console
mongoose.set('debug', true);
// disable colors in debug mode
mongoose.set('debug', { color: false });
// get mongodb-shell friendly output (ISODate)
mongoose.set('debug', { shell: true });
有關更多除錯選項(串流、回呼),請參閱 .set()
下的 'debug' 選項。
Q. 我的 save()
回呼永遠不會執行。我做錯了什麼?
A. 所有 collection
動作(插入、移除、查詢等)都會排隊,直到 Mongoose 成功連線到 MongoDB 為止。很可能您尚未呼叫 Mongoose 的 connect()
或 createConnection()
函式。
在 Mongoose 5.11 中,有一個 bufferTimeoutMS
選項(預設設定為 10000),可設定 Mongoose 在擲回錯誤之前允許操作保持緩衝的時間。
如果您想在整個應用程式中選擇退出 Mongoose 的緩衝機制,請將全域 bufferCommands
選項設定為 false
mongoose.set('bufferCommands', false);
您可能不想選擇退出 Mongoose 的緩衝機制,而是將 bufferTimeoutMS
縮短以使 Mongoose 僅緩衝短時間。
// If an operation is buffered for more than 500ms, throw an error.
mongoose.set('bufferTimeoutMS', 500);
Q. 我應該為每個資料庫操作建立/銷毀新的連線嗎?
A. 不應該。當您的應用程式啟動時開啟您的連線,並保持開啟直到應用程式關閉。
Q. 當我使用 nodemon / 測試框架時,為什麼會收到「OverwriteModelError: Cannot overwrite .. model once compiled」錯誤?
A. mongoose.model('ModelName', schema)
要求 'ModelName' 是唯一的,因此您可以使用 mongoose.model('ModelName')
來存取模型。如果您將 mongoose.model('ModelName', schema);
放在 mocha beforeEach()
掛鉤中,此程式碼將嘗試在每次測試之前建立一個名為 'ModelName' 的新模型,因此您將會收到錯誤。請確保您只用給定的名稱一次建立一個新模型。如果您需要建立具有相同名稱的多個模型,請建立一個新連線並將模型繫結到該連線。
const mongoose = require('mongoose');
const connection = mongoose.createConnection(/* ... */);
// use mongoose.Schema
const kittySchema = mongoose.Schema({ name: String });
// use connection.model
const Kitten = connection.model('Kitten', kittySchema);
Q. 如何變更 Mongoose 將陣列路徑初始化為空陣列的預設行為,以便我可以在建立文件時要求真實資料?
A. 您可以將陣列的預設值設定為 undefined
。
const CollectionSchema = new Schema({
field1: {
type: [String],
default: void 0
}
});
Q. 如何將陣列路徑初始化為 null
?
A. 您可以將陣列的預設值設定為 null
。
const CollectionSchema = new Schema({
field1: {
type: [String],
default: null
}
});
Q. 當使用日期時,為什麼我的聚合 $match 無法傳回我的 find 查詢傳回的文件?
A. Mongoose 不會強制轉換聚合管道階段,因為使用 $project、$group 等,屬性的類型可能會在聚合期間變更。如果您想使用聚合框架依日期查詢,您有責任確保您傳遞的是有效的日期。
Q. 為什麼對日期物件進行就地修改(例如,date.setMonth(1);
)不會儲存?
doc.createdAt.setDate(2011, 5, 1);
doc.save(); // createdAt changes won't get saved!
A. Mongoose 目前不監看對日期物件的就地更新。如果您需要此功能,請隨時在 此 GitHub 問題上討論。有幾種解決方法
doc.createdAt.setDate(2011, 5, 1);
doc.markModified('createdAt');
doc.save(); // Works
doc.createdAt = new Date(2011, 5, 1).setHours(4);
doc.save(); // Works
Q. 為什麼平行在同一個文件上多次呼叫 save()
只會讓第一個儲存呼叫成功,並為其餘呼叫傳回 ParallelSaveErrors?
A. 由於驗證和中介軟體的非同步特性,平行在同一個文件上多次呼叫 save()
可能會導致衝突。例如,驗證,然後隨後使同一路徑無效。
Q. 為什麼任何 12 個字元的字串都會成功轉換為 ObjectId?
A. 從技術上講,任何 12 個字元的字串都是有效的 ObjectId。請考慮使用類似 /^[a-f0-9]{24}$/
的 regex 來測試字串是否正好是 24 個十六進位字元。
Q. 為什麼 Mongoose Maps 中的鍵必須是字串?
A. 因為 Map 最終會儲存在 MongoDB 中,而鍵必須是字串。
問:我使用 Model.find(...).populate(...)
並帶有 limit
選項,但得到的結果比 limit 設定的還少。這是怎麼回事?
答:為了避免為 find
查詢返回的每個文檔執行單獨的查詢,Mongoose 會改用 (文檔數量 * limit) 作為 limit 進行查詢。如果你需要正確的 limit,你應該使用 perDocumentLimit 選項(Mongoose 5.9.0 新增)。請注意,populate()
將會為每個文檔執行單獨的查詢。
問:我的查詢/更新似乎執行了兩次。為什麼會這樣?
答:重複查詢最常見的原因是在查詢中混合使用回調函數和 Promise。這是因為將回調函數傳遞給查詢函數(例如 find()
或 updateOne()
)會立即執行查詢,而調用 then()
會再次執行查詢。
混合使用 Promise 和回調函數可能會導致數組中出現重複的條目。例如,下面的程式碼會在 tags
數組中插入 2 個條目,*而不是* 只有 1 個。
const BlogPost = mongoose.model('BlogPost', new Schema({
title: String,
tags: [String]
}));
// Because there's both `await` **and** a callback, this `updateOne()` executes twice
// and thus pushes the same string into `tags` twice.
const update = { $push: { tags: ['javascript'] } };
await BlogPost.updateOne({ title: 'Introduction to Promises' }, update, (err, res) => {
console.log(res);
});
有什麼要補充的嗎?