如何在mongoose/mongodb中获取文档内部的对象数组中的对象?

4
这是文档的结构。
{
    "_id" : ObjectId("5548b2b79b9567341c77d352"),
    "messages" : [
        {
            "subject" : "fresh subject ",
            "from" : ObjectId("5534b2992a104ed914435c31"),
            "_id" : ObjectId("5548b5dab9279faf1c1b8688"),
            "created" : ISODate("2015-05-05T12:21:46.261Z"),
            "read" : true,
            "message" : "fresh message ",
            "participants" : [
                ObjectId("5534b2992a104ed914435c31"), //logged in user
                ObjectId("5530af38576214dd3553331c")
            ]
        },
        {
            "subject" : " subjet",
            "from" : ObjectId("5530af38576214dd3553331c"),
            "_id" : ObjectId("5548b608b9279faf1c1b8689"),
            "created" : ISODate("2015-05-05T12:22:32.809Z"),
            "read" : true,
            "message" : "is fresh?",
            "participants" : [
                ObjectId("5530af38576214dd3553331c")
            ]
        }
    ],
    "participants" : [
        ObjectId("5534b2992a104ed914435c31"),
        ObjectId("5530af38576214dd3553331c")
    ],
    "__v" : 2
}

在特定对象的消息数组中有几个对象。如果该对象的参与者数组包含登录用户,则只想获取消息数组中的对象。

我有文档的对象ID(5548b2b79b9567341c77d352)和已登录用户的ID(5534b2992a104ed914435c31)。 如何在mongoose中执行相同操作?

3个回答

2
您可以使用 MongoDB 的聚合框架来获取所需的结果。聚合管道将包括一个初始步骤,其中包含一个$match 运算符,根据上述条件过滤文档。下一个管道阶段将是$unwind 运算符,它会从输入文档中解构消息数组以输出每个元素的文档。进一步的 $match 过滤器然后仅返回具有参与者 ID 的文档。最终的步骤通过 $project 运算符将仅具有指定字段的文档传递到结果中:

db.collection.aggregate([
    {
        "$match": {
            "_id" : ObjectId("5548b2b79b9567341c77d352"),
            "messages.participants": ObjectId("5534b2992a104ed914435c31")
        }
    },
    {
        "$unwind": "$messages"
    },
    {
        "$match": {            
            "messages.participants": ObjectId("5534b2992a104ed914435c31")
        }
    },
    {
        "$project": {
            "_id": 0,
            "messages": 1
        }
    }

])

Result:

/* 0 */
{
    "result" : [ 
        {
            "messages" : {
                "subject" : "fresh subject ",
                "from" : ObjectId("5534b2992a104ed914435c31"),
                "_id" : ObjectId("5548b5dab9279faf1c1b8688"),
                "created" : ISODate("2015-05-05T12:21:46.261Z"),
                "read" : true,
                "message" : "fresh message ",
                "participants" : [ 
                    ObjectId("5534b2992a104ed914435c31"), 
                    ObjectId("5530af38576214dd3553331c")
                ]
            }
        }
    ],
    "ok" : 1
}

在Mongoose中,您可以使用以下相同的聚合管道:
// Using the pipeline builder
Model.aggregate({
        "$match: {
            ""_id" : ObjectId("5548b2b79b9567341c77d352"),
            "messages.participants": ObjectId("5534b2992a104ed914435c31")
        }
    })
    .unwind("messages")
    .match({ "messages.participants": ObjectId("5534b2992a104ed914435c31") })
    .project({
        "_id": 0,
        "messages": 1
    })
    .exec(function (err, res) {
        if (err) return handleError(err);
        console.log(res); 
    });

// Or the simple aggregate method
var pipeline = [
    {
        "$match": {
            "_id" : ObjectId("5548b2b79b9567341c77d352"),
            "messages.participants": ObjectId("5534b2992a104ed914435c31")
        }
    },
    {
        "$unwind": "$messages"
    },
    {
        "$match": {            
            "messages.participants": ObjectId("5534b2992a104ed914435c31")
        }
    },
    {
        "$project": {
            "_id": 0,
            "messages": 1
        }
    }

]

Model.aggregate(pipeline, function (err, res) {
    if (err) return handleError(err);
    console.log(res); 
});

但我也想填充“from”和“participants”字段。当我尝试使用populate时,它显示以下错误。对象#<Aggregate>没有方法'populate'。 - Sachin Kalsi
1
@SachinKalsi,你在问题中从未提到如何填充“from”和“participants”字段。这个解决方案直接回答了你在问题中提出的内容。你不能在Mongoose聚合方法中使用populate。在聚合框架中,返回的字段是由你构建的,你可以“重命名”文档属性,这意味着Mongoose无法确定你引用的文档是否会在最终结果中可用。 - chridam

1
除了聚合框架之外,另一种解决方案是使用$elemMatch投影运算符。

例如:
var id = '5548b2b79b9567341c77d352';
var loggedInUserId = '5534b2992a104ed914435c31';
var projection = {
    participants: 1,
    messages: {
        $elemMatch: { participants: new ObjectId(loggedInUserId) }
    }
};

Model.findById(id, projection, function(err, result) {
    console.log(result);
});

这将输出:
{ _id: 5548b2b79b9567341c77d352,
  participants: [ 5534b2992a104ed914435c31, 5530af38576214dd3553331c ],
  messages: 
   [ { participants: [Object],
       message: 'fresh message ',
       read: true,
       created: Tue May 05 2015 09:21:46 GMT-0300 (BRT),
       _id: 5548b5dab9279faf1c1b8688,
       from: 5534b2992a104ed914435c31,
       subject: 'fresh subject ' } ] }

0

您可以使用聚合框架$unwind子文档数组(将它们拆分为自己的文档,以便您可以匹配它们)。我会使用以下查询:

.aggregate([
    {$unwind: "$messages"},
    {$match:
        {"messages.participants": ObjectId("5534b2992a104ed914435c31")}
    }
])

这将为每个与参与者匹配的message子文档在结果集中返回一个单独的message文档。

使用Mongoose构造,代码大致如下:

Conversation
    .aggregate({$unwind: "messages"})
    .match({
        "messages.participants": mongoose.Types.ObjectId("5534b2992a104ed914435c31")
    })
    .exec(cb);

第一个$match并不是严格必要的,但是

但我也想填充“from”和“participants”字段。当我尝试使用populate时,它显示以下错误。 对象#<Aggregate>没有方法'populate'。 - Sachin Kalsi
@SachinKalsi fromparticipants应该已经在结果中。 - Explosion Pills

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接