Skip to main content

Update Expressions

Build type-safe update expressions using lambda expressions for DynamoDB UpdateItem operations.

Overview

Update expressions allow you to modify existing items in DynamoDB. FluentDynamoDb provides a type-safe, expression-based approach that offers compile-time validation, IntelliSense support, and automatic parameter generation.

Benefits

  • Type Safety - Compile-time checking of property names and types
  • IntelliSense - Full IDE support with autocomplete
  • Refactoring - Rename properties safely across your codebase
  • Automatic Parameters - No manual parameter binding required

SET Operations

SET operations assign values to attributes. This is the most common update operation.

Simple Value Assignment

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
Name = "John Doe",
Email = "john@example.com",
Status = "active"
})
.UpdateAsync();

Using Variables

var newName = "John Doe";
var timestamp = DateTime.UtcNow;

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
Name = newName,
UpdatedAt = timestamp
})
.UpdateAsync();

Conditional Assignment with if_not_exists

Sets a value only if the attribute doesn't exist:

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
ViewCount = x.ViewCount.IfNotExists(0),
CreatedAt = x.CreatedAt.IfNotExists(DateTime.UtcNow)
})
.UpdateAsync();

ADD Operations

ADD operations atomically increment numbers or add elements to sets.

Atomic Increment

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
LoginCount = x.LoginCount.Add(1),
ViewCount = x.ViewCount.Add(5)
})
.UpdateAsync();

Atomic Decrement

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
Credits = x.Credits.Add(-10) // Subtract 10
})
.UpdateAsync();

Add to Set

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
Tags = x.Tags.Add("premium", "verified")
})
.UpdateAsync();

REMOVE Operations

REMOVE operations delete entire attributes from an item.

Remove Single Attribute

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
TempData = x.TempData.Remove()
})
.UpdateAsync();

Remove Multiple Attributes

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
TempData = x.TempData.Remove(),
CachedValue = x.CachedValue.Remove()
})
.UpdateAsync();
note

REMOVE deletes the attribute entirely, which is different from setting it to null. Key attributes (partition and sort keys) cannot be removed.

DELETE Operations

DELETE operations remove specific elements from sets (not to be confused with REMOVE which deletes entire attributes).

Delete from String Set

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
Tags = x.Tags.Delete("old-tag", "deprecated")
})
.UpdateAsync();

Delete from Number Set

await table.Update<Product>("prod123")
.Set(x => new ProductUpdateModel
{
CategoryIds = x.CategoryIds.Delete(5, 10)
})
.UpdateAsync();

List Operations

Append to List

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
History = x.History.ListAppend("login", "profile-view")
})
.UpdateAsync();

Prepend to List

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
RecentActivity = x.RecentActivity.ListPrepend("new-event")
})
.UpdateAsync();

Arithmetic Operations

Perform arithmetic directly in SET clauses:

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
Score = x.Score + 10,
Balance = x.Balance - 5.00m
})
.UpdateAsync();

Property-to-Property Arithmetic

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
TotalScore = x.BaseScore + x.BonusScore
})
.UpdateAsync();

Anonymous Types for Multiple Properties

Use anonymous types to update multiple properties in a single expression. This is the recommended pattern for updating several fields at once.

Basic Pattern

await table.Update<User>("user123")
.Set(x => new { Name = "John Doe", Age = 30 })
.UpdateAsync();

With Mixed Operations

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
// SET operations
Name = "John Doe",
Status = "active",
UpdatedAt = DateTime.UtcNow,

// ADD operations
LoginCount = x.LoginCount.Add(1),
Tags = x.Tags.Add("premium"),

// List operations
History = x.History.ListAppend("profile-update"),

// REMOVE operations
TempData = x.TempData.Remove()
})
.UpdateAsync();

Using Variables in Anonymous Types

var newName = "Jane Doe";
var newStatus = "active";
var timestamp = DateTime.UtcNow;

await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
Name = newName,
Status = newStatus,
UpdatedAt = timestamp,
LoginCount = x.LoginCount.Add(1)
})
.UpdateAsync();

Conditional Updates

Combine Set() with Where() to perform conditional updates that only succeed when certain conditions are met.

Basic Conditional Update

await table.Update<User>("user123")
.Where(x => x.Status == "active")
.Set(x => new UserUpdateModel { Name = "Jane Doe" })
.UpdateAsync();

Optimistic Locking with Version

Use version checking to prevent concurrent update conflicts:

var currentVersion = 5;

await table.Update<User>("user123")
.Where(x => x.Version == currentVersion)
.Set(x => new UserUpdateModel
{
Name = "Jane Doe",
Version = currentVersion + 1
})
.UpdateAsync();

Only Update If Exists

await table.Update<User>("user123")
.Where(x => x.UserId.AttributeExists())
.Set(x => new UserUpdateModel { Name = "Jane Doe" })
.UpdateAsync();

Complex Conditions

await table.Update<User>("user123")
.Where(x => x.Status == "active" && x.Age >= 18)
.Set(x => new UserUpdateModel
{
Verified = true,
VerifiedAt = DateTime.UtcNow
})
.UpdateAsync();

Transactions with Updates

Updates can be included in transactions for atomic multi-item operations:

await DynamoDbTransactions.Write
.Add(table.Update<Order>("customer123", "ORDER#order456")
.Set(x => new { Status = "shipped" }))
.Add(table.Update<Inventory>("product789")
.Where(x => x.Quantity >= 1)
.Set(x => new { Quantity = x.Quantity.Add(-1) }))
.CommitAsync();

Format String Alternative

For scenarios requiring more control, you can use format strings:

// Format string approach
await table.Update<User>("user123")
.Set($"SET {User.Fields.Name} = {{0}}, {User.Fields.Status} = {{1}}",
"Jane Doe", "active")
.UpdateAsync();

// ADD with format string
await table.Update<User>("user123")
.Set($"ADD {User.Fields.LoginCount} {{0}}", 1)
.UpdateAsync();

// REMOVE with format string
await table.Update<User>("user123")
.Set($"REMOVE {User.Fields.TempField}")
.UpdateAsync();

Best Practices

Use ADD for Counters

// ✅ Recommended: ADD creates attribute if it doesn't exist
LoginCount = x.LoginCount.Add(1)

// ⚠️ Arithmetic requires attribute to exist
LoginCount = x.LoginCount + 1

Combine Operations Efficiently

// ✅ Good: Single update with multiple operations
await table.Update<User>("user123")
.Set(x => new UserUpdateModel
{
Name = "John",
LoginCount = x.LoginCount.Add(1),
TempData = x.TempData.Remove()
})
.UpdateAsync();

// ❌ Avoid: Multiple separate updates
await table.Update<User>("user123")
.Set(x => new UserUpdateModel { Name = "John" })
.UpdateAsync();
await table.Update<User>("user123")
.Set(x => new UserUpdateModel { LoginCount = x.LoginCount.Add(1) })
.UpdateAsync();

Use Optimistic Locking for Concurrent Updates

// ✅ Prevents lost updates in concurrent scenarios
await table.Update<User>("user123")
.Where(x => x.Version == currentVersion)
.Set(x => new UserUpdateModel
{
Data = newData,
Version = currentVersion + 1
})
.UpdateAsync();

Next Steps