loading...
Cover image for Overcoming The Limitations Of MongoDB C# Driver

Overcoming The Limitations Of MongoDB C# Driver

djnitehawk profile image Đĵ ΝιΓΞΗΛψΚ Updated on ・3 min read

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
  }
)

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>
  }
)

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();

the resulting query sent to mongodb is this:

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

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);

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();

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();

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();

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

nuget nuget tests license

MongoDB.Entities

A light-weight .net standard library which simplifies access to mongodb by abstracting away the official .net mongodb driver and providing some additional features on top of it. The API is clean and intuitive resulting in less lines of code that is more human friendly than driver code.

More Info:

please visit the official website for detailed documentation:

https://mongodb-entities.com

Posted on by:

djnitehawk profile

Đĵ ΝιΓΞΗΛψΚ

@djnitehawk

Developer @ ChangeMon.com (Change Detection & Notification Service)

Discussion

pic
Editor guide