Excalibur.Data.Firestore 3.0.0-alpha.26

This is a prerelease version of Excalibur.Data.Firestore.
dotnet add package Excalibur.Data.Firestore --version 3.0.0-alpha.26
                    
NuGet\Install-Package Excalibur.Data.Firestore -Version 3.0.0-alpha.26
                    
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="Excalibur.Data.Firestore" Version="3.0.0-alpha.26" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Excalibur.Data.Firestore" Version="3.0.0-alpha.26" />
                    
Directory.Packages.props
<PackageReference Include="Excalibur.Data.Firestore" />
                    
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 Excalibur.Data.Firestore --version 3.0.0-alpha.26
                    
#r "nuget: Excalibur.Data.Firestore, 3.0.0-alpha.26"
                    
#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 Excalibur.Data.Firestore@3.0.0-alpha.26
                    
#: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=Excalibur.Data.Firestore&version=3.0.0-alpha.26&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Excalibur.Data.Firestore&version=3.0.0-alpha.26&prerelease
                    
Install as a Cake Tool

Excalibur.Data.Firestore

Google Cloud Firestore data provider implementation for Excalibur cloud-native data access.

Installation

dotnet add package Excalibur.Data.Firestore

Quick Start

Configuration

// In Program.cs or Startup.cs
services.AddFirestore(options =>
{
    options.ProjectId = "your-gcp-project-id";
    options.DatabaseId = "(default)"; // Optional, defaults to (default)
    options.DefaultCollection = "your-collection";
});

// Or using configuration
services.AddFirestore(configuration.GetSection("Firestore"));

appsettings.json

{
  "Firestore": {
    "ProjectId": "your-gcp-project-id",
    "DatabaseId": "(default)",
    "DefaultCollection": "your-collection",
    "UseEmulator": false,
    "EmulatorHost": "localhost:8080"
  }
}

Features

CRUD Operations

public class OrderService
{
    private readonly FirestorePersistenceProvider _provider;

    public OrderService(FirestorePersistenceProvider provider)
    {
        _provider = provider;
    }

    public async Task<Order?> GetOrderAsync(string orderId, string tenantId)
    {
        var partitionKey = new PartitionKey(tenantId, "/tenantId");
        return await _provider.GetByIdAsync<Order>(orderId, partitionKey);
    }

    public async Task<CloudOperationResult<Order>> CreateOrderAsync(Order order)
    {
        var partitionKey = new PartitionKey(order.TenantId, "/tenantId");
        return await _provider.CreateAsync(order, partitionKey);
    }

    public async Task<CloudOperationResult<Order>> UpdateOrderAsync(Order order, string etag)
    {
        var partitionKey = new PartitionKey(order.TenantId, "/tenantId");
        return await _provider.UpdateAsync(order, partitionKey, etag);
    }
}

Queries

public async Task<IReadOnlyList<Order>> GetOrdersByStatusAsync(
    string tenantId,
    string status)
{
    var partitionKey = new PartitionKey(tenantId, "/tenantId");

    // Firestore uses collection path as partition key
    var result = await _provider.QueryAsync<Order>(
        "status = @status",
        partitionKey,
        new Dictionary<string, object> { ["status"] = status });

    return result.Documents;
}

Transactional Batches

public async Task<CloudBatchResult> ProcessOrderBatchAsync(
    string tenantId,
    Order order,
    OrderLine[] lines)
{
    var partitionKey = new PartitionKey(tenantId, "/tenantId");
    var operations = new List<ICloudBatchOperation>
    {
        new CloudBatchCreateOperation(order.Id, order)
    };

    foreach (var line in lines)
    {
        operations.Add(new CloudBatchCreateOperation(line.Id, line));
    }

    return await _provider.ExecuteBatchAsync(partitionKey, operations);
}

Real-time Change Listener

public async Task SubscribeToChangesAsync(CancellationToken cancellationToken)
{
    var subscription = await _provider.CreateChangeFeedSubscriptionAsync<Order>(
        "orders",
        new ChangeFeedOptions
        {
            StartPosition = ChangeFeedStartPosition.Now,
            MaxBatchSize = 100
        },
        cancellationToken);

    await subscription.StartAsync(cancellationToken);

    await foreach (var change in subscription.ReadChangesAsync(cancellationToken))
    {
        Console.WriteLine($"Change detected: {change.DocumentId}, Type: {change.EventType}");
        // Process the change
    }
}

Optimistic Concurrency

public async Task<CloudOperationResult<Order>> SafeUpdateAsync(
    Order order,
    string etag)
{
    var partitionKey = new PartitionKey(order.TenantId, "/tenantId");

    // ETag is derived from Firestore UpdateTime
    var result = await _provider.UpdateAsync(order, partitionKey, etag);

    if (!result.Success && result.StatusCode == 412)
    {
        // Precondition failed - document was modified since etag
        Console.WriteLine("Concurrent modification detected");
    }

    return result;
}

Health Checks

services.AddHealthChecks()
    .AddCheck<FirestoreHealthCheck>(
        name: "firestore",
        tags: new[] { "database", "gcp" });

Configuration Options

Connection Settings

Option Description Default
Name Provider instance name for identification Firestore
ProjectId GCP project ID Required*
DefaultCollection Default collection name for operations -
CredentialsPath Path to service account JSON file -
CredentialsJson Service account JSON content (for containers) -

*Required unless using EmulatorHost.

Performance Settings

Option Description Default
TimeoutInSeconds Operation timeout 30
MaxRetryAttempts Maximum retry attempts for transient failures 3

Emulator Settings

Option Description Default
EmulatorHost Firestore emulator host:port (e.g., "localhost:8080") -

Error Handling

The provider returns CloudOperationResult<T> with status codes for error handling:

var result = await _provider.UpdateAsync(order, partitionKey, etag);

if (!result.Success)
{
    switch (result.StatusCode)
    {
        case 404:
            Console.WriteLine("Document not found");
            break;
        case 409:
            Console.WriteLine("Document already exists");
            break;
        case 412:
            Console.WriteLine("Precondition failed - document was modified (ETag mismatch)");
            break;
        case 429:
            Console.WriteLine("Resource exhausted - too many requests");
            break;
        case 503:
            Console.WriteLine("Service unavailable - retry with backoff");
            break;
    }
}

Firestore-Specific Errors

gRPC Status HTTP Code Description
NOT_FOUND 404 Document or collection not found
ALREADY_EXISTS 409 Document already exists (on create)
FAILED_PRECONDITION 412 Precondition failed (ETag mismatch)
RESOURCE_EXHAUSTED 429 Quota exceeded
ABORTED 409 Transaction aborted (retry)
UNAVAILABLE 503 Service temporarily unavailable

Authentication

Firestore uses Google Cloud authentication. Configure one of:

Application Default Credentials

# Local development
gcloud auth application-default login

# In GCP (GKE, Cloud Run, etc.)
# Automatically uses service account

Service Account Key

services.AddFirestore(options =>
{
    options.ProjectId = "your-project";
    options.CredentialPath = "/path/to/service-account.json";
});

Environment Variable

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"

Container/Kubernetes Deployment

For containerized environments where file paths are impractical:

services.AddFirestore(options =>
{
    options.ProjectId = "your-project";
    // JSON content from Kubernetes secret or environment variable
    options.CredentialsJson = Environment.GetEnvironmentVariable("GCP_CREDENTIALS_JSON");
});

Collection Structure

Firestore uses a hierarchical document model:

// Root collection
var partitionKey = new PartitionKey("orders", "/collection");

// Subcollection
var subPartitionKey = new PartitionKey("orders/order-123/items", "/collection");

Best Practices

  1. Use Composite Indexes for complex queries (required for multiple equality/range filters)
  2. Monitor Read/Write Quotas via GCP Console - Firestore has per-second limits
  3. Use Transactions for atomic operations across documents
  4. Enable Firestore Emulator for local development and testing
  5. Configure Security Rules in production - never rely on client-side validation
  6. Design Document Structure Carefully - Firestore charges per document read
  7. Use Batch Writes for multiple operations (max 500 per batch)
  8. Prefer Shallow Hierarchies - deep subcollections increase complexity
  9. Cache Frequently-Read Data - Firestore charges per read operation
  10. Use Appropriate Data Types - leverage Firestore's native types (timestamps, geo points)

Emulator Support

For local development and testing:

# Start Firestore emulator
gcloud emulators firestore start --host-port=localhost:8080
services.AddFirestore(options =>
{
    options.ProjectId = "test-project";
    options.UseEmulator = true;
    options.EmulatorHost = "localhost:8080";
});

License

See LICENSE files in the repository root.

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 is compatible.  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 is compatible.  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.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Excalibur.Data.Firestore:

Package Downloads
Excalibur.Outbox.Firestore

Google Cloud Firestore implementation of the cloud-native outbox pattern for Excalibur event sourcing.

Excalibur.EventSourcing.Firestore

Google Cloud Firestore event store implementation for Excalibur event sourcing.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0-alpha.26 38 3/5/2026
3.0.0-alpha.19 47 2/26/2026