Mongoose填充ObjectId引用或String

时间:2017-06-03 23:28:37

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

有没有办法将异构数组指定为架构属性,它可以包含ObjectIds和字符串?我希望得到以下内容:

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中的一个。在某些方面,这更复杂,但它确实消除了进行这种条件填充的需要,并且在迭代它们时确定每个条目的类型。我没有尝试过任何答案,但看起来很有希望。

1 个答案:

答案 0 :(得分:0)

如果您满意使用Mixed或至少某些不适用于.populate()的方案,那么您可以将“加入”责任转移到“服务器”而不是使用{{ 3}} MongoDB的功能和一些花哨的匹配。

对我来说,如果我有一个"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类型都有$lookup。在这种情况下,数据在ObjectIdString之间“混合”,则“字符串”值被视为“小于”“BSON对象”的值,ObjectId值“大于”。

这允许您将源代码中的ObjectId值分隔到它们自己的列表中。根据该列表,您specific sort precedence执行“加入”以获取其他集合中的对象。

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

我将补充说明,通过类似地过滤"players"数组的内容以仅包含ObjectId值,然后调用“”,可以在“客户端”操作中应用相同的基本过程。从初始查询的响应“内部”模型“$map的形式”。文档显示了这种使用形式的一个示例,在此网站上的一些答案之前,可以使用mongoose进行“嵌套填充”。

这里的另一个观点是.populate()本身早在.populate()聚合管道运算符出现之前就作为mongoose方法存在,并且是MongoDB本身无法执行的时候的解决方案任何形式的“加入”。因此,操作确实是“客户端”作为仿真,并且实际上只执行您自己发布语句时不需要注意的其他查询。

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