Skip to content
Client v5: BLE, BLE Hosting, HTTP, Jobs - Linux, MacOS, & Blazor Support! Full AOT, RX on BLE only & MANY other features! Power up!

Google Firestore

The Shiny.DocumentDb.Firestore package provides a document store over Google Cloud Firestore using Google.Cloud.Firestore. Each document type maps to a Firestore collection, and documents are stored as native Firestore maps (not an opaque blob), so Firestore auto-indexes fields and many Where/OrderBy predicates push down.

NuGet package Shiny.DocumentDb.Firestore

Firestore is a document-native cloud store and pairs naturally with the IndexedDB provider for offline/mobile scenarios. It has native cursor pagination, transaction-guarded CAS, and a real per-query change feed via snapshot listeners.

  • Serverless, globally-distributed document storage on Google Cloud
  • Mobile/offline-first apps (pairs with the IndexedDB provider)
  • You need live per-query change notifications from any writer
Terminal window
dotnet add package Shiny.DocumentDb.Firestore
  1. Direct instantiation

    using Shiny.DocumentDb.Firestore;
    var store = new FirestoreDocumentStore(new FirestoreDocumentStoreOptions
    {
    ProjectId = "my-gcp-project"
    });
  2. Dependency injection

    using Shiny.DocumentDb;
    builder.Services.AddFirestoreDocumentStore(o =>
    {
    o.ProjectId = "my-gcp-project";
    o.MapVersionProperty<Order>(x => x.Version); // opt-in optimistic concurrency
    });

    AddFirestoreDocumentStore registers IDocumentStore and IDocumentMaintenance as singletons. Pass a pre-built FirestoreDb via o.FirestoreDb, or point at the emulator with FIRESTORE_EMULATOR_HOST.

PropertyTypeDefaultDescription
FirestoreDbFirestoreDb?nullPre-built client (wins over ProjectId)
ProjectIdstring?nullGCP project id
DatabaseIdstring?"(default)"Firestore database id
CollectionPrefixstring?nullPrefix applied to collection names (multi-tenant / test isolation)
TypeNameResolutionTypeNameResolutionShortNameHow collection names are derived
JsonSerializerOptionsJsonSerializerOptions?nullJSON serialization settings
UseReflectionFallbackbooltrueSet false for AOT safety

Mapping methods: MapTypeToCollection, MapIdProperty/MapIdType, MapVersionProperty, AddQueryFilter, and the interceptor hooks.

Single-field equality/range predicates and OrderBy push down to a Firestore query; the full predicate is always re-applied client-side, so results are exact. Non-pushable predicates degrade to a collection read.

var open = await store.Query<Order>()
.Where(o => o.Status == "Open")
.OrderByDescending(o => o.CreatedAt)
.Paginate(0, 25)
.ToList();
// native keyset pagination
var page = await store.Query<Order>().OrderBy(o => o.CreatedAt).ToCursorPage(cursor, take: 50);

Count/Sum/Average use Firestore aggregation queries. GroupBy and the string Query/Count(whereClause) overloads throw — use LINQ.

// In-process
await foreach (var c in ((IObservableDocumentStore)store).NotifyOnChange<Order>(ct)) …
// Native snapshot-listener feed — changes from ANY writer, per query
await using var sub = await ((IChangeFeedDocumentStore)store).SubscribeChanges<Order>(async (c, ct) => …);

Firestore’s NotifyOnChange is a real server-backed per-query feed (unlike Mongo, which throws).

  • Guid / string Ids auto-generate on Insert when default.
  • Int/Long Id auto-generation is unsupported — use Guid/string, or assign the Id.
  • Optimistic concurrency: MapVersionProperty<T> guards the write inside a Firestore transaction with a Precondition.LastUpdated check; a stale write throws ConcurrencyException. Upsert uses SetOptions.MergeAll (server-side merge).
  • No native full-text or spatialSupportsFullText/SupportsSpatial are false (use the Algolia/Elastic extension for search); vector and temporal are not wired in this release.
  • Composite-index requirement for multi-field pushed queries (falls back to a full scan otherwise).
  • String Query/Count(whereClause) and GroupBy throw — use LINQ.
  • Compensating unit of workstore.OpenSession() tracks inserts and undoes them on failure (Firestore transactions require all reads before writes).