常見問題


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 上未定義 nestednested 也必須始終定義為 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);
});

有什麼要補充的嗎?

如果您想為此頁面做出貢獻,請造訪 GitHub 並使用 Edit 按鈕發送 pull request。