从 7.x 迁移到 8.x

从 Mongoose 7.x 迁移到 Mongoose 8.x 时,您应该注意一些重大更改。

如果您仍在使用 Mongoose 6.x 或更早版本,请阅读 Mongoose 6.x 到 7.x 迁移指南 并先升级到 Mongoose 7.x,然后再升级到 Mongoose 8。

我们还建议您在升级到 Mongoose 8 之前查看 MongoDB Node.js 驱动程序的 v6.0.0 版本说明

删除了 findOneAndUpdate()rawResult 选项

findOneAndUpdate()findOneAndReplace()findOneAndDelete()rawResult 选项已被 includeResultMetadata 选项替换。

const filter = { name: 'Will Riker' };
const update = { age: 29 };

const res = await Character.findOneAndUpdate(filter, update, {
  new: true,
  upsert: true,
  // Replace `rawResult: true` with `includeResultMetadata: true`
  includeResultMetadata: true
});

Mongoose 8 中的 includeResultMetadata 的行为与 rawResult 相同。

Document.prototype.deleteOne 现在返回一个查询

在 Mongoose 7 中,doc.deleteOne() 返回一个解析为 doc 的 Promise。在 Mongoose 8 中,doc.deleteOne() 返回一个查询,以便更容易地进行链式操作,并且与 doc.updateOne() 保持一致。

const numberOne = await Character.findOne({ name: 'Will Riker' });

// In Mongoose 7, q is a Promise that resolves to `numberOne`
// In Mongoose 8, q is a Query.
const q = numberOne.deleteOne();

// In Mongoose 7, `res === numberOne`
// In Mongoose 8, `res` is a `DeleteResult`.
const res = await q;

MongoDB Node 驱动程序 6

Mongoose 8 使用 MongoDB Node 驱动程序的 v6.x 版本。MongoDB Node 驱动程序 v6 中有一些值得注意的变化会影响 Mongoose。

  1. ObjectId 构造函数不再接受长度为 12 的字符串。在 Mongoose 7 中,new mongoose.Types.ObjectId('12charstring') 是完全有效的。在 Mongoose 8 中,new mongoose.Types.ObjectId('12charstring') 会抛出错误。

  2. 已删除不推荐使用的 SSL 选项

    • sslCA -> tlsCAFile
    • sslCRL -> tlsCRLFile
    • sslCert -> tlsCertificateKeyFile
    • sslKey -> tlsCertificateKeyFile
    • sslPass -> tlsCertificateKeyFilePassword
    • sslValidate -> tlsAllowInvalidCertificates
    • tlsCertificateFile -> tlsCertificateKeyFile

删除了 findOneAndRemove()

在 Mongoose 7 中,findOneAndRemove()findOneAndDelete() 的别名,Mongoose 为向后兼容性而支持它。Mongoose 8 不再支持 findOneAndRemove()。请改用 findOneAndDelete()

删除了 count()

Model.count()Query.prototype.count() 已在 Mongoose 8 中删除。请改用 Model.countDocuments()Query.prototype.countDocuments()

删除了 id 设置器

在 Mongoose 7.4 中,Mongoose 引入了 id 设置器,它使 doc.id = '0'.repeat(24) 等同于 doc._id = '0'.repeat(24)。在 Mongoose 8 中,该设置器现已删除。

null 对非必需字符串枚举有效

在 Mongoose 8 之前,将带有 enum 的字符串路径设置为 null 会导致验证错误,即使该路径不是 required。在 Mongoose 8 中,即使设置了 enum,如果未设置 required,将字符串路径设置为 null 也是有效的。

const schema = new Schema({
  status: {
    type: String,
    enum: ['on', 'off']
  }
});
const Test = mongoose.model('Test', schema);

// Works fine in Mongoose 8
// Throws a `ValidationError` in Mongoose 7
await Test.create({ status: null });

save() 更新现有文档时应用最小化

在 Mongoose 7 中,Mongoose 仅在保存新文档时应用最小化,而不是在更新现有文档时。

const schema = new Schema({
  nested: {
    field1: Number
  }
});
const Test = mongoose.model('Test', schema);

// Both Mongoose 7 and Mongoose 8 strip out empty objects when saving
// a new document in MongoDB by default
const { _id } = await Test.create({ nested: {} });
let rawDoc = await Test.findById(_id).lean();
rawDoc.nested; // undefined

// Mongoose 8 will also strip out empty objects when saving an
// existing document in MongoDB
const doc = await Test.findById(_id);
doc.nested = {};
doc.markModified('nested');
await doc.save();

let rawDoc = await Test.findById(_id).lean();
rawDoc.nested; // undefined in Mongoose 8, {} in Mongoose 7

在鉴别器路径之前应用基本模式路径

这意味着在 Mongoose 8 中,鉴别器路径上的 getter 和 setter 在基本路径上的 getter 和 setter 之后运行。在 Mongoose 7 中,鉴别器路径上的 getter 和 setter 在基本路径上的 getter 和 setter 之前运行。


const schema = new Schema({
  name: {
    type: String,
    get(v) {
      console.log('Base schema getter');
      return v;
    }
  }
});

const Test = mongoose.model('Test', schema);
const D = Test.discriminator('D', new Schema({
  otherProp: {
    type: String,
    get(v) {
      console.log('Discriminator schema getter');
      return v;
    }
  }
}));

const doc = new D({ name: 'test', otherProp: 'test' });
// In Mongoose 8, prints "Base schema getter" followed by "Discriminator schema getter"
// In Mongoose 7, prints "Discriminator schema getter" followed by "Base schema getter"
console.log(doc.toObject({ getters: true }));

删除了 findOneAndUpdate()overwrite 选项

Mongoose 7 及更早版本支持 findOneAndUpdate()updateOne()update()overwrite 选项。在 Mongoose 7 之前,overwrite 会跳过将 update 参数包装在 $set 中,这意味着 findOneAndUpdate()update() 会覆盖匹配的文档。在 Mongoose 7 中,设置 overwrite 会将 findOneAndUpdate() 转换为 findOneAndReplace(),将 updateOne() 转换为 replaceOne(),以保持向后兼容性。

在 Mongoose 8 中,不再支持 overwrite 选项。如果您想覆盖整个文档,请使用 findOneAndReplace()replaceOne()

更改了 findOneAndUpdate() 使用 orFail() 和 upsert 的行为

在 Mongoose 7 中,findOneAndUpdate(filter, update, { upsert: true }).orFail() 如果插入新文档,则会抛出 DocumentNotFoundError。换句话说,即使插入了新文档,findOneAndUpdate().orFail() 也始终会抛出错误,因为没有找到文档。

在 Mongoose 8 中,findOneAndUpdate(filter, update, { upsert: true }).orFail() 始终成功。findOneAndUpdate().orFail() 现在在没有返回文档时抛出 DocumentNotFoundError,而不是在没有找到文档时。

Create 等待所有保存完成,然后再抛出任何错误

在 Mongoose 7 中,create() 如果任何 save() 抛出错误,则会立即抛出错误(默认情况下)。Mongoose 8 而是等待所有 save() 调用完成,然后再抛出发生的第一个错误。因此,create() 在 Mongoose 7 和 Mongoose 8 中都会抛出相同的错误,只是 Mongoose 8 可能需要更长的时间才能抛出错误。

const schema = new Schema({
  name: {
    type: String,
    enum: ['Badger', 'Mushroom']
  }
});
schema.pre('save', async function() {
  await new Promise(resolve => setTimeout(resolve, 1000));
});
const Test = mongoose.model('Test', schema);

const err = await Test.create([
  { name: 'Badger' },
  { name: 'Mushroom' },
  { name: 'Cow' }
]).then(() => null, err => err);
err; // ValidationError

// In Mongoose 7, there would be 0 documents, because `Test.create()`
// would throw before 'Badger' and 'Mushroom' are inserted
// In Mongoose 8, there will be 2 documents. `Test.create()` waits until
// 'Badger' and 'Mushroom' are inserted before throwing.
await Test.countDocuments();

Model.validate() 返回对象的副本

在 Mongoose 7 中,Model.validate() 有可能修改传入的对象。Mongoose 8 而是先复制传入的对象。

const schema = new Schema({ answer: Number });
const Test = mongoose.model('Test', schema);

const obj = { answer: '42' };
const res = Test.validate(obj);

typeof obj.answer; // 'string' in Mongoose 8, 'number' in Mongoose 7 
typeof res.answer; // 'number' in both Mongoose 7 and Mongoose 8

允许在 TypeScript 中为可选字段使用 null

在 Mongoose 8 中,TypeScript 中自动推断的模式类型允许为可选字段使用 null。在 Mongoose 7 中,可选字段仅允许 undefined,而不是 null

const schema = new Schema({ name: String });
const TestModel = model('Test', schema);

const doc = new TestModel();

// In Mongoose 8, this type is `string | null | undefined`.
// In Mongoose 7, this type is `string | undefined`
doc.name;

模型构造函数属性在 TypeScript 中都是可选的

在 Mongoose 8 中,默认情况下,模型构造函数不需要任何属性。

import {Schema, model, Model} from 'mongoose';

interface IDocument {
  name: string;
  createdAt: Date;
  updatedAt: Date;
}

const documentSchema = new Schema<IDocument>(
  { name: { type: String, required: true } },
  { timestamps: true }
);

const TestModel = model<IDocument>('Document', documentSchema);

// Would throw a compile error in Mongoose 7, compiles in Mongoose 8
const newDoc = new TestModel({
  name: 'Foo'
});

// Explicitly pass generic param to constructor to specify the expected
// type of the model constructor param. The following will cause TS
// to complain about missing `createdAt` and `updatedAt` in Mongoose 8.
const newDoc2 = new TestModel<IDocument>({
  name: 'Foo'
});

从模式推断 distinct() 返回类型

interface User {
  name: string;
  email: string;
  avatar?: string;
}
const schema = new Schema<User>({
  name: { type: String, required: true },
  email: { type: String, required: true },
  avatar: String
});

// Works in Mongoose 8. Compile error in Mongoose 7.
const names: string[] = await MyModel.distinct('name');