DynamoDBv2.Transactions
4.0.14.103
See the version list below for details.
dotnet add package DynamoDBv2.Transactions --version 4.0.14.103
NuGet\Install-Package DynamoDBv2.Transactions -Version 4.0.14.103
<PackageReference Include="DynamoDBv2.Transactions" Version="4.0.14.103" />
<PackageVersion Include="DynamoDBv2.Transactions" Version="4.0.14.103" />
<PackageReference Include="DynamoDBv2.Transactions" />
paket add DynamoDBv2.Transactions --version 4.0.14.103
#r "nuget: DynamoDBv2.Transactions, 4.0.14.103"
#:package DynamoDBv2.Transactions@4.0.14.103
#addin nuget:?package=DynamoDBv2.Transactions&version=4.0.14.103
#tool nuget:?package=DynamoDBv2.Transactions&version=4.0.14.103
DynamoDBv2.Transactions
A high-performance .NET library for Amazon DynamoDB transactions with compile-time source generation — up to 82x faster than reflection-based mapping.
<p align="center">
</p>
<p align="center">
</p>
<p align="center">
</p>
Why This Library?
The standard AWS SDK DynamoDBContext makes an implicit DescribeTable network call to resolve key schemas at runtime. DynamoDBv2.Transactions eliminates that entirely by reading [DynamoDBHashKey] attributes directly — and with the included source generator, all mapping is resolved at compile time with zero reflection overhead.
Performance at a Glance
<table> <tr> <td width="50%">
Mapper: Source-Generated vs Reflection
| Operation | Source-Gen | Reflection | Speedup |
|---|---|---|---|
GetTableName |
13.9 ns / 0 B | 1,135 ns / 144 B | 82x |
MapToAttribute (15 props) |
4,049 ns / 3,232 B | 16,412 ns / 4,000 B | 4.1x |
GetHashKeyAttributeName |
14.4 ns / 0 B | 30.5 ns / 0 B | 2.1x |
GetPropertyAttributedName |
20.7 ns / 0 B | 39.4 ns / 0 B | 1.9x |
GetVersion |
144.8 ns / 56 B | 192.5 ns / 56 B | 1.3x |
</td> <td width="50%">
End-to-End: This Library vs Standard SDK Wrapper
| Scenario | This Library | Standard SDK | Speedup |
|---|---|---|---|
| 1-item transaction | 12.0 ms / 81 KB | 15.8 ms / 84 KB | 1.3x |
| 3-item transaction | 13.4 ms / 115 KB | 46.4 ms / 251 KB | 3.5x |
The standard SDK wrapper issues separate DescribeTable + write calls per item. This library batches everything into a single TransactWriteItems request.
</td> </tr> </table>
All key lookups are zero-allocation via compile-time switch expressions. Just add
partialto your entity class — no other changes needed.
Installation
dotnet add package DynamoDBv2.Transactions
The package includes the source generator automatically — no additional packages required.
Quick Start
1. Define Your Entity
Add partial and standard DynamoDB attributes. The source generator handles everything at compile time:
[DynamoDBTable("Orders")]
public partial class Order : ITransactional
{
[DynamoDBHashKey("PK")]
public string OrderId { get; set; }
[DynamoDBProperty("CustomerName")]
public string CustomerName { get; set; }
[DynamoDBProperty("Total")]
public decimal Total { get; set; }
[DynamoDBProperty("Status")]
public string Status { get; set; }
[DynamoDBVersion]
public long? Version { get; set; }
}
2. Write a Transaction
Transactions execute atomically on DisposeAsync — if any operation fails, they all roll back:
var client = new AmazonDynamoDBClient();
await using (var transactor = new DynamoDbTransactor(client))
{
transactor.CreateOrUpdate(new Order
{
OrderId = "ORD-001",
CustomerName = "Alice",
Total = 99.99m,
Status = "Confirmed"
});
}
That's it. The source generator wires up all mapping at compile time, the transaction executes on dispose.
How It Works
Your Code Compile Time Runtime
| | |
v v v
[DynamoDBTable("Orders")] Source Generator DynamoDbTransactor
public partial class Order generates switch-expr batches operations
| mappings (zero alloc) |
v | v
[DynamoDBHashKey("PK")] [ModuleInitializer] TransactWriteItems
public string OrderId auto-registers at (single call)
app startup
Two mapping strategies, one API:
| Strategy | When | Performance | Setup |
|---|---|---|---|
| Source Generator | Entity class is partial |
Up to 82x faster, zero allocation | Just add partial |
| Cached Reflection | Non-partial classes | Good (warmed caches) | No changes needed |
Both strategies are transparent — the same DynamoDbTransactor API works with either. The source generator is automatically discovered at startup via [ModuleInitializer].
Features
| Feature | Description |
|---|---|
| Source Generator | Compile-time DynamoDB attribute mapping with zero reflection and zero-allocation key lookups |
| AWS SDK v4 | Built for AWSSDK.DynamoDBv2 v4.x, auto-tracked via Dependabot with automated releases |
| Transactional Operations | CreateOrUpdate, Delete, Patch, Update, ConditionCheck — all in a single atomic request |
| Optimistic Concurrency | Automatic version increment via ITransactional — no manual version tracking needed |
| Condition Expressions | Type-safe Equals, NotEquals, GreaterThan, LessThan, VersionEquals checks |
| 100-Item Validation | Enforces DynamoDB's transaction limit before sending the request |
| TransactionOptions | ClientRequestToken (idempotency), ReturnConsumedCapacity, ReturnItemCollectionMetrics |
| Multi-targeting | .NET 8.0, .NET 9.0, and .NET 10.0 |
| Source Link + Deterministic | Step-into debugging from NuGet, reproducible builds |
Usage Examples
Create or Update
await using (var transactor = new DynamoDbTransactor(client))
{
transactor.CreateOrUpdate(new Order
{
OrderId = "ORD-001",
CustomerName = "Alice",
Total = 149.99m,
Status = "Confirmed"
});
}
// Transaction executes atomically on DisposeAsync
Delete
// Delete by hash key (attribute name inferred from [DynamoDBHashKey])
await using (var transactor = new DynamoDbTransactor(client))
{
transactor.DeleteAsync<Order>("ORD-001");
}
// Delete with explicit key name
await using (var transactor = new DynamoDbTransactor(client))
{
transactor.DeleteAsync<Order>("PK", "ORD-001");
}
// Delete with expression (type-safe)
await using (var transactor = new DynamoDbTransactor(client))
{
transactor.DeleteAsync<Order, string>(o => o.OrderId, "ORD-001");
}
Patch a Single Property
Update one field without touching the rest of the item:
// Expression-based (type-safe)
await using (var transactor = new DynamoDbTransactor(client))
{
transactor.PatchAsync<Order, string>("ORD-001", o => o.Status, "Shipped");
}
// Instance-based (patch from an existing object)
await using (var transactor = new DynamoDbTransactor(client))
{
order.Status = "Shipped";
transactor.PatchAsync(order, nameof(order.Status));
}
Conditional Checks
Assert conditions before writing — the entire transaction rolls back if any check fails:
await using (var transactor = new DynamoDbTransactor(client))
{
// Only update if the order total is above the threshold
transactor.ConditionGreaterThan<Order, decimal>("ORD-001", o => o.Total, 50.0m);
transactor.PatchAsync<Order, string>("ORD-001", o => o.Status, "Priority");
}
Available condition methods:
ConditionEquals<TModel, TValue>— field must equal valueConditionNotEquals<TModel, TValue>— field must not equal valueConditionGreaterThan<TModel, TValue>— field must be greater than valueConditionLessThan<TModel, TValue>— field must be less than valueConditionVersionEquals<TModel>— optimistic concurrency version check
Optimistic Concurrency (Versioning)
Implement ITransactional to get automatic version management:
[DynamoDBTable("Orders")]
public partial class Order : ITransactional
{
[DynamoDBHashKey("PK")]
public string OrderId { get; set; }
[DynamoDBVersion]
public long? Version { get; set; } // Auto-incremented on every write
}
// First write: Version is set to 0
await using (var transactor = new DynamoDbTransactor(client))
{
transactor.CreateOrUpdate(new Order { OrderId = "ORD-001" });
}
// Second write: Version auto-increments to 1, with a condition check
// that the current version in DynamoDB matches the expected version.
// If another writer modified the item, this transaction fails.
await using (var transactor = new DynamoDbTransactor(client))
{
transactor.CreateOrUpdate(existingOrder); // Version check is automatic
}
// Explicit version check
await using (var transactor = new DynamoDbTransactor(client))
{
transactor.ConditionVersionEquals<Order>("ORD-001", o => o.Version, 1);
transactor.PatchAsync<Order, string>("ORD-001", o => o.Status, "Completed");
}
Complex Multi-Operation Transaction
Combine multiple operations in a single atomic transaction (up to 100 items):
await using (var transactor = new DynamoDbTransactor(client))
{
// Check: inventory must be sufficient
transactor.ConditionGreaterThan<Inventory, int>(
"SKU-100", i => i.Quantity, 0);
// Create the order
transactor.CreateOrUpdate(new Order
{
OrderId = "ORD-042",
CustomerName = "Bob",
Total = 299.99m,
Status = "Confirmed"
});
// Update inventory count
transactor.PatchAsync<Inventory, int>(
"SKU-100", i => i.Quantity, currentQuantity - 1);
// Log the transaction
transactor.CreateOrUpdate(new AuditLog
{
LogId = Guid.NewGuid().ToString(),
Action = "OrderCreated",
Timestamp = DateTime.UtcNow
});
}
// All 4 operations succeed or fail together
Transaction Options
Configure idempotency and capacity tracking:
await using (var transactor = new DynamoDbTransactor(client))
{
transactor.Options = new TransactionOptions
{
// Idempotency token — retrying with the same token within 10 minutes
// returns the original result instead of executing again
ClientRequestToken = "order-042-confirm-v1",
// Track consumed read/write capacity units
ReturnConsumedCapacity = ReturnConsumedCapacity.TOTAL,
// Track item collection metrics (useful for tables with LSIs)
ReturnItemCollectionMetrics = ReturnItemCollectionMetrics.SIZE
};
transactor.CreateOrUpdate(order);
transactor.PatchAsync<Inventory, int>("SKU-100", i => i.Quantity, newQty);
}
Dependency Injection
Use ITransactionManager for testable, DI-friendly code:
// Registration
services.AddSingleton<IAmazonDynamoDB>(new AmazonDynamoDBClient());
services.AddSingleton<ITransactionManager, TransactionManager>();
// Usage
public class OrderService(ITransactionManager transactionManager)
{
public async Task ConfirmOrder(Order order)
{
await using var transactor = new DynamoDbTransactor(transactionManager);
transactor.CreateOrUpdate(order);
}
}
Source Generator Deep Dive
How Discovery Works
The source generator runs at compile time and discovers entities via two pipelines:
- Auto-discovery — any
partialclass with a[DynamoDBHashKey]property - Explicit opt-in — classes decorated with
[DynamoDbGenerateMapping]
// Auto-discovered (recommended)
[DynamoDBTable("Orders")]
public partial class Order
{
[DynamoDBHashKey("PK")]
public string OrderId { get; set; }
}
// Explicit opt-in
[DynamoDbGenerateMapping]
[DynamoDBTable("Orders")]
public partial class Order
{
[DynamoDBHashKey("PK")]
public string OrderId { get; set; }
}
What Gets Generated
For each discovered entity, the generator emits:
__DynamoDbMetadatanested class with:GetPropertyAttributeName(string)— zero-allocation switch expressionMapToAttributes(T)— compile-time serialization (noPropertyInfo.GetValue())GetVersion(T)— direct property access
DynamoDbMappingRegistration.g.cs—[ModuleInitializer]that registers all types at app startup
Fallback Behavior
Non-partial classes work seamlessly via cached reflection. You can mix both in the same project:
// Source-generated (82x faster lookups)
public partial class FastEntity { ... }
// Reflection fallback (still fast with warmed caches)
public class LegacyEntity { ... }
Detailed Benchmark Results
Mapper: Source-Generated vs Reflection
Isolated mapping operations — no DynamoDB I/O. Entity with 15 properties including all common types.
Reflection results use warmed-up ConcurrentDictionary caches (best-case reflection).
BenchmarkDotNet v0.15.8, Linux Ubuntu 25.10
.NET SDK 9.0.311, .NET 9.0.13, X64 RyuJIT x86-64-v3
Runtime=.NET 9.0 IterationCount=20 LaunchCount=3 WarmupCount=5
| Method | Mean | Allocated | vs Reflection |
|---|---|---|---|
| MapToAttribute (source-generated) | 4,048.56 ns | 3,232 B | 4.1x faster |
| MapToAttribute (reflection) | 16,412.42 ns | 4,000 B | baseline |
| GetPropertyAttributedName (source-gen) | 20.74 ns | 0 B | 1.9x faster |
| GetPropertyAttributedName (reflection) | 39.38 ns | 0 B | baseline |
| GetHashKeyAttributeName (source-gen) | 14.43 ns | 0 B | 2.1x faster |
| GetHashKeyAttributeName (reflection) | 30.49 ns | 0 B | baseline |
| GetVersion (source-generated) | 144.75 ns | 56 B | 1.3x faster |
| GetVersion (reflection) | 192.52 ns | 56 B | baseline |
| GetTableName (source-generated) | 13.87 ns | 0 B | 82x faster |
| GetTableName (reflection) | 1,135.12 ns | 144 B | baseline |
End-to-End: Full Transaction (includes network I/O)
BenchmarkDotNet v0.13.12, Windows 11
AMD Ryzen 9 6900HS, .NET 8.0.2
Job=OutOfProc IterationCount=15 LaunchCount=3 WarmupCount=10
| Method | Mean | Error | StdDev | Allocated |
|---|---|---|---|---|
| DynamoDBv2.Transactions (1 item) | 11.99 ms | 0.046 ms | 0.087 ms | 80.96 KB |
| Standard SDK Wrapper (1 item) | 15.83 ms | 0.236 ms | 0.442 ms | 83.77 KB |
| DynamoDBv2.Transactions (3 items) | 13.37 ms | 0.066 ms | 0.123 ms | 114.74 KB |
| Standard SDK Wrapper (3 items) | 46.44 ms | 0.444 ms | 0.834 ms | 251.01 KB |
The 3-item gap is dramatic because the standard SDK issues separate
DescribeTable+ write calls per item, while this library sends a singleTransactWriteItemsrequest.
Run Benchmarks Yourself
# Mapper benchmarks (source-gen vs reflection)
dotnet run --project test/DynamoDBv2.Transactions.Benchmarks -c Release -- --filter '*MapperBenchmark*'
# End-to-end benchmarks (requires localstack)
dotnet run --project test/DynamoDBv2.Transactions.Benchmarks -c Release -- --filter '*Benchmark*'
Running Tests
# Integration tests (requires Docker + localstack)
docker-compose up --exit-code-from tests tests localstack
# Unit tests
docker-compose up --exit-code-from unittests unittests
Test coverage: 259 unit tests, 33 integration tests, 16 source generator tests — all validated in CI on every push.
Versioning
This library's version tracks the underlying AWSSDK.DynamoDBv2 version:
Format: {aws_major}.{aws_minor}.{aws_patch}.{aws_rev * 100 + lib_rev}
| AWS SDK Version | Library Release | Meaning |
|---|---|---|
| 4.0.14.1 | 4.0.14.100 | Initial release for AWS SDK 4.0.14.1 |
| 4.0.14.1 | 4.0.14.101 | Library-only fix (same AWS SDK) |
| 4.0.15.0 | 4.0.15.0 | New AWS SDK version, lib rev 0 |
The first three segments always tell you which AWS SDK version is inside. New AWS SDK versions are auto-released via Dependabot + CI pipeline.
Contributing
When creating PRs, please ensure:
- No sensitive information (tokens, keys, credentials) is included
- All existing tests pass (
docker-compose up --exit-code-from tests tests localstack) - New features include appropriate test coverage
License
Copyright © 2026, Vitali Bibikov. Code released under the MIT license.
Contact
Vitali Bibikov — bibikovvitaly@gmail.com
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.0.14.109 | 2,192 | 4/9/2026 |
| 4.0.14.108 | 115 | 4/9/2026 |
| 4.0.14.107 | 163 | 4/9/2026 |
| 4.0.14.106 | 922 | 3/10/2026 |
| 4.0.14.105 | 104 | 3/9/2026 |
| 4.0.14.104 | 100 | 3/9/2026 |
| 4.0.14.103 | 101 | 3/9/2026 |
| 4.0.14.102 | 98 | 3/9/2026 |
| 4.0.14.101 | 103 | 3/9/2026 |
| 4.0.14.100 | 101 | 3/9/2026 |
| 4.0.14.1 | 108 | 3/9/2026 |
| 3.7.407 | 102 | 3/8/2026 |
| 3.7.406.3 | 313 | 3/20/2025 |