从 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() 的承诺和回调

mongoose.connect()mongoose.disconnect() 现在如果未指定回调则返回一个承诺,否则返回 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

在 Mongoose 5 中,useMongoClient 选项 已被删除,现在始终为 true。因此,Mongoose 5 不再支持 mongoose.connect() 的几个函数签名,这些签名在 Mongoose 4.x 中如果 useMongoClient 选项关闭则有效。以下是 mongoose.connect() 调用的一些示例,这些示例在 Mongoose 5.x 中无效

  • 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 驱动程序支持的选项之外的几个附加选项

设置器顺序

设置器在 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 中,设置器按照声明的顺序运行。

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 为 ObjectId 引入了一个 _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

Model.geoNear() 已被删除,因为 MongoDB 驱动程序不再支持它

连接字符串的必需 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));

以上函数无论现有字符串是否已编码都可以安全使用。

域套接字

域套接字必须进行 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() 不再接受展开,您必须将聚合管道作为数组传递。以下代码在 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() 才会发生。这使得钩子和自定义查询助手更容易修改数据,因为 mongoose 不会在您的钩子和查询助手运行后才重新构造您传递的数据。它还使得在传递更新后设置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 });

保存后钩子获取流程控制

后钩子现在获得流程控制,这意味着异步保存后钩子和子文档保存后钩子在您的 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 运算符

$pushAll 不再受支持,也不再用于 save() 的内部,因为它已 自 MongoDB 2.4 起弃用。请改用 $push$each

始终使用前向键顺序

retainKeyOrder 选项已被删除,mongoose 现在将始终在克隆对象时保留相同的键位置。如果您有依赖于反向键顺序的查询或索引,则需要更改它们。

在查询上运行设置器

设置器现在默认情况下在查询上运行,并且已删除旧的 runSettersOnQuery 选项。

const schema = new Schema({
  email: { type: String, lowercase: true }
});
const Model = mongoose.model('Test', schema);
Model.find({ email: '[email protected]' }); // Converted to `find({ email: '[email protected]' })`

预编译的浏览器捆绑包

我们不再为浏览器提供预编译版本的 mongoose。如果您想在浏览器中使用 mongoose 模式,您需要使用 browserify/webpack 构建自己的捆绑包。

保存错误

saveErrorIfNotFound 选项已被删除,mongoose 现在如果底层文档未找到,则始终会从 save() 中出错

初始化钩子签名

init 钩子现在完全同步,并且不会将 next() 作为参数接收。

Document.prototype.init() 不再将回调作为参数接收。它始终是同步的,只是出于遗留原因才具有回调。

numAffectedsave()

doc.save() 不再将 numAffected 作为第三个参数传递给其回调。

remove() 和去抖动

doc.remove() 不再进行去抖动

getPromiseConstructor()

getPromiseConstructor() 已消失,直接使用 mongoose.Promise 即可。

从预钩子传递参数

您不能在 mongoose 5.x 中使用 next() 在链中将参数传递给下一个预中间件。在 mongoose 4 中,预中间件中的 next('Test') 会使用 'Test' 作为参数调用下一个中间件。Mongoose 5.x 已删除对该功能的支持。

数组的 required 验证器

在 mongoose 5 中,required 验证器只验证值是否为数组。也就是说,它不会像 mongoose 4 那样对数组失败。

调试输出默认使用标准输出而不是标准错误

在 mongoose 5 中,默认调试函数使用 console.info() 来显示消息,而不是 console.error()

覆盖过滤器属性

在 Mongoose 4.x 中,用对象覆盖作为原语的过滤器属性会默默失败。例如,以下代码将忽略 where(),等效于 Sport.find({ name: 'baseball' })

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

在 Mongoose 5.x 中,以上代码将正确地用 { $ne: 'softball' } 覆盖 'baseball'

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