Skip to main content
Version: 2.0.0

C# Reference

The SpacetimeDB client for C# contains all the tools you need to build native clients for SpacetimeDB modules using C#.

Server module quick reference​

If you are writing a SpacetimeDB module (tables and reducers), use these patterns:

  • Module class: public static partial class Module
  • Tables: [SpacetimeDB.Table(Accessor = "TableName", Public = true)] on partial struct (or partial class) — Accessor controls generated API names, and canonical SQL names are derived unless Name is explicitly set
  • Primary key: Define [SpacetimeDB.PrimaryKey] on one column when you need key-based lookups or updates
  • Reducers: [SpacetimeDB.Reducer] on static methods with ReducerContext ctx as first parameter
  • Required: using SpacetimeDB; and partial on all table structs and the Module class
  • Index: Always use SpacetimeDB.Index.BTree (never bare Index). Bare Index is ambiguous with System.Index. For multi-column: Columns = new[] { nameof(Col1), nameof(Col2) }, not collection expressions [nameof(X)]
  • Sum types: Use TaggedEnum<(VariantA A, VariantB B)> with partial record, not partial class
  • Scheduled tables: ScheduledAt should reference a field of type ScheduleAt in the schedule table

See Tables, Reducers, and Column Types for full server-side documentation. The rest of this page covers the client SDK (connections, subscriptions, callbacks).


Before diving into the reference, you may want to review:

NameDescription
Project setupConfigure a C# project to use the SpacetimeDB C# client SDK.
Generate module bindingsUse the SpacetimeDB CLI to generate module-specific types and interfaces.
DbConnection typeA connection to a remote database.
IDbContext interfaceMethods for interacting with the remote database.
EventContext typeImplements IDbContext for row callbacks.
ReducerEventContext typeImplements IDbContext for reducer callbacks.
SubscriptionEventContext typeImplements IDbContext for subscription callbacks.
ErrorContext typeImplements IDbContext for subscription error callbacks.
Query Builder APIType-safe query builder for typed subscription queries.
Access the client cacheAccess to your local view of the database.
Observe and invoke reducersSend requests to the database to run reducers, and register callbacks to run when notified of reducers.
Identify a clientTypes for identifying users and client connections.

Project setup​

Using the dotnet CLI tool​

If you would like to create a console application using .NET, you can create a new project using dotnet new console and add the SpacetimeDB SDK to your dependencies:

dotnet add package SpacetimeDB.ClientSDK

(See also the CSharp Quickstart for an in-depth example of such a console application.)

Using Unity​

Add the SpacetimeDB Unity Package using the Package Manager. Open the Package Manager window by clicking on Window -> Package Manager. Click on the + button in the top left corner of the window and select "Add package from git URL". Enter the following URL and click Add.

https://github.com/clockworklabs/com.clockworklabs.spacetimedbsdk.git

(See also the Unity Tutorial)

The Unity package includes a SpacetimeDBNetworkManager component. Add one instance to a scene GameObject if you want the SDK to advance active connections from Unity's Update loop automatically. If you do not use the manager, call FrameTick yourself every frame.

Generate module bindings​

Each SpacetimeDB client depends on some bindings specific to your module. Create a module_bindings directory in your project's directory and generate the C# interface files using the Spacetime CLI. From your project directory, run:

mkdir -p module_bindings
spacetime generate --lang csharp --out-dir module_bindings --module-path PATH-TO-MODULE-DIRECTORY

Replace PATH-TO-MODULE-DIRECTORY with the path to your SpacetimeDB module.

Type DbConnection​

A connection to a remote database is represented by the DbConnection class. This class is generated per module and contains information about the types, tables, and reducers defined by your module.

NameDescription
Connect to a databaseConstruct a DbConnection instance.
Advance the connectionPoll the DbConnection or run it in the background.
Access tables and reducersAccess the client cache, request reducer invocations, and register callbacks.

Connect to a database​

class DbConnection
{
    public static DbConnectionBuilder<DbConnection> Builder();
}

Construct a DbConnection by calling DbConnection.Builder(), chaining configuration methods, and finally calling .Build(). At a minimum, you must specify WithUri to provide the URI of the SpacetimeDB instance, and WithDatabaseName to specify the database's name or identity.

NameDescription
WithUri methodSet the URI of the SpacetimeDB instance hosting the remote database.
WithDatabaseName methodSet the name or identity of the remote database.
WithConfirmedReads methodEnable or disable confirmed reads.
WithCompression methodSet the compression method for WebSocket messages.
OnConnect callbackRegister a callback for the first successful connection.
OnAutomaticReconnect callbackRegister a callback for each successful automatic reconnect.
OnConnectError callbackRegister a callback to run if the connection is rejected or the host is unreachable.
OnDisconnect callbackRegister a callback to run when the connection ends.
WithToken methodSupply a token to authenticate with the remote database.
WithAutomaticReconnect methodReconnect automatically after an established connection is lost.
WithTokenProvider methodObtain refreshed credentials before a reconnect attempt.
Build methodFinalize configuration and open the connection.

Method WithUri​

class DbConnectionBuilder<DbConnection>
{
    public DbConnectionBuilder<DbConnection> WithUri(string uri);
}

Configure the URI of the SpacetimeDB instance or cluster which hosts the remote module and database.

Method WithDatabaseName​

class DbConnectionBuilder
{
    public DbConnectionBuilder<DbConnection> WithDatabaseName(string nameOrIdentity);
}

Configure the SpacetimeDB domain name or Identity of the remote database which identifies it within the SpacetimeDB instance or cluster.

Method WithConfirmedReads​

class DbConnectionBuilder
{
    public DbConnectionBuilder<DbConnection> WithConfirmedReads(bool confirmedReads);
}

Configure the connection to request confirmed reads.

When enabled, the server will send query results only after they are confirmed to be durable, i.e. persisted to disk on one or more replicas depending on the replication settings of the database. When set to false, the server will send results as soon as transactions are committed in memory.

Procedures called over this connection inherit the same setting. With confirmed reads enabled, each successful transaction block waits for durability before procedure code continues.

If this method is not called, the server chooses the default.

Method WithCompression​

class DbConnectionBuilder
{
    public DbConnectionBuilder<DbConnection> WithCompression(Compression compression);
}

Configure compression for WebSocket messages. Available options are Compression.Brotli, Compression.Gzip, and Compression.None.

If this method is not called, the SDK uses Compression.Brotli. Use Compression.None only when you need to debug raw message sizes or avoid compression overhead for very small local test payloads.

Callback OnConnect​

class DbConnectionBuilder<DbConnection>
{
    public delegate void ConnectCallback(DbConnection conn, Identity identity, string token);

    public DbConnectionBuilder<DbConnection> OnConnect(ConnectCallback callback);
}

Chain a call to .OnConnect(callback) to your builder to register a callback to run when your new DbConnection successfully initiates its connection to the remote database. The callback accepts three arguments: a reference to the DbConnection, the Identity by which SpacetimeDB identifies this connection, and a private access token which can be saved and later passed to WithToken to authenticate the same user in future connections.

OnConnect runs once per DbConnection, on its first successful connection. Register subscriptions and row callbacks here, or once after Build(). Automatic recovery does not repeat this setup; use OnAutomaticReconnect for work that should run after recovery. With automatic reconnect enabled, the token argument is the retained authentication token, including on Unity WebGL, rather than the transport's short-lived WebSocket token.

Callback OnAutomaticReconnect​

class DbConnectionBuilder<DbConnection>
{
    public DbConnectionBuilder<DbConnection> OnAutomaticReconnect(ConnectCallback callback);
}

Register a callback for each successful automatic reconnect by chaining .OnAutomaticReconnect(callback) alongside .WithAutomaticReconnect(). It uses the same ConnectCallback delegate as OnConnect: the existing connection object, its unchanged identity, and the retained or refreshed authentication token. If your application persists tokens, save the token from both callbacks, including on Unity WebGL.

This callback runs on the thread calling FrameTick(), after the new connection is established and before subscription replay. It does not run for the initial connection or a failed attempt. IsActive is true and IsReconnecting is false, but cached rows may still be stale. Use subscription OnApplied callbacks to know when replay has reconciled the cache.

Existing subscriptions and row callbacks survive recovery. Do not register them again in OnAutomaticReconnect. This callback is optional; the SDK replays retained subscriptions whether or not one is registered.

Callback OnConnectError​

class DbConnectionBuilder<DbConnection>
{
    public delegate void ConnectErrorCallback(Exception error);
    public delegate void ConnectErrorWithReconnectCallback(Exception error, NextReconnect? nextReconnect);

    public DbConnectionBuilder<DbConnection> OnConnectError(ConnectErrorCallback callback);
    public DbConnectionBuilder<DbConnection> OnConnectError(ConnectErrorWithReconnectCallback callback);
}

Register a callback for an initial connection failure or a failed reconnect attempt. The overload accepting NextReconnect? reports the next attempt and delay, or null when no retry will occur. Initial connection failures never retry automatically. Both overloads remain supported.

Callback OnDisconnect​

class DbConnectionBuilder<DbConnection>
{
    public delegate void DisconnectCallback(DbConnection conn, Exception? error);
    public delegate void DisconnectWithReconnectCallback(DbConnection conn, Exception? error, NextReconnect? nextReconnect);

    public DbConnectionBuilder<DbConnection> OnDisconnect(DisconnectCallback callback);
    public DbConnectionBuilder<DbConnection> OnDisconnect(DisconnectWithReconnectCallback callback);
}

Chain a call to .OnDisconnect(callback) to your builder to register a callback to run when your DbConnection disconnects from the remote database, either as a result of a call to Disconnect or due to an error.

With automatic reconnect enabled, losing an established connection reports the scheduled retry through NextReconnect?. Keep the connection and its update loop running when this value is non-null. A null value means the connection has ended permanently. An explicit Disconnect() reports a null error and no retry, including when called during recovery.

Method WithToken​

class DbConnectionBuilder<DbConnection>
{
    public DbConnectionBuilder<DbConnection> WithToken(string? token);
}

Chain a call to .WithToken(token) to your builder to provide an OpenID Connect compliant JSON Web Token to authenticate with, or to explicitly select an anonymous connection. If this method is not called or null is passed, SpacetimeDB will generate a new Identity and sign a new private access token for the connection.

Method WithAutomaticReconnect​

class DbConnectionBuilder<DbConnection>
{
    public DbConnectionBuilder<DbConnection> WithAutomaticReconnect(AutomaticReconnectOptions? options = null);
}

class AutomaticReconnectOptions
{
    public TimeSpan? MinDelay { get; set; } // default 1 second
    public TimeSpan? MaxDelay { get; set; } // default 30 seconds
}

Enable automatic recovery after a connection has succeeded at least once. This is opt-in; without it, a lost connection must be replaced by the application. Initial connection failures do not retry.

Retries use exponential backoff: the delay starts at MinDelay and doubles after each failed attempt up to MaxDelay, with ±50% jitter clamped to those bounds. With the defaults, the base delays are 1, 2, 4, 8, 16, and 30 seconds. There is no attempt limit, and a successful connection resets the retry counter.

To keep clients from overwhelming a database with rapid retries, MinDelay is never lower than 500 ms and MaxDelay is never lower than 1 second or lower than MinDelay. Values below these floors are raised to them and a warning is logged. Explicit Disconnect(), a changed identity, or a terminal protocol or authentication error stops recovery. See token refresh for authentication rejection handling.

The SDK keeps the same DbConnection, Identity, table handles, subscriptions, and callbacks. Each reconnect attempt uses a fresh ConnectionId. Keep calling FrameTick() during outages; it drives retries as well as callbacks.

var conn = DbConnection.Builder()
    .WithUri("http://localhost:3000")
    .WithDatabaseName("my-database")
    .WithAutomaticReconnect()
    .OnConnect((connection, identity, token) => Console.WriteLine($"Connected as {identity}"))
    .OnAutomaticReconnect((connection, identity, token) => Console.WriteLine($"Reconnected as {identity}"))
    .OnDisconnect((connection, error, next) =>
    {
        if (next is { } retry)
            Console.WriteLine($"Retry {retry.Attempt} in {retry.Delay}.");
    })
    .OnConnectError((error, next) => Console.WriteLine(error.Message))
    .Build();

conn.SubscriptionBuilder()
    .OnApplied(ctx => Console.WriteLine("Subscription ready"))
    .SubscribeToAllTables();

Arrange for your application's update loop to call conn.FrameTick(). With automatic reconnect enabled, subscriptions may be created immediately after Build() or during an outage; they are sent once the connection is ready.

During an outage, cached rows remain readable but may be stale. After OnAutomaticReconnect, the SDK replays the retained subscriptions in one batch and reconciles the resulting snapshot with the cache. All tables are updated before row callbacks run; unchanged rows do not fire callbacks, and changed rows produce net insert, update, or delete events. Each successful subscription's OnApplied runs again. A rejected subscription gets OnError after the successful subscriptions are reconciled.

Reducer, procedure, and one-off query calls made while disconnected fail immediately and are not queued. Calls awaiting a result when the connection is lost are not replayed: reducer callbacks receive Status.UnknownResult, while procedures and one-off queries fail with UnknownResultException. The server may already have executed those calls; do not automatically repeat non-idempotent operations.

Method WithTokenProvider​

class DbConnectionBuilder<DbConnection>
{
    public DbConnectionBuilder<DbConnection> WithTokenProvider(Func<Task<string>> provider);
}

Register an optional asynchronous provider of authentication tokens for automatic reconnect. Use it together with WithAutomaticReconnect(). The provider is not called for the initial connection; supply the initial token through WithToken(initialToken).

var conn = DbConnection.Builder()
    .WithUri("http://localhost:3000")
    .WithDatabaseName("my-database")
    .WithToken(initialToken)
    .WithAutomaticReconnect()
    .WithTokenProvider(() => RefreshTokenAsync())
    .Build();

initialToken and RefreshTokenAsync come from your authentication integration. The provider must return a non-empty token for the same identity. To switch users, disconnect and build a new connection.

Before each reconnect attempt, the SDK checks the retained token's JWT exp and iat claims. It calls the provider when the remaining lifetime is at most 30 seconds or 5% of the token's original lifetime, whichever is greater. If expiry cannot be read, it calls the provider on every attempt. Without a provider, the SDK reuses the retained token. This does not schedule background token refresh while the connection is healthy.

If the server rejects a reused token, the next attempt forces a provider call regardless of expiry. Rejection of a freshly provided token, or rejection with no provider available, is terminal. If the provider throws, its task faults, or it returns an empty token, OnConnectError reports the failure and schedules another attempt.

After a successful reconnect, OnAutomaticReconnect receives the token used for authentication. Persist it there if your application saves tokens for future sessions.

The provider is invoked from FrameTick; return a task instead of blocking that thread. Disconnect() prevents a pending provider result from opening another connection, but does not cancel the provider's own asynchronous work.

Type NextReconnect​

public readonly struct NextReconnect
{
    public int Attempt { get; }
    public TimeSpan Delay { get; }
}

Retry information supplied to the connection-error and disconnect callbacks. Attempt starts at 1 and Delay is the scheduled wait before that attempt. A nullable NextReconnect value of null means no retry is scheduled. A server response indicating that the previous session is still closing retains the current attempt number and uses the initial backoff delay.

Method Build​

class DbConnectionBuilder<DbConnection>
{
    public DbConnection Build();
}

After configuring the connection and registering callbacks, attempt to open the connection.

Advance the connection and process messages​

In the interest of supporting a wide variety of client applications with different execution strategies, the SpacetimeDB SDK allows you to choose when the DbConnection spends compute time and processes messages. If you do not arrange for the connection to advance by calling one of these methods, the DbConnection will never advance, and no callbacks will ever be invoked.

NameDescription
FrameTick methodProcess messages on the main thread without blocking.

Method FrameTick​

class DbConnection {
    public void FrameTick();
}

FrameTick processes pending updates and advances reconnect timers and token-provider tasks, then returns rather than blocking. Games might call this method every frame. Keep calling it even when IsActive is false so automatic reconnect can progress.

In Unity projects, a SpacetimeDBNetworkManager component can call FrameTick for active and reconnecting connections automatically. Use either the manager or your own update loop; without one of them, callbacks will not be invoked.

In Godot projects, register the connection with STDBUpdateManager.Add(conn) and keep it registered during recovery, or call FrameTick() from your own _Process method. Remove and disconnect it when the application is done with the connection.

It is not advised to run FrameTick on a background thread, since it modifies dbConnection.Db. If main thread code is also accessing the Db, it may observe data races when FrameTick runs on another thread.

(Note that the SDK already does most of the work for parsing messages on a background thread. FrameTick() does the minimal amount of work needed to apply updates to the Db.)

Access tables and reducers​

Property Db​

class DbConnection
{
    public RemoteTables Db;
    /* other members */
}

The Db property of the DbConnection provides access to the subscribed view of the remote database's tables. See Access the client cache.

Property Reducers​

class DbConnection
{
    public RemoteReducers Reducers;
    /* other members */
}

The Reducers field of the DbConnection provides access to reducers exposed by the module of the remote database. See Observe and invoke reducers.

Interface IDbContext​

interface IDbContext<DbView, RemoteReducers, ..>
{
    /* methods */
}

DbConnection, EventContext, ReducerEventContext, SubscriptionEventContext and ErrorContext all implement IDbContext. IDbContext has methods for inspecting and configuring your connection to the remote database.

The IDbContext interface is implemented by connections and contexts to every module - hence why it takes DbView and RemoteReducers as type parameters.

NameDescription
IRemoteDbContext interfaceModule-specific IDbContext.
Db methodProvides access to the subscribed view of the remote database's tables.
Reducers methodProvides access to reducers exposed by the remote module.
Disconnect methodEnd the connection.
Subscribe to queriesRegister subscription queries to receive updates about matching rows.
Read connection metadataAccess the connection's Identity and ConnectionId

Interface IRemoteDbContext​

Each module's module_bindings exports an interface IRemoteDbContext which inherits from IDbContext, with the type parameters DbView and RemoteReducers bound to the types defined for that module. This can be more convenient when creating functions that can be called from any callback for a specific module, but which access the database or invoke reducers, and so must know the type of the DbView or Reducers.

Method Db​

interface IRemoteDbContext
{
    public DbView Db { get; }
}

Db will have methods to access each table defined by the module.

Example​
var conn = ConnectToDB();

// Get a handle to the User table
var tableHandle = conn.Db.User;

Method Reducers​

interface IRemoteDbContext
{
    public RemoteReducers Reducers { get; }
}

Reducers will have methods to invoke each reducer defined by the module, plus events for observing the result of reducer calls made by this connection.

Example​
var conn = ConnectToDB();

// Register a callback to observe the result of SendMessage calls made by this connection.
conn.Reducers.OnSendMessage += Reducer_OnSendMessageEvent;

Method Disconnect​

interface IRemoteDbContext
{
    public void Disconnect();
}

Permanently close the DbConnection, cancel scheduled reconnects, and ignore any pending token-provider result. This also works during an outage. Repeated calls have no effect. To connect again after an explicit disconnect or terminal failure, build a new connection.

Subscribe to queries​

NameDescription
SubscriptionBuilder typeBuilder-pattern constructor to register subscribed queries.
TypedSubscriptionBuilder typeBuilder for typed query subscriptions.
SubscriptionHandle typeManage an active subscription.

Type SubscriptionBuilder​

NameDescription
ctx.SubscriptionBuilder() constructorBegin configuring a new subscription.
OnApplied callbackRegister a callback to run when matching rows become available.
OnError callbackRegister a callback to run if the subscription fails.
Subscribe methodFinish configuration and subscribe to one or more queries.
AddQuery methodBuild a typed subscription query without writing query strings.
SubscribeToAllTables methodConvenience method to subscribe to the entire database.
Constructor ctx.SubscriptionBuilder()​
interface IRemoteDbContext
{
    public SubscriptionBuilder SubscriptionBuilder();
}

Subscribe to queries by calling ctx.SubscriptionBuilder() and chaining configuration methods, then calling .Subscribe(queries).

Callback OnApplied​
class SubscriptionBuilder
{
    public SubscriptionBuilder OnApplied(Action<SubscriptionEventContext> callback);
}

Register a callback to run when the subscription is applied and the matching rows are inserted into the client cache.

With automatic reconnect enabled, this callback runs again after each successful replay. Use it to mark data ready, and keep one-time setup separate so it is not repeated on every reconnect.

Callback OnError​
class SubscriptionBuilder
{
    public SubscriptionBuilder OnError(Action<ErrorContext, Exception> callback);
}

Register a callback to run if the subscription is rejected or unexpectedly terminated by the server. This is most frequently caused by passing an invalid query to Subscribe.

Method Subscribe​
class SubscriptionBuilder
{
    public SubscriptionHandle Subscribe(string[] querySqls);
}

Subscribe to a set of queries. queries should be an array of SQL query strings.

See the SpacetimeDB SQL Reference for information on the queries SpacetimeDB supports as subscriptions.

For typed query subscriptions, use AddQuery.

Method AddQuery​
class SubscriptionBuilder
{
    public TypedSubscriptionBuilder AddQuery<TRow>(
        Func<QueryBuilder, IQuery<TRow>> build
    );
}

Start a typed query subscription. Once a typed query is added, continue with typed queries on TypedSubscriptionBuilder and finish with Subscribe().

var handle = conn
    .SubscriptionBuilder()
    .AddQuery(q => q.From.User())
    .AddQuery(q => q.From.Message())
    .Subscribe();
Method SubscribeToAllTables​
class SubscriptionBuilder
{
    public void SubscribeToAllTables();
}

Subscribe to all rows from all public tables, including public event tables. This method is provided as a convenience for simple clients. The subscription initiated by SubscribeToAllTables cannot be canceled after it is initiated. You should subscribe to specific queries if you need fine-grained control over the lifecycle of your subscriptions.

Type TypedSubscriptionBuilder​

NameDescription
AddQuery methodAdd another typed query to the same subscription.
Subscribe methodSubscribe to all typed queries added so far.
Method AddQuery (TypedSubscriptionBuilder)​
class TypedSubscriptionBuilder
{
    public TypedSubscriptionBuilder AddQuery<TRow>(
        Func<QueryBuilder, IQuery<TRow>> build
    );
}

Add another typed query. This keeps all added queries grouped under one returned SubscriptionHandle.

Method Subscribe (TypedSubscriptionBuilder)​
class TypedSubscriptionBuilder
{
    public SubscriptionHandle Subscribe();
}

Subscribe to the set of typed queries that were added to the builder.

Query Builder API​

The C# SDK provides a type-safe query builder for subscriptions. You use it through SubscriptionBuilder.AddQuery(...) and TypedSubscriptionBuilder.AddQuery(...).

Entry Point​

Typed query builders are created from generated table accessors under QueryBuilder.From.

var handle = conn
    .SubscriptionBuilder()
    .AddQuery(q => q.From.User())
    .Subscribe();

Building Queries with Where / Filter​

Each generated table accessor supports both Where(...) and Filter(...). They are equivalent. Chaining multiple Where/Filter calls combines conditions with logical AND.

// All users
q.From.User()

// Filtered users
q.From.User().Where(u => u.Online.Eq(true))
q.From.User().Filter(u => u.Name.Neq("Anonymous"))

// Chained filters (AND semantics)
q.From.User()
    .Where(u => u.Score.Gte(1000UL))
    .Filter(u => u.Level.Gte(10U))

Comparison Operators​

OperatorDescriptionExample
EqEqual tou.Online.Eq(true)
NeqNot equal tou.Name.Neq("BOT")
LtLess thanu.Level.Lt(10U)
LteLess than or equal tou.Level.Lte(10U)
GtGreater thanu.Score.Gt(1000UL)
GteGreater than or equal tou.Score.Gte(1000UL)

Boolean Combinators​

Combine conditions with And, Or, and Not:

q.From.User().Where(u => u.Level.Gte(5U).And(u.Level.Lt(10U)))
q.From.User().Where(u => u.Online.Eq(true).Or(u.Name.Eq("Admin")))
q.From.User().Where(u => u.Banned.Eq(true).Not())

Semijoins​

Semijoins match rows across two tables and return rows from one side:

  • LeftSemijoin(...) returns rows from the left side that match at least one row on the right.
  • RightSemijoin(...) returns rows from the right side that match at least one row on the left.
  • The join predicate uses indexed columns (IxCols) and must compare one indexed column from each side with Eq.
  • Filters before a semijoin apply to the pre-join source side. Filters after a semijoin apply to the returned side.
var handle = conn
    .SubscriptionBuilder()
    .AddQuery(q => q.From.Player()
        .Where(p => p.Score.Gte(1000UL))
        .LeftSemijoin(q.From.PlayerLevel(), (p, pl) => p.Id.Eq(pl.PlayerId))
        .Where(p => p.Online.Eq(true)))
    .AddQuery(q => q.From.Player()
        .Where(p => p.Score.Gte(1000UL))
        .RightSemijoin(q.From.PlayerLevel(), (p, pl) => p.Id.Eq(pl.PlayerId))
        .Where(pl => pl.Level.Gte(10U)))
    .Subscribe();

Using Query Builders with Subscriptions​

AddQuery accepts a builder function that returns an IQuery<TRow>. You can add multiple typed queries and subscribe once.

var handle = conn
    .SubscriptionBuilder()
    .AddQuery(q => q.From.User().Where(u => u.Online.Eq(true)))
    .AddQuery(q => q.From.Message().Where(m => m.ChannelId.Eq(1U)))
    .Subscribe();

Type SubscriptionHandle​

A SubscriptionHandle represents a subscribed query or a group of subscribed queries.

The SubscriptionHandle does not contain or provide access to the subscribed rows. Subscribed rows of all subscriptions by a connection are contained within that connection's ctx.Db. See Access the client cache.

NameDescription
IsEnded propertyDetermine whether the subscription has ended.
IsActive propertyDetermine whether the subscription is active and its matching rows are present in the client cache.
Unsubscribe methodDiscard a subscription.
UnsubscribeThen methodDiscard a subscription, and register a callback to run when its matching rows are removed from the client cache.
Property IsEnded​
class SubscriptionHandle
{
    public bool IsEnded;
}

True if this subscription has been terminated due to an unsubscribe call or an error.

Property IsActive​
class SubscriptionHandle
{
    public bool IsActive;
}

True if this subscription has been applied and has not yet been unsubscribed.

Method Unsubscribe​
class SubscriptionHandle
{
    public void Unsubscribe();
}

Terminate this subscription, causing matching rows to be removed from the client cache. Any rows removed from the client cache this way will have OnDelete callbacks run for them.

Unsubscribing is an asynchronous operation. Matching rows are not removed from the client cache immediately. Use UnsubscribeThen to run a callback once the unsubscribe operation is completed.

With automatic reconnect enabled, you can also unsubscribe while offline or while the subscription is pending. An offline unsubscribe ends the handle locally and excludes it from replay; its cached rows remain until the next snapshot reconciliation. In that case, UnsubscribeThen runs when the handle ends, before stale rows are removed.

Returns an error if the subscription has already ended, either due to a previous call to Unsubscribe or UnsubscribeThen, or due to an error.

Method UnsubscribeThen​
class SubscriptionHandle
{
    public void UnsubscribeThen(Action<SubscriptionEventContext>? onEnded);
}

Terminate this subscription, and run the onEnded callback when the subscription is ended and its matching rows are removed from the client cache. Any rows removed from the client cache this way will have OnDelete callbacks run for them.

During an automatic-reconnect outage, the callback instead runs when the handle ends locally. Stale rows are removed on the next snapshot reconciliation, as described under Unsubscribe.

Returns an error if the subscription has already ended, either due to a previous call to Unsubscribe or UnsubscribeThen, or due to an error.

Read connection metadata​

Property Identity​

interface IDbContext
{
    public Identity? Identity { get; }
}

Get the Identity with which SpacetimeDB identifies the connection. This method returns null if the connection was initiated anonymously and the newly-generated Identity has not yet been received, i.e. if called before the OnConnect callback is invoked.

Property ConnectionId​

interface IDbContext
{
    public ConnectionId ConnectionId { get; }
}

Get the ConnectionId with which SpacetimeDB identifies the connection.

Automatic reconnect preserves Identity but assigns a fresh ConnectionId for each attempt.

Property IsActive​

interface IDbContext
{
    public bool IsActive { get; }
}

true when the transport is connected and the connection has not closed. With automatic reconnect enabled, it also requires the initial handshake to have completed; it is false before the first OnConnect and during outages. It becomes true before subscription replay completes, so use subscription OnApplied to determine when cached data is current.

Property IsReconnecting​

class DbConnection
{
    public bool IsReconnecting { get; }
}

true while an automatically reconnecting connection that previously succeeded is waiting for a retry, obtaining a token, or connecting again. It is false during the initial connection, after a successful reconnect handshake, and after a terminal failure or explicit Disconnect(). Continue ticking the connection while this property is true.

Type EventContext​

An EventContext is an IDbContext augmented with an Event property. EventContexts are passed as the first argument to row callbacks OnInsert, OnDelete and OnUpdate.

NameDescription
Event propertyEnum describing the cause of the current row callback.
Db propertyProvides access to the client cache.
Reducers propertyAllows requesting reducers run on the remote database.
Event recordPossible events which can cause a row callback to be invoked.

Property Event​

class EventContext {
    public readonly Event<Reducer> Event;
    /* other fields */
}

The Event contained in the EventContext describes what happened to cause the current row callback to be invoked.

Property Db​

class EventContext {
    public RemoteTables Db;
    /* other fields */
}

The Db property of the context provides access to the subscribed view of the remote database's tables. See Access the client cache.

Field Reducers​

class EventContext {
    public RemoteReducers Reducers;
    /* other fields */
}

The Reducers property of the context provides access to reducers exposed by the remote module. See Observe and invoke reducers.

Record Event​

NameDescription
Reducer variantA reducer ran in the remote database.
SubscribeApplied variantA new subscription was applied to the client cache.
UnsubscribeApplied variantA previous subscription was removed from the client cache after a call to Unsubscribe.
SubscribeError variantA previous subscription was removed from the client cache due to an error.
UnknownTransaction variantA transaction ran in the remote database, but was not attributed to a known reducer.
ReducerEvent recordMetadata about a reducer run. Contained in a Reducer event and ReducerEventContext.
Status recordCompletion status of a reducer run.
Reducer recordModule-specific generated record with a variant for each reducer defined by the module.

Variant Reducer​

record Event<R>
{
    public record Reducer(ReducerEvent<R> ReducerEvent) : Event<R>;
}

Event when we are notified of the result of a reducer call made by this connection. The ReducerEvent contains metadata about the reducer run, including its arguments and termination Status.

For changes caused by other clients' reducer calls, use table row callbacks or event tables rather than reducer callbacks. The server does not broadcast reducer arguments globally.

Variant SubscribeApplied​

record Event<R>
{
    public record SubscribeApplied : Event<R>;
}

Event when our subscription is applied and its rows are inserted into the client cache.

This event is passed to row OnInsert callbacks resulting from the new subscription.

Variant UnsubscribeApplied​

record Event<R>
{
    public record UnsubscribeApplied : Event<R>;
}

Event when our subscription is removed after a call to SubscriptionHandle.Unsubscribe or SubscriptionHandle.UnsubscribeTthen and its matching rows are deleted from the client cache.

This event is passed to row OnDelete callbacks resulting from the subscription ending.

Variant SubscribeError​

record Event<R>
{
    public record SubscribeError(Exception Exception) : Event<R>;
}

Event when a subscription ends unexpectedly due to an error.

This event is passed to row OnDelete callbacks resulting from the subscription ending.

Variant UnknownTransaction​

record Event<R>
{
    public record UnknownTransaction : Event<R>;
}

Event when we are notified of a transaction in the remote database which we cannot associate with a known reducer. This may be an ad-hoc SQL query or a reducer for which we do not have bindings.

This event is passed to row callbacks resulting from modifications by the transaction.

Record ReducerEvent​

record ReducerEvent<R>(
    Timestamp Timestamp,
    Status Status,
    Identity CallerIdentity,
    ConnectionId? CallerConnectionId,
    U128? EnergyConsumed,
    R Reducer
)

A ReducerEvent contains metadata about a reducer run.

Record Status​

record Status : TaggedEnum<(
    Unit Committed,
    string Failed,
    Unit OutOfEnergy,
    Unit UnknownResult
)>;
NameDescription
Committed variantThe reducer ran successfully.
Failed variantThe reducer errored.
OutOfEnergy variantThe reducer was aborted due to insufficient energy.
UnknownResult variantThe connection was lost before the reducer's result arrived.

Variant Committed​

The reducer returned successfully and its changes were committed into the database state. An Event.Reducer passed to a row callback must have this status in its ReducerEvent.

Variant Failed​

The reducer returned an error, panicked, or threw an exception. The record payload is the stringified error message. Formatting of the error message is unstable and subject to change, so clients should use it only as a human-readable diagnostic, and in particular should not attempt to parse the message.

Variant OutOfEnergy​

The reducer was aborted due to insufficient energy balance of the module owner.

Variant UnknownResult​

With automatic reconnect enabled, the SDK reports this status for reducer calls whose results were still pending when the connection was lost. The reducer may have committed; this status does not mean that it failed. The SDK does not retry the call. Check the reconciled state or use an application-level idempotency key before deciding to repeat it.

Regenerate your C# bindings when upgrading. Generated dispatch code forwards this status as an UnknownResultException to OnUnhandledReducerError when no callback is registered for that reducer. Include Status.UnknownResult in your own reducer-result handling as well.

Record Reducer​

The module bindings contains an record Reducer with a variant for each reducer defined by the module. Each variant has a payload containing the arguments to the reducer.

Type ReducerEventContext​

A ReducerEventContext is an IDbContext augmented with an Event property. ReducerEventContexts are passed as the first argument to reducer callbacks.

NameDescription
Event propertyReducerEvent containing reducer metadata.
Db propertyProvides access to the client cache.
Reducers propertyAllows requesting reducers run on the remote database.

Property Event​

class ReducerEventContext {
    public readonly ReducerEvent<Reducer> Event;
    /* other fields */
}

The ReducerEvent contained in the ReducerEventContext has metadata about the reducer which ran.

Property Db​

class ReducerEventContext {
    public RemoteTables Db;
    /* other fields */
}

The Db property of the context provides access to the subscribed view of the remote database's tables. See Access the client cache.

Property Reducers​

class ReducerEventContext {
    public RemoteReducers Reducers;
    /* other fields */
}

The Reducers property of the context provides access to reducers exposed by the remote module. See Observe and invoke reducers.

Type SubscriptionEventContext​

A SubscriptionEventContext is an IDbContext. Unlike the other context types, SubscriptionEventContext doesn't have an Event property. SubscriptionEventContexts are passed to subscription OnApplied and UnsubscribeThen callbacks.

NameDescription
Db propertyProvides access to the client cache.
Reducers propertyAllows requesting reducers run on the remote database.

Property Db​

class SubscriptionEventContext {
    public RemoteTables Db;
    /* other fields */
}

The Db property of the context provides access to the subscribed view of the remote database's tables. See Access the client cache.

Property Reducers​

class SubscriptionEventContext {
    public RemoteReducers Reducers;
    /* other fields */
}

The Reducers property of the context provides access to reducers exposed by the remote module. See Observe and invoke reducers.

Type ErrorContext​

An ErrorContext is an IDbContext augmented with an Event property. ErrorContexts are passed to subscriptions' OnError callbacks.

NameDescription
Event propertyThe error which caused the current error callback.
Db propertyProvides access to the client cache.
Reducers propertyAllows requesting reducers run on the remote database.

Property Event​

class SubscriptionEventContext {
    public readonly Exception Event;
    /* other fields */
}

Property Db​

class ErrorContext {
    public RemoteTables Db;
    /* other fields */
}

The Db property of the context provides access to the subscribed view of the remote database's tables. See Access the client cache.

Property Reducers​

class ErrorContext {
    public RemoteReducers Reducers;
    /* other fields */
}

The Reducers property of the context provides access to reducers exposed by the remote database. See Observe and invoke reducers.

Access the client cache​

All IDbContext implementors, including DbConnection and EventContext, have .Db properties, which in turn have methods for accessing tables in the client cache.

Each table defined by a module has an accessor method on this .Db property, generated from the table accessor name using C# naming conventions (for example, player_score becomes PlayerScore). The table accessor methods return table handles which inherit from RemoteTableHandle and have methods for searching by index.

NameDescription
RemoteTableHandleProvides access to subscribed rows of a specific table within the client cache.
Unique constraint index accessSeek a subscribed row by the value in its unique or primary key column.
BTree index accessSeek subscribed rows by the value in its indexed column.

Type RemoteTableHandle​

Implemented by all table handles.

NameDescription
Row type parameterThe type of rows in the table.
Count propertyThe number of subscribed rows in the table.
Iter methodIterate over all subscribed rows in the table.
OnInsert callbackRegister a callback to run whenever a row is inserted into the client cache.
OnDelete callbackRegister a callback to run whenever a row is deleted from the client cache.
OnUpdate callbackRegister a callback to run whenever a subscribed row is replaced with a new version.

Type Row​

class RemoteTableHandle<EventContext, Row>
{
    /* members */
}

The type of rows in the table.

Property Count​

class RemoteTableHandle
{
    public int Count;
}

The number of rows of this table resident in the client cache, i.e. the total number which match any subscribed query.

Method Iter​

class RemoteTableHandle
{
    public IEnumerable<Row> Iter();
}

An iterator over all the subscribed rows in the client cache, i.e. those which match any subscribed query.

Callback OnInsert​

class RemoteTableHandle
{
    public delegate void RowEventHandler(EventContext context, Row row);
    public event RowEventHandler? OnInsert;
}

The OnInsert callback runs whenever a new row is inserted into the client cache, either when applying a subscription or being notified of a transaction. The passed EventContext contains an Event which can identify the change which caused the insertion, and also allows the callback to interact with the connection, inspect the client cache and invoke reducers. Newly registered or canceled callbacks do not take effect until the following event.

See the quickstart for examples of registering and unregistering row callbacks.

Callback OnDelete​

class RemoteTableHandle
{
    public delegate void RowEventHandler(EventContext context, Row row);
    public event RowEventHandler? OnDelete;
}

The OnDelete callback runs whenever a previously-resident row is deleted from the client cache. Newly registered or canceled callbacks do not take effect until the following event.

See the quickstart for examples of registering and unregistering row callbacks.

Callback OnUpdate​

class RemoteTableHandle
{
    public delegate void RowEventHandler(EventContext context, Row row);
    public event RowEventHandler? OnUpdate;
}

The OnUpdate callback runs whenever an already-resident row in the client cache is updated, i.e. replaced with a new row that has the same primary key. The handle must have a known primary key for callbacks to be triggered. This includes tables with primary keys, query builder views with inferred primary keys, and procedural views declared with PrimaryKey. Newly registered or canceled callbacks do not take effect until the following event.

See the quickstart for examples of registering and unregistering row callbacks.

Unique constraint index access​

For each unique constraint on a table, its table handle has a property which is a unique index handle and whose name is the unique column name. This unique index handle has a method .Find(Column value). If a Row with value in the unique column is resident in the client cache, .Find returns it. Otherwise it returns null.

Example​

Given the following module-side User definition:

[Table(Accessor = "User", Public = true)]
public partial class User
{
    [Unique] // Or [PrimaryKey]
    public Identity Identity;
    ..
}

a client would lookup a user as follows:

User? FindUser(RemoteTables tables, Identity id) => tables.User.Identity.Find(id);

BTree index access​

For each btree index defined on a remote table, its corresponding table handle has a property which is a btree index handle and whose name is the name of the index. This index handle has a method IEnumerable<Row> Filter(Column value) which will return Rows with value in the indexed Column, if there are any in the cache.

Example​

Given the following module-side Player definition:

[Table(Accessor = "Player", Public = true)]
public partial class Player
{
    [PrimaryKey]
    public Identity id;

    [SpacetimeDB.Index.BTree(Accessor = "Level")]
    public uint level;
    ..
}

a client would count the number of Players at a certain level as follows:

int CountPlayersAtLevel(RemoteTables tables, uint level) => tables.Player.Level.Filter(level).Count();

Observe and invoke reducers​

All IDbContext implementors, including DbConnection and EventContext, have a .Reducers property. Generated module bindings expose one invoke method and one result event for each reducer.

For a reducer named send_message, generated C# bindings use PascalCase names:

  • An invoke method, like SendMessage(...). This requests that the module run the reducer.
  • A result event, like OnSendMessage. This event fires on the calling connection for committed, failed, and out-of-energy results, or with Status.UnknownResult if automatic reconnect is enabled and the connection is lost before the result arrives.

Subscribe to reducer result events with += and unsubscribe with -=, as with any C# event.

Reducer result events are not global notifications. They are for reducer calls made by this connection. To notify other clients that something happened, write to a public table or event table and subscribe to it.

Identify a client​

Type Identity​

A unique public identifier for a client connected to a database. See the module docs for more details.

Type ConnectionId​

An opaque identifier for a client connection to a database, intended to differentiate between connections from the same Identity. See the module docs for more details.

Type Timestamp​

A point in time, measured in microseconds since the Unix epoch. See the module docs for more details.

Type TaggedEnum​

A tagged union type. When defining TaggedEnum types in a module, use partial record, not partial class. See the module docs for more details.