Skip to content

Commit

Permalink
implement DynamoDBDerivedTypeAttribute for polymorphism support
Browse files Browse the repository at this point in the history
  • Loading branch information
irina-herciu committed Feb 3, 2025
1 parent c8bcc6c commit 32ec5f6
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 34 deletions.
30 changes: 30 additions & 0 deletions sdk/src/Services/DynamoDBv2/Custom/DataModel/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,36 @@ public DynamoDBPropertyAttribute(string attributeName, bool storeAsEpoch)
public bool StoreAsEpoch { get; set; }
}

/// <summary>
/// DynamoDB attribute that marks a class for polymorphism support.
/// Specifies the field name to be used as the type discriminator and the derived types.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = true)]
public sealed class DynamoDBDerivedTypeAttribute : DynamoDBPropertyAttribute
{
/// <summary>
/// Unique name discriminator of the derived type.
/// </summary>
public string TypeDiscriminator { get; }

/// <summary>
/// Derived type of the Property type.
/// Type must be a subclass of the Property type.
/// </summary>
public Type DerivedType{ get; }

/// <summary>
/// Construct an instance of DynamoDBPolymorphicAttribute
/// </summary>
/// <param name="typeDiscriminator">Name of the field to be used as the type discriminator.</param>
/// <param name="derivedType">Derived type names and their corresponding types.</param>
public DynamoDBDerivedTypeAttribute(string typeDiscriminator, Type derivedType)
{
TypeDiscriminator = typeDiscriminator;
DerivedType = derivedType;
}
}

/// <summary>
/// DynamoDB property that marks up current member as a hash key element.
/// Exactly one member in a class must be marked with this attribute.
Expand Down
15 changes: 15 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,11 @@ public DynamoDBContextConfig()
/// </summary>
/// <remarks>This setting is only applicable to the high-level library. Service calls made via <see cref="AmazonDynamoDBClient"/> will always return <see cref="DateTime"/> attributes in UTC.</remarks>
public bool? RetrieveDateTimeInUtc { get; set; }

/// <summary>
/// Property indicating the name of the attribute used for derived types conversion.
/// </summary>
public string DerivedTypeAttributeName { get; set; }
}

/// <summary>
Expand Down Expand Up @@ -365,6 +370,9 @@ public DynamoDBFlatConfig(DynamoDBOperationConfig operationConfig, DynamoDBConte
ConditionalOperatorValues conditionalOperator = operationConfig.ConditionalOperator;
DynamoDBEntryConversion conversion = operationConfig.Conversion ?? contextConfig.Conversion ?? DynamoDBEntryConversion.CurrentConversion;
MetadataCachingMode metadataCachingMode = operationConfig.MetadataCachingMode ?? contextConfig.MetadataCachingMode ?? DynamoDBv2.MetadataCachingMode.Default;
string derivedTypeAttributeName =
!string.IsNullOrEmpty(operationConfig.DerivedTypeAttributeName) ? operationConfig.DerivedTypeAttributeName :
!string.IsNullOrEmpty(contextConfig.DerivedTypeAttributeName) ? contextConfig.DerivedTypeAttributeName : "$type";

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

State = new OperationState();
}
Expand Down Expand Up @@ -481,6 +490,12 @@ 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 for derived types conversion.
/// Default value is "$type" if not set in the config.
/// </summary>
public string DerivedTypeAttributeName { get; set; }

public class OperationState
{
private CircularReferenceTracking referenceTracking;
Expand Down
79 changes: 56 additions & 23 deletions sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextInternal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ private ItemStorage ObjectToItemStorage<T>(T toStore, bool keysOnly, DynamoDBFla
private ItemStorage ObjectToItemStorage(object toStore, Type objectType, bool keysOnly, DynamoDBFlatConfig flatConfig)
{
ItemStorageConfig config = StorageConfigCache.GetConfig(objectType, flatConfig);
ItemStorage storage = ObjectToItemStorageHelper(toStore, config, flatConfig, keysOnly, flatConfig.IgnoreNullValues.Value);
ItemStorage storage = ObjectToItemStorageHelper(toStore, config, flatConfig, keysOnly, flatConfig.IgnoreNullValues != null && flatConfig.IgnoreNullValues.Value);
return storage;
}
internal ItemStorage ObjectToItemStorageHelper(object toStore, ItemStorageConfig config, DynamoDBFlatConfig flatConfig, bool keysOnly, bool ignoreNullValues)
Expand Down Expand Up @@ -489,15 +489,23 @@ private object FromDynamoDBEntry(SimplePropertyStorage propertyStorage, DynamoDB
Document document = entry as Document;
if (document != null)
{
if (TryFromMap(targetType, document, flatConfig, out output))
if (TryFromMap(targetType, document, flatConfig, propertyStorage.DerivedTypeKeysDictionary, out output))
return output;

var typeAttributeName = flatConfig.DerivedTypeAttributeName;//"$type";
var derivedType = document.ContainsKey(typeAttributeName) ? document[typeAttributeName].AsString() : null;

if (derivedType != null && propertyStorage.DerivedTypeKeysDictionary.TryGetValue(derivedType, out var value))
{
targetType = value;
}

return DeserializeFromDocument(document, targetType, flatConfig);
}

DynamoDBList list = entry as DynamoDBList;
if (list != null &&
TryFromList(targetType, list, flatConfig, out output))
TryFromList(targetType, list, flatConfig, propertyStorage.DerivedTypeKeysDictionary, out output))
{
return output;
}
Expand All @@ -507,14 +515,16 @@ private object FromDynamoDBEntry(SimplePropertyStorage propertyStorage, DynamoDB
entry, entry.GetType().FullName, propertyStorage.PropertyName, propertyStorage.MemberType.FullName));
}
}
private bool TryFromList(Type targetType, DynamoDBList list, DynamoDBFlatConfig flatConfig, out object output)
private bool TryFromList(Type targetType, DynamoDBList list, DynamoDBFlatConfig flatConfig,
Dictionary<string, Type> derivedTypeKeysDictionary, out object output)
{
return targetType.IsArray ?
TryFromListToArray(targetType, list, flatConfig, out output) : //targetType is Array
TryFromListToIList(targetType, list, flatConfig, out output) ; //targetType is IList or has Add method.
TryFromListToArray(targetType, list, flatConfig, derivedTypeKeysDictionary, out output) : //targetType is Array
TryFromListToIList(targetType, list, flatConfig, derivedTypeKeysDictionary, out output) ; //targetType is IList or has Add method.
}

private bool TryFromListToIList(Type targetType, DynamoDBList list, DynamoDBFlatConfig flatConfig, out object output)
private bool TryFromListToIList(Type targetType, DynamoDBList list, DynamoDBFlatConfig flatConfig,
Dictionary<string, Type> derivedTypeKeysDictionary, out object output)
{
if ((!Utils.ImplementsInterface(targetType, typeof(ICollection<>)) &&
!Utils.ImplementsInterface(targetType, typeof(IList))) ||
Expand All @@ -528,7 +538,7 @@ private bool TryFromListToIList(Type targetType, DynamoDBList list, DynamoDBFlat
var collection = Utils.Instantiate(targetType);
IList ilist = collection as IList;
bool useIListInterface = ilist != null;
var propertyStorage = new SimplePropertyStorage(elementType);
var propertyStorage = new SimplePropertyStorage(elementType, derivedTypeKeysDictionary);

MethodInfo collectionAdd = null;
if (!useIListInterface)
Expand All @@ -550,7 +560,8 @@ private bool TryFromListToIList(Type targetType, DynamoDBList list, DynamoDBFlat
return true;
}

private bool TryFromListToArray(Type targetType, DynamoDBList list, DynamoDBFlatConfig flatConfig, out object output)
private bool TryFromListToArray(Type targetType, DynamoDBList list, DynamoDBFlatConfig flatConfig,
Dictionary<string, Type> derivedTypeKeysDictionary, out object output)
{
if (!Utils.CanInstantiateArray(targetType))
{
Expand All @@ -560,8 +571,7 @@ private bool TryFromListToArray(Type targetType, DynamoDBList list, DynamoDBFlat

var elementType = Utils.GetElementType(targetType);
var array = (Array)Utils.InstantiateArray(targetType,list.Entries.Count);
var propertyStorage = new SimplePropertyStorage(elementType);

var propertyStorage = new SimplePropertyStorage(elementType, derivedTypeKeysDictionary);

for (int i = 0; i < list.Entries.Count; i++)
{
Expand All @@ -574,7 +584,8 @@ private bool TryFromListToArray(Type targetType, DynamoDBList list, DynamoDBFlat
return true;
}

private bool TryFromMap(Type targetType, Document map, DynamoDBFlatConfig flatConfig, out object output)
private bool TryFromMap(Type targetType, Document map, DynamoDBFlatConfig flatConfig,
Dictionary<string, Type> derivedTypeKeysDictionary, out object output)
{
output = null;

Expand All @@ -587,7 +598,7 @@ private bool TryFromMap(Type targetType, Document map, DynamoDBFlatConfig flatCo

var dictionary = Utils.Instantiate(targetType);
var idictionary = dictionary as IDictionary;
var propertyStorage = new SimplePropertyStorage(valueType);
var propertyStorage = new SimplePropertyStorage(valueType, derivedTypeKeysDictionary);

foreach (var kvp in map)
{
Expand Down Expand Up @@ -621,7 +632,17 @@ private DynamoDBEntry ToDynamoDBEntry(SimplePropertyStorage propertyStorage, obj
return entry;
}

var type = propertyStorage.MemberType;
Type type;
string typeDiscriminator = null;
if (propertyStorage.DerivedTypesDictionary.ContainsKey(value.GetType()))
{
typeDiscriminator = propertyStorage.DerivedTypesDictionary[value.GetType()];
type = value.GetType();
}
else
{
type = propertyStorage.MemberType;
}

if (canReturnScalarInsteadOfList)
{
Expand All @@ -635,17 +656,18 @@ private DynamoDBEntry ToDynamoDBEntry(SimplePropertyStorage propertyStorage, obj
else
{
Document map;
if (TryToMap(value, type, flatConfig, out map))
if (TryToMap(value, type, flatConfig, propertyStorage.DerivedTypesDictionary, out map))
return map;

DynamoDBList list;
if (TryToList(value, type, flatConfig, out list))
if (TryToList(value, type, flatConfig, propertyStorage.DerivedTypesDictionary, out list))
return list;

return SerializeToDocument(value, type, flatConfig);
return SerializeToDocument(value, type, flatConfig, typeDiscriminator);
}
}
private bool TryToMap(object value, Type type, DynamoDBFlatConfig flatConfig, out Document output)
private bool TryToMap(object value, Type type, DynamoDBFlatConfig flatConfig,
Dictionary<Type, string> derivedTypesDictionary, out Document output)
{
output = null;

Expand All @@ -658,7 +680,7 @@ private bool TryToMap(object value, Type type, DynamoDBFlatConfig flatConfig, ou
return false;

output = new Document();
SimplePropertyStorage propertyStorage = new SimplePropertyStorage(valueType);
SimplePropertyStorage propertyStorage = new SimplePropertyStorage(valueType,derivedTypesDictionary);

foreach (object keyValue in idictionary.Keys)
{
Expand All @@ -677,7 +699,8 @@ private bool TryToMap(object value, Type type, DynamoDBFlatConfig flatConfig, ou
}
return true;
}
private bool TryToList(object value, Type type, DynamoDBFlatConfig flatConfig, out DynamoDBList output)
private bool TryToList(object value, Type type, DynamoDBFlatConfig flatConfig,
Dictionary<Type, string> derivedTypesDictionary, out DynamoDBList output)
{
if (!Utils.ImplementsInterface(type, typeof(ICollection<>)))
{
Expand All @@ -695,15 +718,19 @@ private bool TryToList(object value, Type type, DynamoDBFlatConfig flatConfig, o
}

Type elementType = Utils.GetElementType(type);
SimplePropertyStorage propertyStorage = new SimplePropertyStorage(elementType);

//Todo IH: implement get element type for arrays with inheritance
SimplePropertyStorage propertyStorage = new SimplePropertyStorage(elementType,derivedTypesDictionary);
output = new DynamoDBList();
foreach (var item in enumerable)
{
DynamoDBEntry entry;
if (item == null)
entry = DynamoDBNull.Null;
else
{
entry = ToDynamoDBEntry(propertyStorage, item, flatConfig);
}

output.Add(entry);
}
Expand Down Expand Up @@ -732,7 +759,7 @@ private bool TryToScalar(object value, Type type, DynamoDBFlatConfig flatConfig,
{
try
{
entry = SerializeToDocument(value, elementType, flatConfig);
entry = SerializeToDocument(value, elementType, flatConfig, typeDiscriminator: null);
return true;
}
catch { }
Expand Down Expand Up @@ -781,11 +808,16 @@ private object DeserializeFromDocument(Document document, Type targetType, Dynam
}
// Serializes a given value to Document
// Use only for property conversions, not for full item conversion
private Document SerializeToDocument(object value, Type type, DynamoDBFlatConfig flatConfig)
private Document SerializeToDocument(object value, Type type, DynamoDBFlatConfig flatConfig,string typeDiscriminator)
{
ItemStorageConfig config = StorageConfigCache.GetConfig(type, flatConfig, conversionOnly: true);
var itemStorage = ObjectToItemStorageHelper(value, config, flatConfig, keysOnly: false, ignoreNullValues: flatConfig.IgnoreNullValues.Value);
var doc = itemStorage.Document;
if (typeDiscriminator != null)
{
var typeAttributeName = flatConfig.DerivedTypeAttributeName;
doc[typeAttributeName] = new Primitive(typeDiscriminator);
}
return doc;
}

Expand All @@ -810,6 +842,7 @@ private static bool TrySetValue(object instance, MemberInfo member, object value
return false;
}
}

private static bool TryGetValue(object instance, MemberInfo member, out object value)
{
FieldInfo fieldInfo = member as FieldInfo;
Expand Down
Loading

0 comments on commit 32ec5f6

Please sign in to comment.