A modern, fluent-style API wrapper for Amazon DynamoDB that combines automatic code generation with type-safe operations. Built for .NET 8+, this library eliminates boilerplate through source generation while providing an intuitive, expression-based syntax for all DynamoDB operations. Whether you're building serverless applications, microservices, or enterprise systems, Oproto.FluentDynamoDb delivers a developer-friendly experience without sacrificing performance or flexibility.
The library is designed with AOT (Ahead-of-Time) compilation compatibility in mind, making it ideal for AWS Lambda functions and other performance-critical scenarios. With built-in support for complex patterns like composite entities, transactions, and stream processing, you can focus on your business logic while the library handles the DynamoDB complexity.
Perfect for teams seeking to reduce development time and maintenance overhead, Oproto.FluentDynamoDb provides compile-time safety through source generation, runtime efficiency through optimized request building, and developer productivity through lambda expressions that eliminate manual parameter management.
FluentDynamoDB is a large library. Here's where each subsystem stands.
Production-ready
- Strongly-typed entity modeling and source generation (no reflection, AOT-friendly)
- Single-table, multi-entity patterns with automatic discriminator derivation
- Composite entities with
[RelatedEntity]collections - Lambda expression queries, filters, and update expressions
- Format string and manual expression styles
- Automatic key prefix handling (
KeyInputMode.Auto) - Typed computed key convenience overloads
- Batch operations and transactional helpers
- Query/scan with pagination, projections, and consistent read
- Dynamic fields with typed map accessors and prefix-based operations
- GSI/LSI with automatic index projections and multi-entity consolidation
- Stream processing for Lambda functions
- Logging integration with Microsoft.Extensions.Logging
- FluentResults (Result pattern) alternative to exceptions
- Conditional filter expressions with compile-time short-circuiting
- Schema versioning for forward-compatible source generation
Stable, multi-tenant ready
- KMS-based field encryption with per-property key aliases
- S3-backed blob storage with named providers
- Per-property encryption key routing via
[Encrypted(KeyAlias = "...")]
Experimental / evolving
- Geospatial indexing (GeoHash, S2, H3)
dotnet add package Oproto.FluentDynamoDbNote: The source generator and attributes are bundled in the main package. No additional packages are required for basic usage.
using Oproto.FluentDynamoDb.Attributes;
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey(Prefix = "USER")]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
[DynamoDbAttribute("username")]
public string Username { get; set; } = string.Empty;
[DynamoDbAttribute("email")]
public string Email { get; set; } = string.Empty;
[DynamoDbAttribute("status")]
public string Status { get; set; } = "active";
[DynamoDbAttribute("created")]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}The source generator automatically creates:
- Field constants (
User.Fields.UserId,User.Fields.Username, etc.) - Key builders (
User.Keys.Pk(userId)β"USER#userId") - Mapper methods for converting between your model and DynamoDB items
- Update model class (
UserUpdateModel) for type-safe updates
All support classes are generated as nested classes within your entity for better organization.
using Amazon.DynamoDBv2;
using Oproto.FluentDynamoDb;
using Oproto.FluentDynamoDb.Storage;
var client = new AmazonDynamoDBClient();
var table = new UsersTable(client, "users");
// Create a user β key prefix is applied automatically
var user = new User
{
UserId = "user123", // Stored as "USER#user123" in DynamoDB
Username = "john_doe",
Email = "john@example.com"
};
await table.Users.PutAsync(user);
// Create only (fail if exists)
await table.Users.Put(user).IfNotExists().PutAsync();
// Get a user β prefix auto-detected
var retrieved = await table.Users.GetAsync("user123");
// Get with projection
var projected = await table.Users.Get("user123")
.WithProjection("username, email")
.GetItemAsync();
// Query with lambda expressions (preferred)
var activeUsers = await table.Users.Query(x => x.UserId == User.Keys.Pk("user123"))
.WithFilter(x => x.Status == "active")
.ToListAsync();
// Update with type-safe lambda
await table.Users.Update("user123")
.Set(x => new UserUpdateModel
{
Status = "inactive",
Username = "john_updated"
})
.UpdateAsync();
// Conditional update
await table.Users.Update("user123")
.IfExists()
.Set(x => new UserUpdateModel { Status = "active" })
.Where(x => x.Status == "pending")
.UpdateAsync();
// Delete
await table.Users.DeleteAsync("user123");
// Delete only if exists
await table.Users.Delete("user123").IfExists().DeleteAsync();Next Steps: See the Getting Started Guide for detailed setup instructions and more examples.
Oproto.FluentDynamoDb supports three expression styles. Lambda is preferred for type safety.
Type-safe with IntelliSense. Compile-time validation of property names.
// Query
var orders = await table.Orders.Query(x => x.CustomerId == customerId && x.OrderId.StartsWith("2024"))
.WithFilter(x => x.Status == "active")
.ToListAsync();
// Update
await table.Orders.Update(customerId, orderId)
.Set(x => new OrderUpdateModel { Status = "shipped", ShippedAt = DateTime.UtcNow })
.Where(x => x.Status == "processing")
.UpdateAsync();
// Nested map property access
var seattle = await table.Customers.Query(x => x.TenantId == tenantId)
.WithFilter(x => x.ShippingAddress.City == "Seattle")
.ToListAsync();Positional placeholders β values are auto-mapped to DynamoDB expression attribute values.
var orders = await table.Orders.Query("pk = {0} AND begins_with(sk, {1})", customerId, "2024")
.ToListAsync();For complex scenarios requiring explicit attribute name/value management.
var orders = await table.Orders.Query()
.Where("#pk = :pk AND begins_with(#sk, :prefix)")
.WithAttribute("#pk", "pk")
.WithAttribute("#sk", "sk")
.WithValue(":pk", customerId)
.WithValue(":prefix", "2024")
.ToListAsync();Automatic generation of field constants, key builders, update models, and mapping code at compile time. No reflection, no runtime overhead, full AOT compatibility.
- Learn more: Entity Definition Guide
Key prefixes are applied automatically during Put, Get, Update, and Delete. No more manual Keys.Pk() calls for every operation. The KeyInputMode.Auto default intelligently detects whether a prefix is already present.
// Just set the raw value β prefix applied automatically on write
var order = new Order { Pk = orderId, Sk = lineId };
await table.Orders.PutAsync(order); // Stored as "ORDER#12345", "LINE#abc"
// Auto-detect on read: both work
await table.Orders.GetAsync("12345"); // Applies prefix β "ORDER#12345"
await table.Orders.GetAsync("ORDER#12345"); // Detects prefix, passes throughWrite type-safe queries, filters, updates, and conditions using C# lambda expressions with full IntelliSense support. Supports nested map access, list indexing, Between, StartsWith, Contains, CompareTo, AttributeExists, and more.
await table.Users.Query(x => x.TenantId == tenantId && x.CreatedAt.Between(startDate, endDate))
.WithFilter(x => x.Status == "active" && x.Email.Contains("@company.com"))
.ToListAsync();- Learn more: LINQ Expressions Guide
Define multi-item entities and related data patterns with automatic population based on sort key patterns. Query once, get fully assembled parent + child entities.
var invoice = await table.Invoices.Query(x => x.Pk == pk && x.Sk.StartsWith("INVOICE#INV-001"))
.ToCompositeEntityAsync<Invoice>(); // invoice.Lines auto-populated- Learn more: Composite Entities Guide
Entities with composite computed keys get typed CRUD method overloads β no manual key string construction.
// Computed key: Year + Month + Day β "2024#12#25"
var evt = await table.Events.GetAsync(2024, 12, 25);
await table.Events.DeleteAsync(2024, 12, 25);Efficient batch get/write operations and full transaction support with type-safe expression builders.
await DynamoDbTransactions.Write
.Add(table.Users.Put(newUser).IfNotExists())
.Add(table.Accounts.Update(accountId).Set(x => new AccountUpdateModel { Balance = x.Balance - 100 }))
.ExecuteAsync();- Learn more: Batch Operations | Transactions
Fluent pattern matching for DynamoDB Streams in Lambda functions with support for INSERT, UPDATE, DELETE, and TTL events.
- Learn more: Developer Guide
Protect sensitive data with per-property encryption key routing via KMS. Mark fields with [Sensitive] to exclude from logs, or [Encrypted(KeyAlias = "pii")] for encryption at rest with property-level key separation.
[Encrypted(KeyAlias = "pii")]
[DynamoDbAttribute("ssn")]
public string Ssn { get; set; } = string.Empty;
[Encrypted(KeyAlias = "financial")]
[DynamoDbAttribute("accountNumber")]
public string AccountNumber { get; set; } = string.Empty;- Learn more: Field-Level Security Guide
Capture and work with DynamoDB attributes that aren't explicitly defined in your entity class. Typed map accessors, prefix-based operations, and change tracking for incremental updates.
[DynamoDbTable("products")]
[EnableDynamicFields]
public partial class Product { ... }
var product = await table.Products.GetAsync(productId);
var color = product.DynamicFields.GetString("color");
product.DynamicFields.SetMap("c_child1", new ChildRef { Amount = 100m });
await table.Products.Update(pk, sk)
.Set(x => new ProductUpdateModel { DynamicFields = product.DynamicFields.ChangesOnly() })
.UpdateAsync();- Learn more: Dynamic Fields Guide
Comprehensive logging support via IDynamoDbLogger interface with a Microsoft.Extensions.Logging adapter. Zero overhead when disabled (default NoOpLogger). Over 100 compile-time diagnostics catch configuration errors at build time.
- Learn more: Logging Configuration
Multi-entity single-table designs work without manual discriminator configuration. The source generator derives patterns from key prefixes and computed field formats, with compile-time overlap detection.
// Auto-derived: "ORDER#*" and "LINE#*" patterns β no extra config needed
[SortKey(Prefix = "ORDER")] public string Sk { get; set; } // Order entity
[SortKey(Prefix = "LINE")] public string Sk { get; set; } // OrderLine entityOptional Result pattern alternative to exceptions for all operations via the Oproto.FluentDynamoDb.FluentResults package.
var result = await table.Users.Get(userId).GetItemAsyncResult();
if (result.IsFailed) { /* handle error types */ }var user = await table.Users.GetAsync("user123");
await table.Users.PutAsync(user);
await table.Users.PutAsync(user, KeyCondition.MustNotExist);
await table.Users.DeleteAsync("user123");
await table.Users.DeleteAsync("user123", KeyCondition.MustExist);await table.Users.Put(user)
.IfNotExists()
.PutAsync();
var user = await table.Users.Get("user123")
.WithProjection("username, email")
.UsingConsistentRead()
.GetItemAsync();
await table.Users.Update("user123")
.IfExists()
.Set(x => new UserUpdateModel { Status = "active", Version = x.Version + 1 })
.Where(x => x.Version == currentVersion)
.UpdateAsync();π Getting Started
New to Oproto.FluentDynamoDb? Start here to learn the basics.
- Quick Start - Get up and running in 5 minutes
- Installation - Detailed setup instructions
- First Entity - Deep dive into entity definition
π― Core Features
Master the essential operations and patterns.
- Configuration - FluentDynamoDbOptions and service configuration
- Entity Definition - Attributes, keys, and indexes
- Basic Operations - CRUD operations
- Querying Data - Query and scan operations
- Expression Formatting - Format string syntax
- LINQ Expressions - Type-safe lambda expressions
- Batch Operations - Batch get and write
- Transactions - Multi-item transactions
- Dynamic Fields - Schema-flexible attributes
- Logging Configuration - Logging and diagnostics
π Advanced Topics
Explore advanced patterns and optimizations.
- Composite Entities - Multi-item and related entities
- Global Secondary Indexes - GSI patterns
- Field-Level Security - Encryption and sensitivity
- STS Integration - Custom client configurations
- Performance Optimization - Tuning tips
π Reference
Detailed API and troubleshooting information.
- Attribute Reference - Complete attribute documentation
- Format Specifiers - Format string reference
- Error Handling - Exception patterns
- Diagnostics - All 100+ compile-time diagnostic codes
- Troubleshooting - Common issues and solutions
- Developer Guide - Comprehensive usage guide
- Code Examples - Real-world examples
- Source Generator Guide - Generator details
Oproto.FluentDynamoDb is developed and maintained by Oproto Inc, a company building modern SaaS solutions for small business finance and accounting.
- π’ Company: oproto.com
- π¨βπ» Developer Portal: oproto.io
- π Documentation: fluentdynamodb.dev
- Dan Guisinger - danguisinger.com
Oproto maintains this library as part of a broader open-source ecosystem for building high-quality AWS-native .NET applications. If FluentDynamoDB saves you time or helps your team ship features faster, please consider supporting ongoing development.
π GitHub Sponsors β Recurring support for long-term development.
π Buy Me a Coffee β A simple, one-time "thanks."
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- License: MIT License
Contributions are welcome! Please see our contributing guidelines for more information.
This project is licensed under the MIT License - see the LICENSE file for details.
Built with β€οΈ for the .NET and AWS communities