5

I'm quite new to MongoDB and I'm using it in a Web Api to serve a mobile application.

Now, I need to run an aggregation and since I'm using C#, I would like to do it fluently by using the Aggregate command on a collection which returns me an IAggregateFluent.

However, I'm stuck and the information which I found here on SO doesn't help me, so therefore a new question.

I've built a small collection that holds smartphones with some basic properties, and a single item in the smartphone collection does look like:

{
    "name" : "LG Nexus 5",
    "description" : "A Nexus 5 device, created by Google.",
    "typenr" : "LG-NEX-5/WHITE",
    "props" : [ 
        {
            "type" : "os",
            "value" : "Android"
        }, 
        {
            "type" : "storage",
            "value" : "8"
        }, 
        {
            "type" : "storage",
            "value" : "16"
        }, 
        {
            "type" : "storage",
            "value" : "32"
        }, 
        {
            "type" : "storage",
            "value" : "64"
        }
    ]
}

Now, I've created an aggregation in the shell which looks like 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" 
                } x =>
            } 
        } 
    },

    // Sort the results based on the "_id" field.
    { "$sort" : { "_id" : 1 } }
]);

And now I need to translate this to C#.

First, I do create the following (plain C# code, it just returns an IMongoCollection<Article>).

var collection = context.ArticleRepository;

Here's the model which the collection does return:

public class Article
{
    #region Properties

    /// <summary>
    ///     Unique identifier for the article.
    /// </summary>
    [BsonRepresentation(BsonType.ObjectId)]
    public string Id { get; set; }

    /// <summary>
    ///     Name of the article.
    /// </summary>
    [BsonElement("name")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public BsonString Name { get; set; }

    /// <summary>
    ///     Name of the element but in lowercase.
    /// </summary>
    /// <remarks>
    ///     We'll create this field to enable text-search on this field without respecting capital letters.
    /// </remarks>
    [BsonElement("namelc")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public BsonString LowercaseName { get; set; }

    /// <summary>
    ///     Specification of the article.
    /// </summary>
    [BsonElement("specification")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public BsonString Specificiation { get; set; }

    /// <summary>
    ///     Brand of the article.
    /// </summary>
    [BsonElement("brand")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public BsonString Brand { get; set; }

    /// <summary>
    ///     Supplier of the article.
    /// </summary>
    [BsonElement("supplier")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public Supplier Supplier { get; set; }

    /// <summary>
    ///     Number of the article.
    /// </summary>
    [BsonElement("nr")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public BsonString ArticleNumber { get; set; }

    /// <summary>
    ///     Gtin number of the article.
    /// </summary>
    [BsonElement("gtin")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public string ArticleGtin { get; set; }

    /// <summary>
    ///     type number of the article.
    /// </summary>
    [BsonElement("typeNr")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public string TypeNumber { get; set; }

    /// <summary>
    ///     Properties of the article.
    /// </summary>
    /// <remarks>
    ///     This field can be used to ensure that we can filter on the articles.
    ///     By default, this is an empty list, this avoids initialization logic.
    /// </remarks>
    [BsonElement("props")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public List<ArticleProperty> Properties { get; set; } = new List<ArticleProperty>();

    #endregion
}

/// <summary>
///     Class representing a single supplier in the database.
/// </summary>
/// <remarks>
///     This class is not used as a "root" document inside our database.
///     Instead, it's being embedded into our "Articles" document.
/// </remarks>
public class Supplier
{
    #region Properties

    /// <summary>
    ///     Name of the supplier.
    /// </summary>
    [BsonElement("supplier")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public BsonString Name { get; set; }

    /// <summary>
    ///     Gln of the supplier.
    /// </summary>
    [BsonElement("gln")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public BsonString Gln { get; set; }

    #endregion
}

/// <summary>
///     Class representing a single property for an article in the database.
/// </summary>
/// <remarks>
///     This class is not used as a "root" document inside our database.
///     Instead, it's being embedded into our "Articles" document.
/// </remarks>
public class ArticleProperty
{
    #region Properties

    /// <summary>
    ///     Type of the property.
    /// </summary>
    [BsonElement("type")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public BsonString Type { get; set; }

    /// <summary>
    ///     Value of the property.
    /// </summary>
    [BsonElement("value")]
    [BsonIgnoreIfNull]
    [BsonIgnoreIfDefault]
    public BsonString Value { get; set; }

    #endregion
}

Now, I need to aggregate on this collection, and I'm strugling already with the basics:

// Build the aggregation using the fluent api.
var aggregation = collection.Aggregate()
    .Unwind(x => x.Properties)
    .Group(x => new { x.Properties );

Right now, I only try to group on properties, like in the aggregation but this results in the following error:

CS0411 The type arguments for method 'IAggregateFluent<BsonDocument>.Group<TNewResult>(ProjectionDefinition<BsonDocument, TNewResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

But even when that's working, I also need the extra properties as count and addToSet. Can someone help me with this one. I'm already searching for 2 days on this and it's driving me crazy.

Edit

I've found that a group followed by an unwind does work in C#, but why doesn't it work with the unwind first? I really need the unwind to happen first.

Edit 2 I've managed to get a small portion working, inclusive the group command. See the code below:

var aggregation = collection.Aggregate()
    .Unwind<Smartphone, UnwindedSmartphone>(x => x.Properties)
    .Group(key => key.Property, g => new
    {
        Id = g.Key,
        Count = g.Count()
    });

However, I need some more information on how to push the Articles property from the aggregation command.

1 Answer 1

7

I've found the solution to the problem. The following C# code should be used:

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
        }).Distinct()
    })
    .SortBy(x => x.Id);

This gives me the following aggregation:

db.smartphones.aggregate([{ "$unwind" : "$props" }, { "$group" : { "_id" :     "$props", "Count" : { "$sum" : 1 }, "Articles" : { "$addToSet" : { "Name" :     "$name" } } } }, { "$sort" : { "_id" : 1 } }])
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.