Skip to content

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 System (RKGS).

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 (opens in a new tab) 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.

Requirements

You can check the rai-sdk-csharp (opens in a new tab) repository for the latest version requirements to interact with the RKGS 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 (opens in a new tab).

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 (opens in a new tab) 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

This section covers the API functions that you need to manage users.

🔎

Each user has a role associated with specific permissions. These permissions determine the operations that the user can execute. See User Roles in the Managing Users and OAuth Clients guide for more details.

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.

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));
}

Getting Information for a User

You can get information for a user 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.

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 (opens in a new tab) object.

Managing OAuth Clients

This section covers the API functions that you need to manage OAuth clients.

🔎

Each OAuth client has a specific set of permissions. These permissions determine the operations that the OAuth client can execute. See [Permissions for OAuth Clients](/rkgms/console/user-management(#permissions-for-oauth-clients) in the Managing Users and OAuth Clients guide for more details.

Creating an OAuth Client

You can create an OAuth client as follows:

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:engine
  • create:oauth_client
  • create:user
  • delete:engine
  • delete:database
  • delete:oauth_client
  • list:accesskey
  • list:engine
  • list:database
  • list:oauth_client
  • list:permission
  • list:role
  • list:user
  • read:engine
  • read:credits_usage
  • read:oauth_client
  • read:role
  • read:user
  • rotate:oauth_client_secret
  • run:transaction
  • update:database
  • update:oauth_client
  • update:user

Listing OAuth Clients

You can get a list of OAuth clients as follows:

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

Getting Information for an OAuth Client

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

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

Deleting an OAuth Client

You can delete an OAuth client identified by the string id as follows:

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

Managing Engines

This section covers the API methods you need to use to manage engines.

Creating an Engine

You can create a new engine as follows:

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

By default, the engine size is XS. You can create an engine of a different size by specifying the size parameter:

string engine = "my_engine";
string size = "S";
 
Console.WriteLine(await client.CreateEngineWaitAsync(engine, size));

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

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

Your engine may take some time to reach the “PROVISIONED” state, where it is ready for queries. It is in the “PROVISIONING” state until then.

Listing Engines

You can list all engines associated with your account as follows:

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

This returns a JSON array containing details for each engine:

[
    {
        "id": "******",
        "name": "my_engine",
        "region": "us-east",
        "account_name": "******",
        "created_by": "******",
        "created_on": "2023-07-10T17:15:22.000Z",
        "size": "S",
        "state": "PROVISIONED"
    }
]

To list engines that are in a given state, you can use the state parameter:

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

Possible states are:

  • "REQUESTED".
  • "PROVISIONING".
  • "PROVISIONED".
  • "DEPROVISIONING".

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

Getting Information for an Engine

You can get information for a specific engine using the following command:

string engine = "my_engine";
Console.WriteLine(await client.GetEngineAsync(engine));

This gives you the following output:

{
    "id": "******",
    "name": "my_engine",
    "region": "us-east",
    "account_name": "******",
    "created_by": "******",
    "created_on": "2023-07-10T17:15:22.000Z",
    "size": "S",
    "state": "PROVISIONED"
}

An HTTPError exception will be thrown if the engine does not exist.

Deleting an Engine

You can delete an engine with:

string engine = "my_engine";
Console.WriteLine(await client.DeleteEngineAsync(engine));

If successful, this will return:

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

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

Managing Databases

This section covers the API methods you need to use to manage databases.

Creating a Database

You can create a database as follows:

string database = "my_database";
string engine = "my_engine";
await client.CreateDatabaseAsync(database, engine);

Cloning a Database

You can clone a database by specifying the target and source databases:

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

Any subsequent changes to either database will not affect the other. Cloning a database fails if the source database does not exist.

You cannot clone from a database until an engine has executed at least one transaction on that database.

Listing Databases

You can list the available databases associated with your account as follows:

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

This returns a JSON array containing details for each database:

[
    {
        "id": "6f******",
        "name": "my_database",
        "region": "us-east",
        "account_name": "******",
        "created_by": "******",
        "created_on": "2023-07-20T08:03:03.616Z",
        "state": "CREATED"
    }
]

To filter databases by state, you can use the state parameter. For instance:

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

Possible states are:

  • "CREATED".
  • "CREATING".
  • "CREATION_FAILED".
  • "DELETED".

Getting Information for a Database

You can get information for a specific database as follows:

string database = "my_database";
Console.WriteLine(await client.GetDatabaseAsync(database));

This gives you the following output:

{
    "id": "6f******",
    "name": "my_database",
    "region": "us-east",
    "account_name": "******",
    "created_by": "******",
    "created_on": "2023-07-20T08:03:03.616Z",
    "state": "CREATED"
}

If the database does not exist, an HTTPError exception is thrown.

Deleting a Database

You can delete a database as follows:

string database = "my_database";
Console.WriteLine(await client.DeleteDatabaseAsync(database));

If successful, the response will be of the form:

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

Deleting a database cannot be undone.

🔎

The remaining code examples in this guide assume that you have a running engine in engine and a database in database.

Managing Rel Models

This section covers the API methods you can use to manage Rel models.

Rel models are collections of Rel code that can be added, updated, or deleted from a dedicated database. A running engine — and a database — is required to perform operations on models.

Loading a Model

The LoadModelAsync() method loads a Rel model in a given database:

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

Here’s an example where the model my_model is provided as a string:

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

Loading Multiple Models

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’s an example that loads multiple models at once:

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

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

Listing Models

You can list the models in a database as follows:

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

This returns a JSON array of names:

[
  "rel/alglib",
  "rel/display",
  "rel/graph-basics",
  "rel/graph-centrality",
  "rel/graph-components",
  "rel/graph-degree",
  "rel/graph-measures",
  "rel/graph-paths",
  "rel/histogram",
  "rel/intrinsics",
  "rel/mathopt",
  "rel/mirror",
  "rel/net",
  "rel/stdlib",
  "rel/vega",
  "rel/vegalite"
]

In the example above, you can see all the built-in models associated with a database.

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

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

Getting Information for a Model

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

string modelname = "my_model";
Console.WriteLine(await client.GetModelAsync(database, engine, modelname));

Here, modelname is the name of the model. This gives the following output:

{
  "name": "my_model",
  "value": "def my_range(x) = range(1, 10, 1, x)"
}

In the example above, my_model defines a specific range.

Deleting Models

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

Console.WriteLine(await client.DeleteModelsAsync(database, engine, model_name));

Note that model_name is a List string containing the name of the model or models to be deleted.

Querying a Database

The API function for executing queries against the database is ExecuteWaitAsync(). It is a synchronous function, meaning that the running code is blocked until the transaction is completed or there are several timeouts indicating that the system may be inaccessible. Each query is a complete transaction, executed in the context of the provided database. It is specified as follows:

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

Here’s an example of a read query using 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]}]

Write queries, which update base relations through the control relations insert and delete, must use readOnly=false.

Here’s 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("my_data", data.ToString());
 
StringBuilder query = new StringBuilder();
query.Append("def config:schema:name=\"string\"\n");
query.Append("def config:schema:lastname=\"string\"\n");
query.Append("def config:schema:id=\"int\"\n");
query.Append("def config:syntax:header_row=1\n");
query.Append("def config:data = my_data\n");
query.Append("def delete[:my_base_relation] = my_base_relation\n");
query.Append("def insert[:my_base_relation] = load_csv[config]\n");
 
Console.WriteLine(await client.ExecuteWaitAsync(
    database,
    engine,
    query.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

In order 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 keys:

FieldMeaning
TransactionInformation about the transaction status, including the identifier.
MetadataMetadata information about the results key.
ResultsQuery output information.
ProblemsInformation about any existing problems in the database — which are not necessarily caused by the query.

Transaction

The transaction key is a JSON string with the following fields:

FieldMeaning
IDTransaction identifier.
StateTransaction state. See Transaction States for more details.

For example:

{
    "id": "******",
    "state": "COMPLETED"
}

Metadata

The metadata key is a JSON string with the following fields:

FieldMeaning
Relation IDThis is a relation identifier, for example, "/:output/:two/Int64". It describes the relation name /:output/:two followed by its data schema Int64.
TypesThis is a JSON array that contains the key names of the relation and their data type.

For example:

{
    relation_id {
        arguments {
            tag: CONSTANT_TYPE
            constant_type {
                rel_type {
                    tag: PRIMITIVE_TYPE
                    primitive_type: STRING
                }
                value {
                    arguments {
                        tag: STRING
                        string_val: "output"
                    }
                }
            }
        }
        arguments {
            tag: PRIMITIVE_TYPE
            primitive_type: INT_64
        }
    }
}

Results

The results key is a vector with the following fields:

FieldMeaning
Relation IDThis is a key for the relation, for example, "v1". It refers to the column name in the Arrow table that contains the data, where "v" stands for variable, since a relation’s tuples contain several variables.
TableThis contains the results of the query in a JSON-array format.

For example:

v1: [[1,2,3]]

Problems

The problems key is a JSON string with the following fields:

FieldMeaning
error_codeThe type of error that occurred, for example, "PARSE_ERROR".
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".

For example:

{
    'is_error': True,
    'error_code': 'PARSE_ERROR',
    'path': '',
    'report': '1| def output = {1; 2; 3\n  ^~~~~~~~\n', 'message': 'Missing closing `}`.',
    'is_exception': False,
    'type': '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. 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

The LoadCsvAsync() and LoadJsonAsync() functions allow you to load data into a database. These are not strictly necessary, since the Rel load utilities can also be used for this task. See the CSV Import and JSON Import guides for more details.

💡

It’s advisable to load data using built-in Rel utilities within queries, rather than these specific SDK functions. See Querying a Database for more details.

Loading CSV Data

The LoadCsvAsync() method loads CSV data and inserts the result into the base relation named by the relation argument.

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

Here’s an example:

string my_data = """
cocktail,quantity,price,date
"martini",2,12.50,"2020-01-01"
"sazerac",4,14.25,"2020-02-02"
"cosmopolitan",4,11.00,"2020-03-03"
"bellini",3,12.25,"2020-04-04"
""";
 
var schema = new Dictionary<string, string>
{
    { "cocktail", "string" },
    { "quantity", "int" },
    { "price", "decimal(64,2)" },
    { "date", "date" }
};
 
var opts = new CsvOptions().WithSchema(schema);
var rsp = await client.LoadCsvAsync(database, engine, "my_csv", my_data, opts);

By default, LoadCsvAsync() attempts to infer the schema of the data. The options argument allows you to specify how to parse a given CSV file, including the schema, delimiters, and escape characters.

Loading JSON Data

The LoadJsonAsync() method loads JSON data and inserts them into the base relation named by the relation argument:

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

Here’s an example:

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

In both the LoadCsvAsync() and LoadJsonAsync() methods, the base relation relation is not cleared, allowing for multipart, incremental loads.

You can clear a base relation, such as my_base_relation, as follows:

string query = "def delete[:my_base_relation] = my_base_relation";
Console.WriteLine(await client.ExecuteWaitAsync(database, engine, query, false));

Listing Base Relations

You can list the base relations in a given database as follows:

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

The result is a JSON list of objects.

Managing Transactions

This section covers the API functions you can use to manage transactions.

Canceling Transactions

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.

Was this doc helpful?