從 6.x 遷移至 7.x

從 Mongoose 6.x 遷移至 Mongoose 7.x 時,您應該注意一些向後不相容的變更。

如果您仍在使用 Mongoose 5.x,請先閱讀Mongoose 5.x 至 6.x 遷移指南並升級至 Mongoose 6.x。

strictQuery

strictQuery 現在預設為 false。

const mySchema = new Schema({ field: Number });
const MyModel = mongoose.model('Test', mySchema);

// Mongoose will not strip out `notInSchema: 1` because `strictQuery` is false by default
const docs = await MyModel.find({ notInSchema: 1 });
// Empty array in Mongoose 7. In Mongoose 6, this would contain all documents in MyModel
docs;

已移除 remove()

文件和模型上的 remove() 方法已移除。請改用 deleteOne()deleteMany()

const mySchema = new Schema({ field: Number });
const MyModel = mongoose.model('Test', mySchema);

// Change this:
await MyModel.remove(filter);

// To this:
await MyModel.deleteOne(filter);
// Or this, if you want to delete multiple:
await MyModel.deleteMany(filter);

// For documents, change this:
await doc.remove();

// To this:
await doc.deleteOne();

請記住,deleteOne() 鉤子預設會被視為查詢中介軟體。因此,對於中介軟體,請執行以下操作

// Replace this:
schema.pre('remove', function() {
  /* ... */
});

// With this:
schema.pre('deleteOne', { document: true, query: false }, function() {
  /* ... */
});

已捨棄回呼函式支援

下列函式不再接受回呼函式。它們一律會傳回 Promise。

  • Aggregate.prototype.exec
  • Aggregate.prototype.explain
  • AggregationCursor.prototype.close
  • AggregationCursor.prototype.next
  • AggregationCursor.prototype.eachAsync
  • Connection.prototype.startSession
  • Connection.prototype.dropCollection
  • Connection.prototype.createCollection
  • Connection.prototype.dropDatabase
  • Connection.prototype.openUri
  • Connection.prototype.close
  • Connection.prototype.destroy
  • Document.prototype.populate
  • Document.prototype.validate
  • Mongoose.prototype.connect
  • Mongoose.prototype.createConnection
  • Model.prototype.save
  • Model.aggregate
  • Model.bulkWrite
  • Model.cleanIndexes
  • Model.countDocuments
  • Model.create
  • Model.createCollection
  • Model.createIndexes
  • Model.deleteOne
  • Model.deleteMany
  • Model.distinct
  • Model.ensureIndexes
  • Model.estimatedDocumentCount
  • Model.exists
  • Model.find
  • Model.findById
  • Model.findByIdAndUpdate
  • Model.findByIdAndReplace
  • Model.findOne
  • Model.findOneAndDelete
  • Model.findOneAndUpdate
  • Model.findOneAndRemove
  • Model.insertMany
  • Model.listIndexes
  • Model.replaceOne
  • Model.syncIndexes
  • Model.updateMany
  • Model.updateOne
  • Query.prototype.find
  • Query.prototype.findOne
  • Query.prototype.findOneAndDelete
  • Query.prototype.findOneAndUpdate
  • Query.prototype.findOneAndRemove
  • Query.prototype.findOneAndReplace
  • Query.prototype.validate
  • Query.prototype.deleteOne
  • Query.prototype.deleteMany
  • Query.prototype.exec
  • QueryCursor.prototype.close
  • QueryCursor.prototype.next
  • QueryCursor.prototype.eachAsync

如果您正在使用上述包含回呼函式的函式,我們建議您切換至 async/await,或者如果 async 函式不適用於您,則使用 Promise。如果您需要協助重構舊版程式碼,可以使用 ChatGPT 的Mastering JS 回呼至 async await 工具

// Before
conn.startSession(function(err, session) {
  // ...
});

// After
const session = await conn.startSession();
// Or:
conn.startSession().then(sesson => { /* ... */ });

// With error handling
try {
  await conn.startSession();
} catch (err) { /* ... */ }
// Or:
const [err, session] = await conn.startSession().then(
  session => ([null, session]),
  err => ([err, null])
);

已移除 update()

Model.update()Query.prototype.update()Document.prototype.update() 已移除。請改用 updateOne()

// Before
await Model.update(filter, update);
await doc.update(update);

// After
await Model.updateOne(filter, update);
await doc.updateOne(update);

ObjectId 需要使用 new

在 Mongoose 6 及更舊版本中,您可以定義新的 ObjectId 而無需使用 new 關鍵字

// Works in Mongoose 6
// Throws "Class constructor ObjectId cannot be invoked without 'new'" in Mongoose 7
const oid = mongoose.Types.ObjectId('0'.repeat(24));

在 Mongoose 7 中,ObjectId 現在是JavaScript 類別,因此您需要使用 new 關鍵字。

// Works in Mongoose 6 and Mongoose 7
const oid = new mongoose.Types.ObjectId('0'.repeat(24));

id 設定器

從 Mongoose 7.4 開始,Mongoose 的內建 id 虛擬屬性 (將文件的 _id 儲存為字串) 具有一個設定器,允許透過 id 修改文件的 _id 屬性。

const doc = await TestModel.findOne();

doc.id = '000000000000000000000000';
doc._id; // ObjectId('000000000000000000000000')

如果您建立一個 new TestModel(obj),其中 obj 同時包含 id_id,或者您使用 doc.set(),這可能會導致意想不到的行為

// Because `id` is after `_id`, the `id` will overwrite the `_id`
const doc = new TestModel({
  _id: '000000000000000000000000',
  id: '111111111111111111111111'
});

doc._id; // ObjectId('111111111111111111111111')

由於相容性問題,id 設定器在 Mongoose 8 中已隨後移除

區別符綱要預設使用基本綱要選項

當您使用 Model.discriminator() 時,Mongoose 現在會預設使用區別符基本綱要的選項。這表示您不需要明確設定子綱要選項以符合基本綱要的選項。

const baseSchema = Schema({}, { typeKey: '$type' });
const Base = db.model('Base', baseSchema);

// In Mongoose 6.x, the `Base.discriminator()` call would throw because
// no `typeKey` option. In Mongoose 7, Mongoose uses the base schema's
// `typeKey` by default.
const childSchema = new Schema({}, {});
const Test = Base.discriminator('Child', childSchema);

Test.schema.options.typeKey; // '$type'

已移除 castForQueryWrapper,已更新 castForQuery() 簽名

Mongoose 現在一律使用 3 個引數呼叫 SchemaType castForQuery() 方法:$conditionalvaluecontext。如果您已實作自訂綱要類型,並定義了自己的 castForQuery() 方法,則需要如下更新該方法。

// Mongoose 6.x format:
MySchemaType.prototype.castForQuery = function($conditional, value) {
  if (arguments.length === 2) {
    // Handle casting value with `$conditional` - $eq, $in, $not, etc.
  } else {
    value = $conditional;
    // Handle casting `value` with no conditional
  }
};

// Mongoose 7.x format
MySchemaType.prototype.castForQuery = function($conditional, value, context) {
  if ($conditional != null) {
    // Handle casting value with `$conditional` - $eq, $in, $not, etc.
  } else {
    // Handle casting `value` with no conditional
  }
};

Schema.prototype.add() 中複製綱要選項

Mongoose 現在會在將一個綱要新增至另一個綱要時,複製使用者定義的綱要選項。例如,下面的 childSchema 將會取得 baseSchemaidtoJSON 選項。

const baseSchema = new Schema({ created: Date }, { id: true, toJSON: { virtuals: true } });
const childSchema = new Schema([baseSchema, { name: String }]);

childSchema.options.toJSON; // { virtuals: true } in Mongoose 7. undefined in Mongoose 6.

這適用於使用綱要陣列建立新綱要,以及如下呼叫 add() 時。

childSchema.add(new Schema({}, { toObject: { virtuals: true } }));

childSchema.options.toObject; // { virtuals: true } in Mongoose 7. undefined in Mongoose 6.

ObjectId bsontype 現在使用小寫 d

ObjectIds 的內部 _bsontype 屬性在 Mongoose 7 中等於 'ObjectId',而在 Mongoose 6 中等於 'ObjectID'

const oid = new mongoose.Types.ObjectId();

oid._bsontype; // 'ObjectId' in Mongoose 7, 'ObjectID' in older versions of Mongoose

請更新您使用 _bsontype 檢查物件是否為 ObjectId 的任何地方。這也可能會影響使用 Mongoose 的函式庫。

已移除 mapReduce

MongoDB 不再支援 mapReduce,因此 Mongoose 7 不再具有 Model.mapReduce() 函式。請使用聚合架構來取代 mapReduce()

// The following no longer works in Mongoose 7.
const o = {
  map: function() {
    emit(this.author, 1);
  },
  reduce: function(k, vals) {
    return vals.length;
  }
};

await MR.mapReduce(o);

已移除對自訂 Promise 函式庫的支援

Mongoose 7 不再支援外掛自訂 Promise 函式庫。因此,以下程式碼在 Mongoose 7 中不再使 Mongoose 傳回 Bluebird Promise。

const mongoose = require('mongoose');

// No-op on Mongoose 7
mongoose.Promise = require('bluebird');

如果您想為所有 Promise 全域使用 Bluebird,可以執行以下操作

global.Promise = require('bluebird');

TypeScript 專屬變更

已移除 LeanDocument 以及對 extends Document 的支援

Mongoose 7 不再匯出 LeanDocument 類型,並且不再支援將 extends Document 的文件類型傳遞至 Model<>

// No longer supported
interface ITest extends Document {
  name?: string;
}
const Test = model<ITest>('Test', schema);

// Do this instead, no `extends Document`
interface ITest {
  name?: string;
}
const Test = model<ITest>('Test', schema);

// If you need to access the hydrated document type, use the following code
type TestDocument = ReturnType<(typeof Test)['hydrate']>;

HydratedDocument 的新參數

Mongoose 的 HydratedDocument 類型會將原始文件介面轉換為已注水的 Mongoose 文件的類型,包括虛擬屬性、方法等等。在 Mongoose 7 中,HydratedDocument 的泛型參數已變更。在 Mongoose 6 中,泛型參數為

type HydratedDocument<
  DocType,
  TMethodsAndOverrides = {},
  TVirtuals = {}
> = Document<unknown, any, DocType> &
Require_id<DocType> &
TMethodsAndOverrides &
TVirtuals;

在 Mongoose 7 中,新類型如下。

type HydratedDocument<
  DocType,
  TOverrides = {},
  TQueryHelpers = {}
> = Document<unknown, TQueryHelpers, DocType> &
Require_id<DocType> &
TOverrides;

在 Mongoose 7 中,第一個參數是原始文件介面,第二個參數是任何特定於文件的覆寫 (通常是虛擬屬性和方法),而第三個參數是與文件模型相關聯的任何查詢協助程式。

主要差異在於,在 Mongoose 6 中,第三個泛型參數是文件的虛擬屬性。在 Mongoose 7 中,第三個泛型參數是文件的查詢協助程式

// Mongoose 6 version:
type UserDocument = HydratedDocument<TUser, TUserMethods, TUserVirtuals>;

// Mongoose 7:
type UserDocument = HydratedDocument<TUser, TUserMethods & TUserVirtuals, TUserQueryHelpers>;