从 5.x 迁移到 6.x

请注意:我们计划在 2024 年 3 月 1 日停止对 Mongoose 5 的支持。请查看我们的 版本支持指南.

从 Mongoose 5.x 迁移到 Mongoose 6.x 时,您应该注意几个 向后不兼容的更改

如果您仍然使用 Mongoose 4.x,请阅读 Mongoose 4.x 到 5.x 迁移指南 并先升级到 Mongoose 5.x。

版本要求

Mongoose 现在需要 Node.js >= 12.0.0。Mongoose 仍然支持从 3.0.0 开始的 MongoDB 服务器版本。

MongoDB 驱动程序 4.0

Mongoose 现在使用 MongoDB Node 驱动程序 的 v4.x 版本。请参阅 MongoDB Node 驱动的迁移指南 以获取详细的信息。以下是其中一些最值得注意的更改

  • MongoDB 驱动程序 4.x 是用 TypeScript 编写的,并有自己的 TypeScript 类型定义。这些可能会与 @types/mongodb 冲突,因此如果您遇到 TypeScript 编译器错误,请确保您升级到 @types/mongodb 的最新版本,它是一个空存根。
  • 连接的 poolSize 选项已被 minPoolSizemaxPoolSize 替换。Mongoose 5.x 的 poolSize 选项等效于 Mongoose 6 的 maxPoolSize 选项。maxPoolSize 的默认值已增加到 100。
  • updateOne()updateMany() 的结果现在不同。
  • deleteOne()deleteMany() 的结果不再具有 n 属性。
const res = await TestModel.updateMany({}, { someProperty: 'someValue' });

res.matchedCount; // Number of documents that were found that match the filter. Replaces `res.n`
res.modifiedCount; // Number of documents modified. Replaces `res.nModified`
res.upsertedCount; // Number of documents upserted. Replaces `res.upserted`
const res = await TestModel.deleteMany({});

// In Mongoose 6: `{ acknowledged: true, deletedCount: 2 }`
// In Mongoose 5: `{ n: 2, ok: 1, deletedCount: 2 }`
res;

res.deletedCount; // Number of documents that were deleted. Replaces `res.n`

不再有弃用警告选项

useNewUrlParseruseUnifiedTopologyuseFindAndModifyuseCreateIndex 不再是支持的选项。Mongoose 6 始终表现得好像 useNewUrlParseruseUnifiedTopologyuseCreateIndextrue,而 useFindAndModifyfalse。请从您的代码中删除这些选项。

// No longer necessary:
mongoose.set('useFindAndModify', false);

await mongoose.connect('mongodb://127.0.0.1:27017/test', {
  useNewUrlParser: true, // <-- no longer necessary
  useUnifiedTopology: true // <-- no longer necessary
});

连接的 asPromise() 方法

Mongoose 连接不再是 thenable。这意味着 await mongoose.createConnection(uri) **不再等待 Mongoose 连接**。请改用 mongoose.createConnection(uri).asPromise()。请参阅 #8810.

// The below no longer works in Mongoose 6
await mongoose.createConnection(uri);

// Do this instead
await mongoose.createConnection(uri).asPromise();

mongoose.connect() 返回一个 Promise

mongoose.connect() 函数现在始终返回一个 promise,**而不是** Mongoose 实例。

重复查询执行

Mongoose 不再允许两次执行相同的查询对象。如果您这样做,您将收到 Query was already executed 错误。两次执行同一个查询实例通常表明混合了回调和 promise,但如果您需要两次执行同一个查询,您可以调用 Query#clone() 来克隆查询并重新执行它。请参阅 gh-7398

// Results in 'Query was already executed' error, because technically this `find()` query executes twice.
await Model.find({}, function(err, result) {});

const q = Model.find();
await q;
await q.clone(); // Can `clone()` the query to allow executing the query again

Model.exists(...) 现在返回一个精简文档而不是布尔值

// in Mongoose 5.x, `existingUser` used to be a boolean
// now `existingUser` will be either `{ _id: ObjectId(...) }` or `null`.
const existingUser = await User.exists({ name: 'John' });
if (existingUser) {
  console.log(existingUser._id);
}

strictQuery 现在默认等于 strict

Mongoose 不再支持 strictQuery 选项。您现在必须使用 strict 从 Mongoose 6.0.10 开始,我们恢复了 strictQuery 选项。但是,strictQuery 默认情况下与 strict 绑定。这意味着,默认情况下,Mongoose 会过滤掉不在模式中的查询过滤器属性。

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

// By default, this is equivalent to `User.find()` because Mongoose filters out `notInSchema`
await User.find({ notInSchema: 1 });

// Set `strictQuery: false` to opt in to filtering by properties that aren't in the schema
await User.find({ notInSchema: 1 }, null, { strictQuery: false });
// equivalent:
await User.find({ notInSchema: 1 }).setOptions({ strictQuery: false });

您也可以全局禁用 strictQuery 来覆盖

mongoose.set('strictQuery', false);

MongoError 现在是 MongoServerError

在 MongoDB Node.js Driver v4.x 中,'MongoError' 现在是 'MongoServerError'。请更改任何依赖于硬编码字符串 'MongoError' 的代码。

默认情况下克隆鉴别器模式

Mongoose 现在默认情况下克隆鉴别器模式。这意味着如果您使用递归嵌入的鉴别器,则需要将 { clone: false } 传递给 discriminator()

// In Mongoose 6, these two are equivalent:
User.discriminator('author', authorSchema);
User.discriminator('author', authorSchema.clone());

// To opt out if `clone()` is causing issues, pass `clone: false`
User.discriminator('author', authorSchema, { clone: false });

简化的 isValidObjectId() 和独立的 isObjectIdOrHexString()

在 Mongoose 5 中,mongoose.isValidObjectId() 对数字等值返回 false,这与 MongoDB 驱动程序的 ObjectId.isValid() 函数不一致。从技术上讲,任何 JavaScript 数字都可以转换为 MongoDB ObjectId。

在 Mongoose 6 中,mongoose.isValidObjectId() 只是一个包装器,用于实现与 mongoose.Types.ObjectId.isValid() 的一致性。

Mongoose 6.2.5 现在包含一个 mongoose.isObjectIdOrHexString() 函数,它更好地捕获了 isValidObjectId() 的更常见用例:给定值是 ObjectId 实例还是表示 ObjectId 的 24 个字符的十六进制字符串?

// `isValidObjectId()` returns `true` for some surprising values, because these
// values are _technically_ ObjectId representations
mongoose.isValidObjectId(new mongoose.Types.ObjectId()); // true
mongoose.isValidObjectId('0123456789ab'); // true
mongoose.isValidObjectId(6); // true
mongoose.isValidObjectId(new User({ name: 'test' })); // true

// `isObjectIdOrHexString()` instead only returns `true` for ObjectIds and 24
// character hex strings.
mongoose.isObjectIdOrHexString(new mongoose.Types.ObjectId()); // true
mongoose.isObjectIdOrHexString('62261a65d66c6be0a63c051f'); // true
mongoose.isObjectIdOrHexString('0123456789ab'); // false
mongoose.isObjectIdOrHexString(6); // false

模式定义的文档键顺序

Mongoose 现在以在模式中指定键的顺序保存具有键的对象,而不是以用户定义的对象中的顺序保存。因此,Object.keys(new User({ name: String, email: String }).toObject()['name', 'email'] 还是 ['email', 'name'] 取决于在模式中定义 nameemail 的顺序。

const schema = new Schema({
  profile: {
    name: {
      first: String,
      last: String
    }
  }
});
const Test = db.model('Test', schema);

const doc = new Test({
  profile: { name: { last: 'Musashi', first: 'Miyamoto' } }
});

// Note that 'first' comes before 'last', even though the argument to `new Test()` flips the key order.
// Mongoose uses the schema's key order, not the provided objects' key order.
assert.deepEqual(Object.keys(doc.toObject().profile.name), ['first', 'last']);

sanitizeFiltertrusted()

Mongoose 6 引入了一个新的 sanitizeFilter 选项,用于防御 查询选择器注入攻击。如果启用 sanitizeFilter,Mongoose 将在查询过滤器中的任何对象中包装一个 $eq

// Mongoose will convert this filter into `{ username: 'val', pwd: { $eq: { $ne: null } } }`, preventing
// a query selector injection.
await Test.find({ username: 'val', pwd: { $ne: null } }).setOptions({ sanitizeFilter: true });

要显式允许查询选择器,请使用 mongoose.trusted()

// `mongoose.trusted()` allows query selectors through
await Test.find({ username: 'val', pwd: mongoose.trusted({ $ne: null }) }).setOptions({ sanitizeFilter: true });

删除 omitUndefined:Mongoose 现在在更新中删除 undefined 键,而不是将它们设置为 null

在 Mongoose 5.x 中,在更新操作中将键设置为 undefined 等同于将其设置为 null

let res = await Test.findOneAndUpdate({}, { $set: { name: undefined } }, { new: true });

res.name; // `null` in Mongoose 5.x

// Equivalent to `findOneAndUpdate({}, {}, { new: true })` because `omitUndefined` will
// remove `name: undefined`
res = await Test.findOneAndUpdate({}, { $set: { name: undefined } }, { new: true, omitUndefined: true });

Mongoose 5.x 支持一个 omitUndefined 选项来删除 undefined 键。在 Mongoose 6.x 中,omitUndefined 选项已被删除,Mongoose 将始终删除未定义的键。

// In Mongoose 6, equivalent to `findOneAndUpdate({}, {}, { new: true })` because Mongoose will
// remove `name: undefined`
const res = await Test.findOneAndUpdate({}, { $set: { name: undefined } }, { new: true });

唯一的解决方法是在更新中显式将属性设置为 null

const res = await Test.findOneAndUpdate({}, { $set: { name: null } }, { new: true });

默认函数的文档参数

Mongoose 现在将文档作为第一个参数传递给 default 函数,这对于使用 箭头函数 以及默认值很有帮助。

如果您传递一个期望不同参数的函数到 default,这可能会影响您,例如 default: mongoose.Types.ObjectId。请参阅 gh-9633。如果您传递的默认函数**没有**使用文档,请将 default: myFunction 更改为 default: () => myFunction(),以避免意外地传递可能改变行为的参数。

const schema = new Schema({
  name: String,
  age: Number,
  canVote: {
    type: Boolean,
    // Default functions now receive a `doc` parameter, helpful for arrow functions
    default: doc => doc.age >= 18
  }
});

数组是代理

Mongoose 数组现在是 ES6 代理。您不再需要在直接设置数组索引后 markModified()

const post = await BlogPost.findOne();

post.tags[0] = 'javascript';
await post.save(); // Works, no need for `markModified()`!

typePojoToMixed

使用 type: { name: String } 声明的模式路径在 Mongoose 6 中成为单个嵌套子文档,而不是 Mongoose 5 中的 Mixed。这消除了对 typePojoToMixed 选项的需求。请参阅 gh-7181.

// In Mongoose 6, the below makes `foo` into a subdocument with a `name` property.
// In Mongoose 5, the below would make `foo` a `Mixed` type, _unless_ you set `typePojoToMixed: false`.
const schema = new Schema({
  foo: { type: { name: String } }
});

strictPopulate()

如果对模式中未定义的路径执行 populate(),Mongoose 现在会抛出错误。这仅适用于我们可以推断本地模式的情况,例如当您使用 Query#populate() 时,**而不是**当您在 POJO 上调用 Model.populate() 时。请参阅 gh-5124.

子文档 ref 函数上下文

使用函数 refrefPath 填充子文档时,this 现在是正在填充的子文档,而不是顶层文档。请参阅 #8469.

const schema = new Schema({
  works: [{
    modelId: String,
    data: {
      type: mongoose.ObjectId,
      ref: function(doc) {
        // In Mongoose 6, `doc` is the array element, so you can access `modelId`.
        // In Mongoose 5, `doc` was the top-level document.
        return doc.modelId;
      }
    }
  }]
});

模式保留名称警告

使用 saveisNew 和其他 Mongoose 保留名称作为模式路径名称现在会触发警告,而不是错误。您可以通过在模式选项中设置 suppressReservedKeysWarning 来抑制警告:new Schema({ save: String }, { suppressReservedKeysWarning: true })。请记住,这可能会破坏依赖于这些保留名称的插件。

子文档路径

单个嵌套子文档已重命名为“子文档路径”。因此,SchemaSingleNestedOptions 现在是 SchemaSubdocumentOptions,而 mongoose.Schema.Types.Embedded 现在是 mongoose.Schema.Types.Subdocument。请参阅 gh-10419

创建聚合游标

Aggregate#cursor() 现在返回一个 AggregationCursor 实例,以与 Query#cursor() 保持一致。您不再需要执行 Model.aggregate(pipeline).cursor().exec() 来获取聚合游标,只需执行 Model.aggregate(pipeline).cursor() 即可。

autoCreate 默认设置为 true

autoCreate 默认情况下为 true,**除非** readPreference 为 secondary 或 secondaryPreferred,这意味着 Mongoose 将尝试在创建索引之前创建每个模型的底层集合。如果 readPreference 为 secondary 或 secondaryPreferred,Mongoose 将默认情况下将 autoCreateautoIndex 设置为 false,因为当连接到辅助服务器时,createCollection()createIndex() 都会失败。

不再有 context: 'query'

查询的 context 选项已被删除。现在,Mongoose 始终使用 context = 'query'

使用填充路径的自定义验证器

Mongoose 6 始终使用已取消填充的路径调用验证器(即使用 ID 而不是文档本身)。在 Mongoose 5 中,如果路径已填充,Mongoose 将使用填充的文档调用验证器。请参阅 #8042

副本集的断开连接事件

连接到副本集时,连接现在在丢失到主服务器的连接时发出 'disconnected' 事件。在 Mongoose 5 中,连接仅在丢失到副本集的所有成员的连接时发出 'disconnected' 事件。

但是,Mongoose 6 **不会**在连接断开时缓冲命令。因此,您仍然可以成功执行命令,例如使用 readPreference = 'secondary' 的查询,即使 Mongoose 连接处于断开状态。

删除 execPopulate()

Document#populate() 现在返回一个 promise,并且不再是可链接的。

  • await doc.populate('path1').populate('path2').execPopulate(); 替换为 await doc.populate(['path1', 'path2']);

  • await doc.populate('path1', 'select1').populate('path2', 'select2').execPopulate(); 替换为

    await doc.populate([{path: 'path1', select: 'select1'}, {path: 'path2', select: 'select2'}]);

带有空数组的 create()

在 v6.0 中,await Model.create([]) 在提供空数组时返回空数组,在 v5.0 中它曾经返回 undefined。如果你的任何代码正在检查输出是否为 undefined,你需要修改它,假设 await Model.create(...) 在提供数组时始终返回数组。

删除嵌套路径合并

doc.set({ child: { age: 21 } }) 现在无论 child 是嵌套路径还是子文档,都具有相同的操作方式:Mongoose 将覆盖 child 的值。在 Mongoose 5 中,如果 child 是嵌套路径,此操作将合并 child

ObjectId valueOf()

Mongoose 现在为 ObjectId 添加了 valueOf() 函数。这意味着你现在可以使用 == 将 ObjectId 与字符串进行比较。

const a = ObjectId('6143b55ac9a762738b15d4f0');

a == '6143b55ac9a762738b15d4f0'; // true

不可变的 createdAt

如果你设置了 timestamps: true,Mongoose 现在将使 createdAt 属性 不可变。参见 gh-10139

删除验证器 isAsync

isAsync 不再是 validate 的选项。请改用 async function

删除 safe

safe 不再是模式、查询或 save() 的选项。请改用 writeConcern

SchemaType set 参数

Mongoose 现在使用 priorValue 作为第二个参数调用 setter 函数,而不是在 Mongoose 5 中使用 schemaType

const userSchema = new Schema({
  name: {
    type: String,
    trimStart: true,
    set: trimStartSetter
  }
});

// in v5.x the parameters were (value, schemaType), in v6.x the parameters are (value, priorValue, schemaType).
function trimStartSetter(val, priorValue, schemaType) {
  if (schemaType.options.trimStart && typeof val === 'string') {
    return val.trimStart();
  }
  return val;
}

const User = mongoose.model('User', userSchema);

const user = new User({ name: 'Robert Martin' });
console.log(user.name); // 'robert martin'

toObject()toJSON() 使用嵌套模式 minimize

此更改在技术上已在 5.10.5 版本中发布,但 导致用户从 5.9.x 迁移到 6.x 时出现问题。在 Mongoose < 5.10.5 中,toObject()toJSON() 默认情况下会使用顶层模式的 minimize 选项。

const child = new Schema({ thing: Schema.Types.Mixed });
const parent = new Schema({ child }, { minimize: false });
const Parent = model('Parent', parent);
const p = new Parent({ child: { thing: {} } });

// In v5.10.4, would contain `child.thing` because `toObject()` uses `parent` schema's `minimize` option
// In `>= 5.10.5`, `child.thing` is omitted because `child` schema has `minimize: true`
console.log(p.toObject());

作为解决方法,你可以显式地将 minimize 传递给 toObject()toJSON()

console.log(p.toObject({ minimize: false }));

或者在行内定义 child 模式(仅限 Mongoose 6)以继承父级的 minimize 选项。

const parent = new Schema({
  // Implicitly creates a new schema with the top-level schema's `minimize` option.
  child: { type: { thing: Schema.Types.Mixed } }
}, { minimize: false });

Query.prototype.populate() 没有默认模型

在 Mongoose 5 中,在没有 ref 的混合类型或其他路径上调用 populate() 将回退到使用查询的模型。

const testSchema = new mongoose.Schema({
  data: String,
  parents: Array // Array of mixed
});

const Test = mongoose.model('Test', testSchema);

// The below `populate()`...
await Test.findOne().populate('parents');
// Is a shorthand for the following populate in Mongoose 5
await Test.findOne().populate({ path: 'parents', model: Test });

在 Mongoose 6 中,填充没有 refrefPathmodel 的路径是无操作的。

// The below `populate()` does nothing.
await Test.findOne().populate('parents');

MongoDB 驱动的新的 URL 解析器与某些 npm 包不兼容

Mongoose 6 使用的 MongoDB Node 驱动程序版本依赖于 URL 解析器模块,该模块与其他 npm 包存在一些已知兼容性问题。如果使用这些不兼容的包,可能会导致错误,例如 Invalid URL: mongodb+srv://username:[email protected]/abc你可以在此处找到不兼容包的列表.

TypeScript 更改

Schema 类现在采用 3 个泛型参数,而不是 4 个。第三个泛型参数 SchemaDefinitionType 现在与第一个泛型参数 DocType 相同。将 new Schema<UserDocument, UserModel, User>(schemaDefinition) 替换为 new Schema<UserDocument, UserModel>(schemaDefinition)

Types.ObjectId 现在是一个类,这意味着使用 new mongoose.Types.ObjectId() 创建新的 ObjectId 时,你不能再省略 new。目前,你仍然可以在 JavaScript 中省略 new,但在 TypeScript 中你 **必须** 添加 new

以下遗留类型已被删除

  • ModelUpdateOptions
  • DocumentQuery
  • HookSyncCallback
  • HookAsyncCallback
  • HookErrorCallback
  • HookNextFunction
  • HookDoneFunction
  • SchemaTypeOpts
  • ConnectionOptions

Mongoose 6 为虚拟 getter 和 setter 中的 this 推断文档的类型。在 Mongoose 5.x 中,this 在以下代码中将是 any

schema.virtual('myVirtual').get(function() {
  this; // any in Mongoose 5.x
});

在 Mongoose 6 中,this 将被设置为文档类型。

const schema = new Schema({ name: String });

schema.virtual('myVirtual').get(function() {
  this.name; // string
});

删除 reconnectTriesreconnectInterval 选项

reconnectTriesreconnectInterval 选项已被删除,因为它们不再必要。

MongoDB 节点驱动程序将始终尝试重试任何操作,最多可重试 serverSelectionTimeoutMS 次,即使 MongoDB 停机时间很长。因此,它永远不会耗尽重试次数或尝试重新连接到 MongoDB。