TriCoreDb 0.3.0

dotnet add package TriCoreDb --version 0.3.0
                    
NuGet\Install-Package TriCoreDb -Version 0.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TriCoreDb" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TriCoreDb" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="TriCoreDb" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TriCoreDb --version 0.3.0
                    
#r "nuget: TriCoreDb, 0.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TriCoreDb@0.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TriCoreDb&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=TriCoreDb&version=0.3.0
                    
Install as a Cake Tool

TriCoreDb for .NET

Official .NET client for TriCoreDB, speaking the native tricore wire protocol. It covers SQL (with server-side parameters and session transactions), documents, vectors, graphs, cache and LLM context export over one connection.

NuGet Downloads License

  • No dependencies beyond the .NET base class library.
  • Async throughout, with CancellationToken on every call.
  • Server-side parameters. Values never become part of the SQL text.
  • Transactions, connection pooling, TLS and mutual TLS.

Requirements

  • .NET 8.0 or later
  • A TriCoreDB server speaking protocol 1.0 (tricore-server 0.1.0-rc.1 or later). See Running a server.

Installation

dotnet add package TriCoreDb
using TriCoreDb;

Running a server

The quickest way is the official Docker image, trinesh14/tricoredb.

Local development (no TLS and no encryption, for this machine only). Set TRICORE_ADMIN_PASSWORD in your shell first. Then create the admin and start the server:

docker run --rm -v tricoredb-dev:/var/lib/tricoredb -e TRICORE_ADMIN_PASSWORD --entrypoint /usr/local/bin/tricore trinesh14/tricoredb:0.1.0-rc.1-r2 auth init-admin --user admin --password-env TRICORE_ADMIN_PASSWORD --data-dir /var/lib/tricoredb/data
docker run -d --name tricoredb-dev -p 127.0.0.1:8427:8427 -e TRICORE_TLS=off -e TRICORE_ENCRYPTION=off -e TRICORE_MODULES=all -v tricoredb-dev:/var/lib/tricoredb trinesh14/tricoredb:0.1.0-rc.1-r2

Anything else: by default the image runs with TLS on and an encrypted data volume. Follow the quick start on the Docker Hub page to create the certificate and key, then connect with TLS as shown below.

TRICORE_MODULES=all enables every data model. The image's default is sql, document and cache; a call to a disabled model throws a TriCoreException whose ErrorCode is engine.disabled.

Connect

await using var db = await TriCoreClient.ConnectAsync("127.0.0.1", 8427, "admin", "pw");

8427 is the server's default port. TLS stays off until you pass a TlsOptions. On a plaintext connection the secret crosses the wire in the clear.

var tls = new TlsOptions
{
    CaFile = "/etc/tricore/ca.pem",   // the PEM bundle that signed the server certificate
    ServerName = "db.internal",       // SNI, and the name checked in the certificate
};
await using var db = await TriCoreClient.ConnectAsync("db.internal", 8427, "admin", "pw", tls: tls);

When TLS is on, the driver verifies the chain against CaFile alone and checks the hostname. It does not fall back to the OS root store, so leaving out CaFile gives an empty trust store and the connection fails closed. DangerAcceptInvalidCerts = true turns verification off and is meant for development only. For mTLS, set both ClientCertFile and ClientKeyFile. A Pool applies the same TlsOptions to every connection it opens.

Property Default Meaning
CaFile null (empty trust store) PEM bundle used to verify the server
ServerName "localhost" Expected server name (SNI and certificate check)
ClientCertFile null PEM client certificate chain (mTLS)
ClientKeyFile null PEM private key for that certificate (mTLS)
DangerAcceptInvalidCerts false Development only. Skips all verification

ConnectAsync also accepts connectTimeout, which bounds TCP, TLS, HELLO and AUTH, and readTimeout, which bounds each reply. They are separate deadlines, so a short connect budget never turns into a short query deadline.

Quick start

using System.Text;
using TriCoreDb;

await using var db = await TriCoreClient.ConnectAsync("127.0.0.1", 8427, "admin", "pw");

await db.CacheSetAsync("app", "greeting", Encoding.UTF8.GetBytes("world"));
string? hello = await db.CacheGetTextAsync("app", "greeting");

await db.ExecuteAsync("CREATE TABLE t (id INT PRIMARY KEY, name TEXT)");
await db.ExecuteAsync("INSERT INTO t VALUES (?, ?)", new object?[] { 1, "ada" });
Rows rows = await db.QueryAsync("SELECT id, name FROM t WHERE id = ?", new object?[] { 1 });
Console.WriteLine($"{rows.Columns[1]} = {rows[0][1]}");

SQL

QueryAsync runs SELECT only. It is a read-only boundary, so a caller that holds only read permission can never write through it. ExecuteAsync runs writes and DDL.

Parameters are bound on the server

Each ? placeholder is bound server-side. The values travel beside the statement as a typed JSON array, so a value can never turn into syntax. This needs the SERVER_PARAMS feature bit, which is negotiated at HELLO (db.ServerParamsGranted). If the server did not grant it, the overloads that take arguments throw by name and send nothing. They never fall back to pasting values into the SQL text. If you really want client-side rendering, call SqlParams.Bind(sql, args) yourself.

How .NET values go on the wire:

.NET value Wire form Notes
int, long, other integers JSON number
ulong above long.MaxValue exact JSON number Never narrowed. The server binds it as a decimal and the column decides whether it fits.
double, float JSON number NaN and infinities are refused
decimal text, ToString(CultureInfo.InvariantCulture) Every digit survives
byte[], ReadOnlyMemory<byte> "0x" + lowercase hex For BLOB columns
bool, null, DBNull JSON true/false/null
string, char, Guid, DateTime JSON string

The decimal trade-off. A decimal goes out as text because a JSON number is read through a double, which would lose the digits a decimal exists to keep. The server accepts text for a DECIMAL column but not for a DOUBLE one, so a decimal bound into a DOUBLE column fails by name. Pass a double for a DOUBLE column.

Transactions

The two shapes below behave differently.

  • TransactionAsync(params SqlStatement[]) sends a whole BEGIN … COMMIT script as one request. It works on every node, including sharded and forwarding nodes.
  • BeginAsync / CommitAsync / RollbackAsync keep a rollback boundary open across requests on this connection. They need the SESSION_TXN feature bit (db.SessionTxnGranted). Without it BeginAsync throws by name before sending anything, and never falls back to silent autocommit.
await db.WithTransactionAsync(async tx =>          // commits on return
{
    await tx.ExecuteAsync("UPDATE accounts SET balance = balance - 10 WHERE id = ?", new object?[] { 1 });
    await tx.ExecuteAsync("UPDATE accounts SET balance = balance + 10 WHERE id = ?", new object?[] { 2 });
});                                                // rolls back and rethrows on any exception

await using (var tx = await db.BeginTransactionAsync())   // disposing rolls back unless committed
{
    await db.ExecuteAsync("INSERT INTO t VALUES (5, 'eve')");
    await tx.CommitAsync();
}

A block belongs to its connection's socket. The server refuses a COMMIT sent from any other connection, and a dropped socket rolls the block back. Pool.UseAsync never returns a connection to the pool with a block still open: it rolls the block back first and then throws at the callback that left it open.

ConnectAsync(features:) overrides the feature bitmap sent in HELLO. For example, TriCoreClient.Features & ~TriCoreClient.FeatureSessionTxn masks one bit out.

Pool

await using var pool = new Pool("127.0.0.1", 8427, "admin", "pw", size: 8);
long n = await pool.UseAsync(async c => long.Parse((await c.QueryAsync("SELECT COUNT(*) FROM t"))[0][0]));

The pool fills lazily and stays bounded. When it is exhausted, a caller waits until its timeout and then gets PoolTimeoutException; the pool never grows. A server-side error leaves the connection in the pool, while a poisoned or timed-out connection is retired.

Other families

Every method takes an optional trailing database (default "main") and a CancellationToken.

  • Cache: keys, TTLs and counters (CacheSetAsync, CacheGetAsync, CacheIncrAsync, CacheSetNxAsync, CacheKeysAsync, …), lists (CacheLPushAsync … CacheLIndexAsync), sets (CacheSAddAsync … CacheSMembersAsync), hashes (CacheHSetAsync … CacheHLenAsync) and streams (CacheXAddAsync … CacheXTrimAsync). Values are byte[], and the *Text overloads handle UTF-8. A miss returns null, which is distinct from an empty value.
  • Cache, server 0.1.0-rc.3 and later: sorted sets (CacheZAddAsync, CacheZRangeAsync, CacheZRangeByScoreAsync, …), locks with fencing tokens (CacheLockAcquireAsync, CacheLockRenewAsync, CacheLockReleaseAsync), bitmaps (CacheSetBitAsync … CacheBitOpAsync), HyperLogLog (CachePfAddAsync, CachePfCountAsync, CachePfMergeAsync), CacheMGetAsync / CacheMSetAsync, CacheSetIfAsync, CacheCompareAndSwapAsync, CacheGetDelAsync, CacheGetSetAsync, CacheGetExAsync, stale-while-revalidate reads (CacheSetFreshAsync, CacheGetFreshAsync), blocking pops (CacheBLPopAsync, CacheBRPopAsync), set algebra (CacheSUnionAsync, CacheSInterAsync, CacheSDiffAsync), pipelines, transactions and scripts built from CacheCommand, and Pub/Sub by polling (CachePublishAsync, CachePollAsync).
  • Documents: collections, DocumentInsertAsync, DocumentFindAsync with DocumentFilter, DocumentUpdateManyAsync with DocumentUpdate, secondary indexes, and DocumentAggregateAsync with AggregateStage.
  • Documents, server 0.1.0-rc.3 and later: DocumentReplaceAsync, DocumentInsertManyAsync, DocumentDeleteManyAsync, DocumentBulkWriteAsync with BulkOp, DocumentExplainAsync, collection options (a schema, defaults and versions, as CollectionOptions), and transactions spanning requests.
  • Vectors: VectorCreateCollectionAsync (cosine, dot or l2), upsert, get, delete, VectorSearchAsync with metadata filters, describe and list.
  • Vectors, server 0.1.0-rc.3 and later: VectorUpsertManyAsync, VectorSearchBatchAsync, VectorSearchMultiAsync, VectorSearchByExamplesAsync, filters (VectorCountAsync, VectorDeleteByFilterAsync, VectorUpdateByFilterAsync), named vectors, aliases, the built-in embedder (VectorEmbedAsync, VectorUpsertTextAsync) and index maintenance.
  • Graph: nodes, edges, neighbours, traversal, GraphShortestPathAsync, GraphWeightedShortestPathAsync, degree, paging, and GraphQueryAsync for the read-only Cypher subset.
  • Graph, server 0.1.0-rc.3 and later: GraphAddManyAsync / GraphRemoveManyAsync, in-place updates, GraphDfsAsync, GraphSubgraphAsync, GraphKShortestPathsAsync, GraphAllPathsAsync, algorithms (components, cycles, centrality, communities, triangles, similarity, PageRank), indexes and constraints with GraphFindNodesAsync, Cypher writes through GraphExecuteAsync, and graph transactions.
  • LLM: LlmContextAsync and LlmSchemaAsync render read-only context as TOON, JSON, Markdown or native output.
  • Admin: AdminPingAsync and AdminStatusAsync (these need [modules] cluster = true). PingAsync is a transport-level liveness check.

RequestAsync(JsonObject op) sends any raw operation.

The methods marked "server 0.1.0-rc.3 and later" are answered with an error, raised as TriCoreException, by an older server.

Document and graph transactions

A transaction is named by an id, and a request runs inside it when it carries that id. RequestTxn puts the id on every request the client sends until the transaction ends:

var txn = await db.DocumentBeginTransactionAsync();
db.RequestTxn = txn;
await db.DocumentInsertAsync("orders", order);
await db.DocumentUpdateOneAsync("stock", "sku-1", DocumentUpdate.Inc("held", 1));
await db.DocumentCommitTransactionAsync(txn);   // clears RequestTxn

Errors

Every failure the driver raises derives from TriCoreException. The subtypes are AuthException, ProtocolException, PoolTimeoutException, TriCoreTimeoutException (fatal to its connection) and RequestNotSentException.

ErrorCode holds the server's diagnostics.error_code. Branch on that code, not on the message text.

Leader redirects

In a cluster only the leader accepts writes, and a follower refuses with not_leader. The driver handles that itself (FollowLeader, on by default):

  • when the refusal names the leader, it opens a connection there with the same user, secret and TLS settings (the leader's host is the certificate name), sends the request again, and keeps that connection (Address reports it);
  • while an election is in progress (no leader named), it waits and asks the same node again, pausing 250 ms, then 500 ms, up to 2 s (LeaderRetryDelay);
  • after LeaderRetries (5) it throws the last refusal.

This is safe because a not_leader refusal was applied nowhere. Nothing is sent again inside a session transaction, and never a refusal with ErrorCode == TriCoreException.ErrorCodeOutcomeUnknown: a follower handed that request to the leader and lost the answer, or the leader could not confirm it, so it may have been applied. The leader's address must be reachable from your application; when it is not, the driver keeps its connection and waits. A Pool has the same three properties and applies them to every connection it opens.

To handle redirects yourself, set FollowLeader = false:

db.FollowLeader = false;
try { await db.ExecuteAsync("INSERT INTO t VALUES (1)"); }
catch (TriCoreException ex) when (ex.IsNotLeader)
{
    // ex.LeaderHint is "host:port", or null when the leader is unknown (mid-election)
}

Response exposes the same ErrorCode, LeaderHint and IsNotLeader.

Hardening

  • Inbound frames are capped at 64 KiB for control frames and a protocol ceiling for responses. The cap is checked before any buffer is allocated.
  • A frame with an unknown version is rejected.
  • Overlapping requests on one connection are refused with RequestNotSentException, so one request can never read another's reply.
  • A connection whose stream can no longer be trusted is marked poisoned and refuses further use.

Building from source

scripts/build.ps1         # or scripts/build.sh
scripts/test.ps1          # xUnit; live tests start a private tricore-server
scripts/test.ps1 -Offline # only the tests that need no server
scripts/conformance.ps1   # cross-SDK conformance matrix (needs Node and the spec repo)
scripts/pack.ps1          # nupkg + snupkg into ./artifacts

Live tests look for the server binary in TRICORE_SERVER_BIN, then in a sibling tricore/tricore-db/target/{release,debug} checkout. Each run starts its own server on an ephemeral port with a temporary data directory and stops that process when it finishes. With no binary to start, those tests are skipped rather than failed, so dotnet test is green on a machine that has no server.

examples/TriCoreDb.Examples is a console app with one subcommand per family (basic, sql, nosql, vector, graph, cache, errors, concurrency, all). It reads TRICOREDB_HOST, TRICOREDB_PORT, TRICOREDB_USER and TRICOREDB_PASSWORD.

License

Apache-2.0. See LICENSE.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 133 10/19/2026
0.1.0 126 9/16/2026

See CHANGELOG.md.