从 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 或 Promise(如果 async 函数不适合您)。如果您需要帮助重构遗留代码库,请使用 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

ObjectId 上的内部 _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');

如果您想全局使用 Bluebird 作为所有 Promise,您可以执行以下操作

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>;