首页 > 解决方案 > MongoDB C# Fluent 聚合

问题描述

我正在尝试使用具有流畅接口的聚合管道但没有成功,尽管我没有收到任何错误(结果中的所有字段均为空)。

我有这个用户类:

public class User
{
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id;

    ...

    [BsonElement("last_access")]
    public DateTime LastAccess;
}

实体类:

public class Entity
{
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id;

    ...

    [BsonElement("active")]
    public bool Active;

    [BsonElement("user_id")]
    public string UserId;
}

用户查找类。这用于$lookup.

class UserLookup
{
    public int EntityCount;

    public IEnumerable<User> UsersData;
}

用户结果类。这用于组和投影。

class UserResult
{
    public string UserId;

    public int EntityCount;

    public User UserData;
}

在我的功能中,我有这样的事情:

IMongoCollection<Entity> entityCol = Database.Instance.GetCollection<Entity>("entities");
IMongoCollection<User> usersCol = Database.Instance.GetCollection<User>("users");

IAsyncCursor<UserResult> result = entityCol.Aggregate()
    .Match(e => e.Active)
    .Group(e => e.UserId, g => new UserResult {
        UserId = g.Key,
        EntityCount = g.Count()
    })
    .Lookup<UserResult, User, UserLookup>(usersCol,
        lf => lf.UserId,  // localField. UserResult.UserId
        ff => ff.Id,      // foreignField. User.Id
        r => r.UsersData  // result. UserLookup.UsersData
    )
    .Project(p => new UserResult {
        UserId = p.UserId,
        EntityCount = p.EntityCount,
        UserData = p.UsersData.First()
    })
    .ToCursor();

while (result.MoveNext()) {
    foreach (var ur in result.Current) {
        // ur.UserId = null; ur.UserData = null; ur.EntityCount = 0;
    }
}

我没有收到任何错误,但EntityCount始终为 0 并且两者UserIdUserData为空。基本上,我想要的是:

  1. 获取所有处于活动状态的实体 ( Match)。
  2. 按用户 ID ( Group) 对它们进行分组。
  3. 在 users 集合中查找以获取用户数据 ( Lookup)。
  4. 投影结果以返回具有实体计数和用户数据 ( Project) 的简单对象。

----- 更新 1

好的,在玩了mongo shell之后,我想我发现了问题。似乎 mongo 找不到带有 id 的条目ObjectId,只能找到字符串。这很奇怪,我找到了这个答案,似乎可以使用 ObjectId 找到(至少在过去)。

在 mongo shell 中,如果我使用db.users.find({ _id: ObjectId("...") })它,它不会返回任何内容,但db.users.find({ _id: "..." })会返回预期的用户。

我从头开始编写聚合查询以在 shell 上运行,这里是:

db.entities.aggregate([
    {
        $match: {
            "active": "true",
        }
    },
    {
        $group: {
            "_id" : {
                $toString: "$user_id"
            },
            "EntityCount": { "$sum" : 1 }
        }
    },
    {
        $lookup: {
            from: "users",
            localField: "_id",
            foreignField: "_id",
            as: "UsersData"
        }
    },
    {
        $project: {
            "_id": "$_id",
            "EntityCount": "$EntityCount",
            "UserData": {
                "$arrayElemAt": ["$UsersData", 0]
            },
        }
    },
    { $limit: 2 }
])

请注意,在$group阶段,我将用户 ID 转换为字符串。如果我使用"_id": "$user_id".

最后一个阶段$limit只是为了不炸毁控制台,从而更容易阅读结果。

该查询执行得非常好。

回到 C#

这是 C# 驱动程序使用的最终查询:

[
    {
        "$match": {
            "active": true,
        }
    },
    {
        "$group": {
            "_id": "$user_id",
            "EntityCount": {
                "$sum":1
            }
        }
    },
    {
        "$lookup": {
            "from": "users",
            "localField": "_id",
            "foreignField": "_id",
            "as": "users_data"
        }
    },
    {
        "$project": {
            "UserId": "$user_id",
            "EntityCount": "$EntityCount",
            "UserData": {
                "$arrayElemAt": ["$user_data", 0]
            },
            "_id": 0
        }
    }
]

我不知道为什么,但是在$group阶段,UserId它被忽略的字段(这解释了为什么它在结果中总是为空)。此外,您可以注意到在$lookup阶段_id被设置为 0 。

我将字段UserIdUserResultto重命名Id并添加了属性[BsonElement("_id")]

现在,我在结果中同时获得了用户 ID 和实体计数,但UserData仍然为空。

标签: c#mongodbaggregation-framework

解决方案


查询表格,有效>

IEnumerable<UserResult> result = entityCol.AsQueryable().Where(x => x.Active).ToLookup(x => x.UserId)
    .Select(x => new UserResult {EntityCount = x.Count(), UserId = x.Key}).Join(usersCol.AsQueryable(),
        x => x.UserId, x => x.Id,
        (userResult, user) => new UserResult
            {EntityCount = userResult.EntityCount, UserData = user, UserId = userResult.UserId});

foreach (var ur in result)
{
    // ur.UserId = null; ur.UserData = null; ur.EntityCount = 0;
}

您对 ObjectId - 字符串转换在 Grouping 中不起作用的怀疑是正确的。

这有效:

public class User
{
    [BsonId]
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id;

    [BsonElement("last_access")]
    public DateTime LastAccess;
}

public class Entity
{
    [BsonRepresentation(BsonType.ObjectId)]
    [BsonId]
    public string Id;

    [BsonElement("active")]
    public bool Active;

    [BsonElement("user_id")]
    [BsonRepresentation(BsonType.ObjectId)]
    public string UserId;
}

class UserLookup
{
    public int EntityCount;

    public User[] UsersData;

    [BsonRepresentation(BsonType.ObjectId)]
    public string Id;
}

class UserResult
{
    public string UserId;

    public int EntityCount;

    public User UserData;
}

这样就可以了>

IAsyncCursor<UserResult> result = entityCol.Aggregate()
    .Match(e => e.Active)
    .Group(e => e.UserId, g => new UserResult
    {
        UserId = g.Key,
        EntityCount = g.Count(),
    })
    .Lookup(usersCol,
        lf => lf.UserId,  // localField. UserResult.UserId
        ff => ff.Id,      // foreignField. User.Id
        (UserLookup r) => r.UsersData  // result. UserLookup.UsersData
    )
    .Project(p => new UserResult
    {
        UserId = p.UsersData.First().Id,
        EntityCount = p.EntityCount,
        UserData = p.UsersData.First()
    })
    .ToCursor();

推荐阅读