Overcoming The Limitations Of MongoDB C# Driver
Tags: mongodb, csharp, database, dotnet
Published: 2020-02-26 13:52:59

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](https://mongodb-entities.com) offers a templating system based on tag replacement.

take the following find query for example:
```java
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:

```java
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:

```csharp
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:
```java
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](https://mongodb-entities.com/wiki/Extras-Prop.html) 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:
```csharp
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:

```csharp
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:
```csharp
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:
```csharp
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](https://mongodb-entities.com). you can also checkout the source code on github:
{% github dj-nitehawk/MongoDB.Entities %}