Direct SDK Request Passing
Use native AWS SDK request objects with Oproto.FluentDynamoDb for easy interoperability with existing code and gradual migration paths.
Overview
Direct SDK Request Passing allows you to:
- Inject pre-built AWS SDK request objects into FluentDynamoDb builders
- Get entity hydration and response metadata for SDK requests
- Gradually migrate existing SDK code to FluentDynamoDb
- Use FluentDynamoDb's entity mapping with complex SDK requests
Use Cases
- Migration: Gradually migrate existing AWS SDK code to FluentDynamoDb
- Complex Requests: Use SDK features not yet exposed by FluentDynamoDb builders
- Interoperability: Mix FluentDynamoDb with existing SDK-based code
- Testing: Inject mock or pre-configured requests for testing
WithRequest() Method
All request builders support the WithRequest() method to inject a pre-built SDK request:
Get Operations
// Create SDK request
var sdkRequest = new GetItemRequest
{
TableName = "users",
Key = new Dictionary<string, AttributeValue>
{
["pk"] = new AttributeValue { S = "USER#123" }
},
ConsistentRead = true
};
// Simple inline usage - no need to capture the builder
var user = await table.Users.Get().WithRequest(sdkRequest).GetItemAsync();
// If you need response metadata, capture the builder first
var builder = table.Users.Get().WithRequest(sdkRequest);
var user = await builder.GetItemAsync();
var capacity = builder.Response?.ConsumedCapacity;
Query Operations
// Create SDK request
var sdkRequest = new QueryRequest
{
TableName = "orders",
KeyConditionExpression = "pk = :pk",
ExpressionAttributeValues = new Dictionary<string, AttributeValue>
{
[":pk"] = new AttributeValue { S = "CUSTOMER#123" }
},
ScanIndexForward = false,
Limit = 10
};
// Simple inline usage - no need to capture the builder
var orders = await table.Orders.Query().WithRequest(sdkRequest).ToListAsync();
// If you need response metadata (pagination, counts), capture the builder first
var builder = table.Orders.Query().WithRequest(sdkRequest);
var orders = await builder.ToListAsync();
var lastKey = builder.Response?.LastEvaluatedKey;
var scannedCount = builder.Response?.ScannedCount;
Put Operations
// Create SDK request
var sdkRequest = new PutItemRequest
{
TableName = "users",
Item = User.ToDynamoDb(user),
ConditionExpression = "attribute_not_exists(pk)",
ReturnValues = ReturnValue.ALL_OLD
};
// Simple inline usage - fire and forget
await table.Users.Put().WithRequest(sdkRequest).PutAsync();
// If you need response metadata, capture the builder first
var builder = table.Users.Put().WithRequest(sdkRequest);
await builder.PutAsync();
var capacity = builder.Response?.ConsumedCapacity;
Update Operations
// Create SDK request
var sdkRequest = new UpdateItemRequest
{
TableName = "users",
Key = new Dictionary<string, AttributeValue>
{
["pk"] = new AttributeValue { S = "USER#123" }
},
UpdateExpression = "SET #name = :name, #updated = :updated",
ExpressionAttributeNames = new Dictionary<string, string>
{
["#name"] = "name",
["#updated"] = "updated_at"
},
ExpressionAttributeValues = new Dictionary<string, AttributeValue>
{
[":name"] = new AttributeValue { S = "Jane Doe" },
[":updated"] = new AttributeValue { S = DateTime.UtcNow.ToString("o") }
},
ReturnValues = ReturnValue.ALL_NEW
};
// Simple inline usage
await table.Users.Update().WithRequest(sdkRequest).UpdateAsync();
Delete Operations
// Create SDK request
var sdkRequest = new DeleteItemRequest
{
TableName = "users",
Key = new Dictionary<string, AttributeValue>
{
["pk"] = new AttributeValue { S = "USER#123" }
},
ConditionExpression = "#status = :status",
ExpressionAttributeNames = new Dictionary<string, string>
{
["#status"] = "status"
},
ExpressionAttributeValues = new Dictionary<string, AttributeValue>
{
[":status"] = new AttributeValue { S = "inactive" }
}
};
// Simple inline usage
await table.Users.Delete().WithRequest(sdkRequest).DeleteAsync();
Scan Operations
// Create SDK request
var sdkRequest = new ScanRequest
{
TableName = "users",
FilterExpression = "#status = :status",
ExpressionAttributeNames = new Dictionary<string, string>
{
["#status"] = "status"
},
ExpressionAttributeValues = new Dictionary<string, AttributeValue>
{
[":status"] = new AttributeValue { S = "active" }
},
Limit = 100
};
// Simple inline usage
var users = await table.Users.Scan().WithRequest(sdkRequest).ToListAsync();
Table-Level Convenience Methods
For simpler scenarios, table classes provide convenience methods that accept SDK requests directly:
Synchronous Builder Methods
// Returns a builder for further configuration or execution
var builder = table.Users.Get(sdkRequest);
var builder = table.Users.Query(sdkRequest);
var builder = table.Users.Scan(sdkRequest);
var builder = table.Users.Put(sdkRequest);
var builder = table.Users.Update(sdkRequest);
var builder = table.Users.Delete(sdkRequest);
Async Convenience Methods
// Execute directly and return hydrated entity/entities
var user = await table.Users.GetAsync(sdkRequest);
var orders = await table.Users.QueryAsync(sdkRequest);
var users = await table.Users.ScanAsync(sdkRequest);
Direct Transaction Execution
Execute pre-built transaction requests directly:
TransactWriteItems
// Create SDK transaction request
var transactRequest = new TransactWriteItemsRequest
{
TransactItems = new List<TransactWriteItem>
{
new TransactWriteItem
{
Put = new Put
{
TableName = "orders",
Item = Order.ToDynamoDb(order)
}
},
new TransactWriteItem
{
Update = new Update
{
TableName = "inventory",
Key = new Dictionary<string, AttributeValue>
{
["pk"] = new AttributeValue { S = "PRODUCT#123" }
},
UpdateExpression = "SET quantity = quantity - :qty",
ExpressionAttributeValues = new Dictionary<string, AttributeValue>
{
[":qty"] = new AttributeValue { N = "1" }
}
}
}
}
};
// Execute directly
await DynamoDbTransactions.WriteAsync(client, transactRequest);
TransactGetItems
// Create SDK transaction get request
var transactRequest = new TransactGetItemsRequest
{
TransactItems = new List<TransactGetItem>
{
new TransactGetItem
{
Get = new Get
{
TableName = "users",
Key = new Dictionary<string, AttributeValue>
{
["pk"] = new AttributeValue { S = "USER#123" }
}
}
},
new TransactGetItem
{
Get = new Get
{
TableName = "orders",
Key = new Dictionary<string, AttributeValue>
{
["pk"] = new AttributeValue { S = "ORDER#456" }
}
}
}
}
};
// Execute directly
var response = await DynamoDbTransactions.GetAsync(client, transactRequest);
Direct Batch Execution
Execute pre-built batch requests directly:
BatchWriteItem
// Create SDK batch write request
var batchRequest = new BatchWriteItemRequest
{
RequestItems = new Dictionary<string, List<WriteRequest>>
{
["users"] = new List<WriteRequest>
{
new WriteRequest
{
PutRequest = new PutRequest
{
Item = User.ToDynamoDb(user1)
}
},
new WriteRequest
{
DeleteRequest = new DeleteRequest
{
Key = new Dictionary<string, AttributeValue>
{
["pk"] = new AttributeValue { S = "USER#old" }
}
}
}
}
}
};
// Execute directly
await DynamoDbBatch.WriteAsync(client, batchRequest);
BatchGetItem
// Create SDK batch get request
var batchRequest = new BatchGetItemRequest
{
RequestItems = new Dictionary<string, KeysAndAttributes>
{
["users"] = new KeysAndAttributes
{
Keys = new List<Dictionary<string, AttributeValue>>
{
new Dictionary<string, AttributeValue>
{
["pk"] = new AttributeValue { S = "USER#123" }
},
new Dictionary<string, AttributeValue>
{
["pk"] = new AttributeValue { S = "USER#456" }
}
}
}
}
};
// Execute directly
var response = await DynamoDbBatch.GetAsync(client, batchRequest);
Migration Pattern
Use Direct SDK Request Passing to gradually migrate existing code:
Before (Pure SDK)
public async Task<User?> GetUserAsync(string userId)
{
var request = new GetItemRequest
{
TableName = _tableName,
Key = new Dictionary<string, AttributeValue>
{
["pk"] = new AttributeValue { S = $"USER#{userId}" }
}
};
var response = await _client.GetItemAsync(request);
if (response.Item == null || response.Item.Count == 0)
return null;
// Manual mapping
return new User
{
UserId = response.Item["pk"].S.Replace("USER#", ""),
Name = response.Item["name"].S,
Email = response.Item["email"].S
};
}
After (FluentDynamoDb with SDK Request)
public async Task<User?> GetUserAsync(string userId)
{
// Keep existing request construction
var request = new GetItemRequest
{
TableName = _tableName,
Key = new Dictionary<string, AttributeValue>
{
["pk"] = new AttributeValue { S = $"USER#{userId}" }
}
};
// Use FluentDynamoDb for entity hydration
return await _table.Users.GetAsync(request);
}
Final (Pure FluentDynamoDb)
public async Task<User?> GetUserAsync(string userId)
{
return await _table.Users.GetAsync(User.Keys.Pk(userId));
}
Best Practices
- Use for Migration: Direct SDK Request Passing is ideal for gradual migration, not as a primary API
- Prefer Builders: For new code, use FluentDynamoDb builders for type safety and cleaner syntax
- Inline When Possible: If you don't need response metadata, call the async method inline (e.g.,
await table.Users.Get().WithRequest(req).GetItemAsync()) - Capture Builder for Metadata: Only capture the builder to a variable when you need to access
builder.Responseafter execution - Table Name Consistency: Ensure SDK request table names match your FluentDynamoDb table configuration
See Also
- Client Configuration - DynamoDB client setup
- Request Builders - Standard CRUD operations