| name | foundationdb-aspire |
| description | How to run a FoundationDB cluster and connect to it from .NET — getting the IFdbDatabaseProvider that the keys/transactions/layers skills assume you already have. Covers the ways to get a provider (plain DI services.AddFoundationDb, FdbDatabaseProvider.Create, or Aspire), the Aspire AppHost integration (FoundationDB.Aspire.Hosting — builder.AddFoundationDb starts a Docker cluster, AddFoundationDbCluster connects to an existing one), the Aspire client integration (FoundationDB.Aspire — builder.AddFoundationDb reads the injected connection), wiring with WithReference and WaitFor, launching with the aspire CLI vs plain dotnet run plus launchSettings, the native client (libfdb_c via UseNativeClient, the platforms FoundationDB.Client.Native ships, and the macOS system-library fallback), and the client-vs-cluster version-compatibility rule. Use whenever code opens or connects to a cluster, registers AddFoundationDb, runs an Aspire host, or hits libfdb_c load failures or transactions that hang on a fresh cluster. |
FoundationDB — Running a cluster & connecting from .NET (Aspire, the native client)
The foundationdb-keys-and-layers, foundationdb-transactions, and foundationdb-advanced-layers skills all start from an IFdbDatabase / IFdbDatabaseProvider you already have. This skill is about getting there: standing up a cluster, loading the native client correctly, and connecting — including the .NET Aspire integration shipped in this repo (FoundationDB.Aspire.Hosting + FoundationDB.Aspire).
1. Getting an IFdbDatabaseProvider
Application code resolves an IFdbDatabaseProvider (then await provider.GetDatabase(ct) or, more commonly, the retry-loop methods). Three ways to create one:
| Way | API | When |
|---|
| Plain DI | services.AddFoundationDb(apiVersion, opt => …) → IFdbDatabaseProviderBuilder (registers IFdbDatabaseProvider) | a regular host, you supply the cluster file via config |
| No DI | FdbDatabaseProvider.Create(new FdbDatabaseProviderOptions { ApiVersion = 730, … }) | tools/tests/standalone; call provider.Start() |
| Aspire client | builder.AddFoundationDb("fdb", settings, options) | the connection string is injected by an Aspire AppHost (see §3) |
apiVersion selects the client API level and caps the features you can use; 730 ⇒ FoundationDB 7.3 semantics. The provider also carries a root FdbPath (a directory-layer partition); everything else resolves relative to it.
Plain-DI / no-DI namespaces: FoundationDB.DependencyInjection. These all still need the native client loaded (§5).
2. The Aspire AppHost integration (FoundationDB.Aspire.Hosting)
On a IDistributedApplicationBuilder (the AppHost), namespace Aspire.Hosting:
var fdb = builder.AddFoundationDb("fdb",
apiVersion: 730,
root: "/MyApp",
clusterVersion: "7.3.54",
rollForward: FdbVersionPolicy.Exact)
.WithLifetime(ContainerLifetime.Persistent);
builder.AddProject<Projects.MyApp>("app")
.WithReference(fdb)
.WaitFor(fdb);
AddFoundationDb(...) → IResourceBuilder<FdbClusterResource> runs a container.
AddFoundationDbCluster(name, apiVersion, root, clusterFile?, clusterVersion?) → IResourceBuilder<FdbConnectionResource> instead connects to an already-running cluster (no container) given a cluster file.
FdbVersionPolicy (Exact, etc.) lives in Aspire.Hosting.ApplicationModel. .WithDefaults(timeout, retryLimit, tracing) tunes the connection.
AppHost project setup: Sdk="Aspire.AppHost.Sdk/<ver>"; reference FoundationDB.Aspire.Hosting with IsAspireProjectResource="false" (it's a compile dependency for AddFoundationDb); reference each app project normally (those are launched as resources — their TFM need not match the AppHost).
3. The Aspire client integration (FoundationDB.Aspire)
In the app (not the AppHost), on an IHostApplicationBuilder, namespace Microsoft.Extensions.Hosting:
builder.AddFoundationDb("fdb",
settings => { },
options => options.UseNativeClient());
The app references FoundationDB.Aspire. The injected connection string looks like ApiVersion=730;Root=/MyApp;ClusterFileContents=…;ClusterVersion=7.3.54. Standalone (no AppHost), provide the cluster file via configuration instead.
Don't confuse the two AddFoundationDbs: the AppHost one (IDistributedApplicationBuilder, starts the container) vs the client one (IHostApplicationBuilder, consumes the connection). They have different receivers, so they never collide.
4. Launching the AppHost
Preferred: the aspire CLI — it provisions the dashboard, OTLP, and ports for you:
dotnet tool install --global aspire.cli
aspire run --apphost path/to/MyApp.AppHost.csproj
Fallback: plain dotnet run (or IDE F5) on the AppHost. This requires a Properties/launchSettings.json providing the dashboard + OTLP endpoints, e.g.:
{ "profiles": { "http": { "commandName": "Project",
"applicationUrl": "http://localhost:15200",
"environmentVariables": {
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19200",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20200",
"ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true" } } } }
Without it, a bare dotnet run on the AppHost crashes at startup: "Failed to configure dashboard resource because ASPNETCORE_URLS … was not set / … OTLP endpoint must be provided." The aspire CLI does not need this file.
5. The native client (libfdb_c) — UseNativeClient()
The .NET client talks to the cluster through the native libfdb_c library. options.UseNativeClient(allowSystemFallback: false) loads only the copy shipped by the FoundationDB.Client.Native package.
⚠️ That package ships libfdb_c only for linux-arm64, linux-x64, and win-x64 — there is no macOS build. On macOS you must:
- install a system
libfdb_c.dylib (Homebrew foundationdb, or the official client package) of a matching version (§6) and matching CPU arch (an arm64 process cannot load an x64 dylib), and
- call
UseNativeClient(allowSystemFallback: true).
The loader searches the app's output directory and the standard dyld paths — but not /usr/local/lib by bare name. The reliable fix is to place (or copy) libfdb_c.dylib next to the built app. Symptom when it's missing: DllNotFoundException: Unable to load shared library 'fdb_c'.
6. Client ⇄ cluster version compatibility (the silent hang)
The client library version must be compatible with the cluster version. A 7.3 client cannot talk to a 7.4 cluster.
- Symptom: transactions hang (no exception);
fdbcli against the cluster reports "One or more processes … incompatible" and "database is unavailable", even though the cluster is internally healthy.
- Fix: make the AppHost
clusterVersion (the Docker image tag) match the libfdb_c available to the app. Check the client with fdbcli --version, the server with docker exec <container> fdbcli --version.
- The
apiVersion (e.g. 730) must be ≤ the cluster's supported level; 730 ⇒ 7.3.
For talking to multiple cluster versions from one process, FoundationDB's multi-version client loads several external libfdb_c libraries — but the simplest demo/dev setup is: one cluster version, one matching client library.
7. Golden rules & gotchas
✅ DO
- Pick the provider source by context: Aspire client integration under an AppHost; plain
services.AddFoundationDb(...) / FdbDatabaseProvider.Create(...) otherwise.
- Launch the AppHost with
aspire run; keep a launchSettings.json only as the dotnet run/F5 fallback.
- Pin
clusterVersion to match the installed libfdb_c; verify both with fdbcli --version.
- On macOS, ensure a matching-arch
libfdb_c.dylib is loadable (next to the app) and use UseNativeClient(allowSystemFallback: true).
⚠️ GOTCHAS
- Transactions that hang on a brand-new cluster are almost always a client/cluster version mismatch, not a deadlock — check versions first.
DllNotFoundException: fdb_c ⇒ no native client found; on macOS the package ships none (see §5).
- AppHost
dotnet run crash about ASPNETCORE_URLS / OTLP ⇒ missing launchSettings.json, or just use aspire run.
AddFoundationDb "ambiguous" / wrong overload ⇒ you imported the wrong namespace; the AppHost call is on IDistributedApplicationBuilder (Aspire.Hosting), the client call on IHostApplicationBuilder (Microsoft.Extensions.Hosting).
WithLifetime(ContainerLifetime.Persistent) keeps the cluster's data across runs — handy in dev, but remember to docker rm -f it when you want a clean slate.
8. File map
| What | Where |
|---|
| Aspire AppHost integration | FoundationDB.Aspire.Hosting/FdbAspireHostingExtensions.cs (AddFoundationDb, AddFoundationDbCluster, FdbVersionPolicy) |
| Aspire client integration | FoundationDB.Aspire/FdbAspireComponentExtensions.cs (AddFoundationDb), FdbClientSettings.cs |
| Plain DI / no-DI provider | FoundationDB.Client/DependencyInjection/FdbDatabaseServiceCollectionExtensions.cs, Implementation/FdbDatabaseProvider.cs |
| Native client loader | FoundationDB.Client.Native/ (FdbClientNativeExtensions.cs → UseNativeClient; runtimes/ holds the shipped libs) |