| title | Attribute Reference | ||||||||
|---|---|---|---|---|---|---|---|---|---|
| category | reference | ||||||||
| order | 1 | ||||||||
| keywords |
|
||||||||
| related |
|
Documentation > Reference > Attribute Reference
This reference guide documents all attributes used to define DynamoDB entities with source generation. Each attribute configures how the source generator creates mapping code, field constants, and key builders for your entities.
Oproto.FluentDynamoDb uses attributes to define entity metadata at compile time. The source generator reads these attributes and generates:
- Field constants: Type-safe field name references
- Key builders: Methods to construct partition and sort keys
- Mapper code: Serialization and deserialization logic
Marks a class as a DynamoDB entity and specifies the table name.
This attribute is required on every entity class. It tells the source generator that this class represents a DynamoDB entity and specifies which table it maps to.
| Parameter | Type | Required | Description |
|---|---|---|---|
tableName |
string |
Yes | The DynamoDB table name |
| Property | Type | Default | Description |
|---|---|---|---|
Namespace |
string? |
null |
Custom namespace for the generated table class. If null, uses the entity's namespace. |
EntityDiscriminator |
string? |
null |
[Obsolete] Legacy discriminator (use DiscriminatorProperty/Value instead) |
DiscriminatorProperty |
string? |
null |
DynamoDB attribute name containing the discriminator (e.g., "entity_type", "SK", "PK") |
DiscriminatorValue |
string? |
null |
Exact value to match for this entity type |
DiscriminatorPattern |
string? |
null |
Pattern to match with wildcard support (e.g., "USER#*") |
The discriminator system supports flexible entity type identification for single-table designs:
Use a dedicated attribute to store entity type:
[DynamoDbTable("entities",
DiscriminatorProperty = "entity_type",
DiscriminatorValue = "USER")]
public partial class User { }
[DynamoDbTable("entities",
DiscriminatorProperty = "entity_type",
DiscriminatorValue = "ORDER")]
public partial class Order { }DynamoDB Item:
{
"pk": "USER#123",
"sk": "METADATA",
"entity_type": "USER",
"name": "John"
}Use sort key prefixes to identify entity types:
[DynamoDbTable("entities",
DiscriminatorProperty = "SK",
DiscriminatorPattern = "USER#*")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string TenantId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")]
public string SortKey { get; set; } = string.Empty;
}DynamoDB Item:
{
"pk": "TENANT#abc",
"sk": "USER#123",
"name": "John"
}Discriminator patterns support wildcard matching:
| Pattern | Strategy | Matches | Example |
|---|---|---|---|
USER#* |
StartsWith | Starts with "USER#" | USER#123, USER#abc |
*#USER |
EndsWith | Ends with "#USER" | TENANT#abc#USER |
*#USER#* |
Contains | Contains "#USER#" | TENANT#abc#USER#123 |
USER |
ExactMatch | Exact match only | USER |
Use partition key for entity type identification:
[DynamoDbTable("entities",
DiscriminatorProperty = "PK",
DiscriminatorPattern = "USER#*")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
}// Basic usage
[DynamoDbTable("users")]
public partial class User
{
// Properties...
}
// Custom namespace for generated table class
[DynamoDbTable("users", Namespace = "MyApp.Data.Tables")]
public partial class User
{
// Entity stays in its declared namespace
// Generated UsersTable class is in MyApp.Data.Tables namespace
}
// Attribute-based discriminator
[DynamoDbTable("entities",
DiscriminatorProperty = "entity_type",
DiscriminatorValue = "USER")]
public partial class User
{
// Properties...
}
// Sort key pattern discriminator
[DynamoDbTable("entities",
DiscriminatorProperty = "SK",
DiscriminatorPattern = "USER#*")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string TenantId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")]
public string SortKey { get; set; } = string.Empty;
}
// Legacy (deprecated but still supported)
[DynamoDbTable("entities", EntityDiscriminator = "USER")]
public partial class LegacyUser
{
// Equivalent to:
// DiscriminatorProperty = "entity_type"
// DiscriminatorValue = "USER"
}DiscriminatorValueandDiscriminatorPatternare mutually exclusive- If both are specified,
DiscriminatorValuetakes precedence and a warning is emitted DiscriminatorValueorDiscriminatorPatternrequireDiscriminatorPropertyto be set- If no discriminator is configured, no validation is performed
- Discriminator validation occurs during entity hydration from DynamoDB
- If validation fails, a
DiscriminatorMismatchExceptionis thrown with expected and actual values - Discriminator properties are automatically included in projection expressions
- Pattern matching is optimized at compile-time for performance
Maps a property to a DynamoDB attribute with a specific name and optional formatting.
This attribute defines the mapping between a C# property and a DynamoDB attribute name. It's required for every property you want to persist in DynamoDB. Optionally, you can specify a format string to control how values are serialized when used in LINQ expressions.
| Parameter | Type | Required | Description |
|---|---|---|---|
attributeName |
string |
Yes | The DynamoDB attribute name |
| Property | Type | Default | Description |
|---|---|---|---|
Format |
string? |
null |
Format string applied when property is used in LINQ expressions |
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
[DynamoDbAttribute("email")]
public string Email { get; set; } = string.Empty;
// Format applied in LINQ expressions
[DynamoDbAttribute("created_at", Format = "o")]
public DateTime CreatedAt { get; set; }
// Property name matches attribute name
[DynamoDbAttribute("status")]
public string Status { get; set; } = string.Empty;
}The Format property specifies how values should be formatted when the property is used in LINQ expressions. This ensures consistent formatting across all queries without repeating format specifiers.
DateTime Formats:
[DynamoDbAttribute("created_at", Format = "o")] // ISO 8601 round-trip
public DateTime CreatedAt { get; set; }
[DynamoDbAttribute("date_key", Format = "yyyy-MM-dd")] // Date only
public DateTime DateKey { get; set; }
[DynamoDbAttribute("month_key", Format = "yyyy-MM")] // Year-month
public DateTime MonthKey { get; set; }Numeric Formats:
[DynamoDbAttribute("price", Format = "F2")] // Two decimal places
public decimal Price { get; set; }
[DynamoDbAttribute("sequence", Format = "D10")] // Zero-padded to 10 digits
public int Sequence { get; set; }
[DynamoDbAttribute("weight", Format = "F4")] // Four decimal places
public double Weight { get; set; }When you use a property with a Format in a LINQ expression, the format is automatically applied:
// Format "o" is automatically applied to CreatedAt
var users = await table.Query<User>()
.Where(x => x.PartitionKey == userId && x.CreatedAt > DateTime.UtcNow.AddDays(-30))
.ToListAsync();
// Generates: created_at > "2024-01-15T10:30:00.0000000Z"
// Format "F2" is automatically applied to Price
var products = await table.Query<Product>()
.Where(x => x.Category == "Electronics" && x.Price > 99.99m)
.ToListAsync();
// Generates: price > "99.99"The Format property is applied:
- ✅ In LINQ expressions (
Where<T>(),WithFilter<T>()) - ✅ In condition expressions (
WithCondition<T>()) - ❌ NOT in string-based expressions (use format specifiers instead)
- ❌ NOT during serialization/deserialization (only in query expressions)
// Using Format property (recommended for consistency)
[DynamoDbAttribute("created_at", Format = "o")]
public DateTime CreatedAt { get; set; }
// LINQ expression - format applied automatically
table.Query<User>().Where(x => x.CreatedAt > date)
// String expression - use format specifier
table.Query().Where($"{UserFields.CreatedAt} > {{0:o}}", date)- Use consistent naming conventions (snake_case or camelCase)
- Keep attribute names short to reduce storage costs
- Use abbreviated names for frequently accessed attributes (e.g., "pk" for partition key)
- Use Format property for consistent formatting in LINQ expressions
- Use ISO 8601 formats (
"o"or"s") for sortable DateTime values - Use fixed decimal places (
"F2") for monetary values - Use zero-padding (
"D10") for sortable numeric keys
Marks a property as the partition key for a DynamoDB table.
Identifies the property that serves as the partition key (also called hash key). Every DynamoDB table requires exactly one partition key. The source generator uses this to create key builder methods.
None (constructor has no parameters)
| Property | Type | Default | Description |
|---|---|---|---|
Prefix |
string? |
null |
Optional prefix added to the partition key value |
Separator |
string? |
"#" |
Separator used when combining key components |
[DynamoDbTable("users")]
public partial class User
{
// Simple partition key
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
}
// With prefix for key namespacing
[DynamoDbTable("entities")]
public partial class User
{
[PartitionKey(Prefix = "USER")]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
// Stored in DynamoDB as: "USER#user123"
}
// Custom separator
[DynamoDbTable("entities")]
public partial class Product
{
[PartitionKey(Prefix = "PRODUCT", Separator = "|")]
[DynamoDbAttribute("pk")]
public string ProductId { get; set; } = string.Empty;
// Stored in DynamoDB as: "PRODUCT|prod456"
}The source generator creates a key builder method:
// Generated for User class
public static class UserKeys
{
public static string Pk(string userId) => $"USER#{userId}";
}
// Usage
var key = UserKeys.Pk("user123"); // Returns "USER#user123"Marks a property as the sort key (range key) for a DynamoDB table.
Identifies the property that serves as the sort key. Sort keys are optional but enable range queries and allow multiple items with the same partition key. The source generator creates key builder methods for sort keys.
None (constructor has no parameters)
| Property | Type | Default | Description |
|---|---|---|---|
Prefix |
string? |
null |
Optional prefix added to the sort key value |
Separator |
string? |
"#" |
Separator used when combining key components |
[DynamoDbTable("orders")]
public partial class Order
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string CustomerId { get; set; } = string.Empty;
// Simple sort key
[SortKey]
[DynamoDbAttribute("sk")]
public string OrderId { get; set; } = string.Empty;
}
// With prefix for composite entities
[DynamoDbTable("entities")]
public partial class OrderItem
{
[PartitionKey(Prefix = "ORDER")]
[DynamoDbAttribute("pk")]
public string OrderId { get; set; } = string.Empty;
[SortKey(Prefix = "ITEM")]
[DynamoDbAttribute("sk")]
public string ItemId { get; set; } = string.Empty;
// Stored as: pk="ORDER#order123", sk="ITEM#item456"
}
// Timestamp-based sort key
[DynamoDbTable("events")]
public partial class Event
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
[SortKey(Prefix = "EVENT")]
[DynamoDbAttribute("sk")]
public string Timestamp { get; set; } = string.Empty;
}// Generated for OrderItem class
public static class OrderItemKeys
{
public static string Pk(string orderId) => $"ORDER#{orderId}";
public static string Sk(string itemId) => $"ITEM#{itemId}";
}
// Usage
var partitionKey = OrderItemKeys.Pk("order123");
var sortKey = OrderItemKeys.Sk("item456");Marks a property as the partition key for a Global Secondary Index (GSI).
Defines properties that serve as the partition key for Global Secondary Indexes, enabling alternative query patterns. The key role is encoded directly in the attribute name, eliminating the need for boolean flags. The source generator creates GSI-specific field constants and key builders.
| Parameter | Type | Required | Description |
|---|---|---|---|
indexName |
string |
Yes | The name of the Global Secondary Index |
| Property | Type | Default | Description |
|---|---|---|---|
Name |
string? |
null |
Custom C# property name for the generated index accessor |
ProjectionType |
ProjectionType |
All |
DynamoDB projection type for the index |
DiscriminatorProperty |
string? |
null |
GSI-specific discriminator property (overrides table-level discriminator) |
DiscriminatorValue |
string? |
null |
GSI-specific discriminator exact value |
DiscriminatorPattern |
string? |
null |
GSI-specific discriminator pattern with wildcard support |
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")]
public string Username { get; set; } = string.Empty;
// GSI for querying by email
[GsiPartitionKey("email-index")]
[DynamoDbAttribute("email")]
public string Email { get; set; } = string.Empty;
// GSI for querying by status and created date
[GsiPartitionKey("status-index")]
[DynamoDbAttribute("status")]
public string Status { get; set; } = string.Empty;
[GsiSortKey("status-index")]
[DynamoDbAttribute("created_at")]
public DateTime CreatedAt { get; set; }
}
// Composite GSI key with format
[DynamoDbTable("products")]
public partial class Product
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string ProductId { get; set; } = string.Empty;
[DynamoDbAttribute("category")]
public string Category { get; set; } = string.Empty;
[DynamoDbAttribute("subcategory")]
public string Subcategory { get; set; } = string.Empty;
// Composite GSI key: "Electronics#Laptops"
[GsiPartitionKey("category-index")]
[Computed(nameof(Category), nameof(Subcategory))]
[DynamoDbAttribute("category_sk")]
public string CategoryKey { get; set; } = string.Empty;
}
// GSI-specific discriminator
[DynamoDbTable("entities",
DiscriminatorProperty = "SK",
DiscriminatorPattern = "USER#*")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string TenantId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")]
public string SortKey { get; set; } = string.Empty;
// GSI uses different discriminator pattern
[GsiPartitionKey("StatusIndex",
DiscriminatorProperty = "GSI1SK",
DiscriminatorPattern = "USER#*")]
[DynamoDbAttribute("status")]
public string Status { get; set; } = string.Empty;
[GsiSortKey("StatusIndex")]
[DynamoDbAttribute("gsi1sk")]
public string StatusSortKey { get; set; } = string.Empty;
}// Generated GSI field constants
public static class UserFields
{
public const string Email = "email";
public const string Status = "status";
public const string CreatedAt = "created_at";
}
// Generated GSI metadata
public static class UserIndexes
{
public const string EmailIndex = "email-index";
public const string StatusIndex = "status-index";
}Marks a property as the sort key for a Global Secondary Index (GSI).
Defines properties that serve as the sort key for Global Secondary Indexes. The key role is encoded directly in the attribute name. When both [GsiPartitionKey] and [GsiSortKey] specify Name or ProjectionType for the same index, the [GsiPartitionKey] values take precedence.
| Parameter | Type | Required | Description |
|---|---|---|---|
indexName |
string |
Yes | The name of the Global Secondary Index |
| Property | Type | Default | Description |
|---|---|---|---|
Name |
string? |
null |
Custom C# property name for the generated index accessor (fallback if [GsiPartitionKey] doesn't specify) |
ProjectionType |
ProjectionType |
All |
DynamoDB projection type (fallback if [GsiPartitionKey] doesn't specify) |
[DynamoDbTable("orders")]
public partial class Order
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string OrderId { get; set; } = string.Empty;
[GsiPartitionKey("status-index")]
[DynamoDbAttribute("status")]
public string Status { get; set; } = string.Empty;
[GsiSortKey("status-index")]
[DynamoDbAttribute("created_at")]
public DateTime CreatedAt { get; set; }
}Marks a property as the sort key for a Local Secondary Index (LSI).
Identifies the property that serves as the sort key for a Local Secondary Index. LSIs share the same partition key as the base table but have a different sort key, enabling alternative query patterns without the cost of a GSI. The index type and key role are both immediately clear from the attribute name.
| Feature | Local Secondary Index | Global Secondary Index |
|---|---|---|
| Partition Key | Same as base table | Can be different |
| Sort Key | Different from base table | Can be different |
| Created | At table creation only | Anytime |
| Consistency | Strong or eventual | Eventual only |
| Throughput | Shares with base table | Independent |
| Parameter | Type | Required | Description |
|---|---|---|---|
indexName |
string |
Yes | The name of the Local Secondary Index |
| Property | Type | Default | Description |
|---|---|---|---|
Name |
string? |
null |
Custom C# property name for the generated index accessor |
ProjectionType |
ProjectionType |
All |
DynamoDB projection type for the index |
[DynamoDbTable("orders")]
public partial class Order
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string CustomerId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")]
public string OrderId { get; set; } = string.Empty;
// LSI for querying orders by date within a customer
[LsiSortKey("orders-by-date")]
[DynamoDbAttribute("order_date")]
public string OrderDate { get; set; } = string.Empty;
// Another LSI for querying by status
[LsiSortKey("orders-by-status")]
[DynamoDbAttribute("status")]
public string Status { get; set; } = string.Empty;
[DynamoDbAttribute("total")]
public decimal Total { get; set; }
}The source generator creates IndexMetadata with IndexType = LocalSecondaryIndex:
// Generated index metadata
new IndexMetadata
{
IndexName = "orders-by-date",
IndexType = IndexType.LocalSecondaryIndex,
PartitionKeyAttributeName = "pk", // Same as base table
SortKeyAttributeName = "order_date",
SortKeyAttributeType = "S"
}LSIs are validated during schema validation:
var result = await OrdersTable.ValidateSchemaAsync(client);
// Validation checks:
// - LSI exists on the table
// - Sort key attribute name matches
// - Sort key attribute type matches- LSIs can only be created when the table is created (cannot be added later)
- LSIs share read/write capacity with the base table
- Maximum of 5 LSIs per table
- LSIs support both strong and eventual consistency
- The partition key is always the same as the base table (not specified in the attribute)
Specifies that a property value should be computed from other properties before mapping to DynamoDB.
Creates composite keys or derived values by combining multiple source properties. This is essential for single-table design patterns where you need to construct keys from multiple entity attributes.
| Parameter | Type | Required | Description |
|---|---|---|---|
sourceProperties |
params string[] |
Yes | Names of source properties to combine |
| Property | Type | Default | Description |
|---|---|---|---|
Format |
string? |
null |
Format string for combining values (e.g., "{0}#{1}") |
Separator |
string |
"#" |
Separator when no format is specified |
[DynamoDbTable("entities")]
public partial class Order
{
[DynamoDbAttribute("customer_id")]
public string CustomerId { get; set; } = string.Empty;
[DynamoDbAttribute("order_id")]
public string OrderId { get; set; } = string.Empty;
// Computed partition key: "CUSTOMER#cust123"
[PartitionKey]
[Computed(nameof(CustomerId), Format = "CUSTOMER#{0}")]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
// Computed sort key: "ORDER#order456"
[SortKey]
[Computed(nameof(OrderId), Format = "ORDER#{0}")]
[DynamoDbAttribute("sk")]
public string SortKey { get; set; } = string.Empty;
}
// Multiple source properties with custom format
[DynamoDbTable("products")]
public partial class Product
{
[DynamoDbAttribute("category")]
public string Category { get; set; } = string.Empty;
[DynamoDbAttribute("brand")]
public string Brand { get; set; } = string.Empty;
[DynamoDbAttribute("sku")]
public string Sku { get; set; } = string.Empty;
// Computed key: "Electronics#Sony#SKU12345"
[PartitionKey]
[Computed(nameof(Category), nameof(Brand), nameof(Sku), Format = "{0}#{1}#{2}")]
[DynamoDbAttribute("pk")]
public string CompositeKey { get; set; } = string.Empty;
}
// Using default separator
[DynamoDbTable("users")]
public partial class User
{
[DynamoDbAttribute("first_name")]
public string FirstName { get; set; } = string.Empty;
[DynamoDbAttribute("last_name")]
public string LastName { get; set; } = string.Empty;
// Computed: "John#Doe" (using default # separator)
[Computed(nameof(FirstName), nameof(LastName))]
[DynamoDbAttribute("full_name_key")]
public string FullNameKey { get; set; } = string.Empty;
}- Computed properties are calculated before saving to DynamoDB
- The source generator creates code to automatically populate computed properties
- Format strings use standard .NET string formatting (
{0},{1}, etc.) - If no format is specified, properties are joined with the separator
Specifies that a property value should be extracted from a composite key property after mapping from DynamoDB.
Extracts individual components from composite keys when reading from DynamoDB. This is the inverse of [Computed] - while [Computed] combines values before saving, [Extracted] splits values after loading.
| Parameter | Type | Required | Description |
|---|---|---|---|
sourceProperty |
string |
Yes | Name of the property containing the composite key |
index |
int |
Yes | Zero-based index of the component to extract |
| Property | Type | Default | Description |
|---|---|---|---|
Separator |
string |
"#" |
Separator used to split the composite key |
[DynamoDbTable("entities")]
public partial class Order
{
// Composite partition key stored as "CUSTOMER#cust123"
[PartitionKey]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
// Extract customer ID from partition key
[Extracted(nameof(PartitionKey), 1)]
public string CustomerId { get; set; } = string.Empty;
// Composite sort key stored as "ORDER#order456#2024-01-15"
[SortKey]
[DynamoDbAttribute("sk")]
public string SortKey { get; set; } = string.Empty;
// Extract order ID (index 1)
[Extracted(nameof(SortKey), 1)]
public string OrderId { get; set; } = string.Empty;
// Extract date (index 2)
[Extracted(nameof(SortKey), 2)]
public string OrderDate { get; set; } = string.Empty;
}
// Custom separator
[DynamoDbTable("products")]
public partial class Product
{
// Stored as "Electronics|Laptops|Dell"
[PartitionKey]
[DynamoDbAttribute("pk")]
public string CompositeKey { get; set; } = string.Empty;
[Extracted(nameof(CompositeKey), 0, Separator = "|")]
public string Category { get; set; } = string.Empty;
[Extracted(nameof(CompositeKey), 1, Separator = "|")]
public string Subcategory { get; set; } = string.Empty;
[Extracted(nameof(CompositeKey), 2, Separator = "|")]
public string Brand { get; set; } = string.Empty;
}- Extracted properties are populated after loading from DynamoDB
- The source generator creates code to automatically split and extract values
- Index is zero-based (0 = first component, 1 = second, etc.)
- If the source property doesn't contain enough components, the extracted property will be empty
[DynamoDbTable("orders")]
public partial class Order
{
// Source properties
[DynamoDbAttribute("customer_id")]
public string CustomerId { get; set; } = string.Empty;
[DynamoDbAttribute("order_id")]
public string OrderId { get; set; } = string.Empty;
// Computed when saving: "CUSTOMER#cust123"
[PartitionKey]
[Computed(nameof(CustomerId), Format = "CUSTOMER#{0}")]
[DynamoDbAttribute("pk")]
public string PartitionKey { get; set; } = string.Empty;
// Alternative: Extract when loading (if you don't store customer_id separately)
// [Extracted(nameof(PartitionKey), 1)]
// public string CustomerId { get; set; } = string.Empty;
}Marks a property as a related entity that should be automatically populated based on sort key patterns when querying.
Enables automatic population of related entities in composite entity patterns. When you query for a parent entity, related entities matching the sort key pattern are automatically loaded into the specified property.
| Parameter | Type | Required | Description |
|---|---|---|---|
sortKeyPattern |
string |
Yes | Sort key pattern to match (supports wildcards like "audit#*") |
| Property | Type | Default | Description |
|---|---|---|---|
EntityType |
Type? |
null |
Type of the related entity (defaults to property type) |
// Parent entity
[DynamoDbTable("orders")]
public partial class Order
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string OrderId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")]
public string SortKey { get; set; } = string.Empty;
[DynamoDbAttribute("total")]
public decimal Total { get; set; }
// Related entities with pattern "ITEM#*"
[RelatedEntity("ITEM#*")]
public List<OrderItem> Items { get; set; } = new();
// Single related entity with exact match
[RelatedEntity("SUMMARY")]
public OrderSummary? Summary { get; set; }
// Related audit records
[RelatedEntity("AUDIT#*")]
public List<AuditRecord> AuditRecords { get; set; } = new();
}
// Related entity
[DynamoDbTable("orders")]
public partial class OrderItem
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string OrderId { get; set; } = string.Empty;
[SortKey(Prefix = "ITEM")]
[DynamoDbAttribute("sk")]
public string ItemId { get; set; } = string.Empty;
[DynamoDbAttribute("product_name")]
public string ProductName { get; set; } = string.Empty;
[DynamoDbAttribute("quantity")]
public int Quantity { get; set; }
}
// Usage
var order = await table.Query
.WithKey(OrderFields.OrderId, OrderKeys.Pk("order123"))
.ToCompositeEntityAsync();
// order.Items is automatically populated with all items matching "ITEM#*"
// order.Summary is populated if an item with sk="SUMMARY" exists| Pattern | Description | Example Matches |
|---|---|---|
"ITEM#*" |
Wildcard match | ITEM#1, ITEM#2, ITEM#abc |
"SUMMARY" |
Exact match | SUMMARY only |
"AUDIT#*" |
Prefix match | AUDIT#2024-01-15, AUDIT#log1 |
"*" |
Match all | Any sort key value |
- Related entities are populated when using
ToCompositeEntityAsync<T>() - Wildcard patterns (
*) match multiple items and populate collections - Exact patterns match single items and populate single properties
- If no matching items are found, collections are empty and single properties are null
- The source generator creates the mapping logic automatically
Marks a property as containing sensitive data that should be redacted from logs.
Indicates that a property contains sensitive information (PII, credentials, etc.) that should not appear in log output. When logging is enabled, values of sensitive properties are replaced with [REDACTED] in all log messages.
None (constructor has no parameters)
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string UserId { get; set; } = string.Empty;
[DynamoDbAttribute("name")]
public string Name { get; set; } = string.Empty;
// Redacted from logs
[DynamoDbAttribute("email")]
[Sensitive]
public string Email { get; set; } = string.Empty;
// Redacted from logs
[DynamoDbAttribute("ssn")]
[Sensitive]
public string SocialSecurityNumber { get; set; } = string.Empty;
// Redacted from logs
[DynamoDbAttribute("phone")]
[Sensitive]
public string PhoneNumber { get; set; } = string.Empty;
}When logging is enabled, sensitive property values are redacted:
// Log output:
// Query: pk = :p0 AND email = [REDACTED]
// Parameters: { :p0 = "USER#123" }
// Note: email value is redacted, but parameter name is preservedThe [Sensitive] attribute affects:
- LINQ expression logging (query and filter expressions)
- String-based expression logging
- Query parameter logging
- Put/Update operation logging
- Error messages containing entity data
- All diagnostic output from
IDynamoDbLogger
- Property names are preserved in logs for debugging
- Only values are redacted
- Redaction applies to all log levels when logging is enabled
- No performance impact when logging is disabled
- Commonly combined with
[Encrypted]for maximum protection
Marks an entity class as requiring write operations within a transaction.
Enforces transactional consistency for entities where atomic operations are required. When applied, Put, Update, Delete, and BatchWrite operations throw InvalidOperationException unless performed within a TransactWrite operation.
This is useful for:
- Financial transactions requiring atomic updates
- Inventory management with consistency requirements
- Multi-entity operations that must succeed or fail together
- Audit-critical data that requires transactional guarantees
None (marker attribute)
[DynamoDbTable("FinancialTransactions")]
[RequireWriteTransaction]
public partial class Transaction
{
[PartitionKey]
[DynamoDbAttribute("pk")]
public string AccountId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")]
public string TransactionId { get; set; } = string.Empty;
[DynamoDbAttribute("amount")]
public decimal Amount { get; set; }
}
// ❌ This throws InvalidOperationException:
await table.Transactions.Put(transaction).PutAsync();
// Error: "Entity 'Transaction' is marked with [RequireWriteTransaction] and cannot be
// modified outside of a transaction. Use DynamoDbTransactions.Write() to perform this operation."
// ❌ This also throws:
await table.Transactions.Update(accountId, transactionId)
.Set(x => new TransactionUpdateModel { Amount = 100m })
.UpdateAsync();
// ❌ BatchWrite throws:
await DynamoDbBatch.Write()
.Put(table.Transactions, transaction)
.ExecuteAsync();
// ✅ TransactWrite is allowed:
await DynamoDbTransactions.Write()
.Put(table.Transactions, transaction)
.ExecuteAsync();
// ✅ Multiple operations in a transaction:
await DynamoDbTransactions.Write()
.Put(table.Transactions, debitTransaction)
.Put(table.Transactions, creditTransaction)
.Update(table.Accounts, accountId)
.Set(x => new AccountUpdateModel { Balance = newBalance })
.ExecuteAsync();- Validation occurs at execution time (
PutAsync(),UpdateAsync(),DeleteAsync(),ExecuteAsync()) - The
RequiresWriteTransactionproperty is generated on the entity class EntityMetadata.RequiresWriteTransactionis set for tooling support- No runtime reflection - uses static interface members for AOT compatibility
| Operation | Behavior |
|---|---|
Put().PutAsync() |
Throws InvalidOperationException |
Update().UpdateAsync() |
Throws InvalidOperationException |
Delete().DeleteAsync() |
Throws InvalidOperationException |
BatchWrite().ExecuteAsync() |
Throws InvalidOperationException |
TransactWrite().ExecuteAsync() |
✅ Allowed |
⚠️ Deprecation Notice (v0.9.0): The[Queryable]attribute is deprecated. Query capabilities are now automatically derived from[PartitionKey]and[SortKey]attributes. This attribute will be removed in v1.0.Migration: Remove
[Queryable]attributes from your entities. The source generator automatically determines supported operations:
- Partition Key: Supports
Equalsoperation- Sort Key: Supports
Equals,BeginsWith,Between,GreaterThan,LessThanoperationsUsing this attribute will emit compiler warning
DYNDB103.
Marks a property as queryable and specifies the supported operations and indexes.
Provides metadata about which DynamoDB operations are supported for a property and which indexes it's available in. This attribute is primarily used for future LINQ expression support and documentation purposes.
None (constructor has no parameters)
| Property | Type | Default | Description |
|---|---|---|---|
SupportedOperations |
DynamoDbOperation[] |
[] |
Array of supported DynamoDB operations |
AvailableInIndexes |
string[]? |
null |
Indexes where this property is available for querying |
The DynamoDbOperation enum defines these operations:
| Operation | Description | DynamoDB Operator |
|---|---|---|
Equals |
Equality comparison | = |
BeginsWith |
String prefix match | begins_with() |
Between |
Range comparison | BETWEEN |
GreaterThan |
Greater than | > |
LessThan |
Less than | < |
Contains |
Set/string contains | contains() |
In |
Multiple value match | IN |
[DynamoDbTable("users")]
public partial class User
{
[PartitionKey]
[DynamoDbAttribute("pk")]
[Queryable(SupportedOperations = new[] { DynamoDbOperation.Equals })]
public string UserId { get; set; } = string.Empty;
[SortKey]
[DynamoDbAttribute("sk")]
[Queryable(SupportedOperations = new[] {
DynamoDbOperation.Equals,
DynamoDbOperation.BeginsWith,
DynamoDbOperation.Between
})]
public string Username { get; set; } = string.Empty;
[GsiPartitionKey("email-index")]
[DynamoDbAttribute("email")]
[Queryable(
SupportedOperations = new[] { DynamoDbOperation.Equals },
AvailableInIndexes = new[] { "email-index" }
)]
public string Email { get; set; } = string.Empty;
[GsiPartitionKey("status-index")]
[DynamoDbAttribute("status")]
[Queryable(
SupportedOperations = new[] { DynamoDbOperation.Equals, DynamoDbOperation.In },
AvailableInIndexes = new[] { "status-index" }
)]
public string Status { get; set; } = string.Empty;
[DynamoDbAttribute("tags")]
[Queryable(SupportedOperations = new[] { DynamoDbOperation.Contains })]
public List<string> Tags { get; set; } = new();
}This attribute is currently informational and used for:
- Documentation: Clearly indicates which operations are valid for each property
- Future LINQ Support: Metadata for potential LINQ-to-DynamoDB query translation
- Code Generation: May be used by future source generator enhancements
- Document the intended query patterns for your entities
- Specify operations that make sense for the property type
- List all indexes where the property can be queried
- Use this to communicate query capabilities to other developers
Marks a property as a Time-To-Live (TTL) field for automatic item expiration.
Enables DynamoDB's TTL feature, which automatically deletes items after a specified time. The property value is stored as Unix epoch seconds (number of seconds since January 1, 1970 UTC).
None
DateTimeorDateTime?DateTimeOffsetorDateTimeOffset?
[DynamoDbTable("sessions")]
public partial class Session
{
[DynamoDbAttribute("session_id")]
public string SessionId { get; set; }
[DynamoDbAttribute("ttl")]
[TimeToLive]
public DateTime? ExpiresAt { get; set; }
}
// Usage
var session = new Session
{
SessionId = "sess-123",
ExpiresAt = DateTime.UtcNow.AddHours(1) // Expires in 1 hour
};- Only ONE TTL field is allowed per entity (DYNDB105 error if violated)
- Must enable TTL on the table:
aws dynamodb update-time-to-live --table-name TABLE --time-to-live-specification "Enabled=true, AttributeName=ttl" - DynamoDB typically deletes expired items within 48 hours
- Use UTC times to avoid timezone issues
- Cannot be combined with
[JsonBlob]or[BlobReference](DYNDB104 error)
Marks a property for explicit conversion to a DynamoDB Map (M) type.
Converts custom objects to nested DynamoDB Map structures. The nested type must be marked with [DynamoDbEntity] to generate required mapping code, ensuring AOT compatibility without reflection.
None
// Nested type MUST have [DynamoDbEntity]
[DynamoDbEntity]
public partial class Address
{
[DynamoDbAttribute("street")]
public string Street { get; set; }
[DynamoDbAttribute("city")]
public string City { get; set; }
}
[DynamoDbTable("customers")]
public partial class Customer
{
[DynamoDbAttribute("pk")]
public string CustomerId { get; set; }
[DynamoDbAttribute("address")]
[DynamoDbMap]
public Address ShippingAddress { get; set; }
}- Nested type MUST be marked with
[DynamoDbEntity](DYNDB107 error if missing) - Uses compile-time generated methods instead of reflection for AOT compatibility
- Nested types can themselves contain maps, creating deep hierarchies
Dictionary<string, string>andDictionary<string, AttributeValue>don't need this attribute
Marks a property for JSON serialization before storing in DynamoDB.
Serializes complex objects to JSON strings for storage in DynamoDB string attributes. Supports both System.Text.Json (AOT-compatible) and Newtonsoft.Json via runtime configuration.
None
| Property | Type | Default | Description |
|---|---|---|---|
InlineThreshold |
int? |
null |
Maximum size (bytes) before using external blob storage |
JSON serialization is configured at runtime via FluentDynamoDbOptions:
// Using System.Text.Json (recommended for AOT)
var options = new FluentDynamoDbOptions()
.WithSystemTextJson();
// With custom options
var options = new FluentDynamoDbOptions()
.WithSystemTextJson(new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false
});
// For AOT scenarios with JsonSerializerContext
var options = new FluentDynamoDbOptions()
.WithSystemTextJson(MyJsonContext.Default);
// Using Newtonsoft.Json
var options = new FluentDynamoDbOptions()
.WithNewtonsoftJson();
// With custom settings
var options = new FluentDynamoDbOptions()
.WithNewtonsoftJson(new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
});public class DocumentContent
{
public string Title { get; set; }
public string Body { get; set; }
public Dictionary<string, string> Metadata { get; set; }
}
[DynamoDbTable("documents")]
public partial class Document
{
[DynamoDbAttribute("doc_id")]
public string DocumentId { get; set; }
[DynamoDbAttribute("content")]
[JsonBlob]
public DocumentContent Content { get; set; }
}
// Usage with configured options
var options = new FluentDynamoDbOptions()
.WithSystemTextJson();
var table = new DocumentTable(dynamoDbClient, "documents", options);
// Save document - JSON serialization happens automatically
await table.Documents.Put(document).PutAsync();
// Load document - JSON deserialization happens automatically
var loaded = await table.Documents.Get(documentId).GetItemAsync();Must reference one of:
Oproto.FluentDynamoDb.SystemTextJson(recommended for AOT)Oproto.FluentDynamoDb.NewtonsoftJson
- Requires JSON serializer package reference (DYNDB102 warning if missing)
- Must configure serializer via
FluentDynamoDbOptionsat runtime - If no serializer is configured, a clear runtime exception is thrown
- System.Text.Json supports AOT via
JsonSerializerContext - Can be combined with
[BlobReference]for large objects - Cannot be combined with
[TimeToLive](DYNDB104 error)
Marks a property for external blob storage with only a reference in DynamoDB.
Stores large data externally (e.g., S3) with only a reference key in DynamoDB. Useful for data larger than DynamoDB's 400KB item limit.
| Parameter | Type | Required | Description |
|---|---|---|---|
provider |
BlobProvider |
Yes | Blob storage provider (S3 or Custom) |
| Property | Type | Default | Description |
|---|---|---|---|
BucketName |
string? |
null |
S3 bucket name |
KeyPrefix |
string? |
null |
S3 key prefix |
ProviderType |
Type? |
null |
Custom provider type |
[DynamoDbTable("files")]
public partial class FileMetadata
{
[DynamoDbAttribute("file_id")]
public string FileId { get; set; }
[DynamoDbAttribute("data_ref")]
[BlobReference(BlobProvider.S3, BucketName = "my-files", KeyPrefix = "uploads")]
public byte[] Data { get; set; }
}
// Setup
var s3Client = new AmazonS3Client();
var blobProvider = new S3BlobProvider(s3Client, "my-files", "uploads");
// Save (async methods required for blob operations)
var item = await FileMetadata.ToDynamoDbAsync(file, blobProvider);
// Load
var loaded = await FileMetadata.FromDynamoDbAsync<FileMetadata>(item, blobProvider);Must reference a blob provider package:
Oproto.FluentDynamoDb.BlobStorage.S3
- Requires blob provider package reference (DYNDB103 error if missing)
- Generates async
ToDynamoDbAsyncandFromDynamoDbAsyncmethods - Can be combined with
[JsonBlob]to serialize then store externally - Cannot be combined with
[TimeToLive](DYNDB104 error) - Blob provider must be passed to async methods
These attributes work together to define your DynamoDB entity schema:
- [DynamoDbTable]: Required on every entity class (supports custom
Namespacefor generated table class) - [DynamoDbAttribute]: Required on every persisted property
- [PartitionKey] and [SortKey]: Define table keys
- [GsiPartitionKey] and [GsiSortKey]: Enable alternative query patterns with GSIs
- [LsiSortKey]: Enable alternative query patterns with LSIs (same partition key)
- [Computed] and [Extracted]: Handle composite keys
- [RelatedEntity]: Enable composite entity patterns
- [TimeToLive]: Automatic item expiration
- [DynamoDbMap]: Nested object mapping
- [JsonBlob]: JSON serialization (configured via
FluentDynamoDbOptions) - [BlobReference]: External blob storage
- [Sensitive]: Mark properties for log redaction
- [RequireWriteTransaction]: Enforce transactional writes for entity types
- [Queryable]: Document query capabilities
⚠️ Deprecated in v0.9.0 - query capabilities now derived from key attributes
The source generator reads these attributes at compile time and generates type-safe code for working with DynamoDB.
- Entity Definition - See attributes used in context
- First Entity Guide - Step-by-step entity creation
- Global Secondary Indexes - GSI attribute usage
- Schema Validation - Validate table schema at startup
- Composite Entities - RelatedEntity attribute usage
- Troubleshooting - Common attribute-related issues