Skip to content

About

Oproto.FluentDynamoDb library

Resources

Contributing

Stars

5 stars

Watchers

1 watching

Forks

Latest commit

Β 

History

447 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Oproto.FluentDynamoDb Logo

Oproto.FluentDynamoDb

Build Tests License: MIT Sponsor

NuGet NuGet NuGet NuGet NuGet NuGet NuGet NuGet NuGet

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.

Feature Maturity (1.1.0)

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)

Quick Start

Installation

dotnet add package Oproto.FluentDynamoDb

Note: The source generator and attributes are bundled in the main package. No additional packages are required for basic usage.

Define Your First Entity

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.

Basic Operations

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.

Expression Styles

Oproto.FluentDynamoDb supports three expression styles. Lambda is preferred for type safety.

Lambda Expressions (Preferred)

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();

Format Strings (Concise Alternative)

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();

Manual (Full Control)

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();

Key Features

πŸ”§ Source Generation for Zero Boilerplate

Automatic generation of field constants, key builders, update models, and mapping code at compile time. No reflection, no runtime overhead, full AOT compatibility.

πŸ”‘ Automatic Key Prefix Handling

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 through

🎯 Lambda Expression Support

Write 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();

πŸ”— Composite Entities for Complex Data Models

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

πŸ“ Typed Computed Key Overloads

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);

⚑ Batch Operations and Transactions

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();

🌊 Stream Processing

Fluent pattern matching for DynamoDB Streams in Lambda functions with support for INSERT, UPDATE, DELETE, and TTL events.

πŸ”’ Field-Level Security

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;

πŸ”„ Dynamic Fields Support

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();

πŸ“Š Logging and Diagnostics

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.

🎲 Automatic Discriminator Derivation

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 entity

πŸ“ˆ FluentResults Integration

Optional 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 */ }

Convenience Methods vs Builder API

Convenience Methods (Simple Operations)

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);

Builder API (Complex Operations)

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();

Documentation Guide

New to Oproto.FluentDynamoDb? Start here to learn the basics.

🎯 Core Features

Master the essential operations and patterns.

Explore advanced patterns and optimizations.

πŸ“š Reference

Detailed API and troubleshooting information.

πŸ“„ Additional Resources

About

Oproto.FluentDynamoDb is developed and maintained by Oproto Inc, a company building modern SaaS solutions for small business finance and accounting.

Related Projects

Links

Maintainer

❀️ Support the Project

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."

Community & Support

Contributing

Contributions are welcome! Please see our contributing guidelines for more information.

License

This project is licensed under the MIT License - see the LICENSE file for details.


Built with ❀️ for the .NET and AWS communities

About

Oproto.FluentDynamoDb library

Resources

Contributing

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages