Skip to content

implement polymorphism support for DynamoDB entries #3643

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions generator/.DevConfigs/bc057d5e-a710-458b-ac2e-92e0cf88b741.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"services": [
{
"serviceName": "DynamoDBv2",
"type": "patch",
"changeLogMessages": [
"Implement DynamoDBDerivedTypeAttribute to enable polymorphism support for nested items on save and load data."
]
}
]
}
85 changes: 85 additions & 0 deletions sdk/src/Services/DynamoDBv2/Custom/DataModel/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,91 @@ public DynamoDBTableAttribute(string tableName, bool lowerCamelCaseProperties)
}
}

/// <summary>
/// DynamoDB attribute that marks a class or property for polymorphism support.
/// This allows DynamoDB to store and retrieve instances of derived types
/// while preserving their original types during serialization and deserialization.
/// </summary>
/// <remarks>
/// To enable polymorphic serialization, this attribute should be applied multiple times,
/// once for each possible subtype. The <see cref="TypeDiscriminator"/> serves as a unique
/// identifier used in the database to distinguish between different derived types.
///
/// The name of the stored discriminator attribute in DynamoDB can be configured via
/// <see cref="DynamoDBContextConfig.DerivedTypeAttributeName"/>.
/// If not explicitly set, the SDK will use a default attribute name for the discriminator.
/// </remarks>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = true)]
public sealed class DynamoDBPolymorphicTypeAttribute : DynamoDBAttribute
{
/// <summary>
/// Unique name discriminator of the derived type.
/// </summary>
/// <remarks>
/// The <see cref="TypeDiscriminator"/> is stored in DynamoDB and is used to
/// determine the actual type of the object when deserializing.
/// It should be unique among all declared polymorphic types for a given base type.
///
/// The attribute name under which this discriminator is stored in DynamoDB
/// is configurable via <see cref="DynamoDBContextConfig.DerivedTypeAttributeName"/>.
///
/// Example:
/// <code>
/// [DynamoDBPolymorphicType("dog", typeof(Dog))]
/// [DynamoDBPolymorphicType("cat", typeof(Cat))]
/// public class Animal { }
/// </code>
///
/// When retrieving an item, DynamoDB will use this discriminator value to
/// deserialize into the appropriate derived type.
/// </remarks>
public string TypeDiscriminator { get; }

/// <summary>
/// The specific derived type that corresponds to this polymorphic entry.
/// </summary>
/// <remarks>
/// This should be a subclass of the property type where the attribute is applied.
/// When an instance of this type is stored in DynamoDB, the <see cref="TypeDiscriminator"/>
/// will be saved along with it, allowing correct type resolution during deserialization.
/// </remarks>
[DynamicallyAccessedMembers(InternalConstants.DataModelModeledType)]
public Type DerivedType { get; }

/// <summary>
/// Constructs an instance of <see cref="DynamoDBPolymorphicTypeAttribute"/>.
/// </summary>
/// <param name="typeDiscriminator">
/// A unique string identifier representing this derived type.
/// This value is stored in DynamoDB and used for deserialization.
/// </param>
/// <param name="derivedType">
/// The actual derived type that this attribute represents.
/// Must be a subclass of the property type to which it is applied.
/// </param>
/// <example>
/// Usage for a polymorphic property:
/// <code>
/// public class Zoo
/// {
/// [DynamoDBPolymorphicType("dog", typeof(Dog))]
/// [DynamoDBPolymorphicType("cat", typeof(Cat))]
/// public Animal Resident { get; set; }
/// }
/// </code>
///
/// In this example, when a `Dog` instance is stored, DynamoDB will save `"dog"` as its discriminator
/// under the attribute name configured in <see cref="DynamoDBContextConfig.DerivedTypeAttributeName"/>.
/// When retrieved, the stored discriminator ensures that the value is deserialized as a `Dog` instance.
/// </example>
public DynamoDBPolymorphicTypeAttribute(string typeDiscriminator,
[DynamicallyAccessedMembers(InternalConstants.DataModelModeledType)] Type derivedType)
{
TypeDiscriminator = typeDiscriminator;
DerivedType = derivedType;
}
}

/// <summary>
/// DynamoDB attribute that directs the specified attribute not to
/// be included when saving or loading objects.
Expand Down
34 changes: 34 additions & 0 deletions sdk/src/Services/DynamoDBv2/Custom/DataModel/Configs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,21 @@ public DynamoDBContextConfig()
/// Service calls made via <see cref="AmazonDynamoDBClient"/> will always return
/// <see cref="DateTime"/> attributes in UTC.</remarks>
public bool? RetrieveDateTimeInUtc { get; set; }

/// <summary>
/// Gets or sets the attribute name used to store the type discriminator for polymorphic types in DynamoDB.
/// </summary>
/// <remarks>
/// When working with polymorphic types—where a base class or interface has multiple derived implementations
/// it's essential to preserve the specific type information during serialization and deserialization
/// when using the <see cref="DynamoDBPolymorphicTypeAttribute"/>.
///
/// By default, the SDK uses a predefined attribute name of "$type" to store this type discriminator in your DynamoDB items.
/// However, you can customize this attribute name to align with your application's naming conventions or to avoid
/// conflicts with existing attributes.
/// </remarks>
public string DerivedTypeAttributeName { get; set; }

}

/// <summary>
Expand Down Expand Up @@ -434,6 +449,9 @@ public DynamoDBFlatConfig(DynamoDBOperationConfig operationConfig, DynamoDBConte
!string.IsNullOrEmpty(operationConfig.IndexName) ? operationConfig.IndexName : DefaultIndexName;
List<ScanCondition> queryFilter = operationConfig.QueryFilter ?? new List<ScanCondition>();
ConditionalOperatorValues conditionalOperator = operationConfig.ConditionalOperator;
string derivedTypeAttributeName =
//!string.IsNullOrEmpty(operationConfig.DerivedTypeAttributeName) ? operationConfig.DerivedTypeAttributeName :
!string.IsNullOrEmpty(contextConfig.DerivedTypeAttributeName) ? contextConfig.DerivedTypeAttributeName : "$type";

ConsistentRead = consistentRead;
SkipVersionCheck = skipVersionCheck;
Expand All @@ -449,6 +467,7 @@ public DynamoDBFlatConfig(DynamoDBOperationConfig operationConfig, DynamoDBConte
MetadataCachingMode = metadataCachingMode;
DisableFetchingTableMetadata = disableFetchingTableMetadata;
RetrieveDateTimeInUtc = retrieveDateTimeInUtc;
DerivedTypeAttributeName = derivedTypeAttributeName;

State = new OperationState();
}
Expand Down Expand Up @@ -550,6 +569,21 @@ public DynamoDBFlatConfig(DynamoDBOperationConfig operationConfig, DynamoDBConte
// State of the operation using this config
internal OperationState State { get; private set; }

/// <summary>
/// Property indicating the name of the attribute used to store the type discriminator for polymorphic types in DynamoDB.
/// Default value is "$type" if not set in the config.
/// </summary>
/// <remarks>
/// When working with polymorphic types—where a base class or interface has multiple derived implementations
/// it's essential to preserve the specific type information during serialization and deserialization
/// when using the <see cref="DynamoDBPolymorphicTypeAttribute"/>.
///
/// By default, the SDK uses a predefined attribute name of "$type" to store this type discriminator in your DynamoDB items.
/// However, you can customize this attribute name to align with your application's naming conventions or to avoid
/// conflicts with existing attributes.
/// </remarks>
public string DerivedTypeAttributeName { get; set; }

public class OperationState
{
private CircularReferenceTracking referenceTracking;
Expand Down
Loading