Skip to content

Extra C# Driver Features

Brian Chavez edited this page Dec 18, 2015 · 107 revisions

In general, the C# driver and Java driver are both API compatible. The Java documentation can be followed easily without much trouble.

The C# driver, however, offers additional language syntax that goes beyond the original Java driver. The additional language features are highlighted here.


Optional Arguments

Java and C#

r.table("marvel").getAll("man_of_steel").optArg("index", "code_name")
                 .run(conn);

Additionally, the C# driver supports indexer operators allowing the use of anonymous types as optional arguments.

r.table("marvel").getAll("man_of_steel")[new {index="code_name"}]
                 .run(conn);

Bracket

Java, Python, JavaScript and C#

r.table("marvel").get("12345")["field"]["subfield"] // Python
r.table("marvel").get("12345")("field")("subfield") // JavaScript
r.table("marvel").get("12345").getField("field").getField("subfield") //Java
r.table("marvel").get("12345").getField("field").getField("subfield") // C#

Additionally, the C# driver also supports .bracket() via indexer operator:

r.table("marvel").get("12345")["field"]["subfield"] // C#

Dynamic Language Runtime (DLR) Integration

The Python, JavaScript, and Java drivers all expect the developer to know the shape of a query result. The C# driver follows a similar paradigm. However, supplying additional generic type information can help the DLR to perform a cast into a specific type.

new Foobar {id = "a", Baz = 4, Qux = 4}

// DLR dynamic
/* var result is 4 of type dynamic */
var result = r.table("foobar").get("a")["Baz"].run(conn);

/* long result is 4 of type long */
long result = r.table("foobar").get("a")["Baz"].run(conn);

// Give the compiler and run-time more type information with run<T>()
/* int result is 4 of type int */
int  result = r.table("foobar").get("a")["Baz"].run<int>(conn);

Notice the result declarations long and int and their respective run and run<T> calls. The underlying deserializer, Newtonsoft.Json determined the underlying type (without T) is a long. Given T as int, the deserializer can make a more specific deserialization of the result.


Cursor<T> support

When the result of a query is a stream (ie: query → stream), be sure to declare that the expected result as a Cursor<T>. The following example below shows how to expect a cursor:

Cursor<int> result = r.range(1,4).run<int>();

Notice the DLR magic above, run<int> returns Cursor<int> not int. Since the server's response for the query above is a stream it is the responsibility of the driver to return a cursor. Also note, in the example above, run<T> T specifies the cursor item type.

There is a slight performance cost to run<T> since the execution context involves the DLR. As a best practice, queries that return a cursor should use .runCursor<T>(). .runCursor<T> bypasses the DLR and the execution context remains within the CLR type system to return a Cursor<T> just as any normal CLR method call.

Cursor<int> result = r.range(1,4).runCursor<int>(conn);

foreach(var i in result){
   Console.WriteLine(i);
}
/* Output:
1
2
3
*/

Run Helpers

The C# driver offers additional run helpers that bypass the DLR and offer better query syntax and performance. See the Run Helpers page for more information.


async/await support

Every .run() method and associated .run*() helper supports async and await calling conventions. For example:

var games = new[]
    {
        new Game {id = 2, player = "Bob", points = 15, type = "ranked"},
        new Game {id = 5, player = "Alice", points = 7, type = "free"},
        new Game {id = 11, player = "Bob", points = 10, type = "free"},
        new Game {id = 12, player = "Alice", points = 2, type = "free"},
    };

var result = await r.db(DbName).table(TableName)
                    .insert(games)
                    .runResultAsync(conn);

result.AssertInserted(4);

In the above, var result = await is used in conjunction with the .runResultAsync helper. The Task Parallel Library in .NET is used to await the query's response from the RethinkDB server. This is the recommended approach for highly concurrent scenarios.


Poco Support

This C# driver supports POCO serialization to RethinkDB via Newtonsoft.Json. The default serializer can be overridden by replacing the JsonSerializer in the Converter.Serializer static property.

RethinkDb.Driver.Net.Converter.Serializer = new JsonSerializer(/*custom*/);

Keep in mind, however, there are native types that RethinkDB expects in a specific JSON format. Native ReQL types like time, dates, binary data, and arrays (DateTime, DateTimeOffset, byte[], IEnumerable) need to be in a specific JSON format over-the-wire in order to perform ReQL operations on them. By default, native ReQL types are converted automatically if the default Converter.Serializer is used. More information about ReQL pseudo types can be found here.

Overriding the default Converter.Serializer requires including the pseudo type converters when replacing the default serializer in order to maintain correct type conversions between native types and ReQL pseudo types. Instances of the JSON converters can be found on static properties in the Converter class:

  • Converter.DateTimeConverter
  • Converter.BinaryConverter
  • Converter.GroupingConverter
  • Converter.PocoArrayConverter
  • Converter.PocoExprConverter

Reactive Extensions (Rx) Support

Reactive Extensions (Rx) work well with this driver. Below is an example of how to subscribe to a change feed using Rx:

var changes = r.db("marvel").table("heros")
               .changes()
               .runChanges<Hero>(conn);

var observable = changes.ToObservable();
observable.Subscribe(OnNext, OnError, OnCompleted);

Projects using Rx need to reference Rx-Main using NuGet.

Note: Rx is currently supported in .NET 4.5 Framework. Rx for CoreCLR isn't supported by Rx yet.

More detailed examples can be found here.


Anonymous Type .map() projection

The following is only possible with C#

public class TopPlayer
{
     public int PlayerId { get; set; }
}

var games = new[]
    {
        new Game {id = 2, player = "Bob", points = 15, type = "ranked"},
        new Game {id = 5, player = "Alice", points = 7, type = "free"},
        new Game {id = 11, player = "Bob", points = 10, type = "free"},
        new Game {id = 12, player = "Alice", points = 2, type = "free"},
    };

List<TopPlayer> result =
       r.expr(games)
        .filter(g => g["points"].gt(9))
         // Anonymous type projection to shape the result
         // to fit into TopPlayer
        .map(g => new { PlayerId = g["id"] })
        .run<List<TopPlayer>>(conn);

result.Dump();

result.ShouldBeEquivalentTo(new[]
    {
        new TopPlayer {PlayerId = 2},
        new TopPlayer {PlayerId = 11}
    });

Tip: Use nameof in C#6 to maintain refactorable queries:

.filter(g => g[nameof(Game.points)].gt(9))

Implicit Conversion Operator Overload

The following is only possible with C#

//Objects inside Foobar table:
new Foobar {id = "a", Baz = 1, Qux = 1}
new Foobar {id = "b", Baz = 2, Qux = 2}
new Foobar {id = "c", Baz = 3, Qux = 3}

var exprA = r.table("foobar").get("a")["Baz"]; // 1
var exprB = r.table("foobar").get("b")["Qux"]; // 2

int result = (exprA + exprB + 1).run<int>(conn);

// Everything between (...) executes on the server
// and returns result 4.

The last line, (exprA + exprB + 1) is converted into an AST and sent to the server for evaluation including the + 1) part. The + 1) is not evaluated on the client. Here's what happens:

The compiler/run-time knows exprA is type ReqlExpr, moves right to exprB (also of type ReqlExpr), applies the + operator overload for adding two ReqlExprs who's sum is also ReqlExpr (under the hood, all we're doing is exprA.add(exprB)). Lastly, the evaluation moves right again to the last + 1) but encounters an int type. The implicit conversion operator kicks in (int -> ReqlExpr) and converts int into a Datum(1) (which inherits from ReqlExpr). Finally, the last + Datum(1) can be evaluated. The final equivalent ReQL sequence is: exprA.add(exprB).add(new Datum(1)). Beautiful. ❤️

The benefit of implicit conversion and operator overloads is better language integeration. For example, both filters are equivalent:

.filter(g => g[nameof(Game.points)].gt(9))
.filter(g => g[nameof(Game.points)] > 9)

Happy ReQL-ing! 🚀

Clone this wiki locally