從 4.x 遷移至 5.x

請注意:我們計劃於 2024 年 3 月 1 日停止對 Mongoose 5 的支援。請參閱我們的版本支援指南

從 Mongoose 4.x 遷移至 Mongoose 5.x 時,您應該注意幾個向後不相容的變更

如果您仍在使用 Mongoose 3.x,請閱讀Mongoose 3.x 至 4.x 的遷移指南

版本需求

Mongoose 現在需要 Node.js >= 4.0.0 和 MongoDB >= 3.0.0。MongoDB 2.6Node.js < 4 均已於 2016 年終止生命週期。

查詢中間件

查詢中間件現在會在您呼叫 mongoose.model()db.model() 時編譯。如果您在呼叫 mongoose.model() 後新增查詢中間件,該中間件將不會被呼叫。

const schema = new Schema({ name: String });
const MyModel = mongoose.model('Test', schema);
schema.pre('find', () => { console.log('find!'); });

MyModel.find().exec(function() {
  // In mongoose 4.x, the above `.find()` will print "find!"
  // In mongoose 5.x, "find!" will **not** be printed.
  // Call `pre('find')` **before** calling `mongoose.model()` to make the middleware apply.
});

mongoose.connect() 的 Promise 和回呼

如果未指定回呼,mongoose.connect()mongoose.disconnect() 現在會傳回 promise,否則傳回 null。它不會傳回 Mongoose 單例。

// Worked in mongoose 4. Does **not** work in mongoose 5, `mongoose.connect()`
// now returns a promise consistently. This is to avoid the horrible things
// we've done to allow mongoose to be a thenable that resolves to itself.
mongoose.connect('mongodb://127.0.0.1:27017/test').model('Test', new Schema({}));

// Do this instead
mongoose.connect('mongodb://127.0.0.1:27017/test');
mongoose.model('Test', new Schema({}));

連線邏輯和 useMongoClient

useMongoClient 選項已在 Mongoose 5 中移除,現在始終為 true。因此,如果 useMongoClient 選項關閉,Mongoose 5 不再支援 Mongoose 4.x 中有效的 mongoose.connect() 的數個函式簽名。以下是一些在 Mongoose 5.x 中起作用的 mongoose.connect() 呼叫範例。

  • mongoose.connect('127.0.0.1', 27017);
  • mongoose.connect('127.0.0.1', 'mydb', 27017);
  • mongoose.connect('mongodb://host1:27017,mongodb://host2:27017');

在 Mongoose 5.x 中,如果指定,mongoose.connect()mongoose.createConnection() 的第一個參數必須MongoDB 連線字串。連線字串和選項會傳遞給 MongoDB Node.js 驅動程式的 MongoClient.connect() 函式。Mongoose 不會修改連線字串,儘管 mongoose.connect()mongoose.createConnection() 除了 MongoDB 驅動程式支援的選項外,還支援一些其他選項

Setter 順序

Setter 在 4.x 中以相反順序執行

const schema = new Schema({ name: String });
schema.path('name').
  set(() => console.log('This will print 2nd')).
  set(() => console.log('This will print first'));

在 5.x 中,setter 會按照宣告的順序執行。

const schema = new Schema({ name: String });
schema.path('name').
  set(() => console.log('This will print first')).
  set(() => console.log('This will print 2nd'));

檢查路徑是否已填充

Mongoose 5.1.0 為 ObjectIds 引入了 _id getter,讓您可以取得 ObjectId,而無論路徑是否已填充。

const blogPostSchema = new Schema({
  title: String,
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Author'
  }
});
const BlogPost = mongoose.model('BlogPost', blogPostSchema);

await BlogPost.create({ title: 'test', author: author._id });
const blogPost = await BlogPost.findOne();

console.log(blogPost.author); // '5b207f84e8061d1d2711b421'
// New in Mongoose 5.1.0: this will print '5b207f84e8061d1d2711b421' as well
console.log(blogPost.author._id);

await blogPost.populate('author');
console.log(blogPost.author._id); // '5b207f84e8061d1d2711b421'

因此,檢查 blogPost.author._id 是否存在 不再是檢查 author 是否已填充的方法。請使用 blogPost.populated('author') != nullblogPost.author instanceof mongoose.Types.ObjectId 來檢查是否已填充 author

請注意,您可以呼叫 mongoose.set('objectIdGetter', false) 來變更此行為。

remove()deleteX() 的傳回值

deleteOne()deleteMany()remove() 現在解析為結果物件,而不是完整的 驅動程式 WriteOpResult 物件

// In 4.x, this is how you got the number of documents deleted
MyModel.deleteMany().then(res => console.log(res.result.n));
// In 5.x this is how you get the number of documents deleted
MyModel.deleteMany().then(res => res.n);

聚合游標

4.x 中的 useMongooseAggCursor 選項現在始終開啟。這是 Mongoose 5 中聚合游標的新語法

// When you call `.cursor()`, `.exec()` will now return a mongoose aggregation
// cursor.
const cursor = MyModel.aggregate([{ $match: { name: 'Val' } }]).cursor().exec();
// No need to `await` on the cursor or wait for a promise to resolve
cursor.eachAsync(doc => console.log(doc));

// Can also pass options to `cursor()`
const cursorWithOptions = MyModel.
  aggregate([{ $match: { name: 'Val' } }]).
  cursor({ batchSize: 10 }).
  exec();

geoNear

由於 MongoDB 驅動程式不再支援它,因此 Model.geoNear() 已被移除

連線字串需要 URI 編碼

由於 MongoDB 驅動程式中的變更,連線字串必須進行 URI 編碼。

如果沒有,連線可能會因非法字元訊息而失敗。

包含特定字元的密碼

請參閱受影響字元的完整列表

如果您的應用程式使用了許多不同的連線字串,您的測試案例可能會通過,但生產密碼會失敗。請將所有連線字串編碼以確保安全。

如果您想繼續使用未編碼的連線字串,最簡單的解決方法是使用 mongodb-uri 模組來剖析連線字串,然後產生正確編碼的版本。您可以使用如下的函式

const uriFormat = require('mongodb-uri');
function encodeMongoURI(urlString) {
  if (urlString) {
    const parsed = uriFormat.parse(urlString);
    urlString = uriFormat.format(parsed);
  }
  return urlString;
}

// Your un-encoded string.
const mongodbConnectString = 'mongodb://...';
mongoose.connect(encodeMongoURI(mongodbConnectString));

無論現有字串是否已編碼,上述函式都可以安全使用。

Domain sockets (網域套接字)

網域套接字必須進行 URI 編碼。例如

// Works in mongoose 4. Does **not** work in mongoose 5 because of more
// stringent URI parsing.
const host = '/tmp/mongodb-27017.sock';
mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`);

// Do this instead
const host = encodeURIComponent('/tmp/mongodb-27017.sock');
mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`);

toObject() 選項

toObject()toJSON()options 參數會合併預設值,而不是覆寫它們。

// Note the `toObject` option below
const schema = new Schema({ name: String }, { toObject: { virtuals: true } });
schema.virtual('answer').get(() => 42);
const MyModel = db.model('MyModel', schema);

const doc = new MyModel({ name: 'test' });
// In mongoose 4.x this prints "undefined", because `{ minimize: false }`
// overwrites the entire schema-defined options object.
// In mongoose 5.x this prints "42", because `{ minimize: false }` gets
// merged with the schema-defined options.
console.log(doc.toJSON({ minimize: false }).answer);

Aggregate 參數

aggregate() 不再接受 spread,您必須以陣列形式傳遞您的聚合管道。以下程式碼在 4.x 中有效

MyModel.aggregate({ $match: { isDeleted: false } }, { $skip: 10 }).exec(cb);

上述程式碼在 5.x 中起作用,您必須$match$skip 階段包裝在陣列中。

MyModel.aggregate([{ $match: { isDeleted: false } }, { $skip: 10 }]).exec(cb);

布林值轉換

預設情況下,Mongoose 4 會在沒有錯誤的情況下將任何值強制轉換為布林值。

// Fine in mongoose 4, would save a doc with `boolField = true`
const MyModel = mongoose.model('Test', new Schema({
  boolField: Boolean
}));

MyModel.create({ boolField: 'not a boolean' });

Mongoose 5 只會將以下值轉換為 true

  • true
  • 'true'
  • 1
  • '1'
  • 'yes'

並將以下值轉換為 false

  • false
  • 'false'
  • 0
  • '0'
  • 'no'

所有其他值都會導致 CastError

查詢轉換

update()updateOne()updateMany()replaceOne()remove()deleteOne()deleteMany() 的轉換會在 exec() 之前不會發生。這使得 hook 和自訂查詢輔助程式更容易修改資料,因為 Mongoose 不會在您的 hook 和查詢輔助程式執行完畢之前重新結構化您傳遞的資料。它也使得在傳遞更新後設定 overwrite 選項成為可能。

// In mongoose 4.x, this becomes `{ $set: { name: 'Baz' } }` despite the `overwrite`
// In mongoose 5.x, this overwrite is respected and the first document with
// `name = 'Bar'` will be replaced with `{ name: 'Baz' }`
User.where({ name: 'Bar' }).update({ name: 'Baz' }).setOptions({ overwrite: true });

Post Save Hooks 取得流程控制

Post hook 現在取得流程控制,這意味著非同步 post save hook 和子文件 post save hook 會在您的 save() 回呼之前執行。

const ChildModelSchema = new mongoose.Schema({
  text: {
    type: String
  }
});
ChildModelSchema.post('save', function(doc) {
  // In mongoose 5.x this will print **before** the `console.log()`
  // in the `save()` callback. In mongoose 4.x this was reversed.
  console.log('Child post save');
});
const ParentModelSchema = new mongoose.Schema({
  children: [ChildModelSchema]
});

const Model = mongoose.model('Parent', ParentModelSchema);
const m = new Model({ children: [{ text: 'test' }] });
m.save(function() {
  // In mongoose 5.xm this prints **after** the "Child post save" message.
  console.log('Save callback');
});

$pushAll 運算符

由於 $pushAllMongoDB 2.4 起已棄用,因此不再支援且不再在內部用於 save()。請改用 $push$each

始終使用正向鍵順序

retainKeyOrder 選項已移除,Mongoose 現在在複製物件時始終保留相同的鍵位置。如果您有依賴相反鍵順序的查詢或索引,您將必須變更它們。

在查詢上執行 setter

Setter 現在預設在查詢上執行,並且舊的 runSettersOnQuery 選項已移除。

const schema = new Schema({
  email: { type: String, lowercase: true }
});
const Model = mongoose.model('Test', schema);
Model.find({ email: 'FOO@BAR.BAZ' }); // Converted to `find({ email: 'foo@bar.baz' })`

預編譯的瀏覽器套件

我們不再有針對瀏覽器的 Mongoose 預編譯版本。如果您想在瀏覽器中使用 Mongoose schema,您需要使用 browserify/webpack 建立自己的套件。

儲存錯誤

saveErrorIfNotFound 選項已移除,如果找不到基礎文件,Mongoose 現在始終會從 save() 中傳回錯誤

Init hook 簽名

init hook 現在完全同步,並且不會收到 next() 作為參數。

Document.prototype.init() 不再將回呼作為參數。它始終是同步的,只是為了舊版原因而有一個回呼。

numAffectedsave()

doc.save() 不再將 numAffected 作為第三個參數傳遞給其回呼。

remove() 和防抖

doc.remove() 不再進行防抖

getPromiseConstructor()

getPromiseConstructor() 已消失,只需使用 mongoose.Promise

從 Pre Hooks 傳遞參數

您無法在 Mongoose 5.x 中使用 next() 將參數傳遞給鏈中的下一個 pre 中間件。在 Mongoose 4 中,pre 中間件中的 next('Test') 會以 'Test' 作為參數呼叫下一個中間件。Mongoose 5.x 已移除對此的支援。

陣列的 required 驗證器

在 Mongoose 5 中,required 驗證器僅驗證值是否為陣列。也就是說,它不會像在 Mongoose 4 中一樣因陣列而失敗。

debug 輸出預設為 stdout 而不是 stderr

在 Mongoose 5 中,預設 debug 函式使用 console.info() 來顯示訊息,而不是 console.error()

覆寫篩選器屬性

在 Mongoose 4.x 中,用物件覆寫作為原始值的篩選器屬性會悄悄失敗。例如,以下程式碼會忽略 where(),並且等效於 Sport.find({ name: 'baseball' })

Sport.find({ name: 'baseball' }).where({ name: { $ne: 'softball' } });

在 Mongoose 5.x 中,上述程式碼會正確地將 'baseball' 覆寫為 { $ne: 'softball' }

bulkWrite() 結果

Mongoose 5.x 使用 MongoDB Node.js 驅動程式的 3.x 版本。MongoDB 驅動程式 3.x 變更了 bulkWrite() 呼叫結果的格式,因此不再有最上層的 nInsertednModified 等屬性。新的結果物件結構在此處說明

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

const res = await Model.bulkWrite([{ insertOne: { document: { name: 'test' } } }]);

console.log(res);

在 Mongoose 4.x 中,以上程式碼將會印出

BulkWriteResult {
  ok: [Getter],
  nInserted: [Getter],
  nUpserted: [Getter],
  nMatched: [Getter],
  nModified: [Getter],
  nRemoved: [Getter],
  getInsertedIds: [Function],
  getUpsertedIds: [Function],
  getUpsertedIdAt: [Function],
  getRawResponse: [Function],
  hasWriteErrors: [Function],
  getWriteErrorCount: [Function],
  getWriteErrorAt: [Function],
  getWriteErrors: [Function],
  getLastOp: [Function],
  getWriteConcernError: [Function],
  toJSON: [Function],
  toString: [Function],
  isOk: [Function],
  insertedCount: 1,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds: { '0': 5be9a3101638a066702a0d38 },
  n: 1 }

在 Mongoose 5.x 中,該腳本將會印出

BulkWriteResult {
  result: 
  { ok: 1,
    writeErrors: [],
    writeConcernErrors: [],
    insertedIds: [ [Object] ],
    nInserted: 1,
    nUpserted: 0,
    nMatched: 0,
    nModified: 0,
    nRemoved: 0,
    upserted: [],
    lastOp: { ts: [Object], t: 1 } },
  insertedCount: 1,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds: { '0': 5be9a1c87decfc6443dd9f18 },
  n: 1 }

嚴格 SSL 驗證

MongoDB Node.js 驅動程式的最新版本預設使用嚴格的 SSL 驗證,如果您使用自我簽署憑證,可能會導致錯誤。

如果這阻礙了您升級,您可以將 tlsInsecure 選項設為 true

mongoose.connect(uri, { tlsInsecure: false }); // Opt out of additional SSL validation