Request Builders
FluentDynamoDB provides three approaches for building queries and operations. Each approach offers different trade-offs between type safety, flexibility, and verbosity.
Three API Styles
1. Lambda Expressions (Recommended)
Use C# lambda expressions that get translated to DynamoDB expressions. This is the preferred approach for most use cases.
await table.Query<User>()
.Where(x => x.PartitionKey == userId && x.SortKey.StartsWith("ORDER#"))
.WithFilter(x => x.Status == "ACTIVE" && x.Age >= 18)
.ToListAsync();
Why Lambda Expressions are Recommended:
- Compile-time type checking catches property name typos before runtime
- IntelliSense support provides autocomplete for properties and methods
- Refactoring safety means property renames update expressions automatically
- Automatic parameter generation eliminates manual parameter naming
- Clear error messages guide you to correct usage
2. Format Strings (Alternative)
Use String.Format-style syntax with placeholders for a concise middle ground:
await table.Query<User>()
.Where($"{UserFields.UserId} = {{0}} AND begins_with({UserFields.SortKey}, {{1}})",
UserKeys.Pk(userId), "ORDER#")
.WithFilter($"{UserFields.Status} = {{0}} AND {UserFields.Age} >= {{1}}",
"ACTIVE", 18)
.ToListAsync();
When to Use Format Strings:
- When you need format specifiers for DateTime or numeric values
- When building dynamic queries at runtime
- When lambda expressions don't support a specific DynamoDB feature
3. Manual Expression Building (Explicit Control)
Use explicit parameter binding with WithValue() and WithAttribute() for maximum control:
await table.Query<User>()
.Where("#pk = :pk AND begins_with(#sk, :prefix)")
.WithAttribute("#pk", "pk")
.WithAttribute("#sk", "sk")
.WithValue(":pk", UserKeys.Pk(userId))
.WithValue(":prefix", "ORDER#")
.WithFilter("#status = :status AND #age >= :age")
.WithAttribute("#status", "status")
.WithAttribute("#age", "age")
.WithValue(":status", "ACTIVE")
.WithValue(":age", 18)
.ToListAsync();
When to Use Manual Building:
- Dynamic queries where conditions are built at runtime
- Complex scenarios requiring explicit parameter control
- Migrating existing code that uses raw DynamoDB expressions
- When you need to reuse parameter values multiple times
Comparison
| Feature | Lambda Expressions | Format Strings | Manual Building |
|---|---|---|---|
| Type Safety | ✅ Compile-time checking | ⚠️ Runtime only | ❌ Runtime only |
| IntelliSense | ✅ Full support | ⚠️ Partial | ❌ None |
| Readability | ✅ Clean, concise | ✅ Concise | ⚠️ Verbose |
| Refactoring | ✅ Automatic updates | ⚠️ Manual updates | ❌ Manual updates |
| Flexibility | ⚠️ Supported operators only | ✅ All DynamoDB features | ✅ Full control |
| Dynamic Queries | ❌ Not suitable | ✅ Good support | ✅ Best support |
| Learning Curve | ✅ Familiar C# syntax | ✅ Familiar format syntax | ⚠️ DynamoDB knowledge required |
Side-by-Side Examples
Simple Query
Lambda Expressions (Recommended):
var users = await table.Query<User>()
.Where(x => x.UserId == "user123")
.ToListAsync();
Format Strings:
var users = await table.Query<User>()
.Where($"{UserFields.UserId} = {{0}}", UserKeys.Pk("user123"))
.ToListAsync();
Manual Building:
var users = await table.Query<User>()
.Where("#pk = :pk")
.WithAttribute("#pk", "pk")
.WithValue(":pk", UserKeys.Pk("user123"))
.ToListAsync();
Query with Sort Key and Filter
Lambda Expressions (Recommended):
var orders = await table.Query<Order>()
.Where(x => x.CustomerId == customerId && x.OrderId.StartsWith("ORDER#2024"))
.WithFilter(x => x.Status == "pending" && x.Total > 100.00m)
.ToListAsync();
Format Strings:
var orders = await table.Query<Order>()
.Where($"{OrderFields.CustomerId} = {{0}} AND begins_with({OrderFields.OrderId}, {{1}})",
OrderKeys.Pk(customerId), "ORDER#2024")
.WithFilter($"{OrderFields.Status} = {{0}} AND {OrderFields.Total} > {{1:F2}}",
"pending", 100.00m)
.ToListAsync();
Manual Building:
var orders = await table.Query<Order>()
.Where("#pk = :pk AND begins_with(#sk, :prefix)")
.WithAttribute("#pk", "pk")
.WithAttribute("#sk", "sk")
.WithValue(":pk", OrderKeys.Pk(customerId))
.WithValue(":prefix", "ORDER#2024")
.WithFilter("#status = :status AND #total > :total")
.WithAttribute("#status", "status")
.WithAttribute("#total", "total")
.WithValue(":status", "pending")
.WithValue(":total", 100.00m)
.ToListAsync();
Update with Condition
Lambda Expressions (Recommended):
await table.Update<User>()
.WithKey(user)
.Set(x => new { Name = "Jane Doe", UpdatedAt = DateTime.UtcNow })
.WithCondition(x => x.Version == currentVersion)
.UpdateAsync();
Format Strings:
await table.Update<User>()
.WithKey(UserFields.UserId, UserKeys.Pk("user123"))
.Set($"SET {UserFields.Name} = {{0}}, {UserFields.UpdatedAt} = {{1:o}}",
"Jane Doe", DateTime.UtcNow)
.Where($"{UserFields.Version} = {{0}}", currentVersion)
.UpdateAsync();
Manual Building:
await table.Update<User>()
.WithKey(UserFields.UserId, UserKeys.Pk("user123"))
.Set("SET #name = :name, #updated = :updated")
.WithAttribute("#name", "name")
.WithAttribute("#updated", "updatedAt")
.WithValue(":name", "Jane Doe")
.WithValue(":updated", DateTime.UtcNow)
.Where("#version = :version")
.WithAttribute("#version", "version")
.WithValue(":version", currentVersion)
.UpdateAsync();
Next Steps
Learn more about each approach:
- Lambda Expressions - Comprehensive guide to the recommended type-safe approach
- Formatted Expressions - Format string syntax with placeholders and format specifiers
- String Expressions - Manual parameter binding for explicit control
Related topics: