Cloud Firestore

A flexible, scalable NoSQL cloud database with LINQ-style queries, real-time subscriptions, transactions, and batch operations for Blazor WebAssembly applications.

Overview

FireBlazor provides a powerful, type-safe interface to Cloud Firestore that feels native to .NET developers. Instead of working with JavaScript-style string queries, you can use familiar LINQ patterns to build your queries.

Key Features

  • LINQ-style queries - Use Where, OrderBy, and Take with lambda expressions
  • Real-time subscriptions - Listen to document and collection changes with OnSnapshot
  • Transactions - Perform atomic read-modify-write operations
  • Batch operations - Execute multiple write operations as a single atomic unit
  • Field values - Server timestamps, atomic increments, and array operations
  • Aggregate queries - Count, sum, and average operations

CRUD Operations

FireBlazor provides intuitive methods for Create, Read, Update, and Delete operations on your Firestore documents.

Create

Add new documents to a collection using AddAsync. Firestore will automatically generate a unique document ID.

// Create a new document with auto-generated ID
var result = await Firebase.Firestore
    .Collection<TodoItem>("todos")
    .AddAsync(new TodoItem { Title = "Buy groceries", Completed = false });

if (result.IsSuccess)
{
    var documentId = result.Value.Id;
    Console.WriteLine($"Created document: {documentId}");
}

Read

Query documents using LINQ-style methods. Chain Where, OrderBy, and Take to filter and sort your results.

// Read documents with filtering, ordering, and pagination
var result = await Firebase.Firestore
    .Collection<TodoItem>("todos")
    .Where(t => t.Completed == false)
    .OrderBy(t => t.CreatedAt)
    .Take(10)
    .GetAsync();

if (result.IsSuccess)
{
    foreach (var todo in result.Value)
    {
        Console.WriteLine(todo.Data.Title);
    }
}

Update

Update specific fields in a document using UpdateAsync. Only the specified fields will be modified.

// Update specific fields in a document
var result = await Firebase.Firestore
    .Collection<TodoItem>("todos")
    .Document("doc-id")
    .UpdateAsync(new { Completed = true });

if (result.IsSuccess)
{
    Console.WriteLine("Document updated successfully");
}

Delete

Remove documents from a collection using DeleteAsync.

// Delete a document
var result = await Firebase.Firestore
    .Collection<TodoItem>("todos")
    .Document("doc-id")
    .DeleteAsync();

if (result.IsSuccess)
{
    Console.WriteLine("Document deleted successfully");
}

Querying

FireBlazor supports powerful LINQ-style queries that feel natural to .NET developers. Build complex queries by chaining methods together.

Basic Filtering

// Filter with Where clause
var result = await Firebase.Firestore
    .Collection<TodoItem>("todos")
    .Where(t => t.UserId == currentUserId)
    .Where(t => t.Priority > 3)
    .GetAsync();

Ordering and Pagination

// Order results and limit count
var result = await Firebase.Firestore
    .Collection<TodoItem>("todos")
    .Where(t => t.Completed == false)
    .OrderBy(t => t.CreatedAt)
    .Take(10)
    .GetAsync();

Descending Order

// Sort in descending order
var result = await Firebase.Firestore
    .Collection<TodoItem>("todos")
    .OrderByDescending(t => t.CreatedAt)
    .Take(20)
    .GetAsync();

Real-time Subscriptions

Listen to real-time changes in your Firestore data using OnSnapshot. The callback is invoked whenever the data changes, allowing your UI to stay in sync automatically.

Subscribe to Collection Changes

// Subscribe to real-time updates
var unsubscribe = Firebase.Firestore
    .Collection<TodoItem>("todos")
    .Where(t => t.UserId == currentUserId)
    .OnSnapshot(
        snapshot => {
            _items = snapshot.ToList();
            InvokeAsync(StateHasChanged);
        },
        error => Console.WriteLine(error.Message));

// Later: clean up the subscription
unsubscribe();

Using with FirebaseComponentBase

For automatic subscription cleanup, use the FirebaseComponentBase class:

@inherits FirebaseComponentBase

@code {
    protected override async Task OnInitializedAsync()
    {
        var unsubscribe = Firebase.Firestore
            .Collection<Item>("items")
            .OnSnapshot(snapshot => { /* handle updates */ });

        Subscribe(unsubscribe); // Automatically cleaned up on dispose
    }
}

Transactions

Transactions allow you to perform multiple read and write operations atomically. If any operation fails, the entire transaction is rolled back.

// Transfer funds between accounts atomically
var result = await Firebase.Firestore.TransactionAsync<string>(async tx =>
{
    var fromDoc = Firebase.Firestore.Collection<Account>("accounts").Document("from");
    var toDoc = Firebase.Firestore.Collection<Account>("accounts").Document("to");

    var fromSnap = await tx.GetAsync(fromDoc);
    var toSnap = await tx.GetAsync(toDoc);

    tx.Update(fromDoc, new { Balance = fromSnap.Value.Data.Balance - 100 });
    tx.Update(toDoc, new { Balance = toSnap.Value.Data.Balance + 100 });

    return "Transfer complete";
});
Best Practice

Use transactions for atomic operations that depend on reading the current state before writing. This ensures data consistency even when multiple clients are updating the same documents simultaneously.

Batch Operations

Batch operations allow you to perform multiple write operations as a single atomic unit. Unlike transactions, batches don't support reads, but they're more efficient for bulk writes.

// Perform multiple writes atomically
var collection = Firebase.Firestore.Collection<Item>("items");

var result = await Firebase.Firestore.BatchAsync(batch =>
{
    // Set a new document
    batch.Set(collection.Document("doc1"), item1);

    // Update an existing document
    batch.Update(collection.Document("doc2"), new { Status = "updated" });

    // Delete a document
    batch.Delete(collection.Document("doc3"));
});

Batch vs Transaction

Feature Batch Transaction
Reads No Yes
Writes Yes Yes
Atomic Yes Yes
Use Case Bulk writes Read-modify-write

Field Values

FireBlazor provides special field values for common operations that need to be handled server-side.

ServerTimestamp

Set a field to the server's current timestamp. Useful for tracking when documents were created or modified.

// Set field to server timestamp
await docRef.UpdateAsync(new { UpdatedAt = FieldValue.ServerTimestamp() });

Increment

Atomically increment or decrement a numeric field without reading the current value first.

// Atomically increment a counter
await docRef.UpdateAsync(new { ViewCount = FieldValue.Increment(1) });

// Decrement by using a negative value
await docRef.UpdateAsync(new { Stock = FieldValue.Increment(-1) });

ArrayUnion

Add elements to an array field without overwriting existing elements. Duplicates are automatically ignored.

// Add element to array (ignores duplicates)
await docRef.UpdateAsync(new { Tags = FieldValue.ArrayUnion("new-tag") });

ArrayRemove

Remove elements from an array field.

// Remove element from array
await docRef.UpdateAsync(new { Tags = FieldValue.ArrayRemove("old-tag") });

Aggregate Queries

Perform server-side aggregations without downloading all documents. This is much more efficient for large collections.

CountAsync

Get the count of documents matching a query.

// Count documents in a collection
var count = await Firebase.Firestore
    .Collection<Item>("items")
    .Aggregate()
    .CountAsync();

SumAsync

Calculate the sum of a numeric field across all matching documents.

// Sum a numeric field
var total = await Firebase.Firestore
    .Collection<Order>("orders")
    .Aggregate()
    .SumAsync("amount");

AverageAsync

Calculate the average of a numeric field across all matching documents.

// Calculate average
var avg = await Firebase.Firestore
    .Collection<Product>("products")
    .Aggregate()
    .AverageAsync("price");

Combined Aggregations

// You can also filter before aggregating
var activeUserCount = await Firebase.Firestore
    .Collection<User>("users")
    .Where(u => u.IsActive == true)
    .Aggregate()
    .CountAsync();