13 Comments
  •   Posted in: 
  • UWP

screenshot_11092012_072155

Let’s start with the bad news: If yourWinRT XAML app uses the GridView with grouping, you’re app is quite likely going to hang and crash on Windows RT tablets. It doesn’t matter if the GridView has thousands of items or 50, it will crash. Just start the app on a Windows RT tablet, do some navigation between the GridView and item details and you’ll notice that 1) the application will stop responding to touch and 2) it just closes. And all this time the app will work just fine on the simulator and on the desktop.

The problem

When the grouping is enabled on the GridView, the virtualization doesn’t work. And when the virtualization doesn’t work, the GridView will have severe performance problems and your app will crash. Without the virtualization your app will use much more memory but the problem isn’t entirely caused by this: I’ve seen apps taking less than 70 MB hang and crash when the grouping has been enabled.

The crashing will happen when you navigate back to a page which has:

  • GridView with grouping enabled
  • NavigationCacheMode set to enabled

The solution

Never enable the grouping on the GridView. Without grouping the GridView can handle thousands and thousands of items. The performance will be great.

If you need to group the items, the solution is to do the groups manually:

  1. Put all the items into a single collection. The collection should contain not just the items but also the groups. For example here’s a collection with 5 items, from which 2 are groups:  2012, Movie 1, Movie 2, 2011, Movie 3.
  2. Use the GridView’s ItemTemplateSelector to display the items and groups differently.
  3. If you require Semantic zoom, create a separate collection which contains just the groups. So one collection with all the items and groups as described in 1 and, in addition to that, a collection with just the groups.

The app will not end up looking just like with the built-in grouping, but it will look good enough. And what’s important, it will not crash.

Let’s use the steps described above to transform a crashing WinRT XAML app to an app with great performance.

Example app

Here’s an example app which loads movie details from a Finnish Video On Demand service and displays them on a GridView, grouped by the release year:

screenshot_11092012_074014

It loads and displays 125 movies. It will perform great on the desktop but on the Windows RT tablet it will hang and crash. Before crashing the performance is already sluggish. But navigate few times (usually between 3 to 10 times) to the movie details and back and you’ll notice that the app will stop responding to touch and it will eventually crash.

When we fix the performance by creating the groups manually, we can show thousands movies in a single GridView and still have access to features like the semantic zoom. The trade-off is that the end result looks different: The group headers are part of the grid.

screenshot_11092012_075755

Creating the groups manually

The sample app’s code shows all the steps required to create the groups manually but let’s go through some of the basics.

The templates

First of all, you mush have two templates: One for the items (movies) and one for the group headers (years):

<DataTemplate x:Key="MovieTemplate"> 
    <Grid HorizontalAlignment="Left" Width="250" Height="250"> 
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}"> 
            <Image Source="{Binding Cover}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/> 
        </Border> 
        <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}"> 
            <TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/> 
            <TextBlock Text="{Binding Year}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/> 
        </StackPanel> 
    </Grid> 
</DataTemplate>

<DataTemplate x:Key="MovieCategoryTemplate"> 
    <Grid HorizontalAlignment="Left" Width="250" Height="250"> 
        <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}"> 
            <TextBlock Text="{Binding}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/> 
        </StackPanel> 
    </Grid> 
</DataTemplate>

You also need a TemplateSelector which can select the correct template based on the item:

public class MyTemplateSelector : DataTemplateSelector 
{ 
    protected override DataTemplate SelectTemplateCore(object item, DependencyObject container) 
    { 
        var movie = item as MovieInfo; 
        if (movie != null) 
            return (DataTemplate) App.Current.Resources["MovieTemplate"];

        return (DataTemplate)App.Current.Resources["MovieCategoryTemplate"]; 
    } 
}

The GridView

The GridView shouldn’t set the ItemTemplate, instead it points to the TemplateSelector:

<GridView x:Name="itemGridView"
            TabIndex="1"
            Grid.RowSpan="2"
            Padding="116,157,40,46"
            ItemsSource="{Binding Items}"
            ItemTemplateSelector="{StaticResource MyTemplateSelector}"
            SelectionMode="None"
            IsSwipeEnabled="false" IsItemClickEnabled="True"
            ItemClick="ItemView_ItemClick"> 
</GridView>

The data

In the example above, the GridView uses an ObservableCollection called “Items” as the item source. This collection shouldn’t be grouped by any way. Instead it should contain both the movie groups and the movies:

public ObservableCollection<object> Items { get; set; }
...
var moviesByYear = movies.GroupBy(x => x.Year);
foreach (var group in moviesByYear)
{
    this.Items.Add(group.Key.ToString());

    foreach (var movieInfo in group)
    {
        this.Items.Add(movieInfo);
    }
}

Semantic zoom

If semantic zoom is required, the movie data should be split into two collections: One containing both the movies and the years and the other containing only the years:

public ObservableCollection<object> Items { get; set; }
public ObservableCollection<string> Groups { get; set; }
...		
var moviesByYear = movies.GroupBy(x => x.Year);
foreach (var group in moviesByYear)
{
    // The group is added to two collections: Collection containing only the groups and the collection containing movies and the groups
    this.Groups.Add(group.Key.ToString());
    this.Items.Add(group.Key.ToString());

    // The movies are only added to the collection containing movies and groups
    foreach (var movieInfo in group)
    {
        this.Items.Add(movieInfo);
    }
}

The ZoomedOutView should use the Groups as ItemsSource:

<SemanticZoom.ZoomedOutView> 
               <GridView VerticalAlignment="Center" Margin="200,-100,0,0" x:Name="ZoomedOutGrid" ItemsSource="{Binding Groups}"
                         SelectionMode="None">

screenshot_11092012_083312

In order for the semantic zoom to work correctly the ZoomedInView’s GridView should be manually scrolled to the selected group:

private void SemanticZoom_OnViewChangeStarted(object sender, SemanticZoomViewChangedEventArgs e) 
{ 
    if (e.IsSourceZoomedInView) 
        return;

    this.itemGridView.Opacity = 0; 
}
private void SemanticZoom_OnViewChangeCompleted(object sender, SemanticZoomViewChangedEventArgs e) 
{ 
    if (e.IsSourceZoomedInView) 
        return;

    try
    { 
        var selectedGroup = e.SourceItem.Item as string; 
        if (selectedGroup == null) 
            return;

        itemGridView.ScrollIntoView(selectedGroup, ScrollIntoViewAlignment.Leading); 
    } 
    finally
    { 
        this.itemGridView.Opacity = 1; 
    } 
}

We play with the Opacity to get rid of some flickering.

It’s also possible to zoom out the view when a user clicks a group header, making the GridView to behave like a JumpList in Windows Phone:

void ItemView_ItemClick(object sender, ItemClickEventArgs e) 
{ 
    if (e.ClickedItem is MovieInfo) 
        this.Frame.Navigate(typeof (MovieDetailsPage)); 
    else
        this.Zoom.IsZoomedInViewActive = false; 
}

Conclusion and the source code

The GridView control is a great way to show lots of items to the user. Unfortunately the built-in support for grouping will hang and crash your application on a Windows RT tablet. If grouping is required, create the groups manually.

The sample app (WinRT-GridView-XAML-Performance-Problems) is available from GitHub. By default it starts with the page which has good performance and doesn’t crash on a Windows RT tablet. To try out the version with built-in grouping turned on, change the start page of the app to BadPerformancePage.

21 Comments

image Based on the discussion on StackOverflow, I decided to test out the different IconicTileTemplate sizes. The idea was to try to find out:

  1. What are the correct dimensions for images used in the IconicTileTemplate
  2. What image is used when the iconic tile is in wide mode

The test images

I used the free Syncfusion Metro Studio to create the following images (the actual images have a transparent background):

110x110 pixels

image

202x202 pixels

image

132x202 pixels:

image

72x110 pixels:

image

72x72 pixels:

image

The emulator

I used the 720p emulator to run the tests.

Test run #1

image

  • Small tile: 110x110
  • Medium tile: 202x202
  • Support for large tiles enabled

Results:

The medium tile image is used when the tile is in default size:

image

The small tile image is used when the tile is in small size:

image

The medium tile image is used when the tile is in wide mode:

image

When the tile is in wide mode and it has large content set, the small tile image is used:

image

Test run #2

image

  • Small tile: 72x110
  • Medium tile: 132x202
  • Support for large tiles enabled

Results:

The medium tile image is used when the tile is in default size:

image

The small tile image is used when the tile is in small size:

image

The medium tile image is used when the tile is in wide mode:

image

When the tile is in wide mode and it has large content set, the small tile image is used:

image

Test run #3

image

  • Small tile: 72x72
  • Medium tile: 132x202
  • Support for large tiles enabled

Results:

Tile in wide mode, large content set:

image

Conclusion

The first question was: What are the correct dimensions for images used in the IconicTileTemplate?

As the answer on StackOverflow points out, the images used in IconicTileTemplate should have the following sizes:

  • Small: 72x110
  • Medium: 132x202

When we compare the 132x202 and 202x202 images, the difference is quite minimal: The latter one looks maybe more natural (more like the 1st party apps) and it’s smaller, but both the tiles have the correct aspect ratios:

image

The largest difference can be found when the tile is in wide mode and the tile has content. In these situations the Small image is used. Here’s a screenshot again with 110x110 image:

image

And here’s the one with 72x110:

image

And for comparison, this is the one with 72x72:

image

Only the 72x110 looks correct.

The second question was: What image is used when the iconic tile is in wide mode?

The answer depends on whether your tile has content set or not:

  • Without content: The medium tile image is displayed.
  • With content: The small tile image is displayed.

11 Comments

imageOne of the nice additions to the Windows Phone 8 is that the 3rd party apps can now update the phone’s lock screen. The apps can be grouped to two categories based on how they manipulate the lock screen:

  • Lock screen background image providers
  • Lock screen notification providers

In this tutorial we’ll focus on the notification providers. We’ll take an existing Windows Phone 7 app and upgrade it to use the new Windows Phone 8 lock screen features.

The old app

In this tutorial a Windows Phone 7 news reader app is upgraded to show notifications on the lock screen. The app already have live tiles which shows the count of unread news items and the title of the latest article. The app updates the live tile using the following code:

var tile in ShellTile.ActiveTiles.First(); 
var tileData = new StandardTileData() 
{ 
    Count = newNews.Count > 99 ? 99 : newNews.Count, 
    BackTitle = latestNews.Publisher, 
    BackContent = latestNews.Title 
};

tile.Update(tileData);

Upgrading to Windows Phone 8 (and fixing the problems caused by Async CTP 3)

Upgrading our app to Windows Phone 8 happens through the project’s context menu:

image

It’s quite likely that the upgrade goes smoothly and we have a working WP8 app without having to do any changes. Unless we’ve used the Async CTP3, in which case we’re going to see errors like these:

Cannot find all types required by the 'async' modifier. Are you targeting the wrong framework version, or missing a reference to an assembly?

The solution to these is to remove the reference to AsyncCtpLibrary_Phonefrom your projects and to add the package Microsoft.Bcl.Async through NuGet (make sure to select the “Include prereleases”):

image

Adding the lock screen notification capabilities to the app

Now that we’ve upgraded the app to Windows Phone 8, we’re ready to add the lock screen notification capabilities to the app. This requires only few actions:

  1. The WMAppManifest.xml is modified to include the Lock Screen notification capabilities
  2. A new 30x30 icon is added to the project

Modifying the manifest

First step is to modify the app’s manifest file. This happens by manually editing the WMAppManifest.xml. Right click the file (located in project’s Properties-folder) and select Open With – Source Code (Text) Editor:

image

We need to add a new Extensions element into the file and declare the notification capabilities there:

&lt;Extensions&gt; 
&nbsp; &lt;Extension ExtensionName="LockScreen_Notification_IconCount" ConsumerID="{111DFF24-AA15-4A96-8006-2BFF8122084F}" TaskID="_default" /&gt; 
&nbsp; &lt;Extension ExtensionName="LockScreen_Notification_TextField" ConsumerID="{111DFF24-AA15-4A96-8006-2BFF8122084F}" TaskID="_default" /&gt; 
&lt;/Extensions&gt;

These should be added just under the Tokens-element:

image

The new icon

In order to show notifications on the lock screen, the app must include a new 30x30 pixel sized icon. The documentation states that the “image must contain only white pixels plus some level of transparency.” So let’s add a new “lockscreen.png” to the root of the project and set it’s Build Action to “Content”:

image

We again have to modify the WMAppManifest.xml to include the new icon. When the app was upgraded to Windows Phone 8, the upgrade process added a new DeviceLockImageURI-property inside the Tokens-element. The lock screen icon is declared there:

<DeviceLockImageURI IsRelative="true" IsResource="false">lockscreen.png</DeviceLockImageURI>

The next step

The next step is to just sit back and enjoy. This feature doesn’t require any code changes.When the app updates the live tile, the lock screen is automatically updated too.

But.

This only happens if the user manually adds the app to the Settings – lock screen – “Choose apps to show quick status”:

image

image

In order for the application to show text notifications on the lock screen (in our case the title of the latest news item), the app must be set to show “detailed status”:

image

Testing

Now only thing left is to test the functionality. The app uses a scheduled agent to update the tile. But as we want to be sure that the lock screen is updated, we need to use the Visual Studio’s “Simulation Dashboard”to turn on the lock screen:

image

From the dashboard we can change the emulator to display the lock screen with a single click:

image

Now we can be sure that the lock screen is receiving its notifications:

image

The text content on the lock screen is the value from the “BackContent” propertyof the StandardTileData. The values from Title or BackTitleare not displayed.

Directing the user to make the app a lock screen application

Adding the support for lock screen notifications is rather easy. But because the feature requires a manual interaction from the user, it can be a good idea to push the user a little further. Instead of just asking them to navigate to the settings – lock screen, the following code automatically moves the user to the correct settings page:

await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings-lock:"));

Secondary tiles and notifications

The lock screen notifications are updated through the main tile.

Conclusion

The new lock screen functionalities of the Windows Phone 8 are a nice addition to the OS. And it doesn’t require much from the developer to add these new capabilities: A few lines of XML and a new icon are all that is required. There’s no code involved.

2 Comments
  •   Posted in: 
  • UWP

image

Callisto is excellent open source UI Control library for the Windows 8 Store apps. Caliburn.Micro is a powerful framework for building Windows Phone, Silverlight and Windows 8 Store apps. These helpers make it easier to combine Caliburn.Micro with the Callisto's dialogs (Flyouts).

Content

  • Helper for displaying settings dialogs on the Settings charm
  • Helper for displaying normal dialogs all around the screen

Install

The helpers are available as a source code through NuGet:

Install-Package CaliburnMicroWinRTCallistoHelpers

Installing the package will add a Dialogs-folder into your project. This folder contains the source code for the helpers.

Requirements

Your project must reference Callisto and the Caliburn.Micro must be set right. The helpers have been tested with Callisto 1.2.1.

To learn how to set up Caliburn.Micro for WinRT, you may refer to this tutorial.

Sample

A sample app showing the usage of the helpers is available from the GitHub repository.

Usage

  1. Create normal Caliburn.Micro view model (inheriting from Screen / Conductor) and the view for the view model
  2. Pass the type of the view model to the helper. The helper will create the view model and the view and it will display the dialog.

Normal dialog

Use DialogService.ShowDialog<TViewModel>() to display a dialog.

Parameters

  • PlacementMode (Required): The way the Callisto dialog works is that you provide it an UI control as a placement target. The dialog will open next to this UI control. PlacementMode defines if the dialog should be shown above, under, left or right of this UI control.
  • PlacementTarget (Required): The placement target.
  • onInitialize: Action which is executed before the dialog is shown.
  • onClose: Action which is executed after the dialog has been closed.

Example

Page's XAML:

<Button x:Name="ShowDialog" Content="Show Dialog" HorizontalAlignment="Center"/>

Page's View Model:

public void ShowDialog(FrameworkElement source) 
    { 
        DialogService.ShowDialog<DialogViewModel>(PlacementMode.Left, source); 
    }

Dialog's view model:

public class DialogViewModel : Screen
        { 
            public async void ShowMessage() 
            { 
                var dlg = new MessageDialog("Hello from Dialog"); 

                await dlg.ShowAsync(); 
            } 
        }

Dialog's View:

<UserControl 
        x:Class="caliburn_micro_winrt_getting_started.DialogView" 
        ... 
        Height="200" 
        Width="200"> 

        <Grid > 
            <TextBlock Text="Callisto Dialog" Foreground="Black" Style="{StaticResource SubheaderTextStyle}"/> 
            <Button x:Name="ShowMessage" Content="Show Message" Foreground="Black" HorizontalAlignment="Center" VerticalAlignment="Center"/> 
        </Grid> 
    </UserControl>
&nbsp;

Settings dialog

The settings dialog can be shown similar to a normal dialog:

DialogService.ShowSettings<SettingsViewModel>()

Here’s an example which shows how to initialize the view model before it’s shown (the initialization happen before the view model’s OnInitialize is called). It also shows how to to update the UI after the settings dialog has closed:

DialogService.ShowSettings<SettingsViewModel>(onInitialize: vm => vm.TestSetting = this.CurrentValue, onClosed: (vm, view) => CurrentValue = vm.TestSetting);

Adding the view model to settings pane

On the MainPage of your application, use the AddSetting-extension method:

private static bool initialized; 
        public MainPage() 
        { 
            this.InitializeComponent(); 

            if (initialized) return;

            SettingsPane.GetForCurrentView().CommandsRequested += CommandsRequested; 
            initialized = true; 
        }

        private void CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args) 
        { 
            args.AddSetting<SettingsViewModel>(); 
        }

Parameters

  • onInitialize: Action which is executed before the settings dialog is shown.
  • onClosed: Action which is execute after the dialog has been closed.
  • headerBrush: Can be used to control the color of the settings header
  • backgroundBrush: Can be used to control the background color of the settings page

Dialog's title is automatically determined from the ViewModel's DisplayName-property.

Example

View Model:

public sealed class SettingsViewModel : Screen 
    { 
        public SettingsViewModel() 
        { 
            this.DisplayName = "My Settings"; 
        }

        public bool TestSetting { get; set; } 
    }
&nbsp;

View:

<UserControl 
        x:Class="caliburn_micro_winrt_getting_started.SettingsView" 
        ... 
        > 

        <StackPanel> 
            <CheckBox Content="Test setting" x:Name="TestSetting" Margin="0 20 0 0" /> 
        </StackPanel> 
    </UserControl>
&nbsp;

Source code and sample

The source code for these helpers and the sample app are available from GitHub.

99 Comments

MongoDB version 2.2 was released in late August and the biggest change it brought was the addition of the Aggregation Framework. Previously the aggregations required the usage of map/reduce, which in MongoDB doesn’t perform that well, mainly because of the single-threaded Javascript-based execution.  The aggregation framework steps away from the Javascript and is implemented in C++, with an aim to accelerate performance of analytics and reporting up to 80 percent compared to using MapReduce.

The aim of this post is to show examples of running the MongoDB Aggregation Framework with the official MongoDB C# drivers.

Aggregation Framework and Linq

Even though the current version of the MongoDB C# drivers (1.6) supports Linq, the support doesn’t extend to the aggregation framework. It’s highly probable that the Linq-support will be added later on and there’s already some hints about this in the driver’s source code. But at this point the execution of the aggregations requires the usage of the BsonDocument-objects.

Aggregation Framework and GUIDs

If you use GUIDs in your documents, the aggregation framework doesn’t work. This is because by default the GUIDs are stored in binary format and the aggregations won’t work against documents which contain binary data.. The solution is to store the GUIDs as strings. You can force the C# drivers to make this conversion automatically by configuring the mapping. Given that your C# class has Id-property defined as a GUID, the following code tells the driver to serialize the GUID as a string:

BsonClassMap.RegisterClassMap<MyClass>(cm => 
{ 
    cm.AutoMap(); 
    cm.GetMemberMap(c => c.Id) 
      .SetRepresentation( 
          BsonType.String); 
});

The example data

These examples use the following documents:

> db.examples.find()
{ "_id" : "1", "User" : "Tom", "Country" : "Finland", "Count" : 1 }
{ "_id" : "2", "User" : "Tom", "Country" : "Finland", "Count" : 3 }
{ "_id" : "3", "User" : "Tom", "Country" : "Finland", "Count" : 2 }
{ "_id" : "4", "User" : "Mary", "Country" : "Sweden", "Count" : 1 }
{ "_id" : "5", "User" : "Mary", "Country" : "Sweden", "Count" : 7 }

Example 1: Aggregation Framework Basic usage

This example shows how the aggregation framework can be executed through C#. We’re not going run any calculations to the data, we’re just going to filter it by the User.

To run the aggregations, you can use either the MongoDatabase.RunCommand –method or the helper MongoCollection.Aggregate. We’re going to use the latter:

var coll = localDb.GetCollection("examples"); 
... 
coll.Aggregate(pipeline);

The hardest part when working with Aggregation Framework through C# is building the pipeline. The pipeline is similar concept to the piping in PowerShell. Each operation in the pipeline will make modifications to the data: the operations can for example filter, group and project the data. In C#, the pipeline is a collection of BsonDocument object. Each document represents one operation.

In our first example we need to do only one operation: $match. This operator will filter out the given documents. The following BsonDocument is a pipeline operation which filters out all the documents which don’t have User-field set to “Tom”.

var match = new BsonDocument 
                { 
                    { 
                        "$match", 
                        new BsonDocument 
                            { 
                                {"User", "Tom"} 
                            } 
                    } 
                };

To execute this operation we add it to an array and pass the array to the MongoCollection.Aggregate-method:

var pipeline = new[] { match }; 
var result = coll.Aggregate(pipeline);

The MongoCollection.Aggregate-method returns an AggregateResult-object. It’s ResultDocuments-property (IEnumarable<BsonDocument>) contains the documents which are the output of the aggregation. To check how many results there were, we can get the Count:

var result = coll.Aggregate(pipeline); 
Console.WriteLine(result.ResultDocuments.Count());

image

The result documents are BsonDocument-objects. If you have a C#-class which represent the documents, you can cast the results:

var matchingExamples = result.ResultDocuments 
    .Select(BsonSerializer.Deserialize<ExampleData>) 
    .ToList();

foreach (var example in matchingExamples) 
{ 
    var message = string.Format("{0} - {1}", example.User, example.Count); 
    Console.WriteLine(message); 
}

image

Another alternative is to use C#’s dynamic type. The following extension method uses JSON.net to convert a BsonDocument into a dynamic:

public static class MongoExtensions 
{ 
    public static dynamic ToDynamic(this BsonDocument doc) 
    { 
        var json = doc.ToJson(); 
        dynamic obj = JToken.Parse(json); 
        return obj; 
    } 
}

Here’s a way to convert all the result documents into dynamic objects:

var matchingExamples = result.ResultDocuments 
    .Select(x => x.ToDynamic()) 
    .ToList();

Example 2: Multiple filters & comparison operators

This example filters the data with the following criteria:

  • User: Tom
  • Count: >= 2
var match = new BsonDocument 
                { 
                    { 
                        "$match", 
                        new BsonDocument 
                            { 
                                {"User", "Tom"}, 
                                {"Count", new BsonDocument 
                                                   { 
                                                       { 
                                                           "$gte", 2 
                                                       } 
                                                   }} 
                            } 
                    } 
                };

The execution of this operation is identical to the first example:

var pipeline = new[] { match }; 
var result = coll.Aggregate(pipeline);
var matchingExamples = result.ResultDocuments 
    .Select(x => x.ToDynamic()) 
    .ToList();

Also the result are as expected:

foreach (var example in matchingExamples) 
{ 
    var message = string.Format("{0} - {1}", example.User, example.Count); 
    Console.WriteLine(message); 
}

image

Example 3: Multiple operations

In our first two examples, the pipeline was as simple as possible: It contained only one operation. This example will filter the data with the same exact criteria as the second example, but this time using two $match operations:

    • User: Tom
    • Count: >= 2

var match = new BsonDocument 
                { 
                    { 
                        "$match", 
                        new BsonDocument 
                            { 
                                {"User", "Tom"} 
                            } 
                    } 
                };
var match2 = new BsonDocument 
                { 
                    { 
                        "$match", 
                        new BsonDocument 
                            { 
                                {"Count", new BsonDocument 
                                                   { 
                                                       { 
                                                           "$gte", 2 
                                                       } 
                                                   }} 
                            } 
                    } 
                };

var pipeline = new[] { match, match2 };

The output stays the same:

image

The first operation “match” takes all the documents from the examples collection and removes every document which doesn’t match the criteria User = Tom. The output of this operation (3 documents) then moves to the second operation “match2” of the pipeline. This operation only sees those 3 documents, not the original collection. The operation filters out these documents based on its criteria and moves the result (2 documents) forward. This is where our pipeline ends and this is also our result.

Example 4: Group and sum

Thus far we’ve used the aggregation framework to just filter out the data. The true strength of the framework is its ability to run calculations on the documents. This example shows how we can calculate how many documents there are in the collection, grouped by the user. This is done using the $group-operator:

var group = new BsonDocument 
                { 
                    { "$group", 
                        new BsonDocument 
                            { 
                                { "_id", new BsonDocument 
                                             { 
                                                 { 
                                                     "MyUser","$User" 
                                                 } 
                                             } 
                                }, 
                                { 
                                    "Count", new BsonDocument 
                                                 { 
                                                     { 
                                                         "$sum", 1 
                                                     } 
                                                 } 
                                } 
                            } 
                  } 
                };

The grouping key (in our case the User-field) is defined with the _id. The above example states that the grouping key has one field (“MyUser”) and the value for that field comes from the document’s User-field ($User). In the $group operation the other fields are aggregate functions. This example defines the field “Count” and adds 1 to it for every document that matches the group key (_id).

var pipeline = new[] { group }; 
var result = coll.Aggregate(pipeline);

var matchingExamples = result.ResultDocuments 
    .Select(x => x.ToDynamic()) 
    .ToList();

foreach (var example in matchingExamples) 
{ 
    var message = string.Format("{0} - {1}", example._id.MyUser, example.Count); 
    Console.WriteLine(message); 
}

image

Note the format in which the results are outputted: The user’s name is accessed through _id.MyUser-property.

Example 5: Group and sum by field

This example is similar to example 4. But instead of calculating the amount of documents, we calculate the sum of the Count-fields by the user:

var group = new BsonDocument 
                { 
                    { "$group", 
                        new BsonDocument 
                            { 
                                { "_id", new BsonDocument 
                                             { 
                                                 { 
                                                     "MyUser","$User" 
                                                 } 
                                             } 
                                }, 
                                { 
                                    "Count", new BsonDocument 
                                                 { 
                                                     { 
                                                         "$sum", "$Count" 
                                                     } 
                                                 } 
                                } 
                            } 
                  } 
                };

The only change is that instead of adding 1, we add the value from the Count-field (“$Count”).

image

Example 6: Projections

This example shows how the $project operator can be used to change the format of the output. The grouping in example 5 works well, but to access the user’s name we currently have to point to the _id.MyUser-property. Let’s change this so that user’s name is available directly through UserName-property:

var group = new BsonDocument 
                { 
                    { "$group", 
                        new BsonDocument 
                            { 
                                { "_id", new BsonDocument 
                                             { 
                                                 { 
                                                     "MyUser","$User" 
                                                 } 
                                             } 
                                }, 
                                { 
                                    "Count", new BsonDocument 
                                                 { 
                                                     { 
                                                         "$sum", "$Count" 
                                                     } 
                                                 } 
                                } 
                            } 
                  } 
                };

var project = new BsonDocument 
                { 
                    { 
                        "$project", 
                        new BsonDocument 
                            { 
                                {"_id", 0}, 
                                {"UserName","$_id.MyUser"}, 
                                {"Count", 1}, 
                            } 
                    } 
                };

var pipeline = new[] { group, project };

The code removes the _id –property from the output. It adds the UserName-property, which value is accessed from field _id.MyUser. The projection operations also states that the Count-value should stay as it is.

var matchingExamples = result.ResultDocuments 
    .Select(x => x.ToDynamic()) 
    .ToList();

foreach (var example in matchingExamples) 
{ 
    var message = string.Format("{0} - {1}", example.UserName, example.Count); 
    Console.WriteLine(message); 
}

image

Example 7: Group with multiple fields in the keys

For this example we add a new row into our document collection, leaving us with the following:

{ "_id" : "1", "User" : "Tom", "Country" : "Finland", "Count" : 1 }
{ "_id" : "2", "User" : "Tom", "Country" : "Finland", "Count" : 3 }
{ "_id" : "3", "User" : "Tom", "Country" : "Finland", "Count" : 2 }
{ "_id" : "4", "User" : "Mary", "Country" : "Sweden", "Count" : 1 }
{ "_id" : "5", "User" : "Mary", "Country" : "Sweden", "Count" : 7 }
{ "_id" : "6", "User" : "Tom", "Country" : "England", "Count" : 3 }

This example shows how you can group the data by using multiple fields in the grouping key:

var group = new BsonDocument 
                { 
                    { "$group", 
                        new BsonDocument 
                            { 
                                { "_id", new BsonDocument 
                                             { 
                                                 { "MyUser","$User" }, 
                                                 { "Country","$Country" }, 
                                             } 
                                }, 
                                { 
                                    "Count", new BsonDocument 
                                                 { 
                                                     { "$sum", "$Count" } 
                                                 } 
                                } 
                            } 
                  } 
                };

var project = new BsonDocument 
                { 
                    { 
                        "$project", 
                        new BsonDocument 
                            { 
                                {"_id", 0}, 
                                {"UserName","$_id.MyUser"}, 
                                {"Country", "$_id.Country"}, 
                                {"Count", 1}, 
                            } 
                    } 
                };

var pipeline = new[] { group, project }; 
var result = coll.Aggregate(pipeline);

var matchingExamples = result.ResultDocuments 
    .Select(x => x.ToDynamic()) 
    .ToList();

foreach (var example in matchingExamples) 
{ 
    var message = string.Format("{0} - {1} - {2}", example.UserName, example.Country, example.Count); 
    Console.WriteLine(message); 
}

image

Example 8: Match, group and project

This example shows how you can combine many different pipeline operations. The data is first filtered ($match) by User=Tom, then grouped by the Country (“$group”) and finally the output is formatted into a readable format ($project).

Match:

var match = new BsonDocument 
                { 
                    { 
                        "$match", 
                        new BsonDocument 
                            { 
                                {"User", "Tom"} 
                            } 
                    } 
                };

Group:

var group = new BsonDocument 
                { 
                    { "$group", 
                        new BsonDocument 
                            { 
                                { "_id", new BsonDocument 
                                             { 
                                                 { "Country","$Country" }, 
                                             } 
                                }, 
                                { 
                                    "Count", new BsonDocument 
                                                 { 
                                                     { "$sum", "$Count" } 
                                                 } 
                                } 
                            } 
                  } 
                };

Project:

var project = new BsonDocument 
                { 
                    { 
                        "$project", 
                        new BsonDocument 
                            { 
                                {"_id", 0}, 
                                {"Country", "$_id.Country"}, 
                                {"Count", 1}, 
                            } 
                    } 
                };

Result:

var pipeline = new[] { match, group, project }; 
var result = coll.Aggregate(pipeline);

var matchingExamples = result.ResultDocuments 
    .Select(x => x.ToDynamic()) 
    .ToList();

foreach (var example in matchingExamples) 
{ 
    var message = string.Format("{0} - {1}", example.Country, example.Count); 
    Console.WriteLine(message); 
}

image

More

There are many other interesting operators in the MongoDB Aggregation Framework, like $unwind and $sort. The usage of these operators is identical to ones we used above so it should be possible to copy-paste one of the examples and use it as a basis for these other operations.

Links