Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a fluent Aggregation using MongoDB C# Driver 2.0

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.

like image 370
Complexity Avatar asked Aug 20 '15 06:08

Complexity


People also ask

Is MongoDB good for aggregation?

As with many other database systems, MongoDB allows you to perform a variety of aggregation operations. These allow you to process data records in a variety of ways, such as grouping data, sorting data into a specific order, or restructuring returned documents, as well as filtering data as one might with a query.

How aggregation is performed in MongoDB?

In MongoDB, aggregation operations process the data records/documents and return computed results. It collects values from various documents and groups them together and then performs different types of operations on that grouped data like sum, average, minimum, maximum, etc to return a computed result.

Is MongoDB aggregation fast?

On large collections of millions of documents, MongoDB's aggregation was shown to be much worse than Elasticsearch. Performance worsens with collection size when MongoDB starts using the disk due to limited system RAM. The $lookup stage used without indexes can be very slow.

What is aggregation in MongoDB explain with example?

An aggregation pipeline consists of one or more stages that process documents: Each stage performs an operation on the input documents. For example, a stage can filter documents, group documents, and calculate values. The documents that are output from a stage are passed to the next stage.


1 Answers

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 } }])
like image 194
Complexity Avatar answered Sep 20 '22 05:09

Complexity