【问题标题】:Mongoose: How to ensure row should be unique only if value of property is equal to something?Mongoose:如何确保只有当属性值等于某值时行才应该是唯一的?
【发布时间】:2020-04-17 04:53:45
【问题描述】:

我有一个简单的 Mongoose 架构,如下所示:

const fruitSchema = new mongoose.Schema({
    name: {
        type: String
        required: true
    },
    type: {
        type: String,
        required: true
    }
})

schema.index({ name: 1, type: 1 }, { unique: true })
const Fruit = mongoose.model('Fruit', fruitSchema)

在正常情况下,(name, type) 上的表是唯一的,因此用户可以为每个水果存储多种类型。但是,如果Fruitnameapple,我想允许用户只存储一个type

所以基本上,我想在保存之前进行以下检查:

if (newFruit.name === 'apple') {
        if (Fruit.find({ name: 'apple' }).count >= 1) throw new Error()
}

执行此操作的一种方法是在预保存挂钩中执行上述代码。但是,我只是想知道在 Mongoose 模式本身中是否有一种内置的方式来指定它?

感谢您的帮助!

解决方案: 除了下面@SuleymanSah 提供的解决方案之外,我想我会发布我最终使用的解决方案。

const fruitSchema = new mongoose.Schema({
    fruit: {
      type: String
      required: true,
      async validate(name) {
        if (name === 'apple') {
            let fruit 

            try {
                fruit = await Fruit.findOne({ name })
            } catch (e) {
                console.error('[Fruit Model] An error occurred during validation.')
                console.error(e)
                throw e // rethrow error if findOne call fails since fruit will be null and this validation will pass with the next statement
            }

            if (fruit) throw new Error(`A fruit for ${name} already exists.`)
        }
        },
    type: {
      type: String,
      required: true
    }
})

schema.index({ fruit: 1, type: 1 }, { unique: true })
const Fruit = mongoose.model('Fruit', fruitSchema)

【问题讨论】:

    标签: node.js mongodb validation mongoose indexing


    【解决方案1】:

    你可以像这样使用custom validators

    const schema = new mongoose.Schema({
      name: {
        type: String,
        required: true
      },
      type: {
        type: String,
        required: true,
        validate: {
          validator: async function() {
            if (this.name === "apple") {
              let doc = await this.constructor.findOne({ name: "apple" });
              return Boolean(!doc);
            }
          },
          message: props => "For apple only one type can be."
        }
      }
    });
    

    【讨论】:

    • 嘿,这就是我真正想要的。我也对类型字段进行了一些同步验证。我如何将这些也合并到代码中?谢谢你的帮助!我将您的答案标记为答案。
    • 您好,不客气。我建议您阅读 mongoose 自定义验证文档,并自己实现,如果您遇到困难,可以提出一个新问题。
    • 我通读了文档并使用了这样的验证功能:jsfiddle.net/04o1phLy。这个和你在答案中给出的有什么区别吗?我还注意到您在回答中使用了this.constructor.findOne,而不是Fruit.findOne。有什么理由吗?
    • 在模式中我们无法访问 Fruit。所以我用this.constructor来引用实际的模型。
    • 这似乎对我有用。从某种意义上说,我确实可以访问它。奇怪的。但是,谢谢,这是解决问题的方法。无论如何,我都在使用 Joi 进行前端验证。
    【解决方案2】:

    你可以用 express 这样做,例如保存一个新水果时:

    const express = require("express");
    const router = express.Router();
    
    router.post("/save", (req, res) => {
      const { name, type  } = req.body; //this is coming from front-end
    
    Fruit.findOne({ name }).then(fruit=> {
        if (fruit) return res.status(400).json({ name: "This fruit already exist!" });
    

    这将防止任何同名的水果保存到数据库中

    【讨论】:

    • 谢谢。这是一种解决方法,但不会在模式级别上强制执行。我认为预保存挂钩可能是更好的选择,因为它可以确保数据库级别的这一要求。您是否建议在路由器级别强制执行此操作会更好?
    • 是的,我确实喜欢并建议这样做,因为在我看来,这是在保存之前检查所有控件、先决条件的另一层,这是一个更好的设计和架构 - 就像它的名字一样建议,应该包括模式 - 模型的计划。
    • 知道了。谢谢!将此标记为答案。
    【解决方案3】:

    我认为这是类似的查询 您需要添加索引并将其设置为唯一 restrict to store duplicate values in mongodb

    【讨论】:

    • 好吧,我已经在 (name, type) 上定义了一个唯一的复合索引。问题是它应该在名称上是唯一的,仅适用于行的某个子集。
    • 这行不通,因为在某些情况下它不是唯一的。
    猜你喜欢
    • 2021-10-17
    • 2011-01-07
    • 2021-04-14
    • 2021-03-13
    • 2013-03-05
    • 2016-01-10
    • 1970-01-01
    • 2021-11-10
    • 1970-01-01
    相关资源
    最近更新 更多