| title | Troubleshooting Guide | |||||||
|---|---|---|---|---|---|---|---|---|
| category | reference | |||||||
| order | 4 | |||||||
| keywords |
|
|||||||
| related |
|
Documentation > Reference > Troubleshooting
This guide helps you diagnose and resolve common issues when using Oproto.FluentDynamoDb. Each issue includes the error message, cause, solution, and related documentation.
Symptoms:
- No generated code appears
- Field constants and key builders are missing
- Compiler errors about missing types
Error Message:
error CS0103: The name 'UserFields' does not exist in the current context
error CS0103: The name 'UserKeys' does not exist in the current context
Cause:
- Source generator not installed or not running
- Entity class not marked as
partial - Missing
[DynamoDbTable]attribute - Build cache issues
Solution:
- Verify the main package is installed (source generator is bundled):
dotnet list package | grep Oproto.FluentDynamoDbIf not installed:
dotnet add package Oproto.FluentDynamoDb- Ensure your entity class is marked as
partial:
// ❌ Wrong
[DynamoDbTable("users")]
public class User
{
// ...
}
// ✅ Correct
[DynamoDbTable("users")]
public partial class User
{
// ...
}- Clean and rebuild:
dotnet clean
dotnet build- Check IDE-specific issues:
Visual Studio:
- Close and reopen the solution
- Delete
.vsfolder and restart - Check Tools → Options → Text Editor → C# → Advanced → Enable source generators
Rider:
- Invalidate caches: File → Invalidate Caches / Restart
- Ensure source generators are enabled in settings
VS Code:
- Reload window (Cmd/Ctrl + Shift + P → "Reload Window")
- Delete
objandbinfolders
See Also:
Error Message:
error DYNAMO001: Entity class 'User' must be marked as partial to allow source generation
Cause:
The source generator requires classes to be partial so it can extend them with generated code.
Solution:
Add the partial keyword to your class declaration:
// Before
[DynamoDbTable("users")]
public class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
}
// After
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
}See Also:
Error Message:
error DYNAMO002: Entity 'User' must have exactly one property marked with [PartitionKey]
Cause: Every DynamoDB entity requires exactly one partition key.
Solution:
Add the [PartitionKey] attribute to one property:
[DynamoDbTable("users")]
public partial class User
{
// Add [PartitionKey] attribute
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
[DynamoDbAttribute("email")]
public string Email { get; set; } = string.Empty;
}See Also:
Error Message:
error DYNAMO003: Entity 'User' has multiple properties marked with [PartitionKey]. Only one is allowed.
Cause: An entity can only have one partition key.
Solution:
Remove [PartitionKey] from all but one property. If you need a composite key, use [Computed]:
[DynamoDbTable("users")]
public partial class User
{
[DynamoDbAttribute("tenant_id")]
public string TenantId { get; set; } = string.Empty;
[DynamoDbAttribute("user_id")]
public string UserId { get; set; } = string.Empty;
// Composite partition key
[PartitionKey]
[Computed(nameof(TenantId), nameof(UserId), Format = "{0}#{1}")]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
}See Also:
Symptoms:
- Build succeeds but generated types aren't available
- IntelliSense doesn't show generated members
- Code compiles but IDE shows errors
Cause:
- IDE not recognizing generated code
- Namespace mismatch
- Generated files not included in compilation
Solution:
- Check the generated files exist:
Look in obj/Debug/net8.0/generated/ for files like:
User.Fields.g.csUser.Keys.g.csUser.Mapper.g.cs
- Verify namespace matches:
Generated code uses the same namespace as your entity. Ensure you're using the correct namespace:
using YourNamespace; // Must match entity namespace
var userId = UserFields.UserId; // Should work- Rebuild the project:
dotnet clean
dotnet build- Check for compilation errors:
Generated code won't be available if there are compilation errors. Fix all errors and rebuild.
See Also:
Error Message:
DynamoDbMappingException: Failed to map property 'CreatedAt' from DynamoDB attribute 'created_at'.
Expected type DateTime but got String.
Cause:
- Type mismatch between entity property and DynamoDB attribute
- Missing or incorrect data in DynamoDB
- Incompatible type conversion
Solution:
- Verify property types match DynamoDB data:
// If DynamoDB stores ISO 8601 strings
[DynamoDbAttribute("created_at")]
public DateTime CreatedAt { get; set; }
// If DynamoDB stores Unix timestamps (numbers)
[DynamoDbAttribute("created_at")]
public long CreatedAtTimestamp { get; set; }- Use nullable types for optional data:
// If the attribute might not exist
[DynamoDbAttribute("last_login")]
public DateTime? LastLogin { get; set; }- Check DynamoDB data format:
Use AWS Console or CLI to inspect the actual data:
aws dynamodb get-item \
--table-name users \
--key '{"pk": {"S": "USER#user123"}}'See Also:
Error Message:
InvalidCastException: Unable to cast object of type 'System.String' to type 'System.Int32'
Cause: DynamoDB attribute type doesn't match entity property type.
Solution:
Ensure property types match DynamoDB attribute types:
| DynamoDB Type | C# Type |
|---|---|
| String (S) | string |
| Number (N) | int, long, decimal, double |
| Binary (B) | byte[] |
| Boolean (BOOL) | bool |
| Null (NULL) | Nullable types |
| List (L) | List<T> |
| Map (M) | Complex objects |
| String Set (SS) | HashSet<string> |
| Number Set (NS) | HashSet<int>, HashSet<long> |
// Correct type mappings
[DynamoDbAttribute("age")]
public int Age { get; set; } // DynamoDB Number
[DynamoDbAttribute("tags")]
public HashSet<string> Tags { get; set; } = new(); // DynamoDB String Set
[DynamoDbAttribute("metadata")]
public Dictionary<string, string> Metadata { get; set; } = new(); // DynamoDB MapError Message:
FormatException: Format string contains invalid parameter indices: -1.
Parameter indices must be non-negative integers.
Cause: Invalid format string syntax in expression formatting.
Solution:
Use correct placeholder syntax:
// ❌ Wrong - negative index
.Where($"{UserFields.Status} = {{-1}}", "active")
// ✅ Correct - zero-based positive index
.Where($"{UserFields.Status} = {{0}}", "active")
// ❌ Wrong - missing closing brace
.Where($"{UserFields.Status} = {{0", "active")
// ✅ Correct - properly closed
.Where($"{UserFields.Status} = {{0}}", "active")
// ❌ Wrong - not enough arguments
.Where($"{UserFields.Status} = {{0}} AND {UserFields.Type} = {{1}}", "active")
// ✅ Correct - matching arguments
.Where($"{UserFields.Status} = {{0}} AND {UserFields.Type} = {{1}}", "active", "premium")See Also:
Error Message:
InvalidKeyExpressionException: Property 'Status' is not a key attribute and cannot be used in Query().Where().
Use WithFilter() instead.
Cause: Attempting to use a non-key property in a Query().Where() expression. Key condition expressions can only reference partition key and sort key properties.
Solution:
Move non-key properties to WithFilter():
// ❌ Wrong - Status is not a key attribute
await table.Query
.Where<User>(x => x.PartitionKey == userId && x.Status == "active")
.ExecuteAsync();
// ✅ Correct - Move Status to filter
await table.Query
.Where<User>(x => x.PartitionKey == userId)
.WithFilter<User>(x => x.Status == "active")
.ExecuteAsync();Understanding the Difference:
- Where() - Key condition expression (partition key and sort key only)
- WithFilter() - Filter expression (any property)
See Also:
Error Message:
UnmappedPropertyException: Property 'Email' on type 'User' does not map to a DynamoDB attribute.
Cause:
The property referenced in the expression doesn't have a [DynamoDbAttribute] mapping.
Solution:
Add the [DynamoDbAttribute] to the property:
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
// Add [DynamoDbAttribute] to make it queryable
[DynamoDbAttribute("email")]
public string Email { get; set; } = string.Empty;
}
// Now this works
await table.Query
.Where<User>(x => x.UserId == userId)
.WithFilter<User>(x => x.Email.StartsWith("admin@"))
.ExecuteAsync();See Also:
Error Message:
UnsupportedExpressionException: Method 'ToUpper' cannot be used on entity properties in DynamoDB expressions.
DynamoDB expressions cannot execute C# methods on data.
Cause: Attempting to call a C# method on an entity property. DynamoDB can't execute C# code on stored data.
Solution:
Transform values before the query:
// ❌ Wrong - Can't call ToUpper() on entity property
await table.Query
.WithFilter<User>(x => x.Name.ToUpper() == "JOHN")
.ExecuteAsync();
// ✅ Correct - Transform the comparison value
var upperName = "JOHN";
await table.Query
.WithFilter<User>(x => x.Name == upperName)
.ExecuteAsync();
// ✅ Alternative - Store normalized data
// Add a computed property for case-insensitive queries
[DynamoDbAttribute("name_upper")]
[Computed(nameof(Name))]
public string NameUpper => Name.ToUpper();
// Then query the normalized field
await table.Query
.WithFilter<User>(x => x.NameUpper == "JOHN")
.ExecuteAsync();See Also:
Error Message:
UnsupportedExpressionException: Method 'myFunction' cannot reference the entity parameter or its properties.
DynamoDB expressions cannot execute C# methods with entity data.
Cause: Attempting to pass the entity parameter or its properties to a method call. DynamoDB can't execute your C# methods.
Solution:
Evaluate the method before the query:
// ❌ Wrong - Method references entity parameter
await table.Query
.Where<User>(x => x.Id == ComputeId(x))
.ExecuteAsync();
// ❌ Wrong - Method references entity property
await table.Query
.Where<User>(x => x.Id == ComputeId(x.UserId))
.ExecuteAsync();
// ✅ Correct - Evaluate method with captured values
var userId = GetCurrentUserId();
var computedId = ComputeId(userId);
await table.Query
.Where<User>(x => x.Id == computedId)
.ExecuteAsync();Valid Method Calls: You CAN call methods on captured values (not entity properties):
// ✅ Valid - Method call on captured value
var userId = GetUserId();
await table.Query
.Where<User>(x => x.Id == userId.ToString())
.ExecuteAsync();
// ✅ Valid - Complex expression on captured value
var date = DateTime.Now;
await table.Query
.WithFilter<Order>(x => x.CreatedDate > date.AddDays(-30))
.ExecuteAsync();See Also:
Error Message:
UnsupportedExpressionException: Assignment expressions are not supported in DynamoDB queries.
Use comparison operators (==, <, >, etc.) instead of assignment (=).
Cause: Using assignment operator (=) instead of comparison operator (==).
Solution:
Use comparison operators:
// ❌ Wrong - Assignment operator
await table.Query
.Where<User>(x => x.Id = "user123")
.ExecuteAsync();
// ✅ Correct - Comparison operator
await table.Query
.Where<User>(x => x.Id == "user123")
.ExecuteAsync();See Also:
Error Message:
UnsupportedExpressionException: The operator 'Modulo' is not supported in DynamoDB expressions.
Supported operators: ==, !=, <, >, <=, >=, &&, ||, !
Cause: Using an operator that DynamoDB doesn't support (like modulo %, bitwise operators, etc.).
Solution:
Filter in application code after retrieval:
// ❌ Wrong - Modulo not supported
await table.Query
.WithFilter<User>(x => x.Age % 2 == 0)
.ExecuteAsync();
// ✅ Correct - Filter in application code
var response = await table.Query
.Where<User>(x => x.PartitionKey == pk)
.ExecuteAsync();
var evenAgeUsers = response.Items
.Where(u => u.Age % 2 == 0)
.ToList();Supported Operators:
- Comparison:
==,!=,<,>,<=,>= - Logical:
&&,||,! - DynamoDB functions:
StartsWith(),Contains(),Between(),AttributeExists(),AttributeNotExists(),Size()
See Also:
Error Message:
ExpressionTranslationException: Expression is too complex to translate.
Consider using string-based expressions with Where(string) or WithFilter(string) for complex scenarios.
Cause: The expression is too complex for the translator to handle.
Solution:
Use string-based expressions for complex scenarios:
// ❌ Too complex for expression translator
await table.Query
.WithFilter<User>(x =>
x.Items.Where(i => i.Active).Select(i => i.Price).Sum() > 100)
.ExecuteAsync();
// ✅ Use string-based expression
await table.Query
.WithFilter($"size({UserFields.Items}) > {{0}}", 0)
.ExecuteAsync();
// Then filter in application code
var response = await table.Query
.Where<User>(x => x.PartitionKey == pk)
.ExecuteAsync();
var filtered = response.Items
.Where(u => u.Items.Where(i => i.Active).Sum(i => i.Price) > 100)
.ToList();See Also:
Error Message:
ValidationException: Invalid UpdateExpression: Attribute name is a reserved keyword;
reserved keyword: status
Cause: Using a DynamoDB reserved word without an attribute name placeholder.
Solution:
Use WithAttributeName for reserved words:
// ❌ Wrong - 'status' is a reserved word
await table.Query
.WithKey(UserFields.UserId, UserKeys.Pk("user123"))
.Where($"{UserFields.Status} = {{0}}", "active")
.ExecuteAsync<User>();
// ✅ Correct - use attribute name placeholder
await table.Query
.WithKey(UserFields.UserId, UserKeys.Pk("user123"))
.WithAttributeName("#status", UserFields.Status)
.Where($"#status = {{0}}", "active")
.ExecuteAsync<User>();Common Reserved Words:
status,name,type,data,timestampyear,month,day,hour,minuteorder,date,time,value,key
See Also:
Symptoms:
- Queries taking longer than expected
- High consumed capacity units
- Timeout errors
Cause:
- Scanning instead of querying
- Missing or inefficient indexes
- Large result sets without pagination
- Inefficient filter expressions
Solution:
- Use Query instead of Scan:
// ❌ Slow - scans entire table
await table.Scan
.Where($"{UserFields.Status} = {{0}}", "active")
.ExecuteAsync<User>();
// ✅ Fast - queries with partition key
await table.Query
.WithKey(UserFields.Status, "active") // Requires GSI
.ExecuteAsync<User>();- Add appropriate indexes:
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
// Add GSI for querying by status
[GlobalSecondaryIndex("status-index", IsPartitionKey = true)]
[DynamoDbAttribute("status")]
public string Status { get; set; } = string.Empty;
}- Use pagination:
// ❌ Loads all results at once
var response = await table.Query
.WithKey(UserFields.Status, "active")
.ExecuteAsync<User>();
// ✅ Paginate results
var response = await table.Query
.WithKey(UserFields.Status, "active")
.Take(100) // Limit page size
.ExecuteAsync<User>();
// Process next page
if (response.LastEvaluatedKey != null)
{
var nextPage = await table.Query
.WithKey(UserFields.Status, "active")
.Take(100)
.WithExclusiveStartKey(response.LastEvaluatedKey)
.ExecuteAsync<User>();
}- Use projection expressions:
// ❌ Retrieves all attributes
var response = await table.Query
.WithKey(UserFields.UserId, UserKeys.Pk("user123"))
.ExecuteAsync<User>();
// ✅ Only retrieves needed attributes
var response = await table.Query
.WithKey(UserFields.UserId, UserKeys.Pk("user123"))
.WithProjection($"{UserFields.UserId}, {UserFields.Email}, {UserFields.Name}")
.ExecuteAsync<User>();See Also:
Symptoms:
- Unexpectedly high read/write capacity consumption
- Throttling errors
- High AWS costs
Cause:
- Inefficient queries or scans
- Not using batch operations
- Consistent reads when not needed
- Large items
Solution:
- Use batch operations:
// ❌ Multiple individual requests
foreach (var userId in userIds)
{
await table.Get
.WithKey(UserFields.UserId, UserKeys.Pk(userId))
.ExecuteAsync<User>();
}
// ✅ Single batch request using static entry point
var batch = DynamoDbBatch.Get;
foreach (var userId in userIds)
{
batch.Add(table.Users.Get(userId));
}
var response = await batch.ExecuteAsync();- Use eventually consistent reads:
// ❌ Consistent read (2x capacity)
var response = await table.Get
.WithKey(UserFields.UserId, UserKeys.Pk("user123"))
.WithConsistentRead(true)
.ExecuteAsync<User>();
// ✅ Eventually consistent read (1x capacity)
var response = await table.Get
.WithKey(UserFields.UserId, UserKeys.Pk("user123"))
.ExecuteAsync<User>(); // Consistent read is false by default- Monitor item sizes:
// Check consumed capacity
var response = await table.Put
.WithItem(user)
.WithReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL)
.ExecuteAsync();
Console.WriteLine($"Consumed capacity: {response.ConsumedCapacity.CapacityUnits}");See Also:
Error Message:
error NU1107: Version conflict detected for AWSSDK.DynamoDBv2
Cause: Multiple packages requiring different versions of AWS SDK.
Solution:
- Check installed packages:
dotnet list package --include-transitive | grep AWSSDK- Explicitly specify AWS SDK version:
<ItemGroup>
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.300" />
<PackageReference Include="Oproto.FluentDynamoDb" Version="0.3.0" />
</ItemGroup>- Update all packages:
dotnet add package AWSSDK.DynamoDBv2
dotnet add package Oproto.FluentDynamoDbError Message:
warning IL2026: Using member 'System.Reflection.MethodInfo.Invoke' which has 'RequiresUnreferencedCodeAttribute'
Cause: Using reflection-based features incompatible with AOT compilation.
Solution:
The library is AOT-compatible when using source generation. Ensure you're:
- Using source-generated code:
// ✅ AOT-compatible - uses generated code
var user = await table.Get
.WithKey(UserFields.UserId, UserKeys.Pk("user123"))
.ExecuteAsync<User>();- Avoiding reflection-based patterns:
// ❌ Not AOT-compatible
var propertyInfo = typeof(User).GetProperty("UserId");
propertyInfo.SetValue(user, "user123");
// ✅ AOT-compatible
user.UserId = "user123";See Also:
Error Message:
error CS0246: The type or namespace name 'Amazon' could not be found
Cause: AWS SDK not installed.
Solution:
Install required packages:
dotnet add package AWSSDK.DynamoDBv2
dotnet add package Oproto.FluentDynamoDbNote: The source generator is bundled with the main package.
Verify installation:
dotnet list packageTo inspect generated code:
- Enable source generator output:
Add to your .csproj:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>- View generated files:
After building, check:
obj/Generated/Oproto.FluentDynamoDb.SourceGenerator/
- Add generated files to source control (optional):
<ItemGroup>
<Compile Include="$(CompilerGeneratedFilesOutputPath)/**/*.cs" Visible="true" />
</ItemGroup>For local development and testing:
- Install DynamoDB Local:
docker run -p 8000:8000 amazon/dynamodb-local- Configure client for local endpoint:
var config = new AmazonDynamoDBConfig
{
ServiceURL = "http://localhost:8000"
};
var client = new AmazonDynamoDBClient(config);
var table = new DynamoDbTableBase(client, "users");- Create test tables:
aws dynamodb create-table \
--table-name users \
--attribute-definitions AttributeName=pk,AttributeType=S \
--key-schema AttributeName=pk,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--endpoint-url http://localhost:8000Problem: Null values causing issues in queries or updates.
Solution:
- Use nullable types:
[DynamoDbAttribute("middle_name")]
public string? MiddleName { get; set; }- Check for null before operations:
if (user.MiddleName != null)
{
await table.Update
.WithKey(UserFields.UserId, UserKeys.Pk(user.UserId))
.Set($"SET {UserFields.MiddleName} = {{0}}", user.MiddleName)
.ExecuteAsync();
}- Use attribute_exists for conditional operations:
await table.Update
.WithKey(UserFields.UserId, UserKeys.Pk("user123"))
.Set($"SET {UserFields.Name} = {{0}}", "New Name")
.WithCondition($"attribute_exists({UserFields.UserId})")
.ExecuteAsync();Problem: Serializing complex objects to DynamoDB.
Solution:
DynamoDB supports nested objects (Maps):
public class Address
{
public string Street { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string ZipCode { get; set; } = string.Empty;
}
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
// Complex type stored as DynamoDB Map
[DynamoDbAttribute("address")]
public Address Address { get; set; } = new();
// List of complex types
[DynamoDbAttribute("previous_addresses")]
public List<Address> PreviousAddresses { get; set; } = new();
}Symptoms
- After awaiting
GetItemAsync,ToListAsync,PutAsync, etc.,DynamoDbOperationContext.Currentis unexpectedlynull - FluentAssertions or xUnit assertions verifying context metadata fail
Cause
Unit test frameworks (e.g., xUnit) restore the original execution context when an awaited task completes. Because DynamoDbOperationContext uses AsyncLocal, the value assigned inside the library is lost once the framework resumes the test method.
Solution
Subscribe to the internal diagnostics event before invoking the operation and capture the context inside the same asynchronous flow. Remember to unsubscribe in a finally block.
using Oproto.FluentDynamoDb.Context;
OperationContextData? captured = null;
void Handler(OperationContextData? ctx) => captured = ctx;
DynamoDbOperationContextDiagnostics.ContextAssigned += Handler;
try
{
await builder.ToListAsync<MyEntity>();
captured.Should().NotBeNull();
captured!.RawItems.Should().NotBeNull();
}
finally
{
DynamoDbOperationContextDiagnostics.ContextAssigned -= Handler;
}Warning
DynamoDbOperationContextDiagnosticsis intended for diagnostics and test scenarios only. Production code should continue to read metadata fromDynamoDbOperationContext.Current.
If you're still experiencing issues:
-
Check existing issues:
-
Create a minimal reproduction:
- Isolate the problem
- Create a small, complete example
- Include error messages and stack traces
-
Provide context:
- Library version
- .NET version
- AWS SDK version
- Operating system
- IDE and version
-
Review documentation: