Skip to content

Commit 49f166f

Browse files
authored
implement polymorphism support for DynamoDB entries (#3643)
1 parent 66486a1 commit 49f166f

File tree

9 files changed

+856
-135
lines changed

9 files changed

+856
-135
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"services": [
3+
{
4+
"serviceName": "DynamoDBv2",
5+
"type": "patch",
6+
"changeLogMessages": [
7+
"Implement DynamoDBDerivedTypeAttribute to enable polymorphism support for nested items on save and load data."
8+
]
9+
}
10+
]
11+
}

sdk/src/Services/DynamoDBv2/Custom/DataModel/Attributes.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,91 @@ public DynamoDBTableAttribute(string tableName, bool lowerCamelCaseProperties)
7171
}
7272
}
7373

74+
/// <summary>
75+
/// DynamoDB attribute that marks a class or property for polymorphism support.
76+
/// This allows DynamoDB to store and retrieve instances of derived types
77+
/// while preserving their original types during serialization and deserialization.
78+
/// </summary>
79+
/// <remarks>
80+
/// To enable polymorphic serialization, this attribute should be applied multiple times,
81+
/// once for each possible subtype. The <see cref="TypeDiscriminator"/> serves as a unique
82+
/// identifier used in the database to distinguish between different derived types.
83+
///
84+
/// The name of the stored discriminator attribute in DynamoDB can be configured via
85+
/// <see cref="DynamoDBContextConfig.DerivedTypeAttributeName"/>.
86+
/// If not explicitly set, the SDK will use a default attribute name for the discriminator.
87+
/// </remarks>
88+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = true)]
89+
public sealed class DynamoDBPolymorphicTypeAttribute : DynamoDBAttribute
90+
{
91+
/// <summary>
92+
/// Unique name discriminator of the derived type.
93+
/// </summary>
94+
/// <remarks>
95+
/// The <see cref="TypeDiscriminator"/> is stored in DynamoDB and is used to
96+
/// determine the actual type of the object when deserializing.
97+
/// It should be unique among all declared polymorphic types for a given base type.
98+
///
99+
/// The attribute name under which this discriminator is stored in DynamoDB
100+
/// is configurable via <see cref="DynamoDBContextConfig.DerivedTypeAttributeName"/>.
101+
///
102+
/// Example:
103+
/// <code>
104+
/// [DynamoDBPolymorphicType("dog", typeof(Dog))]
105+
/// [DynamoDBPolymorphicType("cat", typeof(Cat))]
106+
/// public class Animal { }
107+
/// </code>
108+
///
109+
/// When retrieving an item, DynamoDB will use this discriminator value to
110+
/// deserialize into the appropriate derived type.
111+
/// </remarks>
112+
public string TypeDiscriminator { get; }
113+
114+
/// <summary>
115+
/// The specific derived type that corresponds to this polymorphic entry.
116+
/// </summary>
117+
/// <remarks>
118+
/// This should be a subclass of the property type where the attribute is applied.
119+
/// When an instance of this type is stored in DynamoDB, the <see cref="TypeDiscriminator"/>
120+
/// will be saved along with it, allowing correct type resolution during deserialization.
121+
/// </remarks>
122+
[DynamicallyAccessedMembers(InternalConstants.DataModelModeledType)]
123+
public Type DerivedType { get; }
124+
125+
/// <summary>
126+
/// Constructs an instance of <see cref="DynamoDBPolymorphicTypeAttribute"/>.
127+
/// </summary>
128+
/// <param name="typeDiscriminator">
129+
/// A unique string identifier representing this derived type.
130+
/// This value is stored in DynamoDB and used for deserialization.
131+
/// </param>
132+
/// <param name="derivedType">
133+
/// The actual derived type that this attribute represents.
134+
/// Must be a subclass of the property type to which it is applied.
135+
/// </param>
136+
/// <example>
137+
/// Usage for a polymorphic property:
138+
/// <code>
139+
/// public class Zoo
140+
/// {
141+
/// [DynamoDBPolymorphicType("dog", typeof(Dog))]
142+
/// [DynamoDBPolymorphicType("cat", typeof(Cat))]
143+
/// public Animal Resident { get; set; }
144+
/// }
145+
/// </code>
146+
///
147+
/// In this example, when a `Dog` instance is stored, DynamoDB will save `"dog"` as its discriminator
148+
/// under the attribute name configured in <see cref="DynamoDBContextConfig.DerivedTypeAttributeName"/>.
149+
/// When retrieved, the stored discriminator ensures that the value is deserialized as a `Dog` instance.
150+
/// </example>
151+
public DynamoDBPolymorphicTypeAttribute(string typeDiscriminator,
152+
[DynamicallyAccessedMembers(InternalConstants.DataModelModeledType)] Type derivedType)
153+
{
154+
TypeDiscriminator = typeDiscriminator;
155+
DerivedType = derivedType;
156+
}
157+
}
158+
74159
/// <summary>
75160
/// DynamoDB attribute that directs the specified attribute not to
76161
/// be included when saving or loading objects.

sdk/src/Services/DynamoDBv2/Custom/DataModel/Configs.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,21 @@ public DynamoDBContextConfig()
140140
/// Service calls made via <see cref="AmazonDynamoDBClient"/> will always return
141141
/// <see cref="DateTime"/> attributes in UTC.</remarks>
142142
public bool? RetrieveDateTimeInUtc { get; set; }
143+
144+
/// <summary>
145+
/// Gets or sets the attribute name used to store the type discriminator for polymorphic types in DynamoDB.
146+
/// </summary>
147+
/// <remarks>
148+
/// When working with polymorphic types—where a base class or interface has multiple derived implementations
149+
/// it's essential to preserve the specific type information during serialization and deserialization
150+
/// when using the <see cref="DynamoDBPolymorphicTypeAttribute"/>.
151+
///
152+
/// By default, the SDK uses a predefined attribute name of "$type" to store this type discriminator in your DynamoDB items.
153+
/// However, you can customize this attribute name to align with your application's naming conventions or to avoid
154+
/// conflicts with existing attributes.
155+
/// </remarks>
156+
public string DerivedTypeAttributeName { get; set; }
157+
143158
}
144159

145160
/// <summary>
@@ -434,6 +449,9 @@ public DynamoDBFlatConfig(DynamoDBOperationConfig operationConfig, DynamoDBConte
434449
!string.IsNullOrEmpty(operationConfig.IndexName) ? operationConfig.IndexName : DefaultIndexName;
435450
List<ScanCondition> queryFilter = operationConfig.QueryFilter ?? new List<ScanCondition>();
436451
ConditionalOperatorValues conditionalOperator = operationConfig.ConditionalOperator;
452+
string derivedTypeAttributeName =
453+
//!string.IsNullOrEmpty(operationConfig.DerivedTypeAttributeName) ? operationConfig.DerivedTypeAttributeName :
454+
!string.IsNullOrEmpty(contextConfig.DerivedTypeAttributeName) ? contextConfig.DerivedTypeAttributeName : "$type";
437455

438456
ConsistentRead = consistentRead;
439457
SkipVersionCheck = skipVersionCheck;
@@ -449,6 +467,7 @@ public DynamoDBFlatConfig(DynamoDBOperationConfig operationConfig, DynamoDBConte
449467
MetadataCachingMode = metadataCachingMode;
450468
DisableFetchingTableMetadata = disableFetchingTableMetadata;
451469
RetrieveDateTimeInUtc = retrieveDateTimeInUtc;
470+
DerivedTypeAttributeName = derivedTypeAttributeName;
452471

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

572+
/// <summary>
573+
/// Property indicating the name of the attribute used to store the type discriminator for polymorphic types in DynamoDB.
574+
/// Default value is "$type" if not set in the config.
575+
/// </summary>
576+
/// <remarks>
577+
/// When working with polymorphic types—where a base class or interface has multiple derived implementations
578+
/// it's essential to preserve the specific type information during serialization and deserialization
579+
/// when using the <see cref="DynamoDBPolymorphicTypeAttribute"/>.
580+
///
581+
/// By default, the SDK uses a predefined attribute name of "$type" to store this type discriminator in your DynamoDB items.
582+
/// However, you can customize this attribute name to align with your application's naming conventions or to avoid
583+
/// conflicts with existing attributes.
584+
/// </remarks>
585+
public string DerivedTypeAttributeName { get; set; }
586+
553587
public class OperationState
554588
{
555589
private CircularReferenceTracking referenceTracking;

0 commit comments

Comments
 (0)