If you use Firestore with .NET, you've probably written code like this:
query.WhereEqualTo("Location.home_country", "Portugal");
That "Location.home_country" is a string. Nobody checks it at compile time. If you typo it, rename a property, or forget that your C# property Country maps to home_country in Firestore, you won't know until runtime.
I kept running into this in my own projects, so I wrote a typed wrapper around Google's official Firestore client that uses lambda expressions instead of strings.
What it looks like
// Before — string-based, no compile-time checking
query.WhereEqualTo("Locaiton.home_country", "Portugal"); // typo, compiles fine
// After — lambda-based, compiler catches errors
query.WhereEqualTo(u => u.Locaiton.Country, "Portugal");
// CS1061: 'User' does not contain a definition for 'Locaiton'
The library reads [FirestoreProperty] attributes automatically, so you always use the C# property name and it resolves the Firestore storage name for you.
Updates are type-safe too
With the official client, you can pass any object as a field value. With the typed client, the value type is inferred from the property:
// won't compile — Age is int
await doc.UpdateAsync(u => u.Age, "eighteen");
// works
await doc.UpdateAsync(u => u.Age, 18);
Multi-field updates also work:
var update = new UpdateDefinition<User>()
.Set(u => u.Age, 18)
.Set(u => u.Location.Country, "Spain");
await document.UpdateAsync(update);
Compare that to the official client:
var updates = new Dictionary<FieldPath, object>
{
{ new FieldPath("Age"), 18 },
{ new FieldPath("Location.home_country"), "Spain" }
};
await document.UpdateAsync(updates);
How it works
The library uses a MemberExpression visitor to walk the lambda expression tree, check each property for [FirestoreProperty] attributes, and build the correct Firestore field path. Simple fields resolve in about 450ns, nested fields in about 1μs. In practice, this is invisible next to the network call to Firestore.
Everything else (transactions, listeners, batched writes, subcollections) is delegated directly to the official Google.Cloud.Firestore client. You're not giving up any functionality.
Getting started
dotnet add package Firestore.Typed.Client
FirestoreDb db = await FirestoreDb.CreateAsync("your-project-id");
TypedCollectionReference<User> collection = db.TypedCollection<User>("users");
TypedQuery<User> query = collection
.WhereGreaterThanOrEqualTo(u => u.Age, 18)
.WhereEqualTo(u => u.Location.Country, "Portugal")
.OrderBy(u => u.Age);
TypedQuerySnapshot<User> results = await query.GetSnapshotAsync();
foreach (TypedDocumentSnapshot<User> doc in results.Documents)
{
User user = doc.Object;
}
Targets .NET Standard 2.0, so it works with .NET Framework 4.6.1+ through .NET 10.
Source: https://github.com/mihail-brinza/firestore-dotnet-typed-client
Top comments (0)