DEV Community

Cover image for Overcoming The Limitations Of MongoDB C# Driver
Dĵ ΝιΓΞΗΛψΚ
Dĵ ΝιΓΞΗΛψΚ

Posted on • Edited on

Overcoming The Limitations Of MongoDB C# Driver

the mongodb driver has it's limits and sometimes you need to compose advanced queries either by hand or using a query editor/composer. to be able to run those queries and inject values from your C# code, you're required to compose BsonDocuments or do string concatenation which leads to an ugly, unreadable, magic string infested abomination.

in order to combat this problem and also to couple your C# entity schema to raw queries, the library MongoDB.Entities offers a templating system based on tag replacement.

take the following find query for example:

db.Book.find(
  {
    Title : 'book_name',
    Price : book_price
  }
)
Enter fullscreen mode Exit fullscreen mode

to couple this text query to your C# models and pass in the values for title and price, simply surround the parts you want replaced with < and > in order to turn them in to replacement tags/markers like this:

db.Book.find(
  {
    <Title> : '<book_name>',
    <Price> : <book_price>
  }
)
Enter fullscreen mode Exit fullscreen mode

the templating system is based on a special class called Template. you simply instantiate a 'Template' object supplying the tagged/marked text query to the constructor. then you chain method calls on the Template object to replace each tag you've marked on the query like so:

var query = new Template<Book>(@"
{
  <Title> : '<book_name>',
  <Price> : <book_price>
}")
.Path(b => b.Title)    
.Path(b => b.Price)
.Tag("book_name","The Power Of Now")
.Tag("book_price","10.95");

var result = await DB.Find<Book>()
                     .Match(query)
                     .ExecuteAsync();
Enter fullscreen mode Exit fullscreen mode

the resulting query sent to mongodb is this:

db.Book.find(
  {
    Title : 'The Power Of Now',
    Price : 10.95
  }
)
Enter fullscreen mode Exit fullscreen mode

the .Tag() method simply replaces matching tags on the text query with the supplied value. you don't have to use the < and > characters while using the .Tag() method. infact, avoid it as the tags won't match if you use them.

the .Path() method is one of many offered by the Prop class you can use to get the full 'dotted' path of a property by supplying a lambda/member expression. please see the documentation of the 'Prop' class here for the other methods available.

notice, that most of these 'Prop' methods only require a single parameter. whatever member expression you supply to them gets converted to a property/field path like this:

expression: x => x.Authors[0].Books[0].Title

resulting path: Authors.Books.Title

if your text query has a tag <Authors.Books.Title> it will get replaced by the resulting path from the 'Prop' class method.

the template system will throw an exception in the event of the following 3 scenarios.

  1. the input query/text has no tags marked using < and > characters.
  2. the input query has tags that you forget to specify replacements for.
  3. you have specified replacements that doesn't have a matching tag in the query.

this kind of runtime errors are preferable than your code failing silently because the queries didn't produce any results or produced the wrong results.

Some Examples:

Aggregation Pipeline:

var pipeline = new Template<Book>(@"
[
    {
      $match: { <Title>: '<book_name>' }
    },
    {
      $sort: { <Price>: 1 }
    },
    {
      $group: {
        _id: '$<AuthorId>',
        product: { $first: '$$ROOT' }
      }
    },
    {
      $replaceWith: '$product'
    }
]")
.Path(b => b.Title)
.Path(b => b.Price)
.Path(b => b.AuthorId)
.Tag("book_name", "MongoDB Templates");

var book = await DB.PipelineSingleAsync(pipeline);
Enter fullscreen mode Exit fullscreen mode

Find With Match Expression:

var query = new Template<Author>(@"
{
  $and: [
    { $gt: [ '$<Age>', <author_age> ] },
    { $eq: [ '$<Surname>', '<author_surname>' ] }
  ]
}")
.Path(a => a.Age)
.Path(a => a.Surname)
.Tag("author_age", "54")
.Tag("author_surname", "Tolle");

var authors = await DB.Find<Author>()
                      .MatchExpression(query)
                      .ExecuteAsync();
Enter fullscreen mode Exit fullscreen mode

Update With Aggregation Pipeline Stages:

var pipeline = new Template<Author>(@"
[
  { $set: { <FullName>: { $concat: ['$<Name>',' ','$<Surname>'] } } },
  { $unset: '<Age>'}
]")             
.Path(a => a.FullName)
.Path(a => a.Name)
.Path(a => a.Surname)
.Path(a => a.Age);

await DB.Update<Author>()
        .Match(a => a.ID == "xxxxx")
        .WithPipeline(pipeline)
        .ExecutePipelineAsync();
Enter fullscreen mode Exit fullscreen mode

Update With Array Filters:

var filters = new Template<Author>(@"
[
  { '<a.Age>': { $gte: <age> } },
  { '<b.Name>': 'Echkart Tolle' }
]")
.Elements(0, author => author.Age)
.Elements(1, author => author.Name);
.Tag("age", "55")        

var update = new Template<Book>(@"
{ $set: { 
    '<Authors.$[a].Age>': <age>,
    '<Authors.$[b].Name>': '<name>'
  } 
}")
.PosFiltered(book => book.Authors[0].Age)
.PosFiltered(book => book.Authors[1].Name)
.Tag("age", "55")
.Tag("name", "Updated Name");

await DB.Update<Book>()
        .Match(book => book.ID == "xxxxxxxx")
        .WithArrayFilters(filters)
        .Modify(update)
        .ExecuteAsync();
Enter fullscreen mode Exit fullscreen mode

Next Steps...

hope the above text query template system has piqued your interest enough to go deeper into learning how to use mongodb with C#. the package MongoDB.Entities makes it extremely easy to communicate with mongodb server. every aspect of it's api has been documented on the official website. you can also checkout the source code on github:

GitHub logo dj-nitehawk / MongoDB.Entities

A data access library for MongoDB with an elegant api, LINQ support and built-in entity relationship management

license nuget nuget tests discord

MongoDB.Entities

A light-weight .net standard library with barely any overhead that aims to simplify access to mongodb by abstracting the official driver while adding useful features on top of it resulting in an elegant API surface which produces beautiful, human friendly data access code.

More Info:

please visit the official website for detailed documentation:

https://mongodb-entities.com

Top comments (6)

Collapse
 
tunaxor profile image
Angel Daniel Munoz Gonzalez • Edited

if you don't mind doing some F# perhaps you'll find this library useful :)
github.com/AngelMunoz/Mondocks
it's a similar approach to the templates with a little bit of F#'s type safety on top but flexible enough to do whatever mongo requires you to do let me know if you have any feedback on it 😁

Collapse
 
djnitehawk profile image
Dĵ ΝιΓΞΗΛψΚ

thanks will check it out!

Collapse
 
tunaxor profile image
Angel Daniel Munoz Gonzalez

By the way, I like your work with the entities, I've used them before for some personal stuff, I didn't know about the templates kudos for the great work!

Thread Thread
 
djnitehawk profile image
Dĵ ΝιΓΞΗΛψΚ

thanks for the feedback. mondocks looks quite interesting too. I just need to brush up on my f# first 😁

Collapse
 
rainxh11 profile image
Ahmed Chakhoum

this library is a godsend, aggregations with the official driver is such a pain to set up
this is really amazing

Collapse
 
djnitehawk profile image
Dĵ ΝιΓΞΗΛψΚ

thanks for the feedback 🙏
join our discord server if you ever need any help discord.com/invite/CM5mw2G