Explore Developer Center's New Chatbot! MongoDB AI Chatbot can be accessed at the top of your navigation to answer all your MongoDB questions.

Introducing MongoDB 8.0, the fastest MongoDB ever!
MongoDB Developer
C#
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Languageschevron-right
C#chevron-right

Query Your Data With ASP.NET Core, OData, and the MongoDB Entity Framework Core Provider

Markus Wildgruber7 min read • Published May 31, 2024 • Updated Jul 08, 2024
C#
Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Among the good news of MongoDB.local NYC '24, was the announcement that MongoDB Entity Framework Core Provider has been made globally available. The provider integrates with Entity Framework and empowers .NET developers to easily access data in MongoDB databases with a well known framework.
This post shows how you can set up the Provider in a C# project and build an API with ASP.NET Core that offers an OData compatible endpoint. For those new to OData, this is a protocol that is used to build queryable REST APIs. User interfaces can access these APIs and use them to provide tabular views that offer a rich set of user interactions, like filtering, sorting or paging. As OData is an open standard, there is a wide variety of tools that can access OData compatible endpoints, e.g. Microsoft Excel or Microsoft Power BI.
If you want to follow along with the samples in this post, we create an endpoint that queries the movies in the sample_flix-database. The easiest way to get an instance of this sample database is to load the sample data in a MongoDB Atlas cluster. If you do not have a cluster already, you can set up a free MongoDB Atlas M0 cluster (see this link for a guide).
While this post will use a newly created ASP.NET Core Web API project, it is perfectly fine to add OData to offer the endpoints in addition to existing REST endpoints. When creating a new project, you might want to enable OpenAPI support for an easy way to test the API later on. Also, the new project should use controllers instead of a minimal API.
Additional information wizard showing default values

Create the Entity Framework DbContext

After setting up the ASP.NET Core project, we will first define the Entity Framework DbContext and then set it up with the MongoDB Entity Framework Core Provider.
The first step is to add the MongoDB Entity Framework Core Provider package to the project either through the UI or the dotnet CLI:
1dotnet add package MongoDB.EntityFrameworkCore
Then we define the entity class that contains the properties that we want to read from the database:
1// requires using MongoDB.Bson;
2public class Movie
3{
4 public string Id { get; set; } = ObjectId.GenerateNewId().ToString();
5 public string Title { get; set; } = string.Empty;
6 public string? Plot { get; set; }
7 public IEnumerable<string>? Genres { get; set; }
8 public IEnumerable<string>? Cast { get; set; }
9 // additional properties as required
10}
Some of the properties (e.g. Plot) are nullable reference types. This asserts that missing values in the database do not result in an error when reading the documents.
The next step is to set up the DbContext and add the configuration for the entity. This includes binding the entity to a collection and, as we are working with existing documents that follow a different naming convention, mapping the property names of the document to the ones of the .NET POCO:
1// requires using Microsoft.EntityFrameworkCore;
2// requires using MongoDB.Bson;
3// requires using MongoDB.EntityFrameworkCore.Extensions;
4
5public class MflixDbContext : DbContext
6{
7 public MflixDbContext(DbContextOptions options)
8 : base(options)
9 {
10 }
11
12 public DbSet<Movie> Movies { get; init; }
13
14 protected override void OnModelCreating(ModelBuilder modelBuilder)
15 {
16 base.OnModelCreating(modelBuilder);
17 var movieEntity = modelBuilder.Entity<Movie>();
18 movieEntity.ToCollection("movies");
19 movieEntity.Property(x => x.Id)
20 .HasElementName("_id")
21 .HasConversion<ObjectId>();
22 movieEntity.Property(x => x.Title).HasElementName("title");
23 movieEntity.Property(x => x.Plot).HasElementName("plot");
24 movieEntity.Property(x => x.Genres).HasElementName("genres");
25 movieEntity.Property(x => x.Cast).HasElementName("cast");
26 }
27}
The Id property deserves special attention: in the MongoDB document, the _id property is stored as an ObjectId. In an OData interface, using strings instead of an ObjectId as the identifier value is much easier. By adding value conversion, we can have the best of both worlds: ObjectIds in MongoDB and strings in .NET.
To finalize the set up of the MongoDB Entity Framework Core Provider, we register the client, database, and MflixDbContext in the IoC container:
1// requires using Microsoft.EntityFrameworkCore;
2// requires using MongoDB.Driver;
3// requires using MongoDBEFODataVerify;
4
5builder.Services.AddSingleton<IMongoClient>(prov =>
6{
7 // Retrieve connection string from configuration
8 return new MongoClient("<your MongoDB connection string>");
9});
10builder.Services.AddSingleton(prov =>
11{
12 var client = prov.GetRequiredService<IMongoClient>();
13 // Retrieve database name from configuration
14 return client.GetDatabase("sample_mflix");
15});
16builder.Services.AddDbContext<MflixDbContext>((prov, options) =>
17{
18 var database = prov.GetRequiredService<IMongoDatabase>();
19 options.UseMongoDB(
20 database.Client,
21 database.DatabaseNamespace.DatabaseName);
22});
If you are only using Entity Framework, it would be sufficient to register MflixDbContext and create the client in the factory method. The above sample code registers the client and database instance separately so that you can inject them independently if required.
With the DbContext set up, we could also use controller scaffolding in order to generate the code for REST controllers if needed. However, for the sake of this post we will now create an OData endpoint from scratch.

Add OData to the project

The first step when adding OData to a project is to reference the Microsoft.AspNetCore.OData package, either through the UI or using the dotnet CLI:
1dotnet add package Microsoft.AspNetCore.OData
Next, we add a method that defines the EDM model for the OData interface in Program.cs:
1// requires using Microsoft.OData.Edm;
2// requires using Microsoft.OData.ModelBuilder;
3
4IEdmModel GetEdmModel()
5{
6 var model = new ODataConventionModelBuilder();
7 model.EnableLowerCamelCase();
8 model.EntitySet<Movie>("Movies");
9 return model.GetEdmModel();
10}
EnableLowerCamelCase changes the naming convention of the properties to start with a lowercase character. This matches the behavior of JSON serialization in a standard REST API.
We then register the OData components with the IoC container by adding the following code in Program.cs:
1// requires using Microsoft.OData;
2
3// This should be already in the code
4builder.Services
5 .AddControllers()
6// This is new
7 .AddOData(options => options
8 .Select()
9 .Filter()
10 .OrderBy()
11 .Count()
12 .SetMaxTop(100)
13 .AddRouteComponents("odata", GetEdmModel()));
The code above enables various OData operations and restricts the maximum number of entities a client can request to prevent an overly large result set.
Lastly, we add a controller that provides the OData endpoint for our query. The name of the controller should be MoviesController to match the registration of the entity set in GetEdmModel:
1// requires using Microsoft.AspNetCore.OData.Query;
2// requires using Microsoft.AspNetCore.OData.Routing.Controllers;
3
4public class MoviesController : ODataController
5{
6 private readonly MflixDbContext _dbContext;
7
8 public MoviesController(MflixDbContext dbContext)
9 {
10 _dbContext = dbContext;
11 }
12
13 [EnableQuery(
14 PageSize = 1000,
15 AllowedOrderByProperties = "title, plot")]
16 public IQueryable<Movie> Get()
17 {
18 return _dbContext.Movies;
19 }
20}
We inject an instance of MflixDbContext into the controller to retrieve the movies and mark the Get method with the EnableQuery attribute. The PageSize property allows us to set the maximum number of results sent to the client. Using AllowedOrderByProperties restricts the properties that can be used in sort operations.
The name must match the name that is serialized - lower camel case in our sample as we called EnableLowerCamelCase in GetEdmModel earlier. There are various other options that you can use to adjust the behavior of the query endpoint. Please see the ASP.NET Core OData documentation for details.

Test the endpoint

Now we are ready to test the OData endpoint and start it in the debugger. If you have enabled OpenAPI and Swagger UI in your project, you can use Swagger UI for a simple test. This is enough to check basic readiness of the endpoint and serves as a good starting point.
For more sophisticated requests, either enter the OData URL in a browser or use a tool like Postman or curl with OData URLs.
A simple request to https://localhost:\<YOUR PORT\>/odata/Movies should return 10 movies in the following format:
1{
2 "@odata.context": "https://localhost:7104/odata/$metadata#Movies",
3 "value": [
4 {
5 "id": "573a1390f29313caabcd4135",
6 "title": "Blacksmith Scene",
7 "plot": "Three men hammer on an anvil...",
8 "genres": [
9 "Short"
10 ],
11 "cast": [
12 "Charles Kayser",
13 "John Ott"
14 ]
15 },
16 // 999 more results
17 ],
18 "@odata.nextLink": "https://localhost:7104/odata/Movies?$skip=10"
19}
In order to make use of OData features like paging and filtering, you can also try the following requests:
  • Filter by title:
    https://localhost:\<YOUR PORT\>/odata/Movies?$filter=title eq 'The Godfather'
  • Filter by part of title - case insensitive:
    https://localhost:\<YOUR PORT\>/odata/Movies?$filter=contains(tolower(title), 'godfather')
  • Paging with $skip, $top and $count:
    https://localhost:\<YOUR PORT\>/odata/Movies?$count=true&$top=10&$skip=10
  • Sorting with $orderBy:
    https://localhost:\<YOUR PORT\>/odata/Movies?$orderBy=title ASC

OData using MongoDB C# Driver

What if you already have an API and are using MongoDB C# Driver without Entity Framework? Plain and simple, all you need for OData to work is an IQueryable<T>.
This can be provided easily by IMongoCollection<T>.AsQueryable() so that we could also build the OData endpoint like this:
1// requires using Microsoft.AspNetCore.OData.Query;
2// requires using Microsoft.AspNetCore.OData.Routing.Controllers;
3// requires using MongoDB.Driver;
4
5public class MoviesController : ODataController
6{
7 private readonly IMongoCollection<Movie> _collMovies;
8
9 public MoviesController(IMongoDatabase db)
10 {
11 _collMovies = db.GetCollection<Movie>("movies");
12 }
13
14 [EnableQuery(
15 PageSize = 1000,
16 AllowedOrderByProperties = "title, plot")]
17 public IQueryable<Movie> Get()
18 {
19 return _collMovies.AsQueryable();
20 }
21}
Please note that you also need to configure the mapping for the Movie class either by adding attributes or defining the class map imperatively.

Advanced query options

Up to now, we have built an OData endpoint that supports basic options for filtering, sorting and paging with the Microsoft.AspNetCore.OData package. Unfortunately, this package does not support $select and $expand in your requests. If you want to go one step further and also add support for these operations, you need to rely on a package provided by MongoDB and reference MongoDB.AspNetCore.OData instead of the Microsoft package. All you need to do is replace EnableQuery with MongoEnableQuery and your endpoint will support these operations.
1// requires using Microsoft.AspNetCore.OData.Routing.Controllers;
2// requires using MongoDB.AspNetCore.OData.Query;
3// requires using MongoDB.Driver;
4
5public class MoviesController : ODataController
6{
7 private readonly IMongoCollection<Movie> _collMovies;
8
9 public MoviesController(IMongoDatabase db)
10 {
11 _collMovies = db.GetCollection<Movie>("movies");
12 }
13
14 // Change this from EnableQuery to MongoEnableQuery
15 [MongoEnableQuery(
16 PageSize = 1000,
17 AllowedOrderByProperties = "title, plot")]
18 public IQueryable<Movie> Get()
19 {
20 return _collMovies.AsQueryable();
21 }
22}
At the time of this writing, you need to follow the approach described in the section OData using MongoDB C# Driver above and cannot use a DbSet<T> provided by MongoDB Entity Framework Core Provider.
For details on the package see this repository on Github.

Summary

In this post we showed how easy it is to add powerful query options to your API, with an OData endpoint that builds on Entity Framework and MongoDB. This API can be used to provide rich query options in the UI of your apps or can be accessed from popular tools like Microsoft Excel. As both frameworks are very powerful and offer a lot of options, be sure to check out the documentation on MongoDB Entity Framework Core Provider and ASP.NET Core OData.
Do you already use OData or MongoDB Entity Framework Core Provider? Let us know in the forums.
Top Comments in Forums
There are no comments on this article yet.
Start the Conversation

Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Code Example

How to Use MongoDB Client-Side Field Level Encryption (CSFLE) with C#


Sep 23, 2022 | 18 min read
Article

How to Use Realm Effectively in a Xamarin.Forms App


Sep 09, 2024 | 18 min read
Tutorial

Working With MongoDB Transactions With C# and the .NET Framework


Sep 11, 2024 | 3 min read
Tutorial

Getting Started with the Realm SDK for Unity


Feb 03, 2023 | 8 min read
Table of Contents