Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ff2a5a9
Add EventListeners a SapDelegate when Sappy is present
lisandroct Aug 3, 2026
f19f842
Improve collision handling when Sappy is not present
lisandroct Aug 3, 2026
9d9d000
Clean
lisandroct Aug 11, 2026
41e9465
Fix Unity package .asmdef
lisandroct Aug 11, 2026
8e5f99d
Start SappyIntegration
lisandroct Aug 12, 2026
702c849
Implement DI
lisandroct Aug 12, 2026
ae69e67
Fix compilation errors
lisandroct Aug 12, 2026
4c89d45
Allow overriding CustomFactory
lisandroct Aug 12, 2026
b01d2fb
Improve collisions check
lisandroct Aug 12, 2026
40049fb
Add .meta files
lisandroct Aug 13, 2026
d31f648
Add Extensions
lisandroct Aug 13, 2026
de537c3
Performance improvements suggested by Codex
lisandroct Aug 14, 2026
eaa8e87
Cache method delegates
lisandroct Aug 14, 2026
199e485
Improve performance
lisandroct Aug 14, 2026
9e5d80d
Remove DelegateIndex
lisandroct Aug 14, 2026
7bc1d6e
Squeeze more performance
lisandroct Aug 14, 2026
89b4e18
Use native events by default
lisandroct Aug 17, 2026
bd7a03c
Clean
lisandroct Aug 17, 2026
b33f454
Fix bug where no custom factory could be used
lisandroct Aug 17, 2026
1c43c8a
Benchmark by Codex
lisandroct Aug 18, 2026
9f26359
Add missing meta files
lisandroct Aug 19, 2026
0505330
Allow duplicates and simplify code
lisandroct Aug 24, 2026
aabf20f
Add documentation
lisandroct Sep 3, 2026
37bfc79
Improve documentation
lisandroct Sep 3, 2026
e8f48ea
Make documentation more explicit
lisandroct Sep 3, 2026
b9ce9ba
Move benchmark under tests~
lisandroct Sep 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Before diving into the reference, you may want to review:
| [`ErrorContext` type](#type-errorcontext) | Implements [`IDbContext`](#interface-idbcontext) for subscription error callbacks. |
| [Query Builder API](#query-builder-api) | Type-safe query builder for typed subscription queries. |
| [Access the client cache](#access-the-client-cache) | Access to your local view of the database. |
| [Configure event dispatch](#configure-event-dispatch) | Choose native C# events or a custom event listener backend. |
| [Observe and invoke reducers](#observe-and-invoke-reducers) | Send requests to the database to run reducers, and register callbacks to run when notified of reducers. |
| [Identify a client](#identify-a-client) | Types for identifying users and client connections. |

Expand Down Expand Up @@ -1035,6 +1036,55 @@ The `OnUpdate` callback runs whenever an already-resident row in the client cach

See [the quickstart](../../00100-intro/00200-quickstarts/00600-c-sharp.md) for examples of registering and unregistering row callbacks.

### Configure event dispatch

By default, the C# SDK stores table row callbacks as regular native C# events. For most applications, no extra setup is required:

```csharp
conn.Db.User.OnInsert += OnUserInsert;
conn.Db.User.OnInsert -= OnUserInsert;
```

Native events are simple, idiomatic, and should be your default choice unless profiling shows that event subscription management is a problem in your application.

If your client frequently adds and removes many row callbacks, the cost of native multicast delegate updates can become noticeable. For those cases, the SDK can use a custom event listener backend instead:

```csharp
using SpacetimeDB;

SpacetimeDB.EventHandling.Backend.UseCustomListeners();

var conn = DbConnection.Builder()
.WithUri("http://localhost:3000")
.WithDatabaseName("my-database")
.Build();
```

Call `Backend.UseCustomListeners()` before creating the generated `DbConnection`. Table handles capture the selected backend when they are constructed, so changing the backend later does not update existing handles.

The default custom backend keeps listeners in an indexed collection. It is useful when you have many listener removals, duplicate subscriptions, or integration code that attaches and detaches callbacks aggressively. Registering and unregistering callbacks still uses the same generated `OnInsert`, `OnDelete`, and `OnUpdate` event APIs.

Reducer result events, such as `conn.Reducers.OnSendMessage`, are always regular C# events.

If your project includes [Sappy](https://github.com/clockworklabs/SappyEvents/), the SDK can use Sappy-backed listener storage:

```csharp
using SpacetimeDB.SappyIntegration;

SpacetimeDB.EventHandling.Backend.UseCustomListeners(new SappyEventListenersFactory());
```

Use the Sappy backend only in projects that already reference Sappy. It is intended for applications that have standardized on Sappy's event/listener model; it is not required for normal C# or Unity clients.

For Sappy-backed table callbacks, register and unregister generated Sappy targets through the listener accessors instead of using normal C# event syntax:

```csharp
conn.Db.User.OnInsertListeners.AddSapTarget(Sappy.OnUserInsert);
conn.Db.User.OnInsertListeners.RemoveSapTarget(Sappy.OnUserInsert);
```

Use the matching listener accessor for each row callback: `OnInsertListeners`, `OnDeleteListeners`, and `OnUpdateListeners`. This lets Sappy manage the callback target directly, which is required for the Sappy backend to behave correctly and avoid unnecessary delegate-management overhead.

### 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.
Expand Down
3 changes: 3 additions & 0 deletions sdks/csharp/src/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("com.clockworklabs.spacetimedbsdk.sappyintegration")]
11 changes: 11 additions & 0 deletions sdks/csharp/src/AssemblyInfo.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 0 additions & 100 deletions sdks/csharp/src/EventHandling/AbstractEventHandler.cs

This file was deleted.

24 changes: 24 additions & 0 deletions sdks/csharp/src/EventHandling/Backend.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace SpacetimeDB.EventHandling
{
public static class Backend
{
internal static bool UseNativeDispatch { get; private set; } = true;
private static IEventListenersFactory? CustomFactory { get; set; }

public static void UseNativeEvents()
{
UseNativeDispatch = true;
CustomFactory = null;
}

public static void UseCustomListeners(IEventListenersFactory? factory = null)
{
UseNativeDispatch = false;
CustomFactory = factory;
}

internal static IEventListeners<T> Create<T>() where T : Delegate => CustomFactory?.Create<T>() ?? new EventListeners<T>();
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading