DgraphNet - Dgraph client for .NET
This project is no longer actively maintained, as Dgraph now has an official client for the .NET platform (thank you guys!). The official client can be found here: https://github.com/dgraph-io/dgraph.net
A minimal implementation for a Dgraph client for .NET, using grpc. It exposes both synchronous and asynchronous API.
The library targets both .NET Standard 2.0
and .NET 4.6.1
.
This client follows the Dgraph Go client closely.
Before using this client, we highly recommend that you go through docs.dgraph.io, and understand how to run and work with Dgraph.
Table of Contents
Download
Install DgraphNet.Client NuGet package.
For extensions, install DgraphNet.Client.Extensions package.
Quickstart
Build and run the [DgraphNet.Client.Sample] project in the Samples
folder, which
contains an end-to-end example of using the DgraphNet client. Follow the
instructions in the README of that project.
Using the Client
The DgraphNetClient
requires either a DgraphConnection
or a DgraphConnectionPool
.
Create a single connection
The connection will create a gRPC channel.
var connection = new DgraphConnection("localhost", 9080, ChannelCredentials.Insecure);
You can also use a Channel
instance.
var channel = new Channel("localhost:9080", ChannelCredentials.Insecure);
var connection = new DgraphConnection(channel);
Create a connection pool
Connections in a pool must belong to the same Dgraph cluster.
var pool = new DgraphConnectionPool()
.Add(connection1);
.Add(connection2);
Create the client
A DgraphNetClient
object can be initialised by passing it a DgraphConnectionPool
.
Connecting to multiple Dgraph servers in the same cluster allows for better distribution of workload.
Alternatively, you can initialize the client with a single DgraphConnection
: a pool with a single connection will be created by the client.
The following code snippet shows just one connection.
var connection = new DgraphConnection("localhost", 9080, ChannelCredentials.Insecure);
var pool = new DgraphConnectionPool().Add(connection);
var client = new DgraphNetClient(pool);
// or client = new DgraphNetClient(connection);
You can also specify a deadline (in seconds) after which the client will time out when making requests to the server.
var client = new DgraphNetClient(pool, 60); // 1 min timeout
When you don't need your client anymore, you have to close the connections used by it. The CloseAsync()
method of the client will close all the connections of its pool.
await client.CloseAsync();
Alter the database
To set the schema, create an Operation
object, set the schema and pass it to
DgraphClient#Alter
method.
string schema = "name: string @index(exact) .";
Operation op = new Operation { Schema = schema };
client.Alter(op);
Operation
contains other fields as well, including drop predicate and
drop all. Drop all is useful if you wish to discard all the data, and start from
a clean slate, without bringing the instance down.
// Drop all data including schema from the dgraph instance. This is useful
// for small examples such as this, since it puts dgraph into a clean
// state.
client.Alter(new Operation { DropAll = true });
Create a transaction
To create a transaction, call DgraphNetClient#NewTransaction()
method, which returns a
new Transaction
object. This operation incurs no network overhead.
It is a good practise to use the transaction in a using
block: Transaction#Dispose
method will call Transaction#Discard
.
Calling Transaction#Discard()
after Transaction#Commit()
is a no-op
and you can call Discard()
multiple times with no additional side-effects.
using(Transaction txn = client.NewTransaction())
{
txn.Commit();
txn.Discard(); // Commit() was already called, it is a no-op.
}
Run a mutation
Transaction#Mutate
runs a mutation. It takes in a Mutation
object,
which provides two main ways to set data: JSON and RDF N-Quad. You can choose
whichever way is convenient.
We're going to use JSON. First we define a Person
class to represent a person.
This data will be seralized into JSON.
class Person {
public string Name { get; set; }
public Person() {}
}
Next, we initialise a Person
object, serialize it and use it in Mutation
object.
using(Transaction txn = client.NewTransaction())
{
// Create data
Person p = new Person();
p.Name = "Alice";
// Serialize it with Newtonsoft.Json
var json = JsonConvert.SerializeObject(p);
// Run mutation
Mutation mu = new Mutation { SetJson = ByteString.CopyFromUtf8(json) };
txn.mutate(mu);
txn.Commit();
}
Sometimes, you only want to commit mutation, without querying anything further.
In such cases, you can use a CommitNow
field in Mutation
object to
indicate that the mutation must be immediately committed.
Run a query
You can run a query by calling Transaction#Query()
. You will need to pass in a GraphQL+-
query string, and a map (optional, could be empty) of any variables that you might want to
set in the query.
The response would contain a Json
field, which has the JSON encoded result. You will need
to decode it before you can do anything useful with it.
Let's run the following query:
query all($a: string) {
all(func: eq(name, $a)) {
name
}
}
First we must create a People
class that will help us deserialize the JSON result:
class People {
public List<Person> All { get; set; }
public People() {}
}
Then we run the query, deserialize the result and print it out:
// Query
string query =
"query all($a: string){\n" +
" all(func: eq(name, $a)) {\n" +
" name\n" +
" }\n" +
"}\n";
IDictionary<string, string> vars = new Dictionary<string, string>
{
{ "$a", "Alice" }
};
Response res = client.NewTransaction().QueryWithVars(query, vars);
// Deserialize
People ppl = JsonConvert.DeserializeObject<People>(res.Json.ToStringUtf8());
// Print results
Console.WriteLine($"people found: {ppl.All.Count}");
foreach(var p in ppl.All)
{
Console.WriteLine(p.name);
}
This should print:
people found: 1
Alice
Commit a transaction
A transaction can be committed using the Transaction#Commit()
method. If your transaction
consisted solely of calls to Transaction#Query()
, and no calls to Transaction#Mutate()
,
then calling Transaction#Commit()
is not necessary.
An error will be returned if other transactions running concurrently modify the same data that was modified in this transaction. It is up to the user to retry transactions when they fail.
Transaction txn = client.NewTransaction();
try {
// ...
// Perform any number of queries and mutations
// ...
// and finally...
await txn.CommitAsync()
} catch (TxnConflictException ex) {
// Retry or handle exception.
} finally {
// Clean up. Calling this after txn.CommitAsync() is a no-op
// and hence safe.
await txn.DiscardAsync();
}
Extensions
The DgraphNet.Client.Extensions
package provides useful extensions for the client. It will be completed over time.
Queries
Query<T>
and QueryWithVars<T>
: deserializes the query result into the specified type, thanks to Newtonsoft.Json.
public void QueryMichaelAccounts()
{
string query =
"{\n"
+ " accounts(func: anyofterms(first, \"Michael\")) {\n"
+ " first\n"
+ " last\n"
+ " age\n"
+ " }\n"
+ "}";
var res = _client.NewTransaction().Query<AccountQuery>(query);
foreach(var account in res.Accounts)
{
Console.WriteLine(account.First);
}
}
class AccountQuery
{
public Account[] Accounts { get; set; }
}
class Account
{
public string First { get; set; }
public string Last { get; set; }
public int Age { get; set; }
}
Schema
A safe Schema builder.
For predicates:
// first: [string] @index(hash, fulltext) @count @upsert .
var schema = Schema.Predicate("first")
.String()
.Index(StringIndexType.Hash|StringIndexType.FullText)
.List()
.Count()
.Upsert()
.Build();
await _client.AlterAsync(new Operation { Schema = schema });
For edges:
// friends: uid @reverse @count .
var schema = Schema.Edge("friends")
.Count()
.Reverse()
.Build();
await _client.AlterAsync(new Operation { Schema = schema });
Development
This project is developed with Visual Studio 2017 Community
. It targets both .NET Standard and .NET Framework 4.6.1.
Alternatively, you can use Visual Studio Code
and the dotnet
CLI from .NET Core.
Building the source
In Visual Studio 2017, simply build the solution. Otherwise, run in the solution folder:
dotnet build
In order to generate the proto files for Dgraph, run protoc.bat
.
The CodeCakeBuilder
project is a build project based on CodeCake build system: it builds, runs test projects, and generates NuGet packages.
Code Style
Follow the .editorconfig
file, supported by Visual Studio and Visual Studio Code.
Versioning
The project follows CSemVer specification, an operational subset of Semantic Versioning 2.0.0.
Running unit tests
This project uses NUnit 3 for unit tests.
- In Visual Studio, you can use the integrated
Test Explorer
tool in order to run tests. - In Visual Studio Code, you can install
.NET Core Test Explorer
extension in order to run tests.
Alternatively, you can run this project:
dotnet run --project Tests\DgraphNet.Client.NetCore.Tests
It will run the tests in a console application thanks to NUnitLite.