【问题标题】:MongoDB nested Document Validation for sub-documents子文档的 MongoDB 嵌套文档验证
【发布时间】:2017-07-25 07:10:31
【问题描述】:

我得到了一个结构如下的文档。我的问题是如何在数据库端进行嵌套部分“角色”验证。我的要求是:

  • 角色大小可以为 0 或大于 1。
  • 如果创建了角色,则存在角色的 name 和 created_by。

    {
      "_id": "123456",
      "name": "User Name",
      "roles": [
        {
          "name": "mobiles_user",
           "last_usage_at": {
             "$date": 1457000592991
            },
            "created_by": "987654",
            "created_at": {
              "$date": 1457000592991
            }
        },
        {
          "name": "webs_user",
           "last_usage_at": {
             "$date": 1457000592991
            },
            "created_by": "987654",
            "created_at": {
              "$date": 1457000592991
            }
        },
      ]
    }
    

目前,我只对那些非嵌套属性执行以下操作:

db.createCollection( "users",
   { "validator" : {
     "_id" : {
         "$type" : "string"
      },
      "email" : {
         "$regex" : /@gmail\.com$/
      },
      "name" : {
         "$type" : "string"
      }
   }
} )

谁能告诉我如何进行嵌套文档验证?

【问题讨论】:

  • 澄清一下:你的意思是大小可以是0、2、3等,但不能是1?
  • 你能更新接受的答案吗?我不希望来这里的人被误导认为这是不可能的,我已经证明了这是可能的。

标签: mongodb


【解决方案1】:

是的,您可以通过否定 $elemMatch 来验证文档中的所有子文档,并且可以确保大小不是 1。但它肯定不漂亮!而且也不是很明显。

> db.createCollection('users', {
...   validator: {
...     name: {$type: 'string'},
...     roles: {$exists: 'true'},
...     $nor: [
...       {roles: {$size: 1}},
...       {roles: {$elemMatch: {
...         $or: [
...           {name: {$not: {$type: 'string'}}},
...           {created_by: {$not: {$type: 'string'}}},
...         ]
...       }}}
...     ],
...   }  
... })
{ "ok" : 1 }

这很令人困惑,但它确实有效!这意味着只接受roles 的大小既不是1 也不是roles 的元素具有name 不是string 或created_by 不是string 的元素的文档.

这是基于这样一个事实,在逻辑上,

对于所有 x:f(x) 和 g(x)

相当于

不存在 x s.t.: 不存在 f(x) 或不存在 g(x)

我们必须使用后者,因为 MongoDB 只给了我们一个存在操作符。

证明

有效证件有效:

> db.users.insert({
...   name: 'hello',
...   roles: [],
... })
WriteResult({ "nInserted" : 1 })

> db.users.insert({
...   name: 'hello',
...   roles: [
...     {name: 'foo', created_by: '2222'},
...     {name: 'bar', created_by: '3333'},
...   ]
... })
WriteResult({ "nInserted" : 1 })

如果roles 中缺少某个字段,则会失败:

> db.users.insert({
...   name: 'hello',
...   roles: [
...     {name: 'foo', created_by: '2222'},
...     {created_by: '3333'},
...   ]
... })
WriteResult({
    "nInserted" : 0,
    "writeError" : {
        "code" : 121,
        "errmsg" : "Document failed validation"
    }
})

如果roles 中的字段类型错误,则失败:

> db.users.insert({
...   name: 'hello',
...   roles: [
...     {name: 'foo', created_by: '2222'},
...     {name: 'bar', created_by: 3333},
...   ]
... })
WriteResult({
    "nInserted" : 0,
    "writeError" : {
        "code" : 121,
        "errmsg" : "Document failed validation"
    }
})

如果roles 的大小为 1,则失败:

> db.users.insert({
...   name: 'hello',
...   roles: [
...     {name: 'foo', created_by: '2222'},
...   ]
... })
WriteResult({
    "nInserted" : 0,
    "writeError" : {
        "code" : 121,
        "errmsg" : "Document failed validation"
    }
})

不幸的是,我唯一想不通的是如何确保角色是一个数组。 roles: {$type: 'array'} 似乎一切都失败了,我想是因为它实际上是在检查元素是否为 'array' 类型?

【讨论】:

    【解决方案2】:

    编辑:此答案不正确,可以验证数组中的所有子文档。见答案:https://stackoverflow.com/a/43102783/200224

    你真的不能。您可以执行以下操作:

    "roles.name": { "$type": "string" }
    

    但真正的意思是这些属性中的“至少一个”需要匹配指定的类型。这意味着这实际上是有效的:

    {
        "_id" : "123456",
        "name" : "User Name",
        "roles" : [
                {
                        "name" : "mobiles_user",
                        "last_usage_at" : ISODate("2016-03-03T10:23:12.991Z"),
                        "created_by" : "987654",
                        "created_at" : ISODate("2016-03-03T10:23:12.991Z")
                },
                {
                        "name" : "webs_user",
                        "last_usage_at" : ISODate("2016-03-03T10:23:12.991Z"),
                        "created_by" : "987654",
                        "created_at" : ISODate("2016-03-03T10:23:12.991Z")
                },
                {
                        "name" : 1
                }
        ]
    }
    

    毕竟是"documement validation",它本质上不太适合数组中的子文档,或者包含数组中的任何数据。

    实现的核心依赖于query operators 可用的表达式,并且由于 MongoDB 在标准查询表达式中缺少任何等同于 “所有数组条目必须匹配此值” 的内容,因此没有直接具体说明无法表达为验证器条件。

    在“查询”表达式中检查数组内容的唯一可能性是使用$where,并且注意到这不是文档验证的可用选项。

    即使是可用于查询的$size 运算符必须与特定的“大小”值匹配,并且不能使用不等式条件。因此,您“可以”验证严格的尺寸,但不能验证最小尺寸,除非:

    "roles.0": { "$exists": true }
    

    这是一个“婴儿期”的功能,有些实验性,所以未来的版本可能会解决这个问题。

    但就目前而言,您更好的选择是在客户端代码中执行此类“模式验证”(您将获得更好的异常报告)。已经存在许多采用这种方法的库。

    【讨论】:

    • 非常感谢您提供这样一个描述性的答案。嗯你是对的。验证确实需要转移到其他地方,并且由于我将其构建为 API,因此我需要在服务器端执行此操作。此外,如果 MongoDB 能够提供更具描述性的响应,说明失败的位置和原因,那就太好了。
    • @LPing 这是一个非常新的特性,官方也曾有一段时间表示 MongoDB 将“从不”强制执行模式验证。这显然已经改变了,因此很可能会在沿线的某个地方添加允许进一步验证的功能。
    • @BlakesSeven 你确定使用$not 和$elemMatch 没有丑陋的方法吗?
    猜你喜欢
    • 2019-01-19
    • 1970-01-01
    • 2018-03-09
    • 2017-04-02
    • 2016-11-23
    • 1970-01-01
    • 2021-11-12
    • 2021-05-01
    相关资源
    最近更新 更多