Skip to content

Latest commit

 

History

History
131 lines (94 loc) · 4.8 KB

Mongoose 唯一索引(unique).md

File metadata and controls

131 lines (94 loc) · 4.8 KB

Mongoose 唯一索引(unique)

Mongoose 的唯一索引 unique 选项都作用是,对于给定的路径,每个文档必须具有唯一的值。例如,下面是如何告诉 Mongoose 用户的 email 必须是唯一的。

const mongoose = require('mongoose')

const userSchema = new mongoose.Schema({
  email: {
    type: String,
    unique: true // email 必须是唯一的
  }
})

const User = mongoose.model('User', userSchema)

如果你尝试使用相同的 name 创建两个用户,你将得到一个重复键错误。

// 抛出 MongoError: E11000 duplicate key error collection
await User.create([{ email: 'test@163.com' }, { email: 'test2@163.com' }])

const doc = new User({ email: 'test@163.com' })
// 抛出 MongoError: E11000 duplicate key error collection
await doc.save()

更新还可能引发重复键错误。例如,如果你创建了一个具有唯一电子邮件地址的用户,然后将其电子邮件地址更新为非唯一值,你将得到相同的错误。

await User.create({ email: 'test2@163.com' })

// 抛出 MongoError: E11000 duplicate key error collection
await User.updateOne({ email: 'test2@163.com' }, { email: 'test@163.com' })

unique 定义索引,而不是验证器

一个常见的问题是 unique 选项告诉 Mongoose 定义一个唯一索引。这意味着当你使用 validate() 时,Mongoose 不会检查唯一性。

await User.create({ email: 'test@163.com' })

const doc = new User({ email: 'test@163.com' })
await doc.validate() // 不会抛出错误

在编写自动测试时,unique 定义索引而不是验证器这一点很重要。如果删除 User 模型所连接的数据库,还将删除 unique 索引,并且可以保存重复的索引。

await mongoose.connection.dropDatabase()

// 成功,因为 unique 索引已消失!
await User.create([{ email: 'test@163.com' }, { email: 'test@163.com' }])

在生产环境中,通常不会删除数据库,因此这在生产环境中很少成为问题。

编写 Mongoose 测试时,通常建议使用 deleteMany() 清除测试之间的数据,而不是 dropDatabase()。这样可以确保删除所有文档,而无需清除数据库级别的配置,如索引和排序规则 deleteMany() 也比 dropDatabase() 快得多

但是,如果选择在测试之间删除数据库,则可以使用 Model.syncIndexes() 方法重新生成所有唯一索引。

await mongoose.connection.dropDatabase()

// 重新生成所有索引
await User.syncIndexes()

// 抛出 MongoError: E11000 duplicate key error collection
await User.create([{ email: 'test@163.com' }, { email: 'test@163.com' }])

处理 null

null 是一个不同的值,你不能保存两个具有 nullemail 用户。同样,不能保存两个没有 email 属性的用户。

// 抛出,因为两个文档都有 undefined
await User.create([{}, {}])

// 抛出,因为两个文档都有 null
await User.create([{ email: null }, { email: null }])

一种解决方法是使用 required 属性 ,这将不允许 nullundefined 的值存在:

const userSchema = new mongoose.Schema({
  email: {
    type: String,
    required: true,
    unique: true // email 必须是唯一的
  }
})

如果你需要 email 是唯一的,除非它没有定义,你可以改为定义一个 Sparse Indexes(稀疏索引)email 上。如下所示:

const userSchema = new mongoose.Schema({
  email: {
    type: String, // email 必须是唯一的,除非没有定义
    index: {
      unique: true,
      sparse: true
    }
  }
})

向用户提供友好的重复键错误

要使 MongoDB E11000 错误消息对用户友好,你可以使用 mongoose-beautiful-unique-validation 包。

const schema = new mongoose.Schema({ name: String })
schema.plugin(require('mongoose-beautiful-unique-validation'))

const UserModel = mongoose.model('User', schema)

const doc = await UserModel.create({ name: 'O.O' })

try {
  // 尝试创建具有相同 _id 的文档。这将始终失败,因为 MongoDB 集合在 _id 上始终具有唯一索引。
  await UserModel.create(Object.assign({}, doc.toObject()))
} catch (err) {
  // _id 不是唯一的。
  console.log(err.errors['_id'].message)
}