Skip to content
  • RKGMS
  • SDK GUIDES
  • C#

RelationalAI SDK for C#

This guide presents the main features of the RelationalAI SDK for C#, which is used to interact with RelationalAI’s Relational Knowledge Graph Management System (RKGMS).

C# logo

The rai-sdk-csharp package is open source and is available in the GitHub repository.


RelationalAI/rai-sdk-csharp

It includes self-contained examples of the main API functionality. Contributions and pull requests are welcome.

Note: This guide applies to rai-sdk-csharp, the latest iteration of the RelationalAI SDK for C#. The relationalai-sdk package is deprecated.

The last section describes how the different RAI Console notebook cell types can be implemented with the RelationalAI SDK for C#.

Requirements

You can check the rai-sdk-csharp repository for the latest version requirements to interact with the RKGMS using the RelationalAI SDK for C#.

Installation

To use the RelationalAI SDK for C#, you first need to clone it from the GitHub repository:

git clone https://github.com/RelationalAI/rai-sdk-csharp

Next, you have to build it:

cd rai-sdk-csharp
dotnet build

In order to use the RelationalAI SDK for C# as a dependency, you have to use the NuGet package.

Configuration

The RelationalAI SDK for C# can access your RAI Server credentials using a configuration file. See SDK Configuration for more details.

The class Config represents a configuration to connect to the RAI Server. You can load the configuration with a C# application as follows:

using System;
using RelationalAI.Services;
using RelationalAI.Utils;
 
namespace RAIapp
{
    public class Config
    {
        static void Main(string[] args)
        {
            var config = Config.Read("", "default");
        }
    }
}

To load a different configuration, you can replace "default" with a different profile name.

Creating a Client

Most API operations use a Client object that contains the necessary settings for making requests against the RelationalAI REST APIs. To create a client using the default profile in your ~/.rai/config file, you can use:

using System;
using RelationalAI.Services;
using RelationalAI.Utils;
 
namespace RAIapp
{
    public class Config
    {
        static void Main(string[] args)
        {
            var config = Config.Read("", "default");
            var context = new Client.Context(config);
            var client = new Client(context);
        }
    }
}

Note that a Context object is needed to create the client.

You can test your configuration and client by listing the databases as follows:

cd rai-sdk-csharp/RelationalAI.Examples
dotnet run ListDatabases

This should print a list with database info, assuming your keys have the corresponding permissions. See Listing Databases below.

The remaining code examples in this document assume that you have a valid client in the client variable and that you have imported the necessary RelationalAI packages, such as the ones in the previous examples. For example:

using RelationalAI.Services;
using RelationalAI.Utils;
using RelationalAI.Models;
...

Managing Users

A client with the right permissions can create, disable, and list the users under the account.

Creating a User

You can create a user as follows:

Console.WriteLine(await client.CreateUserAsync(email, roles));

Here, email is a string, identifying the user, and roles is a list of Role objects, each one representing a role. The roles currently supported are user and admin. user is the default role if no role is specified.

Deleting a User

You can delete a user through:

Console.WriteLine(await client.DeleteUserAsync(id));

In this case, id is a string representing a given user’s ID, as stored within a User object.

Disabling and Enabling a User

You can disable a user through:

Console.WriteLine(await client.DisableUserAsync(id));

Again, id is a string representing a given user’s ID. You can reenable the user as follows:

Console.WriteLine(await client.EnableUserAsync(id));

Listing Users

You can list users as follows:

var users = await client.ListUsersAsync();
foreach (var user in users)
{
    Console.WriteLine(user.ToString(true));
}

Retrieving User Details

You can retrieve user details from the system as follows:

Console.WriteLine(await client.GetUserAsync(id));

Again, id is a string ID uniquely identifying the user, for example, "auth0|XXXXXXXXXXXXXXXXXX".

Finding Users Using Email

You can look up a user’s details by specifying their email:

Console.WriteLine(await client.FindUserAsync(email));

In this case, email is a string.

Managing OAuth Clients

OAuth clients can be managed with the following functions, provided you have the corresponding permissions:

Console.WriteLine(await client.CreateOAuthClientAsync(name, permissions));

Here name is a string identifying the client. permissions is a list of permissions from the following supported permissions:

  • create:accesskey
  • create:compute
  • create:oauth_client
  • create:user
  • delete:compute
  • delete:database
  • delete:oauth_client
  • list:accesskey
  • list:compute
  • list:database
  • list:oauth_client
  • list:permission
  • list:role
  • list:user
  • read:compute
  • read:credits_usage
  • read:oauth_client
  • read:role
  • read:user
  • rotate:oauth_client_secret
  • run:transaction
  • update:database
  • update:oauth_client
  • update:user

You can get a list of OAuth clients as follows:

Console.WriteLine(await client.ListOAuthClientsAsync());

You can get details for a specific OAuth client, identified by the string id, as follows:

Console.WriteLine(await client.GetOAuthClientAsync(id));

This is how to delete the OAuth client identified by the string id:

Console.WriteLine(await client.DeleteOAuthClientAsync(id));

Each OAuth client has its own set of permissions, which determine the operations it can execute. Depending on the permissions, some operations, such as listing other users or creating or deleting compute engines, may fail. Refer to the RAI Console Managing Users guide for further details.

Managing Engines

To query and update RelationalAI databases, you will need a running engine. The following API calls create and manage them.

Creating an Engine

You can create a new engine as follows. Note that the default size is XS:

string engine = "csharp_sdk_engine";
 
Console.WriteLine(await client.CreateEngineWaitAsync(engine));

Or, you can specify the size:

string engine = "csharp_sdk_engine";
string size = "XS";
 
Console.WriteLine(await client.CreateEngineWaitAsync(engine, size));

API requests return a JSON value. Here is a sample result for CreateEngineWaitAsync():

{
    "id": "#########",
    "name": "csharp_sdk_engine",
    "region": "#########",
    "account_name": "#########",
    "created_by": "#########",
    "deleted_on": null,
    "size": "XS",
    "state": "REQUESTED"
}

Valid sizes are given as a string and can be one of:

  • XS (extra small).
  • S (small).
  • M (medium).
  • L (large).
  • XL (extra large).

Note: It may take some time before your engine is in the “PROVISIONED” state, where it is ready for queries. It will be in the “PROVISIONING” state before that.

To list all the engines that are currently provisioned, you can use ListEnginesAsync():

var engines = await client.ListEnginesAsync();
foreach (var engine in engines)
{
    Console.WriteLine(engine.ToString(true));
}

If there is an error with the request, an HTTPError exception will be thrown.

Most of the API examples below assume there is a running engine (in the “PROVISIONED” state) in the engine string variable, and a test database in the database string variable:

// replace with your values for testing:
string database = "mydatabase";
string engine = "myengine";

Deleting an Engine

You can delete an engine with:

Console.WriteLine(await client.DeleteEngineAsync(engine));

If successful, this will return:

{"status":
    {
        "name":"myengine",
        "state":"deleting",
        "message":"engine \"myengine\" deleted successfully"
    }
}

Note that since RelationalAI decouples computation from storage, deleting an engine does not delete any cloud databases. See Managing Engines for more details.

Getting Info for an Engine

The details for a specific compute engine can be retrieved with GetEngineAsync():

Console.WriteLine(await client.GetEngineAsync(engine));

An HTTPError exception will be thrown if the engine specified in GetEngineAsync() does not exist.

Managing Databases

Creating a Database

You can create a database by calling a client object’s CreateDatabaseAsync() method, as follows:

Console.WriteLine(await client.CreateDatabaseAsync(database, engine));

The result from a successful create_database call will look like this:

{
    "id": "#########",
    "name": "mydatabase",
    "region": "#########",
    "account_name": "#########",
    "created_by": "#########",
    "deleted_on": null,
    "deleted_by": null,
    "default_compute_name": null,
    "state": "CREATED"
}

Cloning a Database

You can use CloneDatabaseAsync() to clone a database by specifying source and destination arguments:

string sourceDB = database;
string targetDB = "cloneDB";
 
Console.WriteLine(await client.CloneDatabaseAsync(targetDB, engine, sourceDB));

With this API call, you can clone a database from mydatabase to "cloneDB", creating an identical copy. Any subsequent changes to either database will not affect the other. Cloning a database fails if the source database does not exist.

Retrieving Database Details

You can view the details of a database using the GetDatabaseAsync() method of a client object:

Console.WriteLine(await client.GetDatabaseAsync(database));

The response is a JSON object. If the database does not exist, an HTTPError exception is thrown.

Listing Databases

Using ListDatabasesAsync() will list the databases available to the account:

var databases = await client.ListDatabasesAsync();
foreach (var database in databases)
{
    Console.WriteLine(database);
}

A variation of the ListDatabasesAsync() method takes a parameter state as input that can be used to filter databases by state. Example states are: “CREATED”, “CREATING”, or “CREATION_FAILED”.

var databases = await client.ListDatabasesAsync(state);
foreach (var database in databases)
{
    Console.WriteLine(database);
}

Deleting Databases

You can delete a database with DeleteDatabaseAsync() if the client has the right permissions:

Console.WriteLine(await client.DeleteDatabaseAsync(database));

If successful, the response will be of the form:

{"name": "#########", "message": "deleted successfully"}

Deleting a database cannot be undone.

Rel Model Sources

Rel model sources are collections of Rel code that can be added, updated, or deleted from a particular database. Since they update a database, a running engine — and a database — is required to perform operations on sources.

Installing Rel Model Sources

The LoadModelAsync() method of a client object installs a Rel model source in a given database:

LoadModelAsync(string database, string engine, string name, string model);

Here is an example where the model source mymodel is provided as a string:

string sourceString = "def R = \"hello\", \"world\"";
 
Console.WriteLine(await client.LoadModelAsync(database, engine, "mymodel", sourceString));

You can also provide a Dictionary with a collection of models, together with their names. In this case, you need to use the LoadModelsAsync() method:

LoadModelsAsync(string database, string engine, Dictionary<string, string> models);

Here is an example that loads multiple models at once:

Dictionary<string, string> myModels  = new Dictionary<string, string>();
myModels.Add("model1", "def R = {1 ; \"hello\"}");
myModels.Add("model2", "def P = {2 ; \"world\"}");
 
Console.WriteLine(await client.LoadModelsAsync(database, engine, myModels));

Note that if the database already contains an installed source with the same given name, it is replaced by the new source.

Deleting a Rel Model Source

You can delete a model source from a database using the DeleteModelAsync() method:

Console.WriteLine(await client.DeleteModelAsync(database, engine, "mymodel"));

In this case, the string "mymodel" is the name of the source to be deleted.

Listing Installed Rel Model Sources

You can list the sources in a database using the following code:

Console.WriteLine(await client.ListModelNamesAsync(database, engine));

To see the contents of a named model source, you can use:

string sourcename = "mysource";
Console.WriteLine(await client.GetModelAsync(database, engine, sourcename));

Here, sourcename is the name of the model.

You can also list all models and their sources using ListModelsAsync():

Console.WriteLine(await client.ListModelsAsync(database, engine));

Querying a Database

The high-level API method for running a single query/transaction against the database is ExecuteWaitAsync(). The function call blocks until the transaction is completed or there are several timeouts indicating that the system may be inaccessible. It is specified as follows:

ExecuteWaitAsync(
    string database,
    string engine,
    string query,
    bool readOnly = false,
    Dictionary<string, string> inputs = null
)

Queries meant to update base relations with insert and delete must use readOnly=false.

Here is an example of ExecuteWaitAsync():

string query = "def output = {1;2;3}";
Console.WriteLine(await client.ExecuteWaitAsync(database, engine, query));

This gives the output:

[{"relationId":"/:output/Int64","table":[1,2,3]}]

As another example, here is an API call to load some CSV data and store them in the base relation my_base_relation:

StringBuilder data = new StringBuilder();
data.Append("name,lastname,id\n");
data.Append("John,Smith,1\n");
data.Append("Peter,Jones,2\n");
 
Dictionary<string, string> inputs  = new Dictionary<string, string>();
inputs.Add("mydata", data.ToString());
 
StringBuilder source = new StringBuilder();
source.Append("def config:schema:name=\"string\"\n");
source.Append("def config:schema:lastname=\"string\"\n");
source.Append("def config:schema:id=\"int\"\n");
source.Append("def config:syntax:header_row=1\n");
source.Append("def config:data = mydata\n");
source.Append("def delete[:my_base_relation] = mybase_relation\n");
source.Append("def insert[:my_base_relation] = load_csv[config]\n");
 
Console.WriteLine(await client.ExecuteWaitAsync(
    database,
    engine,
    source.ToString(),
    false,
    inputs));

The RelationalAI SDK for C# also supports asynchronous transactions, through ExecuteAsync() that takes similar parameters to ExecuteWaitAsync(). In summary, when you issue a query to the database, the return output contains a transaction ID that can subsequently be used to retrieve the actual query results.

ExecuteAsync() is defined similarly to ExecuteWaitAsync(), but in this case the running process is not blocked.

string query = "def output = {1; 2; 3}";
var rsp = await client.ExecuteAsync(database, engine, query);

Then, you can poll the transaction until it has completed or aborted. Finally, you can fetch the results:

if (rsp.Transaction.State.ToString() == "Completed" || rsp.Transaction.State.ToString() == "Aborted")
{
    var id = rsp.Transaction.Id;
    var results = await client.GetTransactionResultsAsync(id);
    Console.WriteLine(results.ToString());
}

Similarly to client.GetTransactionResultsAsync(), you can also get metadata and problems for a given transaction ID:

var metadata = await GetTransactionMetadataAsync(id);
var problems = await GetTransactionProblemsAsync(id);

The query size is limited to 64MB. An HTTPError exception will be thrown if the request exceeds this API limit.

Getting Multiple Relations Back

As in the RAI Console, if you want to return multiple relations, you can define subrelations of output. For example:

string query = "def a = 1;2 def b = 3;4 def output:one = a def output:two = b";
Console.WriteLine(await client.ExecuteWaitAsync(database, engine, query));

It gives this output:

{"relationId":"/:output/:one/Int64","table":[1,2]},
{"relationId":"/:output/:two/Int64","table":[3,4]}

Result Structure

The response contains the following fields:

FieldMeaning
transactionInformation about transaction status, including identifier.
metadataA JSON string with metadata about the results and their data type. It is a Protobuf message.
resultsA JSON string with the query output information.
problemsInformation about any existing problems in the database — which are not necessarily caused by the query.

Each query is a complete transaction, executed in the context of the provided database.

Problems are also a JSON string with the following fields:

FieldMeaning
error_codeThe type of error that happened. Refer to Error Messages for the full list.
is_errorWhether an error occurred or there was some other problem.
is_exceptionWhether an exception occurred or there was some other problem.
messageA short description of the problem.
pathA file path for the cases when such a path was used.
reportA long description of the problem.
typeThe type of problem, for example, "ClientProblem".

Specifying Inputs

The ExecuteWaitAsync() method implements an optional inputs Dictionary that can be used to map relation names to string constants for the duration of the query, analogous to the file upload feature of RAI Console notebooks. For example:

Dictionary<string, string> inputs  = new Dictionary<string, string>();
inputs.Add("foo", "asdf");
 
var rsp = await client.ExecuteWaitAsync(
    database,
    engine,
    "def output = foo",
    false,
    inputs);
 
Console.WriteLine(rsp);

This will return the string "asdf" back.

Functions that transform a file and write the results to a base relation can be written like this. The Rel calls load_csv and load_json can be used in this way, via the data parameter, to write results to a base relation. See, for example, the sample code using load_csv in Querying a Database.

Loading Data: LoadCsvAsync and LoadJsonAsync

As a convenience, the RelationalAI SDK for C# includes LoadCsvAsync and LoadJsonAsync member functions in the client class. These are not strictly necessary, since the load utilities in Rel itself can be used in a non-read-only ExecuteWaitAsync() query that uses the inputs parameter. See, for example, the sample code using LoadCsvAsync in Querying a Database.

The C# method LoadCsvAsync() loads data and inserts the result into the base relation named by the relation argument. Additionally, LoadCsvAsync() attempts to guess the schema of the data. For more control over the schema, you can use a non-read-only ExecuteWaitAsync() query using the inputs option:

LoadCsvAsync(string database, string engine, string relation,
        string data, CsvOptions options);

The CSVOptions class allows you to specify how to parse a given CSV file. Through a CSVOptions object you can specify, for example, the delimiter and the escape character of a given file.

Similarly to LoadCsvAsync(), LoadJsonAsync() loads the data string as JSON and inserts it into the base relation named by the relation argument:

LoadJsonAsync(string database, string engine, string relation, string data);

Example:

var rsp = client.LoadJsonAsync(database, engine, "myjson",  "{\"a\" : \"b\"}");

Note: In both cases, the relation base relation is not cleared, allowing for multipart, incremental loads. To clear it, you can issue a non-read-only query of the form:

def delete[:relation] = relation

Listing Base Relations

You can list base relations as follows:

Console.WriteLine(await client.ListEdbsAsync(database, engine));

This will list the base relations in the given database. The result is a JSON list of objects.

Transaction Cancellation

You can cancel an ongoing transaction as follows:

Console.WriteLine(await client.CancelTransactionAsync(id));

The argument id is a string that represents the transaction ID.

Notebook Cell Types

As explained in Working With RAI Notebooks there are four notebook cell types: Query, Install, Update, and Markdown. This section describes how the three cell types Query, Install, and Update map to the RelationalAI SDK for C# API calls.

Query Cells

The Query cell in a RAI Console notebook corresponds to an ExecuteWaitAsync() (or ExecuteAsync()) function call, as shown below:

string query = "def output = {1;2;3}";
Console.WriteLine(await client.ExecuteAsync(database, engine, query, true));

Note that no rules/definitions are persisted in this case.

Install Cells

To persist derived relation definitions, you can use the LoadModelAsync() API function of a client object. That is, the Install cell in a RAI Console notebook corresponds to the LoadModelAsync() C# method, as shown below:

var rsp = client.LoadModelAsync(
    database,
    engine,
    "arity1_k_def",
    "def k = {2;3;50}"
);

You can check that the relation above was persisted using the ExecuteWaitAsync() API method:

Console.WriteLine(await client.ExecuteAsync(database, engine, "def output = k", true));

This is the output:

[{"relationId":"/:output/Int64","table":[2,3,50]}]

Update Cells

The Update cell in a RAI Console notebook corresponds to an ExecuteWaitAsync() function call with the parameter readonly=false. For example:

var rsp = client.ExecuteWaitAsync(
    database,
    engine,
    "def insert:employee = {(1, \"Han Solo\"); (2, \"Bart Simpson\")}",
    false
);

You can modify the employee base relation:

var rsp = client.ExecuteWaitAsync(
    database,
    engine,
    "def insert:employee = {(3, \"King\"); (4, \"Queen\")}",
    false
);

You can now query the modified employee base relation:

var rsp = client.ExecuteWaitAsync(
    database,
    engine,
    "def output=employee",
    true
);
Console.WriteLine(rsp);
[
    1,
    2,
    3,
    4
],
[
    "Han Solo",
    "Bart Simpson",
    "King",
    "Queen"
]

To update base relations, always use the readonly=false parameter in the ExecuteWaitAsync() call.

Was this doc helpful?