I'm trying to use the aggregate pipeline with fluent interfaces with no success, though I'm not receiving any error (all fields from result are null).
I have this User class:
public class User
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id;
...
[BsonElement("last_access")]
public DateTime LastAccess;
}
Entity class:
public class Entity
{
[BsonRepresentation(BsonType.ObjectId)]
public string Id;
...
[BsonElement("active")]
public bool Active;
[BsonElement("user_id")]
public string UserId;
}
UserLookup class. This is used for the $lookup.
class UserLookup
{
public int EntityCount;
public IEnumerable<User> UsersData;
}
UserResult class. This is used for group and projection.
class UserResult
{
public string UserId;
public int EntityCount;
public User UserData;
}
In my function, I have something like this:
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;
}
}
I don't receive any error, but EntityCount is always 0 and both UserId and UserData are null. Basically, what I want is:
- Get all entities that are active (
Match). - Group them by user id (
Group). - Lookup in the users collection to get the user data (
Lookup). - Project the result to return a simple object with entity count and the user data (
Project).
----- Update 1
Ok, after playing with mongo shell, I think I found the problem. It seems the mongo can't find entries by id with ObjectId, only with strings. This is weird, I found this answer and it seems it's possible to find using ObjectId (at least in the past).
In mongo shell, if I use db.users.find({ _id: ObjectId("...") }) it returns nothing, but with db.users.find({ _id: "..." }) it returns the expected user.
I wrote that aggregate query from scratch to run on shell, here it is:
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 }
])
Note in the $group stage that I'm converting the user id to string. Won't work if I use "_id": "$user_id".
The last stage $limit is just there to not blow out the console, making it easier to read the result.
This query executes perfectly fine.
Back to C#
This is the final query that C# driver uses:
[
{
"$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
}
}
]
I don't know why, but at the $group stage, the UserId field it's being ignored (this explains why it's always null in the result). Also, you can note that the _id is being set to 0 in the $lookup stage.
I renamed the field UserId from UserResult to Id and added the attribute [BsonElement("_id")].
Now,I get both user id and entity count in the result, but the UserData is still null.