猫鼬填充ObjectId引用或字符串 [英] Mongoose populate either ObjectId reference or String

查看:104
本文介绍了猫鼬填充ObjectId引用或字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有一种方法可以将异构数组指定为架构属性,使其可以同时包含ObjectId和字符串?我想要以下内容:

Is there a way to specify a heterogeneous array as a schema property where it can contain both ObjectIds and strings? I'd like to have something like the following:

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

是我自己管理的Mixed类型的唯一选择吗?我遇到过区分符,它看上去很有希望,但看起来只适用于子文档,而不是其他架构的引用.当然,我可以只有一个UserModel引用并创建一个UserModel,它仅存储IP地址或我用来标识它们的任何内容,但是看来它很快会在空间方面迅速失去控制(为我遇到的每个IP都建立一个模型听起来很糟糕).

Is the only option a Mixed type that I manage myself? I've run across discriminators, which look somewhat promising, but it looks like it only works for subdocuments and not references to other schemas. Of course, I could just have a UserModel reference and create a UserModel that just stores the IP address or whatever I'm using to identify them, but that seems like it could quickly get hugely out of control in terms of space (having a model for every IP I come across sounds bad).

示例:

一个游戏有一个登录用户,三个匿名用户,该文档应如下所示:

A game has one logged in user, three anonymous users, the document should look something like this:

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

理想情况下,它将被填充到:

Ideally this would be populated to:

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

我决定走不同的路线:我不使用混合类型,而是使用不同的属性来区分它们.像这样:

I've decided to go a different route: instead of mixing types, I'm differentiating with different properties. Something like this:

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

我有一个验证器,可确保为给定条目仅填充usersessionID之一.从某些方面讲,这比较复杂,但是确实避免了进行这种条件填充并弄清楚每个条目在其上进行迭代时所需要的类型的需求.我还没有尝试过任何答案,但是它们看起来很有希望.

I have a validator that ensures only one of user or sessionID are populated for a given entry. In some ways this is more complex, but it does obviate the need to do this kind of conditional populating and figuring out what type each entry is when iterating over them. I haven't tried any of the answers, but they look promising.

推荐答案

如果您愿意使用Mixed或至少某些不适用于.populate()的方案,则可以转移加入"责任到服务器",而不是使用 $lookup 功能MongoDB和一些花哨的匹配.

If you are content to go with using Mixed or at least some scheme that will not work with .populate() then you can shift the "join" responsibility to the "server" instead using the $lookup functionality of MongoDB and a little fancy matching.

对我来说,如果我有这样的"games"收集文档:

For me if I have a "games" collection document like this:

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

然后,我将语句发送到服务器以与"users"收集数据连接",其中存在ObjectId,如下所示:

Then I send the statement to the server to "join" with the "users" collection data where an ObjectId is present like this:

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"
          }
        }
      }
    }
  }}
])

当加入用户对象时,将给出以下结果:

Which gives the result when joined to the users object as:

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

因此,在考虑"players"数组中的条目时,花式"部分实际上依赖于此逻辑语句:

So the "fancy" part really relies on this logical statement when considering the entries in the "players" array:

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

这是MongoDB的工作原理,它是ObjectId,实际上所有BSON类型都具有ObjectId值是大于"

How this works is that to MongoDB, an ObjectId and actually all BSON types have a specific sort precedence. In this case where the data is "Mixed" between ObjectId and String then the "string" values are considered "less than" the value of a "BSON Object", and the ObjectId values are "greater than".

这允许您将ObjectId值与源数组分开到它们自己的列表中.有了该列表,您 $lookup 即可执行加入",即可从其他集合中获取对象.

This allows you to separate the ObjectId values from the source array into their own list. Given that list, you $lookup to perform the "join" at get the objects from the other collection.

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

In order to put them back, I'm using $map to "transpose" each element of the original "players" where the matched ObjectId was found with the related object. An alternate approach would be to "split" the two types, do the $lookup and $concatArrays between the Users and the "strings". But that would not maintain the original array order, so $map may be a better fit.

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

I will add of note that the same basic process can be applied in a "client" operation by similarly filtering the content of the "players" array to contain just the ObjectId values and then calling the "model" form of .populate() from "inside" the response of the initial query. The documentation shows an example of that form of usage, as do some answers on this site before it was possible to do a "nested populate" with mongoose.

这里的另一点想法是.populate()本身作为猫鼬方法存在的时间早于 $lookup 聚合管道运算符应运而生,并且是MongoDB本身无法执行任何类型的联接"的一种解决方案.因此,这些操作实际上是作为仿真的客户端"端,并且实际上仅执行您自己发出语句时不需要了解的其他查询.

The other point of mind here is that .populate() itself existed as a mongoose method long before the $lookup aggregation pipeline operator came about, and was a solution for a time when MongoDB itself was incapable of performing a "join" of any sort. So the operations are indeed "client" side as an emulation and really only perform additional queries that you do not need to be aware of in issuing the statements yourself.

因此,在现代情况下,通常最好使用服务器"功能,并避免为了获得结果而涉及多个查询的开销.

Therefore it should generally be desirable in a modern scenario to use the "server" features, and avoid the overhead involved with multiple queries in order to get the result.

这篇关于猫鼬填充ObjectId引用或字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆