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, andTakewith 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";
});
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();