【问题标题】:Mongoose populate either ObjectId reference or StringMongoose 填充 ObjectId 引用或字符串
【发布时间】:2017-11-05 01:48:46
【问题描述】:

有没有办法将异构数组指定为模式属性,其中它可以包含 ObjectId 和字符串?我想要以下内容:

var GameSchema = new mongoose.schema({
    players: {
        type: [<UserModel reference|IP address/socket ID/what have you>]
    }

我自己管理的唯一选项是Mixed 类型吗?我遇到过discriminators,它看起来很有希望,但看起来它只适用于子文档而不是对其他模式的引用。当然,我可以有一个UserModel 引用并创建一个UserModel,它只存储IP 地址或我用来识别它们的任何东西,但这似乎很快就会在空间方面失去控制(我遇到的每个 IP 都有一个模型听起来很糟糕)。

编辑:

例子:

一个游戏有一个登录用户,三个匿名用户,文档应该是这样的:

{ players: [ ObjectId("5fd88ea85...."), "192.0.0.1", "192.1.1.1", "192.2.2.1"] }

理想情况下,这将填充到:

{ players: [ UserModel(id: ..., name: ...),  "192.0.0.1", "192.1.1.1", "192.2.2.1"] }

编辑:

我决定走一条不同的路线:我不是混合类型,而是区分不同的属性。像这样的:

players: [
    {
        user: <object reference>,
        sessionID: <string>,
        color: {
           type: String
        },
        ...other properties...
    }
]

我有一个验证器,可确保为给定条目仅填充 usersessionID 之一。在某些方面,这更复杂,但它确实避免了在迭代它们时进行这种条件填充和确定每个条目的类型的需要。我没有尝试任何答案,但它们看起来很有希望。

【问题讨论】:

  • 你想用这个做什么?这实际上很重要,因为它取决于您打算做什么。既然您提到了ObjectId,那么建议您要引用另一个/相同集合中的项目。如果你混合类型,那么像.populate() 这样的东西将不起作用。
  • 我的目标是允许登录用户或匿名用户加入游戏。登录用户应该有与他们的登录相关联的游戏,匿名用户(显然)没有登录。 populate() 是我不确定的事情之一;如果 Mongoose 支持我的用例,那么大概有一种方法可以巧妙地populate 仅在数组中那些是 ObjectIds 的条目,但你是说这不会发生?
  • 是的,我是说这不会发生。与其尝试在 cmets 中进行解释,不如花时间在您的问题中扩展您的用例(最好使用示例数据和期望的结果)。解决问题的一个好方法是不要“你认为解决方案是什么”来表达它,而是简单地根据最终结果来解释问题你要。为了给出最好的建议,这些要点可能会更清楚,
  • 您可以使用mongoose-ajv-plugin 并使用JSON 模式(由AJV library 实现)进行对象验证。这将允许您验证数组,但不会神奇地让您populate 它。免责声明:我是插件的作者。
  • @Jthorpe 这似乎有助于解决“混合”类型的问题,但除非我遗漏了什么,否则似乎没有任何东西可以帮助使用数据所在的.populate()引用的ObjectId 或不相关的“字符串”,如果我阅读正确,这是主要意图。如果您有适用于.populate() 的示例,那么我认为这将是一个有用的示例。 AFAIK .populate() 对此大发雷霆,但 MongoDB 可以通过一些争论自行“加入”。

标签: node.js mongodb mongoose mongodb-query aggregation-framework


【解决方案1】:

如果您满足于使用Mixed 或至少一些不适用于.populate() 的方案,那么您可以将“加入”职责转移到“服务器”,而不是使用MongoDB 的$lookup 功能还有一点花哨的搭配。

如果我有一个像这样的"games" 集合文档:

{
        "_id" : ObjectId("5933723c886d193061b99459"),
        "players" : [
                ObjectId("5933723c886d193061b99458"),
                "10.1.1.1",
                "10.1.1.2"
        ],
        "__v" : 0
}

然后我将语句发送到服务器以“加入”"users" 集合数据,其中存在 ObjectId,如下所示:

Game.aggregate([
  { "$addFields": {
    "users": {
      "$filter": {
        "input": "$players",
        "as": "p",
        "cond": { "$gt": [ "$$p", {} ] }
      }
    }
  }},
  { "$lookup": {
    "from": "users",
    "localField": "users",
    "foreignField": "_id",
    "as": "users"
  }},
  { "$project": {
    "players": {
      "$map": {
        "input": "$players",
        "as": "p",
        "in": {
          "$cond": {
            "if": { "$gt": [ "$$p", {} ] },
            "then": {
              "$arrayElemAt": [
                { "$filter": {
                  "input": "$users",
                  "as": "u",
                  "cond": { "$eq": [ "$$u._id", "$$p" ] }
                }},
                0
              ]
            },
            "else": "$$p"
          }
        }
      }
    }
  }}
])

加入用户对象时的结果如下:

{
        "_id" : ObjectId("5933723c886d193061b99459"),
        "players" : [
                {
                        "_id" : ObjectId("5933723c886d193061b99458"),
                        "name" : "Bill",
                        "__v" : 0
                },
                "10.1.1.1",
                "10.1.1.2"
        ]
}

所以在考虑"players" 数组中的条目时,“花哨”部分确实依赖于这个逻辑语句:

  "$filter": {
    "input": "$players",
    "as": "p",
    "cond": { "$gt": [ "$$p", {} ] }
  }

对于 MongoDB,这是一个ObjectId,实际上所有 BSON 类型都有一个specific sort precedence。在这种情况下,如果数据在 ObjectIdString 之间“混合”,则“字符串”值被认为“小于”“BSON 对象”的值,而 ObjectId 值“大于” .

这允许您将ObjectId 值从源数组中分离到它们自己的列表中。鉴于该列表,您 $lookup 执行“加入”以从其他集合中获取对象。

为了将它们放回去,我使用$map 来“转置”原始"players" 的每个元素,其中匹配的ObjectId 与相关对象一起找到。另一种方法是“拆分”这两种类型,在Users 和“字符串”之间执行$lookup$concatArrays。但这不会保持原来的数组顺序,所以$map 可能更合适。


我要补充一点,相同的基本过程可以通过类似地过滤"players"数组的内容以仅包含ObjectId值然后调用“模型”形式应用于“客户端”操作.populate() 来自“内部”初始查询的响应。该文档显示了这种使用形式的示例,在可以使用猫鼬进行“嵌套填充”之前,本网站上的一些答案也是如此。

这里要注意的另一点是,.populate() 本身早在 $lookup 聚合管道运算符出现之前就作为一种猫鼬方法存在,并且在 MongoDB 本身无法执行“连接”时是一种解决方案任何种类的。因此,这些操作确实是作为模拟的“客户端”,实际上只执行您在自己发出语句时不需要注意的额外查询。

因此,在现代场景中,通常需要使用“服务器”功能,并避免为获得结果而涉及多个查询的开销。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-03
    • 2017-04-20
    • 2021-03-19
    • 2021-08-25
    • 1970-01-01
    • 2021-04-17
    • 2022-06-14
    • 2012-01-22
    相关资源
    最近更新 更多