Skip to main content

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

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

FeatureLambda ExpressionsFormat StringsManual 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:

Related topics:

  • Filters - Filter expressions for additional result filtering
  • Updates - Update expressions for modifying items