Using LINQ to Query MongoDB in a .NET Core Application
Rate this tutorial
If you've been keeping up with my series of tutorials around .NET Core and MongoDB, you'll likely remember that we explored using the Find operator to query for documents as well as an aggregation pipeline. Neither of these previously explored subjects are too difficult, but depending on what you're trying to accomplish, they could be a little messy. Not to mention, they aren't necessarily "the .NET way" of doing business.
This is where LINQ comes into the mix of things!
With Language Integrated Queries (LINQ), we can use an established and well known C# syntax to work with our MongoDB documents and data.
In this tutorial, we're going to look at a few LINQ queries, some as a replacement to simple queries using the MongoDB Query API and others as a replacement to more complicated aggregation pipelines.
To be successful with this tutorial, you should already have the following ready to go:
- .NET Core 6+
- MongoDB Atlas, the free tier or better
When it comes to MongoDB Atlas, you'll need to have a cluster deployed and properly configured with user roles and network rules. If you need help with this, take a look at my previous tutorial on the subject. You will also need the sample datasets installed.
While this tutorial is part of a series, you don't need to have read the others to be successful. However, you'd be doing yourself a favor by checking out the other ways you can do business with .NET Core and MongoDB.
To keep this tutorial simple and easy to understand, we're going to create a new console application and work from that.
Execute the following from the CLI to create a new project that is ready to go with the MongoDB driver:
1 dotnet new console -o MongoExample 2 cd MongoExample 3 dotnet add package MongoDB.Driver
For this tutorial, our MongoDB Atlas URI string will be stored as an environment variable on our computer. Depending on your operating system, you can do something like this:
1 export ATLAS_URI="YOUR_ATLAS_URI_HERE"
The Atlas URI string can be found in your MongoDB Atlas Dashboard after clicking the "Connect" button and choosing your programming language.
Open the project's Program.cs file and add the following C# code:
1 using MongoDB.Bson; 2 using MongoDB.Driver; 3 using MongoDB.Driver.Linq; 4 5 MongoClientSettings settings = MongoClientSettings.FromConnectionString( 6 Environment.GetEnvironmentVariable("ATLAS_URI") 7 ); 8 9 settings.LinqProvider = LinqProvider.V3; 10 11 MongoClient client = new MongoClient(settings);
In the above code, we are explicitly saying that we want to use LINQ Version 3 rather than Version 2, which is the default in MongoDB. While you can accomplish many LINQ-related tasks in MongoDB with Version 2, you'll get a much better experience with Version 3.
We're going to take it slow and work our way up to bigger and more complicated queries with LINQ.
In case you've never seen the "sample_mflix" database that is part of the sample datasets that MongoDB offers, it's a movie database with several collections. We're going to focus strictly on the "movies" collection which has documents that look something like this:
1 { 2 "_id": ObjectId("573a1398f29313caabceb515"), 3 "title": "Batman", 4 "year": 1989, 5 "rated": "PG-13", 6 "runtime": 126, 7 "plot": "The Dark Knight of Gotham City begins his war on crime with his first major enemy being the clownishly homicidal Joker.", 8 "cast": [ "Michael Keaton", "Jack Nicholson", "Kim Basinger" ] 9 }
There are quite a bit more fields to each of the documents in that collection, but the above fields are enough to get us going.
To use LINQ, we're going to need to create mapped classes for our collection. In other words, we won't want to be using
BsonDocument
when writing our queries. At the root of your project, create a Movie.cs file with the following C# code:1 using MongoDB.Bson; 2 using MongoDB.Bson.Serialization.Attributes; 3 4 [ ]5 public class Movie { 6 7 [ ]8 [ ]9 public string Id { get; set; } 10 11 [ ]12 public string Title { get; set; } = null!; 13 14 [ ]15 public int Year { get; set; } 16 17 [ ]18 public int Runtime { get; set; } 19 20 [ ]21 [ ]22 public string Plot { get; set; } = null!; 23 24 [ ]25 [ ]26 public List<string> Cast { get; set; } = null!; 27 28 }
We used a class like the above in our previous tutorials. We've just defined a few of our fields, mapped them to BSON fields in our database, and told our class to ignore any extra fields that may exist in our database that we chose not to define in our class.
Let's say that we want to return movies that were released between 1980 and 1990. If we weren't using LINQ, we'd be doing something like the following in our Program.cs file:
1 using MongoDB.Bson; 2 using MongoDB.Driver; 3 4 MongoClient client = new MongoClient( 5 Environment.GetEnvironmentVariable("ATLAS_URI") 6 ); 7 8 IMongoCollection<Movie> moviesCollection = client.GetDatabase("sample_mflix").GetCollection<Movie>("movies"); 9 10 BsonDocument filter = new BsonDocument{ 11 { 12 "year", new BsonDocument{ 13 { "$gt", 1980 }, 14 { "$lt", 1990 } 15 } 16 } 17 }; 18 19 List<Movie> movies = moviesCollection.Find(filter).ToList(); 20 21 foreach(Movie movie in movies) { 22 Console.WriteLine($"{movie.Title}: {movie.Plot}"); 23 }
However, since we want to use LINQ, we can update our Program.cs file to look like the following:
1 using MongoDB.Driver; 2 using MongoDB.Driver.Linq; 3 4 MongoClientSettings settings = MongoClientSettings.FromConnectionString( 5 Environment.GetEnvironmentVariable("ATLAS_URI") 6 ); 7 8 settings.LinqProvider = LinqProvider.V3; 9 10 MongoClient client = new MongoClient(settings); 11 12 IMongoCollection<Movie> moviesCollection = client.GetDatabase("sample_mflix").GetCollection<Movie>("movies"); 13 14 IMongoQueryable<Movie> results = 15 from movie in moviesCollection.AsQueryable() 16 where movie.Year > 1980 && movie.Year < 1990 17 select movie; 18 19 foreach(Movie result in results) { 20 Console.WriteLine("{0}: {1}", result.Title, result.Plot); 21 }
In the above code, we are getting a reference to our collection and creating a LINQ query. To break down the LINQ query to see how it relates to MongoDB, we have the following:
- The "WHERE" operator is the equivalent to doing a "$MATCH" or a filter within MongoDB. The documents have to match the criteria in this step.
- The "SELECT" operator is the equivalent to doing a projection or using the "$PROJECT" operator. We're defining which fields should be returned from the query—in this case, all fields that we've defined in our class.
To diversify our example a bit, we're going to change the match condition to match within an array, something non-flat.
Change the LINQ query to look like the following:
1 var results = 2 from movie in moviesCollection.AsQueryable() 3 where movie.Cast.Contains("Michael Keaton") 4 select new { movie.Title, movie.Plot };
A few things changed in the above code along with the filter. First, you'll notice that we are matching on the
Cast
array as long as "Michael Keaton" exists in that array. Next, you'll notice that we're doing a projection to only return the movie title and the movie plot instead of all other fields that might exist in the data.We're going to make things slightly more complex now in terms of our query. This time we're going to do what would have been a MongoDB aggregation pipeline, but this time using LINQ.
Change the C# code in the Program.cs file to look like the following:
1 using MongoDB.Driver; 2 using MongoDB.Driver.Linq; 3 4 MongoClientSettings settings = MongoClientSettings.FromConnectionString( 5 Environment.GetEnvironmentVariable("ATLAS_URI") 6 ); 7 8 settings.LinqProvider = LinqProvider.V3; 9 10 MongoClient client = new MongoClient(settings); 11 12 IMongoCollection<Movie> moviesCollection = client.GetDatabase("sample_mflix").GetCollection<Movie>("movies"); 13 14 var results = 15 from movie in moviesCollection.AsQueryable() 16 where movie.Cast.Contains("Ryan Reynolds") 17 from cast in movie.Cast 18 where cast == "Ryan Reynolds" 19 group movie by cast into g 20 select new { Cast = g.Key, Sum = g.Sum(x => x.Runtime) }; 21 22 foreach(var result in results) { 23 Console.WriteLine("{0} appeared on screen for {1} minutes!", result.Cast, result.Sum); 24 }
In the above LINQ query, we're doing a series of steps, just like stages in an aggregation pipeline. These stages can be broken down like the following:
- Match all documents where "Ryan Reynolds" is in the cast.
- Unwind the array of cast members so the documents sit adjacent to each other. This will flatten the array for us.
- Do another match on the now smaller subset of documents, filtering out only results that have "Ryan Reynolds" in them.
- Group the remaining results by the cast, which will only be "Ryan Reynolds" in this example.
- Project only the group key, which is the cast member, and the sum of all the movie runtimes.
If you haven't figured it out yet, what we attempted to do was determine the total amount of screen time Ryan Reynolds has had. We isolated our result set to only documents with Ryan Reynolds, and then we summed the runtime of the documents that were matched.
While the full scope of the MongoDB aggregation pipeline isn't supported with LINQ, you'll be able to accomplish quite a bit, resulting in a lot cleaner looking code. To get an idea of the supported operators, take a look at the MongoDB LINQ documentation.
You just got a taste of LINQ with MongoDB in your .NET Core applications. While you don't have to use LINQ, as demonstrated in a few previous tutorials, it's common practice amongst C# developers.
Top Comments in Forums
There are no comments on this article yet.