Data aggregations are very helpful whenever you need to create metrics or get more insights from the data. Furthermore, joining multiple MongoDb collections may provide more meaningful results. This article will be a light introduction on how to run these on MongoDb using .NET Driver and LINQ.
Notes Before Starting
This article is the third article, continuing Part 1: How to search good places to travel (MongoDb LINQ & .NET Core), and Part 2: Paging in MongoDB – How to actually avoid poor performance? All share the same GitHub project, each of them having specific code methods. Please follow the steps presented in Part 1 on how to install and configure MongoDb, as well as, the section about the initial data upload.
To Install
Here are all the things needed to be installed:
Run Project
In brief, once the MongoDb installation is complete, run the next steps:
- Run the import.cmd from Data\Import folder - Clone or download the project (https://github.com/fpetru/WebApiQueryMongoDb) - the attached archive does not include the data files (it includes just the solution)
- Open solution in Visual Studio, compile and run
GroupBy in MongoDb
MongoDb has for a long time an aggregation framework and with the .NET Driver, its features fit nice into standard LINQ operators (such as: $project
=> Select()
, $limit
=> Take()
, $match
=> Where()
, etc.). LINQ is ideally suited to building up a pipeline of operations and to submit to the server as a single command.
In our example, grouping by City
, and finding all available travel items would look like this:
public async Task<IEnumerable<object>> GetTravelDestinations(string cityName)
{
var groupTravelItemsByCity = _context.TravelItems.AsQueryable()
.Where(city => string.IsNullOrEmpty(cityName)
|| city.City.Contains(cityName))
.GroupBy(s => new { s.City })
.Select(n => new
{
value = n.Key.City,
data = n.Count()
});
return await groupTravelItemsByCity.Take(100).ToListAsync();
}
The results are made available to external applications using the Get
function from controller:
[NoCache]
[HttpGet("{type}")]
public async Task<IActionResult> Get(string type, [FromQuery]string city)
{
if (!string.IsNullOrEmpty(city) && city.Length > 1)
return Ok(await _displayRepository.GetTravelDestinations(city));
return NotFound();
}
I have used IActionResult
interface to be able to return 404 in case the request does not follow the requirements: city
needs to be provided, with a minimum length of 2 characters.
More About Aggregation in MongoDb
All standard LINQ to SQL aggregate operators are supported: Average
, Count
, Max
, Min
, and Sum
. We could also group by
using more attributes. Here is an example, grouping first after City
and then after each associated Action
, and also using the aggregate functions (like Count
, Max
and Min
):
public async Task<IEnumerable<object>> GetTravelItemStat()
{
var groupTravelItemsByCityAndAction = _context.TravelItems.AsQueryable()
.Where(s => s.City == "Paris" || s.City == "Berlin")
.GroupBy(s => new { s.City, s.Action })
.Select(n => new
{
Location = n.Key,
Count = n.Count(),
MaxPrice = n.Max(p => p.Price),
MinPrice = n.Min(p => p.Price)
});
return await groupTravelItemsByCityAndAction.Take(100).ToListAsync();
}
Join Support from MongoDb
Here is an example of running a join
between 2 collections, using the LINQ as a query expression. It is a LEFT join
query, starting with the first (left-most) collection (TravelItems
) and then matching second (right-most) collection (CityExtended
).
This means that it filters resultant items (CityExtended
). The overall result could be projected in an anonymous type (our example below), or in a new entity:
public async Task<IEnumerable<object>> GetTravelItemsOfCityAsync(string cityName)
{
var query = from travelItem in _context.TravelItems.AsQueryable()
join city in _context.CityExtended.AsQueryable()
on travelItem.City equals city.Name
into CityExtendedMatchingItems
where (travelItem.City == cityName)
select new
{
Action = travelItem.Action,
Name = travelItem.Name,
FirstCityMatched = CityExtendedMatchingItems.First(),
};
return await query.Take(10).ToListAsync();
}
Access the WebApi using JavaScript
Accessing the webapi from a simple static HTML with JavaScript, could look like this:
In order to make available an HTML file within the project, we would first need to enable the access to the static files (e.g. HTML, CSS, images). These are typically located in the web root (<content-root>/wwwroot) folder. For development, we could set this as project’s web root – see method (UseContentRoot
):
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();
In order for static files to be served, we also need to configure the Middleware to add static files to the pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseStaticFiles();
}
Once the static files are enabled, we will use jQuery library to access the WebApi, and also to display the results (with the autocomplete widget). This code was originally available from here.)
Here is the full JavaScript code for both autocomplete function, and WebApi server calling.
<script type="text/javascript">
$(document).ready(function () {
$('#autocomplete').autocomplete({
minLength: 2,
source: function (request, response) {
var webApiUrl = './api/display/GroupBy' + '?city=' + request.term;
$.getJSON(webApiUrl, request, function (data, status, xhr) {
response(data);
});
},
});
});
</script>
You Might Be Interested Also