从 4.x 迁移到 5.x
¥Migrating from 4.x to 5.x
请注意:我们计划于 2024 年 3 月 1 日停止对 Mongoose 5 的支持。请参阅我们的 版本支持指南。
¥Please note: we plan to discontinue Mongoose 5 support on March 1, 2024. Please see our version support guide.
从 Mongoose 4.x 迁移到 Mongoose 5.x 时,你应该注意几个 向后突破的变化。
¥There are several backwards-breaking changes you should be aware of when migrating from Mongoose 4.x to Mongoose 5.x.
如果你仍在使用 Mongoose 3.x,请阅读 Mongoose 3.x 到 4.x 迁移指南。
¥If you're still on Mongoose 3.x, please read the Mongoose 3.x to 4.x migration guide.
版本要求
¥Version Requirements
Mongoose 现在需要 Node.js >= 4.0.0 和 MongoDB >= 3.0.0。MongoDB 2.6 和 Node.js < 4 均已于 2016 年停产。
¥Mongoose now requires Node.js >= 4.0.0 and MongoDB >= 3.0.0. MongoDB 2.6 and Node.js < 4 where both EOL-ed in 2016.
查询中间件
¥Query Middleware
现在,当你调用 mongoose.model()
或 db.model()
时,会编译查询中间件。如果在调用 mongoose.model()
之后添加查询中间件,则该中间件将不会被调用。
¥Query middleware is now compiled when you call mongoose.model()
or db.model()
. If you add query middleware after calling mongoose.model()
, that middleware will not get called.
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 和回调
¥Promises and Callbacks for mongoose.connect()
如果未指定回调,则 mongoose.connect()
和 mongoose.disconnect()
现在返回 promise,否则返回 null
。它不会返回 Mongoose 单例。
¥mongoose.connect()
and mongoose.disconnect()
now return a promise if no callback specified, or null
otherwise. It does not return the mongoose singleton.
// 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
¥Connection Logic and useMongoClient
useMongoClient
选项 在 Mongoose 5 中被删除,现在始终是 true
。因此,如果 useMongoClient
选项关闭,Mongoose 5 不再支持在 Mongoose 4.x 中工作的 mongoose.connect()
的多个函数签名。以下是在 Mongoose 5.x 中不起作用的 mongoose.connect()
调用的一些示例。
¥The useMongoClient
option was
removed in Mongoose 5, it is now always true
. As a consequence, Mongoose 5
no longer supports several function signatures for mongoose.connect()
that
worked in Mongoose 4.x if the useMongoClient
option was off. Below are some
examples of mongoose.connect()
calls that do not work in 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 驱动程序支持的选项之外,还有一些其他选项。
¥In Mongoose 5.x, the first parameter to mongoose.connect()
and mongoose.createConnection()
, if specified, must be a MongoDB connection string. The
connection string and options are then passed down to the MongoDB Node.js driver's MongoClient.connect()
function. Mongoose does not modify the connection string, although mongoose.connect()
and mongoose.createConnection()
support a few additional options in addition to the ones the MongoDB driver supports.
二传顺序
¥Setter Order
Setter 在 4.x 中以相反的顺序运行:
¥Setters run in reverse order in 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 按照声明的顺序运行。
¥In 5.x, setters run in the order they're declared.
const schema = new Schema({ name: String });
schema.path('name').
set(() => console.log('This will print first')).
set(() => console.log('This will print 2nd'));
检查路径是否已填充
¥Checking if a path is populated
Mongoose 5.1.0 向 ObjectIds 引入了 _id
getter,无论路径是否已填充,你都可以获取 ObjectId。
¥Mongoose 5.1.0 introduced an _id
getter to ObjectIds that lets you get an ObjectId regardless of whether a path
is populated.
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') != null
或 blogPost.author instanceof mongoose.Types.ObjectId
检查 author
是否已填充。
¥As a consequence, checking whether blogPost.author._id
is no longer viable as a way to check whether author
is populated. Use blogPost.populated('author') != null
or blogPost.author instanceof mongoose.Types.ObjectId
to check whether author
is populated instead.
请注意,你可以调用 mongoose.set('objectIdGetter', false)
来更改此行为。
¥Note that you can call mongoose.set('objectIdGetter', false)
to change this behavior.
remove()
和 deleteX()
的返回值
¥Return Values for remove()
and deleteX()
deleteOne()
、deleteMany()
和 remove()
现在解析为结果对象而不是完整的 驱动程序 WriteOpResult
对象。
¥deleteOne()
, deleteMany()
, and remove()
now resolve to the result object
rather than the full driver WriteOpResult
object.
// 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);
聚合游标
¥Aggregation Cursors
4.x 中的 useMongooseAggCursor
选项现在始终打开。这是 mongoose 5 中聚合游标的新语法:
¥The useMongooseAggCursor
option from 4.x is now always on. This is the new syntax for aggregation cursors in 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 驱动程序不再支持它
¥Model.geoNear()
has been removed because the MongoDB driver no longer supports it
连接字符串所需的 URI 编码
¥Required URI encoding of connection strings
由于 MongoDB 驱动程序的更改,连接字符串必须进行 URI 编码。
¥Due to changes in the MongoDB driver, connection strings must be URI encoded.
如果不是,连接可能会失败并显示非法字符消息。
¥If they are not, connections may fail with an illegal character message.
包含特定字符的密码
¥Passwords which contain certain characters
见 受影响角色的完整列表。
¥See a full list of affected characters.
如果你的应用由许多不同的连接字符串使用,则你的测试用例可能会通过,但生产密码将失败。对所有连接字符串进行编码以确保安全。
¥If your app is used by a lot of different connection strings, it's possible that your test cases will pass, but production passwords will fail. Encode all your connection strings to be safe.
如果你想继续使用未编码的连接字符串,最简单的解决方法是使用 mongodb-uri
模块来解析连接字符串,然后生成正确编码的版本。你可以使用这样的函数:
¥If you want to continue to use unencoded connection strings, the easiest fix is to use
the mongodb-uri
module to parse the connection strings, and then produce the properly encoded
versions. You can use a function like this:
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));
无论现有字符串是否已编码,上面的函数都可以安全使用。
¥The function above is safe to use whether the existing string is already encoded or not.
域套接字
¥Domain sockets
域套接字必须进行 URI 编码。例如:
¥Domain sockets must be URI encoded. For example:
// 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()
Options
options
参数到 toObject()
和 toJSON()
合并默认值而不是覆盖它们。
¥The options
parameter to toObject()
and toJSON()
merge defaults rather than overwriting them.
// 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 Parameters
aggregate()
不再接受扩展,你必须将聚合管道作为数组传递。下面的代码在 4.x 中有效:
¥aggregate()
no longer accepts a spread, you must pass your aggregation pipeline as an array. The below code worked in 4.x:
MyModel.aggregate({ $match: { isDeleted: false } }, { $skip: 10 }).exec(cb);
上面的代码在 5.x 中不起作用,你必须将 $match
和 $skip
阶段封装在一个数组中。
¥The above code does not work in 5.x, you must wrap the $match
and $skip
stages in an array.
MyModel.aggregate([{ $match: { isDeleted: false } }, { $skip: 10 }]).exec(cb);
布尔转换
¥Boolean Casting
默认情况下,mongoose 4 会将任何值强制转换为布尔值,不会出现错误。
¥By default, mongoose 4 would coerce any value to a boolean without error.
// 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
:
¥Mongoose 5 only casts the following values to true
:
true
'true'
1
'1'
'yes'
以及 false
的以下值:
¥And the following values to false
:
false
'false'
0
'0'
'no'
所有其他值都会导致 CastError
¥All other values will cause a CastError
查询转换
¥Query Casting
update()
、updateOne()
、updateMany()
、replaceOne()
、remove()
、deleteOne()
和 deleteMany()
的选角要到 exec()
才会进行。这使得钩子和自定义查询助手更容易修改数据,因为 Mongoose 在钩子和查询助手运行之前不会重组你传入的数据。它还可以在传递更新后设置 overwrite
选项。
¥Casting for update()
, updateOne()
, updateMany()
, replaceOne()
,
remove()
, deleteOne()
, and deleteMany()
doesn't happen until exec()
.
This makes it easier for hooks and custom query helpers to modify data, because
mongoose won't restructure the data you passed in until after your hooks and
query helpers have ran. It also makes it possible to set the overwrite
option
after passing in an update.
// 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 Get Flow Control
Post 钩子现在获得流量控制,这意味着异步 post 保存钩子和子文档 post 保存钩子在 save()
回调之前执行。
¥Post hooks now get flow control, which means async post save hooks and child document post save hooks execute before your save()
callback.
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
运算符
¥The $pushAll
Operator
$pushAll
不再受支持,也不再在 save()
内部使用,因为它已经是 自 MongoDB 2.4 起已弃用。使用 $push
和 $each
代替。
¥$pushAll
is no longer supported and no longer used internally for save()
, since it has been deprecated since MongoDB 2.4. Use $push
with $each
instead.
始终使用前向键顺序
¥Always Use Forward Key Order
retainKeyOrder
选项已被删除,mongoose 现在在克隆对象时将始终保留相同的关键位置。如果你的查询或索引依赖于反向键顺序,则必须更改它们。
¥The retainKeyOrder
option was removed, mongoose will now always retain the same key position when cloning objects. If you have queries or indexes that rely on reverse key order, you will have to change them.
对查询运行设置器
¥Run setters on queries
Setter 现在默认在查询上运行,并且旧的 runSettersOnQuery
选项已被删除。
¥Setters now run on queries by default, and the old runSettersOnQuery
option
has been removed.
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' })`
预编译浏览器包
¥Pre-compiled Browser Bundle
我们不再为浏览器提供预编译版本的 mongoose。如果你想在浏览器中使用 mongoose 结构,则需要使用 browserify/webpack 构建自己的包。
¥We no longer have a pre-compiled version of mongoose for the browser. If you want to use mongoose schemas in the browser, you need to build your own bundle with browserify/webpack.
保存错误
¥Save Errors
saveErrorIfNotFound
选项已删除,如果未找到底层文档,mongoose 现在将始终从 save()
中出错
¥The saveErrorIfNotFound
option was removed, mongoose will now always error out from save()
if the underlying document was not found
初始化钩子签名
¥Init hook signatures
init
钩子现在完全同步,并且不接收 next()
作为参数。
¥init
hooks are now fully synchronous and do not receive next()
as a parameter.
Document.prototype.init()
不再将回调作为参数。它始终是同步的,只是由于遗留原因有一个回调。
¥Document.prototype.init()
no longer takes a callback as a parameter. It
was always synchronous, just had a callback for legacy reasons.
numAffected
和 save()
¥numAffected
and save()
doc.save()
不再将 numAffected
作为第三个参数传递给其回调。
¥doc.save()
no longer passes numAffected
as a 3rd param to its callback.
remove()
和去抖
¥remove()
and debouncing
doc.remove()
不再反跳
¥doc.remove()
no longer debounces
getPromiseConstructor()
getPromiseConstructor()
没了,就用 mongoose.Promise
吧。
¥getPromiseConstructor()
is gone, just use mongoose.Promise
.
从 Pre Hook 传递参数
¥Passing Parameters from Pre Hooks
在 mongoose 5.x 中,你无法使用 next()
将参数传递给链中的下一个预中间件。在 mongoose 4 中,前中间件中的 next('Test')
会以 '测试' 作为参数调用下一个中间件。Mongoose 5.x 已删除对此的支持。
¥You cannot pass parameters to the next pre middleware in the chain using next()
in mongoose 5.x. In mongoose 4, next('Test')
in pre middleware would call the
next middleware with 'Test' as a parameter. Mongoose 5.x has removed support for this.
required
数组验证器
¥required
validator for arrays
在 mongoose 5 中,required
验证器仅验证该值是否是数组。也就是说,它不会像 mongoose 4 中那样因空数组而失败。
¥In mongoose 5 the required
validator only verifies if the value is an
array. That is, it will not fail for empty arrays as it would in
mongoose 4.
调试输出默认为 stdout 而不是 stderr
¥debug output defaults to stdout instead of stderr
在 mongoose 5 中,默认调试功能使用 console.info()
来显示消息,而不是 console.error()
。
¥In mongoose 5 the default debug function uses console.info()
to display messages instead of console.error()
.
覆盖过滤器属性
¥Overwriting filter properties
在 Mongoose 4.x 中,用一个对象覆盖一个基础类型过滤器属性将会失败。例如,下面的代码将忽略 where()
并等效于 Sport.find({ name: 'baseball' })
¥In Mongoose 4.x, overwriting a filter property that's a primitive with one that is an object would silently fail. For example, the below code would ignore the where()
and be equivalent to Sport.find({ name: 'baseball' })
Sport.find({ name: 'baseball' }).where({ name: { $ne: 'softball' } });
在 Mongoose 5.x 中,上面的代码将正确地用 { $ne: 'softball' }
覆盖 'baseball'
¥In Mongoose 5.x, the above code will correctly overwrite 'baseball'
with { $ne: 'softball' }
bulkWrite()
结果
¥bulkWrite()
results
Mongoose 5.x 使用 MongoDB Node.js 驱动程序 的 3.x 版本。MongoDB 驱动程序 3.x 更改了 bulkWrite()
调用 结果的格式,因此不再有顶层 nInserted
、nModified
等属性。新的结果对象结构是 此处描述。
¥Mongoose 5.x uses version 3.x of the MongoDB Node.js driver. MongoDB driver 3.x changed the format of
the result of bulkWrite()
calls so there is no longer a top-level nInserted
, nModified
, etc. property. The new result object structure is described here.
const Model = mongoose.model('Test', new Schema({ name: String }));
const res = await Model.bulkWrite([{ insertOne: { document: { name: 'test' } } }]);
console.log(res);
在 Mongoose 4.x 中,上面的内容将打印:
¥In Mongoose 4.x, the above will print:
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 中,脚本将打印:
¥In Mongoose 5.x, the script will print:
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 验证
¥Strict SSL Validation
MongoDB Node.js 驱动程序默认使用严格的 SSL 验证 的最新版本,如果你使用 自签名证书,可能会导致错误。
¥The most recent versions of the MongoDB Node.js driver use strict SSL validation by default, which may lead to errors if you're using self-signed certificates.
如果这阻止你升级,你可以将 tlsInsecure
选项设置为 true
。
¥If this is blocking you from upgrading, you can set the tlsInsecure
option to true
.
mongoose.connect(uri, { tlsInsecure: false }); // Opt out of additional SSL validation