從 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 仍然支援 MongoDB 伺服器版本回溯到 3.0.0。

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 驅動程式 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
  }
});

陣列是 Proxy

Mongoose 陣列現在是 ES6 Proxy。您不再需要在直接設定陣列索引後使用 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 中的混合類型。這消除了對 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 將會預設為 false,針對 autoCreateautoIndex,因為當連線到次要節點時,createCollection()createIndex() 都會失敗。

不再有 context: 'query'

已移除查詢的 context 選項。現在 Mongoose 始終使用 context = 'query'

具有填充路徑的自訂驗證器

Mongoose 6 始終會使用已解除填充的路徑(也就是使用 ID 而非文件本身)來呼叫驗證器。在 Mongoose 5 中,如果路徑已填充,Mongoose 會使用已填充的文件來呼叫驗證器。請參閱 #8042

具有複本集的「已斷開連線」事件

當連線到複本集時,當與主要節點的連線遺失時,連線現在會發出「已斷開連線」的訊號。在 Mongoose 5 中,連線僅在遺失與複本集所有成員的連線時才會發出「已斷開連線」的訊號。

但是,Mongoose 6 在連線斷開時不會緩衝命令。因此,即使 Mongoose 連線處於斷開連線狀態,您仍然可以使用 readPreference = 'secondary' 成功執行查詢等命令。

移除 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 現在會將 valueOf() 函數新增至 ObjectIds。這表示您現在可以使用 == 將 ObjectId 與字串進行比較。

const a = ObjectId('6143b55ac9a762738b15d4f0');

a == '6143b55ac9a762738b15d4f0'; // true

不可變的 createdAt

如果您設定 timestamps: true,Mongoose 現在會將 createdAt 屬性設為 immutable(不可變的)。請參閱 gh-10139

移除驗證器 isAsync

isAsync 不再是 validate 的選項。請改用 async function(非同步函式)。

移除 safe

safe 不再是 schemas(結構描述)、queries(查詢)或 save() 的選項。請改用 writeConcern

SchemaType set 參數

Mongoose 現在呼叫 setter 函式時,會將 priorValue 作為第二個參數,而不是 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() 預設會使用最上層 schema 的 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 }));

或者定義 inline(內聯)的 child schema(子結構描述)(僅限 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:password@development.xyz.mongodb.net/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 會推斷 virtual getters(虛擬 getter)和 setters(虛擬 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 Node 驅動程式將永遠嘗試重試任何操作,直到達到 serverSelectionTimeoutMS,即使 MongoDB 停機很長一段時間也是如此。因此,它永遠不會用完重試次數或嘗試重新連線到 MongoDB。