结构

¥Schemas

如果你还没有这样做,请花一点时间阅读 快速开始 以了解 Mongoose 的工作原理。如果你要从 7.x 迁移到 8.x,请花点时间阅读 迁移指南

¥If you haven't yet done so, please take a minute to read the quickstart to get an idea of how Mongoose works. If you are migrating from 7.x to 8.x please take a moment to read the migration guide.

定义你的架构

Mongoose 中的一切都始于结构。每个结构都映射到一个 MongoDB 集合,并定义该集合中文档的形状。

¥Everything in Mongoose starts with a Schema. Each schema maps to a MongoDB collection and defines the shape of the documents within that collection.

import mongoose from 'mongoose';
const { Schema } = mongoose;

const blogSchema = new Schema({
  title: String, // String is shorthand for {type: String}
  author: String,
  body: String,
  comments: [{ body: String, date: Date }],
  date: { type: Date, default: Date.now },
  hidden: Boolean,
  meta: {
    votes: Number,
    favs: Number
  }
});

如果你想稍后添加其他键,请使用 Schema#add 方法。

¥If you want to add additional keys later, use the Schema#add method.

代码 blogSchema 中的每个键都在文档中定义一个属性,该属性将被转换为其关联的 SchemaType。例如,我们定义了属性 title 和属性 date,前者将转换为 字符串 SchemaType,后者将转换为 Date SchemaType。

¥Each key in our code blogSchema defines a property in our documents which will be cast to its associated SchemaType. For example, we've defined a property title which will be cast to the String SchemaType and property date which will be cast to a Date SchemaType.

请注意,如果属性仅需要类型,则可以使用简写符号来指定(将上面的 title 属性与 date 属性进行对比)。

¥Notice above that if a property only requires a type, it can be specified using a shorthand notation (contrast the title property above with the date property).

还可以为键分配包含进一步键/类型定义的嵌套对象,如上面的 meta 属性。只要键的值是不具有 type 属性的 POJO,就会发生这种情况。

¥Keys may also be assigned nested objects containing further key/type definitions like the meta property above. This will happen whenever a key's value is a POJO that doesn't have a type property.

在这些情况下,Mongoose 只为树中的叶子创建实际的结构路径。(如上面的 meta.votesmeta.favs),并且分支没有实际路径。这样做的一个副作用是上面的 meta 不能有自己的验证。如果需要在树上进行验证,则需要在树上创建路径 - 有关如何执行此操作的更多信息,请参阅 子文档 部分。另请阅读 SchemaTypes 指南的 混合 小节以了解一些问题。

¥In these cases, Mongoose only creates actual schema paths for leaves in the tree. (like meta.votes and meta.favs above), and the branches do not have actual paths. A side-effect of this is that meta above cannot have its own validation. If validation is needed up the tree, a path needs to be created up the tree - see the Subdocuments section for more information on how to do this. Also read the Mixed subsection of the SchemaTypes guide for some gotchas.

允许的 SchemaType 是:

¥The permitted SchemaTypes are:

了解有关 此处的结构类型 的更多信息。

¥Read more about SchemaTypes here.

结构不仅定义文档的结构和属性转换,还定义文档 实例方法静态模型方法复合索引 和称为 中间件 的文档生命周期钩子。

¥Schemas not only define the structure of your document and casting of properties, they also define document instance methods, static Model methods, compound indexes, and document lifecycle hooks called middleware.

创建模型

要使用我们的结构定义,我们需要将 blogSchema 转换为我们可以使用的 模型。为此,我们将其传递到 mongoose.model(modelName, schema)

¥To use our schema definition, we need to convert our blogSchema into a Model we can work with. To do so, we pass it into mongoose.model(modelName, schema):

const Blog = mongoose.model('Blog', blogSchema);
// ready to go!

标识

默认情况下,Mongoose 会向你的结构添加 _id 属性。

¥By default, Mongoose adds an _id property to your schemas.

const schema = new Schema();

schema.path('_id'); // ObjectId { ... }

当你使用自动添加的 _id 属性创建新文档时,Mongoose 会为你的文档创建一个新的 ObjectId 类型的 _id

¥When you create a new document with the automatically added _id property, Mongoose creates a new _id of type ObjectId to your document.

const Model = mongoose.model('Test', schema);

const doc = new Model();
doc._id instanceof mongoose.Types.ObjectId; // true

你还可以用你自己的 _id 覆盖 Mongoose 的默认 _id。请小心:Mongoose 将拒绝保存没有 _id 的顶层文档,因此如果你定义自己的 _id 路径,则你有责任设置 _id

¥You can also overwrite Mongoose's default _id with your own _id. Just be careful: Mongoose will refuse to save a top-level document that doesn't have an _id, so you're responsible for setting _id if you define your own _id path.

const schema = new Schema({
  _id: Number // <-- overwrite Mongoose's default `_id`
});
const Model = mongoose.model('Test', schema);

const doc = new Model();
await doc.save(); // Throws "document must have an _id before saving"

doc._id = 1;
await doc.save(); // works

Mongoose 还向子文档添加了 _id 属性。你可以按如下方式禁用子文档上的 _id 属性。Mongoose 确实允许保存没有 _id 属性的子文档。

¥Mongoose also adds an _id property to subdocuments. You can disable the _id property on your subdocuments as follows. Mongoose does allow saving subdocuments without an _id property.

const nestedSchema = new Schema(
  { name: String },
  { _id: false } // <-- disable `_id`
);
const schema = new Schema({
  subdoc: nestedSchema,
  docArray: [nestedSchema]
});
const Test = mongoose.model('Test', schema);

// Neither `subdoc` nor `docArray.0` will have an `_id`
await Test.create({
  subdoc: { name: 'test 1' },
  docArray: [{ name: 'test 2' }]
});

或者,你可以使用以下语法禁用 _id

¥Alternatively, you can disable _id using the following syntax:

const nestedSchema = new Schema({
  _id: false, // <-- disable _id
  name: String
});

实例方法

Models 的实例是 documents。文档有很多自己的 内置实例方法。我们还可以定义自己的自定义文档实例方法。

¥Instances of Models are documents. Documents have many of their own built-in instance methods. We may also define our own custom document instance methods.

// define a schema
const animalSchema = new Schema({ name: String, type: String },
  {
  // Assign a function to the "methods" object of our animalSchema through schema options.
  // By following this approach, there is no need to create a separate TS type to define the type of the instance functions.
    methods: {
      findSimilarTypes(cb) {
        return mongoose.model('Animal').find({ type: this.type }, cb);
      }
    }
  });

// Or, assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function(cb) {
  return mongoose.model('Animal').find({ type: this.type }, cb);
};

现在我们所有的 animal 实例都有一个可用的 findSimilarTypes 方法。

¥Now all of our animal instances have a findSimilarTypes method available to them.

const Animal = mongoose.model('Animal', animalSchema);
const dog = new Animal({ type: 'dog' });

dog.findSimilarTypes((err, dogs) => {
  console.log(dogs); // woof
});
  • 覆盖默认的 Mongoose 文档方法可能会导致不可预测的结果。详细信息请参见 this

    ¥Overwriting a default mongoose document method may lead to unpredictable results. See this for more details.

  • 上面的例子直接使用 Schema.methods 对象来保存实例方法。你还可以按照 此处 的描述使用 Schema.method() 辅助程序。

    ¥The example above uses the Schema.methods object directly to save an instance method. You can also use the Schema.method() helper as described here.

  • 不要使用 ES6 箭头函数声明方法 (=>)。箭头函数 明确阻止绑定 this,因此你的方法将无法访问该文档,并且上面的示例将不起作用。

    ¥Do not declare methods using ES6 arrow functions (=>). Arrow functions explicitly prevent binding this, so your method will not have access to the document and the above examples will not work.

静态

你还可以向模型添加静态函数。添加静态的等效方法有以下三种:

¥You can also add static functions to your model. There are three equivalent ways to add a static:

  • 将函数属性添加到结构构造函数的第二个参数 (statics)

    ¥Add a function property to the second argument of the schema-constructor (statics)

  • schema.statics 添加函数属性

    ¥Add a function property to schema.statics

  • 调用 Schema#static() 功能

    ¥Call the Schema#static() function


// define a schema
const animalSchema = new Schema({ name: String, type: String },
  {
  // Assign a function to the "statics" object of our animalSchema through schema options.
  // By following this approach, there is no need to create a separate TS type to define the type of the statics functions.
    statics: {
      findByName(name) {
        return this.find({ name: new RegExp(name, 'i') });
      }
    }
  });

// Or, Assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function(name) {
  return this.find({ name: new RegExp(name, 'i') });
};
// Or, equivalently, you can call `animalSchema.static()`.
animalSchema.static('findByBreed', function(breed) { return this.find({ breed }); });

const Animal = mongoose.model('Animal', animalSchema);
let animals = await Animal.findByName('fido');
animals = animals.concat(await Animal.findByBreed('Poodle'));

不要使用 ES6 箭头函数声明静态 (=>)。箭头函数为 明确阻止绑定 this,因此由于 this 的值,上述示例将不起作用。

¥Do not declare statics using ES6 arrow functions (=>). Arrow functions explicitly prevent binding this, so the above examples will not work because of the value of this.

查询助手

你还可以添加查询辅助函数,它们类似于实例方法,但用于 Mongoose 查询。查询辅助程序方法让你可以扩展 mongoose 的 可链式查询构建器 API

¥You can also add query helper functions, which are like instance methods but for mongoose queries. Query helper methods let you extend mongoose's chainable query builder API.


// define a schema
const animalSchema = new Schema({ name: String, type: String },
  {
  // Assign a function to the "query" object of our animalSchema through schema options.
  // By following this approach, there is no need to create a separate TS type to define the type of the query functions.
    query: {
      byName(name) {
        return this.where({ name: new RegExp(name, 'i') });
      }
    }
  });

// Or, Assign a function to the "query" object of our animalSchema
animalSchema.query.byName = function(name) {
  return this.where({ name: new RegExp(name, 'i') });
};

const Animal = mongoose.model('Animal', animalSchema);

Animal.find().byName('fido').exec((err, animals) => {
  console.log(animals);
});

Animal.findOne().byName('fido').exec((err, animal) => {
  console.log(animal);
});

索引

MongoDB 支持 二级指标。对于 mongoose,我们在 Schema at the path levelschema 级别中定义这些索引。创建 复合索引 时,需要在结构级别定义索引。

¥MongoDB supports secondary indexes. With mongoose, we define these indexes within our Schema at the path level or the schema level. Defining indexes at the schema level is necessary when creating compound indexes.

const animalSchema = new Schema({
  name: String,
  type: String,
  tags: { type: [String], index: true } // path level
});

animalSchema.index({ name: 1, type: -1 }); // schema level

其他索引选项请参见 SchemaType#index()

¥See SchemaType#index() for other index options.

当你的应用启动时,Mongoose 会自动为结构中的每个定义的索引调用 createIndex。Mongoose 将为每个索引依次调用 createIndex,并在所有 createIndex 调用成功或出现错误时在模型上发出 'index' 事件。虽然有利于开发,但建议在生产中禁用此行为,因为索引创建可能会导致 显着的性能影响。通过将结构的 autoIndex 选项设置为 false 来禁用该行为,或者通过将选项 autoIndex 设置为 false 在连接上全局禁用该行为。

¥When your application starts up, Mongoose automatically calls createIndex for each defined index in your schema. Mongoose will call createIndex for each index sequentially, and emit an 'index' event on the model when all the createIndex calls succeeded or when there was an error. While nice for development, it is recommended this behavior be disabled in production since index creation can cause a significant performance impact. Disable the behavior by setting the autoIndex option of your schema to false, or globally on the connection by setting the option autoIndex to false.

mongoose.connect('mongodb://user:pass@127.0.0.1:port/database', { autoIndex: false });
// or
mongoose.createConnection('mongodb://user:pass@127.0.0.1:port/database', { autoIndex: false });
// or
mongoose.set('autoIndex', false);
// or
animalSchema.set('autoIndex', false);
// or
new Schema({ /* ... */ }, { autoIndex: false });

当索引构建完成或发生错误时,Mongoose 将在模型上发出 index 事件。

¥Mongoose will emit an index event on the model when indexes are done building or an error occurred.

// Will cause an error because mongodb has an _id index by default that
// is not sparse
animalSchema.index({ _id: 1 }, { sparse: true });
const Animal = mongoose.model('Animal', animalSchema);

Animal.on('index', error => {
  // "_id index cannot be sparse"
  console.log(error.message);
});

另请参见 Model#ensureIndexes 方法。

¥See also the Model#ensureIndexes method.

虚拟

虚拟 是你可以获取和设置但不会持久保存到 MongoDB 的文档属性。getter 对于格式化或组合字段很有用,而 setter 对于将单个值分解为多个值进行存储非常有用。

¥Virtuals are document properties that you can get and set but that do not get persisted to MongoDB. The getters are useful for formatting or combining fields, while setters are useful for de-composing a single value into multiple values for storage.

// define a schema
const personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
});

// compile our model
const Person = mongoose.model('Person', personSchema);

// create a document
const axl = new Person({
  name: { first: 'Axl', last: 'Rose' }
});

假设你想打印出该人的全名。你可以自己做:

¥Suppose you want to print out the person's full name. You could do it yourself:

console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose

但每次都输入名字和姓氏 concatenating 会很麻烦。如果你想对名称(例如 删除变音符号)进行一些额外处理怎么办?虚拟属性获取者 允许你定义不会持久保存到 MongoDB 的 fullName 属性。

¥But concatenating the first and last name every time can get cumbersome. And what if you want to do some extra processing on the name, like removing diacritics? A virtual property getter lets you define a fullName property that won't get persisted to MongoDB.

// That can be done either by adding it to schema options:
const personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
}, {
  virtuals: {
    fullName: {
      get() {
        return this.name.first + ' ' + this.name.last;
      }
    }
  }
});

// Or by using the virtual method as following:
personSchema.virtual('fullName').get(function() {
  return this.name.first + ' ' + this.name.last;
});

现在,每次你访问 fullName 属性时,Mongoose 都会调用你的 getter 函数:

¥Now, mongoose will call your getter function every time you access the fullName property:

console.log(axl.fullName); // Axl Rose

如果你使用 toJSON()toObject() Mongoose 默认情况下将不包含虚拟机。将 { virtuals: true } 传递到 toJSON()toObject() 以包含虚拟。

¥If you use toJSON() or toObject() Mongoose will not include virtuals by default. Pass { virtuals: true } to toJSON() or toObject() to include virtuals.

// Convert `doc` to a POJO, with virtuals attached
doc.toObject({ virtuals: true });

// Equivalent:
doc.toJSON({ virtuals: true });

上述 toJSON() 的警告还包括在 Mongoose 文档上调用 JSON.stringify() 的输出,因为 JSON.stringify() calls toJSON()。要在 JSON.stringify() 输出中包含虚拟值,你可以在调用 JSON.stringify() 之前在文档上调用 toObject({ virtuals: true }),或者在结构上设置 toJSON: { virtuals: true } 选项。

¥The above caveat for toJSON() also includes the output of calling JSON.stringify() on a Mongoose document, because JSON.stringify() calls toJSON(). To include virtuals in JSON.stringify() output, you can either call toObject({ virtuals: true }) on the document before calling JSON.stringify(), or set the toJSON: { virtuals: true } option on your schema.

// Explicitly add virtuals to `JSON.stringify()` output
JSON.stringify(doc.toObject({ virtuals: true }));

// Or, to automatically attach virtuals to `JSON.stringify()` output:
const personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
}, {
  toJSON: { virtuals: true } // <-- include virtuals in `JSON.stringify()`
});

你还可以向虚拟设备添加自定义设置器,以便你通过 fullName 虚拟设备设置名字和姓氏。

¥You can also add a custom setter to your virtual that will let you set both first name and last name via the fullName virtual.

// Again that can be done either by adding it to schema options:
const personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
}, {
  virtuals: {
    fullName: {
      get() {
        return this.name.first + ' ' + this.name.last;
      },
      set(v) {
        this.name.first = v.substr(0, v.indexOf(' '));
        this.name.last = v.substr(v.indexOf(' ') + 1);
      }
    }
  }
});

// Or by using the virtual method as following:
personSchema.virtual('fullName').
  get(function() {
    return this.name.first + ' ' + this.name.last;
  }).
  set(function(v) {
    this.name.first = v.substr(0, v.indexOf(' '));
    this.name.last = v.substr(v.indexOf(' ') + 1);
  });

axl.fullName = 'William Rose'; // Now `axl.name.first` is "William"

虚拟属性设置器在其他验证之前应用。因此,即使需要 firstlast 名称字段,上面的示例仍然有效。

¥Virtual property setters are applied before other validation. So the example above would still work even if the first and last name fields were required.

只有非虚拟属性才能作为查询和字段选择的一部分。由于虚拟数据不存储在 MongoDB 中,因此你无法使用它们进行查询。

¥Only non-virtual properties work as part of queries and for field selection. Since virtuals are not stored in MongoDB, you can't query with them.

你可以 在这里了解有关虚拟的更多信息

¥You can learn more about virtuals here.

别名

别名是一种特殊类型的虚拟,其中 getter 和 setter 无缝地获取和设置另一个属性。这对于节省网络带宽很方便,因此你可以将数据库中存储的短属性名称转换为较长的名称,以提高代码可读性。

¥Aliases are a particular type of virtual where the getter and setter seamlessly get and set another property. This is handy for saving network bandwidth, so you can convert a short property name stored in the database into a longer name for code readability.

const personSchema = new Schema({
  n: {
    type: String,
    // Now accessing `name` will get you the value of `n`, and setting `name` will set the value of `n`
    alias: 'name'
  }
});

// Setting `name` will propagate to `n`
const person = new Person({ name: 'Val' });
console.log(person); // { n: 'Val' }
console.log(person.toObject({ virtuals: true })); // { n: 'Val', name: 'Val' }
console.log(person.name); // "Val"

person.name = 'Not Val';
console.log(person); // { n: 'Not Val' }

你还可以在嵌套路径上声明别名。使用嵌套结构和 subdocuments 更容易,但你也可以内联声明嵌套路径别名,只要使用完整嵌套路径 nested.myProp 作为别名即可。

¥You can also declare aliases on nested paths. It is easier to use nested schemas and subdocuments, but you can also declare nested path aliases inline as long as you use the full nested path nested.myProp as the alias.

const childSchema = new Schema({
  n: {
    type: String,
    alias: 'name'
  }
}, { _id: false });

const parentSchema = new Schema({
  // If in a child schema, alias doesn't need to include the full nested path
  c: childSchema,
  name: {
    f: {
      type: String,
      // Alias needs to include the full nested path if declared inline
      alias: 'name.first'
    }
  }
});

选项

结构有一些可配置选项,可以传递给构造函数或 set 方法:

¥Schemas have a few configurable options which can be passed to the constructor or to the set method:

new Schema({ /* ... */ }, options);

// or

const schema = new Schema({ /* ... */ });
schema.set(option, value);

有效选项:

¥Valid options:

option: autoIndex

默认情况下,成功连接到 MongoDB 后,Mongoose 的 init() 功能 通过调用 Model.createIndexes() 创建模型结构中定义的所有索引。自动创建索引非常适合开发和测试环境。但索引构建也会给生产数据库带来巨大的负载。如果你想在生产中仔细管理索引,可以将 autoIndex 设置为 false。

¥By default, Mongoose's init() function creates all the indexes defined in your model's schema by calling Model.createIndexes() after you successfully connect to MongoDB. Creating indexes automatically is great for development and test environments. But index builds can also create significant load on your production database. If you want to manage indexes carefully in production, you can set autoIndex to false.

const schema = new Schema({ /* ... */ }, { autoIndex: false });
const Clock = mongoose.model('Clock', schema);
Clock.ensureIndexes(callback);

默认情况下,autoIndex 选项设置为 true。你可以通过设置 mongoose.set('autoIndex', false); 更改此默认值

¥The autoIndex option is set to true by default. You can change this default by setting mongoose.set('autoIndex', false);

option: autoCreate

Mongoose 在构建索引之前,默认会调用 Model.createCollection() 在 MongoDB 中创建底层集合。调用 createCollection() 会根据 排序规则选项 设置 集合的默认排序规则,如果设置了 capped 结构选项,则将该集合建立为上限集合。

¥Before Mongoose builds indexes, it calls Model.createCollection() to create the underlying collection in MongoDB by default. Calling createCollection() sets the collection's default collation based on the collation option and establishes the collection as a capped collection if you set the capped schema option.

你可以通过使用 mongoose.set('autoCreate', false)autoCreate 设置为 false 来禁用此行为。与 autoIndex 一样,autoCreate 对于开发和测试环境很有帮助,但你可能希望在生产环境中禁用它,以避免不必要的数据库调用。

¥You can disable this behavior by setting autoCreate to false using mongoose.set('autoCreate', false). Like autoIndex, autoCreate is helpful for development and test environments, but you may want to disable it for production to avoid unnecessary database calls.

不幸的是,createCollection() 无法更改现有集合。例如,如果你将 capped: { size: 1024 } 添加到架构中并且现有集合没有上限,则 createCollection() 将不会覆盖现有集合。这是因为 MongoDB 服务器不允许在不先删除集合的情况下更改集合的选项。

¥Unfortunately, createCollection() cannot change an existing collection. For example, if you add capped: { size: 1024 } to your schema and the existing collection is not capped, createCollection() will not overwrite the existing collection. That is because the MongoDB server does not allow changing a collection's options without dropping the collection first.

const schema = new Schema({ name: String }, {
  autoCreate: false,
  capped: { size: 1024 }
});
const Test = mongoose.model('Test', schema);

// No-op if collection already exists, even if the collection is not capped.
// This means that `capped` won't be applied if the 'tests' collection already exists.
await Test.createCollection();

option: bufferCommands

默认情况下,当连接断开时,Mongoose 会缓冲命令,直到驱动程序设法重新连接。要禁用缓冲,请将 bufferCommands 设置为 false。

¥By default, mongoose buffers commands when the connection goes down until the driver manages to reconnect. To disable buffering, set bufferCommands to false.

const schema = new Schema({ /* ... */ }, { bufferCommands: false });

结构 bufferCommands 选项会覆盖全局 bufferCommands 选项。

¥The schema bufferCommands option overrides the global bufferCommands option.

mongoose.set('bufferCommands', true);
// Schema option below overrides the above, if the schema option is set.
const schema = new Schema({ /* ... */ }, { bufferCommands: false });

option: bufferTimeoutMS

如果 bufferCommands 打开,此选项设置 Mongoose 缓冲在抛出错误之前等待的最长时间。如果未指定,Mongoose 将使用 10000(10 秒)。

¥If bufferCommands is on, this option sets the maximum amount of time Mongoose buffering will wait before throwing an error. If not specified, Mongoose will use 10000 (10 seconds).

// If an operation is buffered for more than 1 second, throw an error.
const schema = new Schema({ /* ... */ }, { bufferTimeoutMS: 1000 });

option: capped

Mongoose 支持 MongoDB capped 集合。要将底层 MongoDB 集合指定为 capped,请将 capped 选项设置为 bytes 中集合的最大大小。

¥Mongoose supports MongoDBs capped collections. To specify the underlying MongoDB collection be capped, set the capped option to the maximum size of the collection in bytes.

new Schema({ /* ... */ }, { capped: 1024 });

如果你想传递 max 等其他选项,也可以将 capped 选项设置为对象。在这种情况下,你必须显式传递 size 选项,这是必需的。

¥The capped option may also be set to an object if you want to pass additional options like max. In this case you must explicitly pass the size option, which is required.

new Schema({ /* ... */ }, { capped: { size: 1024, max: 1000, autoIndexId: true } });

option: collection

默认情况下,Mongoose 通过将模型名称传递给 utils.toCollectionName 方法来生成集合名称。此方法使名称复数。如果你需要为集合使用不同的名称,请设置此选项。

¥Mongoose by default produces a collection name by passing the model name to the utils.toCollectionName method. This method pluralizes the name. Set this option if you need a different name for your collection.

const dataSchema = new Schema({ /* ... */ }, { collection: 'data' });

option: discriminatorKey

当你定义 discriminator 时,Mongoose 会向你的结构添加一个路径,该路径存储文档是哪个鉴别器的实例。默认情况下,Mongoose 添加 __t 路径,但你可以设置 discriminatorKey 来覆盖此默认路径。

¥When you define a discriminator, Mongoose adds a path to your schema that stores which discriminator a document is an instance of. By default, Mongoose adds an __t path, but you can set discriminatorKey to overwrite this default.

const baseSchema = new Schema({}, { discriminatorKey: 'type' });
const BaseModel = mongoose.model('Test', baseSchema);

const personSchema = new Schema({ name: String });
const PersonModel = BaseModel.discriminator('Person', personSchema);

const doc = new PersonModel({ name: 'James T. Kirk' });
// Without `discriminatorKey`, Mongoose would store the discriminator
// key in `__t` instead of `type`
doc.type; // 'Person'

option: excludeIndexes

excludeIndexestrue 时,Mongoose 将不会从给定的子文档结构创建索引。此选项仅在子文档路径或文档数组路径中使用结构时才有效,如果在模型的顶层结构上设置,Mongoose 会忽略此选项。默认为 false

¥When excludeIndexes is true, Mongoose will not create indexes from the given subdocument schema. This option only works when the schema is used in a subdocument path or document array path, Mongoose ignores this option if set on the top-level schema for a model. Defaults to false.

const childSchema1 = Schema({
  name: { type: String, index: true }
});

const childSchema2 = Schema({
  name: { type: String, index: true }
}, { excludeIndexes: true });

// Mongoose will create an index on `child1.name`, but **not** `child2.name`, because `excludeIndexes`
// is true on `childSchema2`
const User = new Schema({
  name: { type: String, index: true },
  child1: childSchema1,
  child2: childSchema2
});

option: id

默认情况下,Mongoose 为每个结构分配一个 id 虚拟 getter,它返回文档的 _id 字段转换为字符串,或者在 ObjectIds 的情况下,返回其十六进制字符串。如果你不希望将 id getter 添加到你的结构中,你可以通过在结构构建时传递此选项来禁用它。

¥Mongoose assigns each of your schemas an id virtual getter by default which returns the document's _id field cast to a string, or in the case of ObjectIds, its hexString. If you don't want an id getter added to your schema, you may disable it by passing this option at schema construction time.

// default behavior
const schema = new Schema({ name: String });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p.id); // '50341373e894ad16347efe01'

// disabled id
const schema = new Schema({ name: String }, { id: false });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p.id); // undefined

option: _id

如果未将某个字段传递给 结构 构造函数,Mongoose 默认会为每个结构分配一个 _id 字段。分配的类型是 ObjectId,以与 MongoDB 的默认行为一致。如果你根本不想将 _id 添加到你的结构中,你可以使用此选项禁用它。

¥Mongoose assigns each of your schemas an _id field by default if one is not passed into the Schema constructor. The type assigned is an ObjectId to coincide with MongoDB's default behavior. If you don't want an _id added to your schema at all, you may disable it using this option.

你只能在子文档上使用此选项。Mongoose 无法在不知道 id 的情况下保存文档,因此如果你尝试保存没有 _id 的文档,将会收到错误消息。

¥You can only use this option on subdocuments. Mongoose can't save a document without knowing its id, so you will get an error if you try to save a document without an _id.

// default behavior
const schema = new Schema({ name: String });
const Page = mongoose.model('Page', schema);
const p = new Page({ name: 'mongodb.org' });
console.log(p); // { _id: '50341373e894ad16347efe01', name: 'mongodb.org' }

// disabled _id
const childSchema = new Schema({ name: String }, { _id: false });
const parentSchema = new Schema({ children: [childSchema] });

const Model = mongoose.model('Model', parentSchema);

Model.create({ children: [{ name: 'Luke' }] }, (error, doc) => {
  // doc.children[0]._id will be undefined
});

option: minimize

默认情况下,Mongoose 将通过删除空对象来建立 "minimize" 结构。

¥Mongoose will, by default, "minimize" schemas by removing empty objects.

const schema = new Schema({ name: String, inventory: {} });
const Character = mongoose.model('Character', schema);

// will store `inventory` field if it is not empty
const frodo = new Character({ name: 'Frodo', inventory: { ringOfPower: 1 } });
await frodo.save();
let doc = await Character.findOne({ name: 'Frodo' }).lean();
doc.inventory; // { ringOfPower: 1 }

// will not store `inventory` field if it is empty
const sam = new Character({ name: 'Sam', inventory: {} });
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // undefined

可以通过将 minimize 选项设置为 false 来覆盖此行为。然后它将存储空对象。

¥This behavior can be overridden by setting minimize option to false. It will then store empty objects.

const schema = new Schema({ name: String, inventory: {} }, { minimize: false });
const Character = mongoose.model('Character', schema);

// will store `inventory` if empty
const sam = new Character({ name: 'Sam', inventory: {} });
await sam.save();
doc = await Character.findOne({ name: 'Sam' }).lean();
doc.inventory; // {}

要检查对象是否为空,可以使用 $isEmpty() 辅助程序:

¥To check whether an object is empty, you can use the $isEmpty() helper:

const sam = new Character({ name: 'Sam', inventory: {} });
sam.$isEmpty('inventory'); // true

sam.inventory.barrowBlade = 1;
sam.$isEmpty('inventory'); // false

option: read

允许在结构级别设置 query#read 选项,为我们提供了一种将默认 ReadPreferences 应用于从模型派生的所有查询的方法。

¥Allows setting query#read options at the schema level, providing us a way to apply default ReadPreferences to all queries derived from a model.

const schema = new Schema({ /* ... */ }, { read: 'primary' });            // also aliased as 'p'
const schema = new Schema({ /* ... */ }, { read: 'primaryPreferred' });   // aliased as 'pp'
const schema = new Schema({ /* ... */ }, { read: 'secondary' });          // aliased as 's'
const schema = new Schema({ /* ... */ }, { read: 'secondaryPreferred' }); // aliased as 'sp'
const schema = new Schema({ /* ... */ }, { read: 'nearest' });            // aliased as 'n'

每个首选项的别名也是允许的,因此我们可以简单地传递 'sp',而不必键入 'secondaryPreferred' 并导致拼写错误。

¥The alias of each pref is also permitted so instead of having to type out 'secondaryPreferred' and getting the spelling wrong, we can simply pass 'sp'.

读取选项还允许我们指定标签集。这些告诉 driver 它应该尝试从副本集的哪些成员中读取。了解有关标签集 此处此处 的更多信息。

¥The read option also allows us to specify tag sets. These tell the driver from which members of the replica-set it should attempt to read. Read more about tag sets here and here.

注意:你还可以在连接时指定驱动程序读取首选项 strategy 选项:

¥NOTE: you may also specify the driver read preference strategy option when connecting:

// pings the replset members periodically to track network latency
const options = { replset: { strategy: 'ping' } };
mongoose.connect(uri, options);

const schema = new Schema({ /* ... */ }, { read: ['nearest', { disk: 'ssd' }] });
mongoose.model('JellyBean', schema);

option: writeConcern

允许在结构级别设置 写下关注

¥Allows setting write concern at the schema level.

const schema = new Schema({ name: String }, {
  writeConcern: {
    w: 'majority',
    j: true,
    wtimeout: 1000
  }
});

option: shardKey

当我们有 分片 MongoDB 架构 时,使用 shardKey 选项。每个分片集合都有一个分片键,该键必须存在于所有插入/更新操作中。我们只需将此结构选项设置为相同的分片键即可完成。

¥The shardKey option is used when we have a sharded MongoDB architecture. Each sharded collection is given a shard key which must be present in all insert/update operations. We just need to set this schema option to the same shard key and we’ll be all set.

new Schema({ /* ... */ }, { shardKey: { tag: 1, name: 1 } });

请注意,Mongoose 不会为你发送 shardcollection 命令。你必须自己配置你的分片。

¥Note that Mongoose does not send the shardcollection command for you. You must configure your shards yourself.

option: strict

strict 选项(默认启用)可确保传递给模型构造函数的、结构中未指定的值不会保存到数据库中。

¥The strict option, (enabled by default), ensures that values passed to our model constructor that were not specified in our schema do not get saved to the db.

const thingSchema = new Schema({ /* ... */ })
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // iAmNotInTheSchema is not saved to the db

// set to false..
const thingSchema = new Schema({ /* ... */ }, { strict: false });
const thing = new Thing({ iAmNotInTheSchema: true });
thing.save(); // iAmNotInTheSchema is now saved to the db!!

这也会影响使用 doc.set() 设置属性值。

¥This also affects the use of doc.set() to set a property value.

const thingSchema = new Schema({ /* ... */ });
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing;
thing.set('iAmNotInTheSchema', true);
thing.save(); // iAmNotInTheSchema is not saved to the db

可以通过传递第二个布尔参数在模型实例级别覆盖该值:

¥This value can be overridden at the model instance level by passing a second boolean argument:

const Thing = mongoose.model('Thing');
const thing = new Thing(doc, true);  // enables strict mode
const thing = new Thing(doc, false); // disables strict mode

strict 选项也可以设置为 "throw",这将导致产生错误而不是丢弃坏数据。

¥The strict option may also be set to "throw" which will cause errors to be produced instead of dropping the bad data.

注意:无论架构选项如何,架构中不存在的实例上设置的任何键/值都会被忽略。

¥NOTE: Any key/val set on the instance that does not exist in your schema is always ignored, regardless of schema option.

const thingSchema = new Schema({ /* ... */ });
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing;
thing.iAmNotInTheSchema = true;
thing.save(); // iAmNotInTheSchema is never saved to the db

option: strictQuery

Mongoose 支持单独的 strictQuery 选项以避免查询过滤器的严格结构。这是因为空查询过滤器会导致 Mongoose 返回模型中的所有文档,这可能会导致问题。

¥Mongoose supports a separate strictQuery option to avoid strict mode for query filters. This is because empty query filters cause Mongoose to return all documents in the model, which can cause issues.

const mySchema = new Schema({ field: Number }, { strict: true });
const MyModel = mongoose.model('Test', mySchema);
// Mongoose will filter out `notInSchema: 1` because `strict: true`, meaning this query will return
// _all_ documents in the 'tests' collection
MyModel.find({ notInSchema: 1 });

strict 选项确实适用于更新。strictQuery 选项仅用于查询过滤器。

¥The strict option does apply to updates. The strictQuery option is just for query filters.

// Mongoose will strip out `notInSchema` from the update if `strict` is
// not `false`
MyModel.updateMany({}, { $set: { notInSchema: 1 } });

Mongoose 有一个单独的 strictQuery 选项,可以将 filter 参数的严格结构切换为查询。

¥Mongoose has a separate strictQuery option to toggle strict mode for the filter parameter to queries.

const mySchema = new Schema({ field: Number }, {
  strict: true,
  strictQuery: false // Turn off strict mode for query filters
});
const MyModel = mongoose.model('Test', mySchema);
// Mongoose will not strip out `notInSchema: 1` because `strictQuery` is false
MyModel.find({ notInSchema: 1 });

一般来说,我们不建议将用户定义的对象作为查询过滤器传递:

¥In general, we do not recommend passing user-defined objects as query filters:

// Don't do this!
const docs = await MyModel.find(req.query);

// Do this instead:
const docs = await MyModel.find({ name: req.query.name, age: req.query.age }).setOptions({ sanitizeFilter: true });

在 Mongoose 7 中,strictQuery 默认为 false。但是,你可以全局覆盖此行为:

¥In Mongoose 7, strictQuery is false by default. However, you can override this behavior globally:

// Set `strictQuery` to `true` to omit unknown fields in queries.
mongoose.set('strictQuery', true);

option: toJSON

toObject 选项完全相同,但仅在调用文档的 toJSON 方法 时适用。

¥Exactly the same as the toObject option but only applies when the document's toJSON method is called.

const schema = new Schema({ name: String });
schema.path('name').get(function(v) {
  return v + ' is my name';
});
schema.set('toJSON', { getters: true, virtuals: false });
const M = mongoose.model('Person', schema);
const m = new M({ name: 'Max Headroom' });
console.log(m.toObject()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom' }
console.log(m.toJSON()); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }
// since we know toJSON is called whenever a js object is stringified:
console.log(JSON.stringify(m)); // { "_id": "504e0cd7dd992d9be2f20b6f", "name": "Max Headroom is my name" }

要查看所有可用的 toJSON/toObject 选项,请阅读 this

¥To see all available toJSON/toObject options, read this.

option: toObject

文档有一个 toObject 方法,可将 mongoose 文档转换为纯 JavaScript 对象。此方法接受一些选项。我们可以在结构级别声明这些选项,并默认将它们应用于结构的所有文档,而不是在每个文档的基础上应用这些选项。

¥Documents have a toObject method which converts the mongoose document into a plain JavaScript object. This method accepts a few options. Instead of applying these options on a per-document basis, we may declare the options at the schema level and have them applied to all of the schema's documents by default.

要让所有虚拟值显示在 console.log 输出中,请将 toObject 选项设置为 { getters: true }

¥To have all virtuals show up in your console.log output, set the toObject option to { getters: true }:

const schema = new Schema({ name: String });
schema.path('name').get(function(v) {
  return v + ' is my name';
});
schema.set('toObject', { getters: true });
const M = mongoose.model('Person', schema);
const m = new M({ name: 'Max Headroom' });
console.log(m); // { _id: 504e0cd7dd992d9be2f20b6f, name: 'Max Headroom is my name' }

要查看所有可用的 toObject 选项,请阅读 this

¥To see all available toObject options, read this.

option: typeKey

默认情况下,如果你的结构中有一个带有键 'type' 的对象,mongoose 会将其解释为类型声明。

¥By default, if you have an object with key 'type' in your schema, mongoose will interpret it as a type declaration.

// Mongoose interprets this as 'loc is a String'
const schema = new Schema({ loc: { type: String, coordinates: [Number] } });

然而,对于像 地理 JSON 这样的应用,'type' 属性很重要。如果要控制 mongoose 使用哪个键来查找类型声明,请设置 'typeKey' 结构选项。

¥However, for applications like geoJSON, the 'type' property is important. If you want to control which key mongoose uses to find type declarations, set the 'typeKey' schema option.

const schema = new Schema({
  // Mongoose interprets this as 'loc is an object with 2 keys, type and coordinates'
  loc: { type: String, coordinates: [Number] },
  // Mongoose interprets this as 'name is a String'
  name: { $type: String }
}, { typeKey: '$type' }); // A '$type' key means this object is a type declaration

option: validateBeforeSave

默认情况下,文档在保存到数据库之前会自动验证。这是为了防止保存无效文档。如果你想手动处理验证,并且能够保存未通过验证的对象,你可以将 validateBeforeSave 设置为 false。

¥By default, documents are automatically validated before they are saved to the database. This is to prevent saving an invalid document. If you want to handle validation manually, and be able to save objects which don't pass validation, you can set validateBeforeSave to false.

const schema = new Schema({ name: String });
schema.set('validateBeforeSave', false);
schema.path('name').validate(function(value) {
  return value != null;
});
const M = mongoose.model('Person', schema);
const m = new M({ name: null });
m.validate(function(err) {
  console.log(err); // Will tell you that null is not allowed.
});
m.save(); // Succeeds despite being invalid

option: versionKey

versionKey 是 Mongoose 首次创建每个文档时设置的属性。该键值包含文档的内部 revisionversionKey 选项是一个字符串,表示用于版本控制的路径。默认为 __v。如果这与你的应用冲突,你可以这样配置:

¥The versionKey is a property set on each document when first created by Mongoose. This keys value contains the internal revision of the document. The versionKey option is a string that represents the path to use for versioning. The default is __v. If this conflicts with your application you can configure as such:

const schema = new Schema({ name: 'string' });
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
await thing.save(); // { __v: 0, name: 'mongoose v3' }

// customized versionKey
new Schema({ /* ... */ }, { versionKey: '_somethingElse' })
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'mongoose v3' });
thing.save(); // { _somethingElse: 0, name: 'mongoose v3' }

请注意,Mongoose 的默认版本控制不是完整的 乐观并发 解决方案。Mongoose 的默认版本控制仅在数组上运行,如下所示。

¥Note that Mongoose's default versioning is not a full optimistic concurrency solution. Mongoose's default versioning only operates on arrays as shown below.

// 2 copies of the same document
const doc1 = await Model.findOne({ _id });
const doc2 = await Model.findOne({ _id });

// Delete first 3 comments from `doc1`
doc1.comments.splice(0, 3);
await doc1.save();

// The below `save()` will throw a VersionError, because you're trying to
// modify the comment at index 1, and the above `splice()` removed that
// comment.
doc2.set('comments.1.body', 'new comment');
await doc2.save();

如果需要 save() 的乐观并发支持,可以设置 optimisticConcurrency 选项

¥If you need optimistic concurrency support for save(), you can set the optimisticConcurrency option

还可以通过将 versionKey 设置为 false 来禁用文档版本控制。不要禁用版本控制,除非你 知道你在做什么

¥Document versioning can also be disabled by setting the versionKey to false. DO NOT disable versioning unless you know what you are doing.

new Schema({ /* ... */ }, { versionKey: false });
const Thing = mongoose.model('Thing', schema);
const thing = new Thing({ name: 'no versioning please' });
thing.save(); // { name: 'no versioning please' }

Mongoose 仅在你使用 save() 时更新版本密钥。如果使用 update()findOneAndUpdate() 等,Mongoose 将不会更新版本密钥。作为解决方法,你可以使用以下中间件。

¥Mongoose only updates the version key when you use save(). If you use update(), findOneAndUpdate(), etc. Mongoose will not update the version key. As a workaround, you can use the below middleware.

schema.pre('findOneAndUpdate', function() {
  const update = this.getUpdate();
  if (update.__v != null) {
    delete update.__v;
  }
  const keys = ['$set', '$setOnInsert'];
  for (const key of keys) {
    if (update[key] != null && update[key].__v != null) {
      delete update[key].__v;
      if (Object.keys(update[key]).length === 0) {
        delete update[key];
      }
    }
  }
  update.$inc = update.$inc || {};
  update.$inc.__v = 1;
});

option: optimisticConcurrency

乐观并发 是一种策略,可确保你正在更新的文档在使用 find()findOne() 加载文档和使用 save() 更新文档之间不会发生更改。

¥Optimistic concurrency is a strategy to ensure the document you're updating didn't change between when you loaded it using find() or findOne(), and when you update it using save().

例如,假设你有一个包含 photos 列表的 House 模型,以及表示该房屋是否出现在搜索中的 status。假设具有 'APPROVED' 状态的房屋必须至少有两个 photos。你可以实现批准内部文档的逻辑,如下所示:

¥For example, suppose you have a House model that contains a list of photos, and a status that represents whether this house shows up in searches. Suppose that a house that has status 'APPROVED' must have at least two photos. You might implement the logic of approving a house document as shown below:

async function markApproved(id) {
  const house = await House.findOne({ _id });
  if (house.photos.length < 2) {
    throw new Error('House must have at least two photos!');
  }

  house.status = 'APPROVED';
  await house.save();
}

markApproved() 函数单独看起来是正确的,但可能存在一个潜在的问题:如果另一个函数在 findOne() 调用和 save() 调用之间删除了房子的照片怎么办?例如,下面的代码将会成功:

¥The markApproved() function looks right in isolation, but there might be a potential issue: what if another function removes the house's photos between the findOne() call and the save() call? For example, the below code will succeed:

const house = await House.findOne({ _id });
if (house.photos.length < 2) {
  throw new Error('House must have at least two photos!');
}

const house2 = await House.findOne({ _id });
house2.photos = [];
await house2.save();

// Marks the house as 'APPROVED' even though it has 0 photos!
house.status = 'APPROVED';
await house.save();

如果你在 House 模型的结构上设置 optimisticConcurrency 选项,上述脚本将引发错误。

¥If you set the optimisticConcurrency option on the House model's schema, the above script will throw an error.

const House = mongoose.model('House', Schema({
  status: String,
  photos: [String]
}, { optimisticConcurrency: true }));

const house = await House.findOne({ _id });
if (house.photos.length < 2) {
  throw new Error('House must have at least two photos!');
}

const house2 = await House.findOne({ _id });
house2.photos = [];
await house2.save();

// Throws 'VersionError: No matching document found for id "..." version 0'
house.status = 'APPROVED';
await house.save();

option: collation

为每个查询和聚合设置默认 collation这是适合初学者的排序规则概述

¥Sets a default collation for every query and aggregation. Here's a beginner-friendly overview of collations.

const schema = new Schema({
  name: String
}, { collation: { locale: 'en_US', strength: 1 } });

const MyModel = db.model('MyModel', schema);

MyModel.create([{ name: 'val' }, { name: 'Val' }]).
  then(() => {
    return MyModel.find({ name: 'val' });
  }).
  then((docs) => {
    // `docs` will contain both docs, because `strength: 1` means
    // MongoDB will ignore case when matching.
  });

option: timeseries

如果你在结构上设置 timeseries 选项,Mongoose 将为你从该结构创建的任何模型创建 时间序列集合

¥If you set the timeseries option on a schema, Mongoose will create a timeseries collection for any model that you create from that schema.

const schema = Schema({ name: String, timestamp: Date, metadata: Object }, {
  timeseries: {
    timeField: 'timestamp',
    metaField: 'metadata',
    granularity: 'hours'
  },
  autoCreate: false,
  expireAfterSeconds: 86400
});

// `Test` collection will be a timeseries collection
const Test = db.model('Test', schema);

option: skipVersioning

skipVersioning 允许从版本控制中排除路径(即,即使更新这些路径,内部修订也不会增加)。除非你知道自己在做什么,否则请勿这样做。对于子文档,请使用完全限定路径将其包含在父文档中。

¥skipVersioning allows excluding paths from versioning (i.e., the internal revision will not be incremented even if these paths are updated). DO NOT do this unless you know what you're doing. For subdocuments, include this on the parent document using the fully qualified path.

new Schema({ /* ... */ }, { skipVersioning: { dontVersionMe: true } });
thing.dontVersionMe.push('hey');
thing.save(); // version is not incremented

option: timestamps

timestamps 选项告诉 Mongoose 将 createdAtupdatedAt 字段分配给你的结构。指定的类型是 日期

¥The timestamps option tells Mongoose to assign createdAt and updatedAt fields to your schema. The type assigned is Date.

默认情况下,字段名称为 createdAtupdatedAt。通过设置 timestamps.createdAttimestamps.updatedAt 自定义字段名称。

¥By default, the names of the fields are createdAt and updatedAt. Customize the field names by setting timestamps.createdAt and timestamps.updatedAt.

timestamps 的底层工作方式是:

¥The way timestamps works under the hood is:

  • 如果你创建一个新文档,mongoose 只需将 createdAtupdatedAt 设置为创建时间。

    ¥If you create a new document, mongoose simply sets createdAt, and updatedAt to the time of creation.

  • 如果更新文档,mongoose 会将 updatedAt 添加到 $set 对象。

    ¥If you update a document, mongoose will add updatedAt to the $set object.

  • 如果你在更新操作上设置 upsert: true,mongoose 将使用 $setOnInsert 运算符将 createdAt 添加到文档中,以防 upsert 操作导致新的插入文档。

    ¥If you set upsert: true on an update operation, mongoose will use $setOnInsert operator to add createdAt to the document in case the upsert operation resulted into a new inserted document.

const thingSchema = new Schema({ /* ... */ }, { timestamps: { createdAt: 'created_at' } });
const Thing = mongoose.model('Thing', thingSchema);
const thing = new Thing();
await thing.save(); // `created_at` & `updatedAt` will be included

// With updates, Mongoose will add `updatedAt` to `$set`
await Thing.updateOne({}, { $set: { name: 'Test' } });

// If you set upsert: true, Mongoose will add `created_at` to `$setOnInsert` as well
await Thing.findOneAndUpdate({}, { $set: { name: 'Test2' } });

// Mongoose also adds timestamps to bulkWrite() operations
// See https://mongoose.nodejs.cn/docs/api/model.html#model_Model-bulkWrite
await Thing.bulkWrite([
  {
    insertOne: {
      document: {
        name: 'Jean-Luc Picard',
        ship: 'USS Stargazer'
      // Mongoose will add `created_at` and `updatedAt`
      }
    }
  },
  {
    updateOne: {
      filter: { name: 'Jean-Luc Picard' },
      update: {
        $set: {
          ship: 'USS Enterprise'
        // Mongoose will add `updatedAt`
        }
      }
    }
  }
]);

默认情况下,Mongoose 使用 new Date() 来获取当前时间。如果你想覆盖 Mongoose 用来获取当前时间的函数,你可以设置 timestamps.currentTime 选项。Mongoose 每当需要获取当前时间时就会调用 timestamps.currentTime 函数。

¥By default, Mongoose uses new Date() to get the current time. If you want to overwrite the function Mongoose uses to get the current time, you can set the timestamps.currentTime option. Mongoose will call the timestamps.currentTime function whenever it needs to get the current time.

const schema = Schema({
  createdAt: Number,
  updatedAt: Number,
  name: String
}, {
  // Make Mongoose use Unix time (seconds since Jan 1, 1970)
  timestamps: { currentTime: () => Math.floor(Date.now() / 1000) }
});

option: pluginTags

Mongoose 支持定义全局插件,即适用于所有结构的插件。

¥Mongoose supports defining global plugins, plugins that apply to all schemas.

// Add a `meta` property to all schemas
mongoose.plugin(function myPlugin(schema) {
  schema.add({ meta: {} });
});

有时,你可能只想将给定的插件应用于某些结构。在这种情况下,你可以将 pluginTags 添加到结构中:

¥Sometimes, you may only want to apply a given plugin to some schemas. In that case, you can add pluginTags to a schema:

const schema1 = new Schema({
  name: String
}, { pluginTags: ['useMetaPlugin'] });

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

如果你使用 tags 选项调用 plugin(),Mongoose 将仅将该插件应用于在 pluginTags 中具有匹配条目的结构。

¥If you call plugin() with a tags option, Mongoose will only apply that plugin to schemas that have a matching entry in pluginTags.

// Add a `meta` property to all schemas
mongoose.plugin(function myPlugin(schema) {
  schema.add({ meta: {} });
}, { tags: ['useMetaPlugin'] });

option: selectPopulatedPaths

默认情况下,Mongoose 会自动为你 select() 任何填充的路径,除非你明确排除它们。

¥By default, Mongoose will automatically select() any populated paths for you, unless you explicitly exclude them.

const bookSchema = new Schema({
  title: 'String',
  author: { type: 'ObjectId', ref: 'Person' }
});
const Book = mongoose.model('Book', bookSchema);

// By default, Mongoose will add `author` to the below `select()`.
await Book.find().select('title').populate('author');

// In other words, the below query is equivalent to the above
await Book.find().select('title author').populate('author');

要选择不选择默认填充的字段,请在结构中将 selectPopulatedPaths 设置为 false

¥To opt out of selecting populated fields by default, set selectPopulatedPaths to false in your schema.

const bookSchema = new Schema({
  title: 'String',
  author: { type: 'ObjectId', ref: 'Person' }
}, { selectPopulatedPaths: false });
const Book = mongoose.model('Book', bookSchema);

// Because `selectPopulatedPaths` is false, the below doc will **not**
// contain an `author` property.
const doc = await Book.findOne().select('title').populate('author');

option: storeSubdocValidationError

由于遗留原因,当单个嵌套结构的子路径中存在验证错误时,Mongoose 也会记录单个嵌套结构路径中也存在验证错误。例如:

¥For legacy reasons, when there is a validation error in subpath of a single nested schema, Mongoose will record that there was a validation error in the single nested schema path as well. For example:

const childSchema = new Schema({ name: { type: String, required: true } });
const parentSchema = new Schema({ child: childSchema });

const Parent = mongoose.model('Parent', parentSchema);

// Will contain an error for both 'child.name' _and_ 'child'
new Parent({ child: {} }).validateSync().errors;

在子结构上设置 storeSubdocValidationErrorfalse 以使 Mongoose 只报告父错误。

¥Set the storeSubdocValidationError to false on the child schema to make Mongoose only reports the parent error.

const childSchema = new Schema({
  name: { type: String, required: true }
}, { storeSubdocValidationError: false }); // <-- set on the child schema
const parentSchema = new Schema({ child: childSchema });

const Parent = mongoose.model('Parent', parentSchema);

// Will only contain an error for 'child.name'
new Parent({ child: {} }).validateSync().errors;

option: collectionOptions

collationcapped 等选项会影响 Mongoose 在创建新集合时传递给 MongoDB 的选项。Mongoose 结构支持大多数 MongoDB createCollection() 选项,但不是全部。你可以使用 collectionOptions 选项来设置任何 createCollection() 选项;当为你的结构调用 createCollection() 时,Mongoose 将使用 collectionOptions 作为默认值。

¥Options like collation and capped affect the options Mongoose passes to MongoDB when creating a new collection. Mongoose schemas support most MongoDB createCollection() options, but not all. You can use the collectionOptions option to set any createCollection() options; Mongoose will use collectionOptions as the default values when calling createCollection() for your schema.

const schema = new Schema({ name: String }, {
  autoCreate: false,
  collectionOptions: {
    capped: true,
    max: 1000
  }
});
const Test = mongoose.model('Test', schema);

// Equivalent to `createCollection({ capped: true, max: 1000 })`
await Test.createCollection();

option: autoSearchIndex

autoIndex 类似,但会自动创建架构中定义的任何 图集搜索索引。与 autoIndex 不同,此选项默认为 false。

¥Similar to autoIndex, except for automatically creates any Atlas search indexes defined in your schema. Unlike autoIndex, this option defaults to false.

const schema = new Schema({ name: String }, { autoSearchIndex: true });
schema.searchIndex({
  name: 'my-index',
  definition: { mappings: { dynamic: true } }
});
// Will automatically attempt to create the `my-index` search index.
const Test = mongoose.model('Test', schema);

使用 ES6 类

结构有一个 loadClass() 方法,你可以使用它从 ES6 级 创建 Mongoose 结构:

¥Schemas have a loadClass() method that you can use to create a Mongoose schema from an ES6 class:

下面是使用 loadClass() 从 ES6 类创建结构的示例:

¥Here's an example of using loadClass() to create a schema from an ES6 class:

class MyClass {
  myMethod() { return 42; }
  static myStatic() { return 42; }
  get myVirtual() { return 42; }
}

const schema = new mongoose.Schema();
schema.loadClass(MyClass);

console.log(schema.methods); // { myMethod: [Function: myMethod] }
console.log(schema.statics); // { myStatic: [Function: myStatic] }
console.log(schema.virtuals); // { myVirtual: VirtualType { ... } }

插件化

结构也是 pluggable,它允许我们将可重用的功能打包到插件中,这些插件可以与社区或仅在你的项目之间共享。

¥Schemas are also pluggable which allows us to package up reusable features into plugins that can be shared with the community or just between your projects.

进一步阅读

这是 Mongoose 结构的替代介绍

¥Here's an alternative introduction to Mongoose schemas.

为了充分利用 MongoDB,你需要学习 MongoDB 结构设计的基础知识。SQL 结构设计(第三范式)是针对 最小化存储成本 设计的,而 MongoDB 结构设计是为了尽可能快地进行常见查询。MongoDB 结构设计博客系列的 6 个经验法则 是学习快速查询基本规则的绝佳资源。

¥To get the most out of MongoDB, you need to learn the basics of MongoDB schema design. SQL schema design (third normal form) was designed to minimize storage costs, whereas MongoDB schema design is about making common queries as fast as possible. The 6 Rules of Thumb for MongoDB Schema Design blog series is an excellent resource for learning the basic rules for making your queries fast.

希望掌握 Node.js 中 MongoDB 结构设计的用户应该查看 MongoDB Node.js 驱动程序 的原作者 Christian Kvalheim 的 MongoDB 结构设计小书。本书向你展示如何为一系列用例(包括电子商务、维基和预约)实现高性能结构。

¥Users looking to master MongoDB schema design in Node.js should look into The Little MongoDB Schema Design Book by Christian Kvalheim, the original author of the MongoDB Node.js driver. This book shows you how to implement performant schemas for a laundry list of use cases, including e-commerce, wikis, and appointment bookings.

下一步

现在我们已经介绍了 Schemas,让我们来看看 SchemaTypes

¥Now that we've covered Schemas, let's take a look at SchemaTypes.