Hot Chocolate has a data integration for MongoDB. With this integration, you can translate paging, filtering, sorting, and projections, directly into native MongoDB queries.
You can find a example project in Hot Chocolate Examples
Get Started
To use the MongoDB integration, you need to install the package HotChocolate.Data.MongoDb
.
dotnet add package HotChocolate.Data.MongoDb
MongoExecutable
The whole integration builds around IExecutable<T>
.
The integration provides you the extension method AsExecutable
on IMongoCollection<T>
, IAggregateFluent<T>
and IFindFluent<T>
The execution engine picks up the IExecutable
and executes it efficiently.
You are free to use any form of aggregation or find a pipeline before you execute AsExecutable
[UsePaging][UseProjection][UseSorting][UseFiltering]public IExecutable<Person> GetPersons([Service] IMongoCollection<Person> collection){ return collection.AsExecutable();}
[UseFirstOrDefault]public IExecutable<Person> GetPersonById( [Service] IMongoCollection<Person> collection, Guid id){ return collection.Find(x => x.Id == id).AsExecutable();}
Filtering
To use MongoDB filtering you need to register the convention on the schema builder:
services .AddGraphQLServer() .AddQueryType<Query>() .AddMongoDbFiltering();
To use MongoDB filtering alongside with
IQueryable
/IEnumerable
, you have to register the MongoDB convention under a different scope. You can specify the scope on the schema builder by executingAddMongoDbFiltering("yourScope")
. You then have to specify this scope on each method you use MongoDb filtering:[UseFiltering(Scope = "yourScope")]
orUseFiltering(scope = "yourScope")
Your filters are now converted to BsonDocument
s and applied to the executable.
GraphQL Query:
query GetPersons { persons( where: { name: { eq: "Yorker Shorton" } addresses: { some: { street: { eq: "04 Leroy Trail" } } } } ) { name addresses { street city } }}
Mongo Query
{ "find": "person", "filter": { "Name": { "$eq": "Yorker Shorton" }, "Addresses": { "$elemMatch": { "Street": { "$eq": "04 Leroy Trail" } } } }}
Sorting
To use MongoDB sorting you need to register the convention on the schema builder:
services .AddGraphQLServer() .AddQueryType<Query>() .AddMongoDbSorting();
To use MongoDB Sorting alongside with
IQueryable
/IEnumerable
, you have to register the MongoDB convention under a different scope. You can specify the scope on the schema builder by executingAddMongoDbSorting("yourScope")
. You then have to specify this scope on each method you use MongoDb Sorting:[UseSorting(Scope = "yourScope")]
orUseSorting(scope = "yourScope")
Your sorting is now converted to BsonDocument
s and applied to the executable.
GraphQL Query:
query GetPersons { persons(order: [{ name: ASC }, { mainAddress: { city: DESC } }]) { name addresses { street city } }}
Mongo Query
{ "find": "person", "filter": {}, "sort": { "Name": 1, "MainAddress.City": -1 }}
Projections
To use MongoDB projections you need to register the convention on the schema builder:
services .AddGraphQLServer() .AddQueryType<Query>() .AddMongoDbProjections();
To use MongoDB Projections alongside with
IQueryable
/IEnumerable
, you have to register the MongoDB convention under a different scope. You can specify the scope on the schema builder by executingAddMongoDbProjections("yourScope")
. You then have to specify this scope on each method you use MongoDb Projections:[UseProjections(Scope = "yourScope")]
orUseProjections(scope = "yourScope")
Projections do not always lead to a performance increase. Even though MongoDB processes and transfers less data, it more often than not harms query performance. This Medium article by Tek Loon explains how and when to use projections well.
GraphQL Query:
query GetPersons { persons { name addresses { city } }}
Mongo Query
{ "find": "person", "filter": {}, "projection": { "Addresses.City": 1, "Name": 1 }}
Paging
The integration comes with providers for offset and cursor-based pagination
Cursor Pagination
To use cursor based pagination annoate you resolver with [UseMongoDbPaging]
or .UseMongoDbPaging()
[UseMongoDbPaging]public IExecutable<Person> GetPersons([Service] IMongoCollection<Person> collection){ return collection.AsExecutable();}
You can then execute queries like the following one:
query GetPersons { persons(first: 50, after: "OTk=") { nodes { name addresses { city } } pageInfo { endCursor hasNextPage hasPreviousPage startCursor } }}
Offset Pagination
To use cursor based pagination annoate you resolver with [UseMongoDbPaging]
or .UseMongoDbPaging()
[UseMongoDbOffsetPaging]public IExecutable<Person> GetPersons([Service] IMongoCollection<Person> collection){ return collection.AsExecutable();}
You can then execute queries like the following one:
query GetPersons { persons(skip: 50, take: 50) { items { name addresses { city } } pageInfo { hasNextPage hasPreviousPage } }}
FirstOrDefault / SingleOrDefault
Sometimes you may want to return only a single object of a collection.
To limit the response to one element you can use the UseFirstOrDefault
or UseSingleOrDefault
middleware.
Hot Chocolate will rewrite the type of the field from a list type to an object type.
[UseFirstOrDefault]public IExecutable<Person> GetPersonById( [Service] IMongoCollection<Person> collection, Guid id){ return collection.Find(x => x.Id == id).AsExecutable();}