-
-
Notifications
You must be signed in to change notification settings - Fork 131
Extra C# Driver Features
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 below.
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);
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#
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.
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 the .runCursor<T>() run helper. .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
*/
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.
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 example 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.
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 in static properties of the Converter class:
Converter.DateTimeConverterConverter.BinaryConverterConverter.GroupingConverterConverter.PocoArrayConverterConverter.PocoExprConverter
The example below shows how to insert and retrieve a User POCO object into a table:
var user = new User
{
Name = "Brian",
Phone = "555.555.5555",
Birthday = new DateTime(1990, 8, 18, 0, 0, 0, DateTimeKind.Utc),
};
var result = r.db(DbName).table(TableName)
.insert(user).run(conn);
var poco = r.db(DbName).table(TableName)
.get("pocoId").runAtom<User>(conn);
Additionally, JObject support is first class. Insert and retrieval of a JObject is shown below:
var user = new JObject
{
["Name"] = "Brian",
["Phone"] = "555.555.5555",
["Birthday"] = new DateTime(1990, 8, 18, 0, 0, 0, DateTimeKind.Utc),
};
var result = r.db(DbName).table(TableName)
.insert(user).run(conn);
var jobj = r.db(DbName).table(TableName)
.get("myId").runAtom<JObject>(conn)
Similar to POCO support the RethinkDb.Driver.Net.Converter.Serializer is used when serializing JObject types.
The C# driver supports the use of anonymous types in place of POCOs. For example, inserting an anonymous type into a table:
var user = new {
Name = "Brian",
Phone = "555.555.5555",
Birthday = new DateTime(1990, 8, 18, 0, 0, 0, DateTimeKind.Utc),
};
var result = r.db(DbName).table(TableName)
.insert(user).runResult(conn);
Similar to POCO support the RethinkDb.Driver.Net.Converter.Serializer is used when serializing anonymous types.
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["points"].gt(9))
.filter(g => g[nameof(Game.points)].gt(9))
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.
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! 🚀
- Home
- Query Examples
- Logging
- Connections & Pooling
- Extra C# Features
- GOTCHA Goblins!
- LINQ to ReQL Provider
- Differences
- Java ReQL API Documentation