Reputation: 5820
I'm writing a MongoDB application and I'm using the aggregation pipeline in C# fow complex queries.
When I copy the aggregation generated by C# in the shell, everything seems fine. However, when the aggregation is executed in C#, some properties are being set to null
. Please see below for more information.
First, let me show you my model:
public class Smartphone
{
#region Properties
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("name")]
[BsonIgnoreIfNull]
[BsonIgnoreIfDefault]
public string Name { get; set; }
[BsonElement("description")]
[BsonIgnoreIfNull]
[BsonIgnoreIfDefault]
public string Description { get; set; }
[BsonElement("typenr")]
[BsonIgnoreIfNull]
[BsonIgnoreIfDefault]
public string Type { get; set; }
[BsonElement("props")]
[BsonIgnoreIfNull]
[BsonIgnoreIfDefault]
public List<SmartphoneProperty> Properties { get; set; }
#endregion
}
public class UnwindedSmartphone
{
#region Properties
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("name")]
[BsonIgnoreIfNull]
[BsonIgnoreIfDefault]
public string Name { get; set; }
[BsonElement("description")]
[BsonIgnoreIfNull]
[BsonIgnoreIfDefault]
public string Description { get; set; }
[BsonElement("typenr")]
[BsonIgnoreIfNull]
[BsonIgnoreIfDefault]
public string Type { get; set; }
[BsonElement("props")]
[BsonIgnoreIfNull]
[BsonIgnoreIfDefault]
public SmartphoneProperty Property { get; set; }
#endregion
}
public class SmartphoneProperty
{
#region Properties
[BsonElement("type")]
[BsonIgnoreIfNull]
[BsonIgnoreIfDefault]
public string Type { get; set; }
[BsonElement("value")]
[BsonIgnoreIfNull]
[BsonIgnoreIfDefault]
public string Value { get; set; }
#endregion
}
A single document in my collection does look like the following:
{
"_id" : ObjectId("55d45c0285afc59c146f66f0"),
"name" : "LG Nexus 6",
"description" : "A Nexus 6 device, created by Google.",
"typenr" : "LG-NEX-5/BLACK",
"props" : [
{
"type" : "os",
"value" : "Android"
},
{
"type" : "storage",
"value" : "8"
},
{
"type" : "storage",
"value" : "16"
},
{
"type" : "storage",
"value" : "32"
},
{
"type" : "storage",
"value" : "64"
}
]
}
The aggregation command that needs to be executed is the following:
// Get all the amount of filters that are defined.
db.smartphones.aggregate([
// Unwind the "props".
{ "$unwind" : "$props" },
// Grouping phase.
// Group by unique properties, add a count for the amount of articles, and add articles to an element named "articles".
// We use the function "$addToSet" here to ensure that only unique articles are being added.
{
"$group" : {
"_id" : "$props",
count : { "$sum" : 1 },
articles: {
"$addToSet": {
name: "$name",
description: "$description",
typenr: "$typenr"
}
}
}
},
// Sort the results based on the "_id" field.
{ "$sort" : { "_id" : 1 } }
]);
A single result of this aggregation returns me the following:
"_id" : {
"type" : "storage",
"value" : "128"
},
"count" : 1.0000000000000000,
"articles" : [
{
"name" : "Apple iPhone 6",
"description" : "An iPhone 6 device, created by Apple.",
"typenr" : "APP-IPHONE-6/BLACK"
}
]
Now, in C#, I've written the aggregation in the following way:
var aggregation = collection.Aggregate()
.Unwind<Smartphone, UnwindedSmartphone>(x => x.Properties)
.Group(key => key.Property, g => new
{
Id = g.Key,
count = g.Count(),
articles = g.Select(x => new
{
name = x.Name,
description = x.Description,
typenr = x.Type
}).Distinct()
})
.SortBy(x => x.Id);
If I check the command that this aggregation is transformed to, it appears to be the following:
// Get all the amount of filters that are defined.
db.smartphones.aggregate([
// Unwind the "props".
{ "$unwind" : "$props" },
// Grouping phase.
// Group by unique properties, add a count for the amount of articles, and add articles to an element named "articles".
// We use the function "$addToSet" here to ensure that only unique articles are being added.
{
"$group" : {
"_id" : "$props",
"count" : { "$sum" : 1 },
"articles" : {
"$addToSet" : {
"name" : "$name",
"description" : "$description",
"typenr" : "$typenr"
}
}
}
},
{ "$sort" : { "_id" : 1 } }
])
So, it's the same as the original aggregation which I tried to convert into C# code, apart that properties are enclosed with double quotes, but that's not an issue.
If I execute this aggregation in the shell, the result is fine.
However, when it's executed in C#, the articles
properties have a null
value for name
, description
and typenr
.
Anyone who knows why this is happening?
Upvotes: 2
Views: 2390
Reputation: 5820
Ok,
Thanks to @BlakesSeven who pointed me in the right direction (see his comment on my question, I'm now aware why I'm having that particular issue).
I need to convert the select
statement in the group
may not return an anonymous type.
Rather than that, I need to return a typed object.
This means that my code needs to change to the following:
var aggregation = collection.Aggregate()
.Unwind<Smartphone, UnwindedSmartphone>(x => x.Properties)
.Group(key => key.Property, g => new
{
Count = g.Count(),
Articles = g.Select(x => new AggregatedSmartphoneArticle
{
Name = x.Name,
Description = x.Description,
Type = x.Type
}).Distinct()
});
Now everything is working.
Thanks again. Using typed code is so much clearer than using those BsonDocument
.
Upvotes: 1