From 32ec5f6e88ae26a297d67ed3f75891714021a722 Mon Sep 17 00:00:00 2001 From: irina-herciu Date: Mon, 3 Feb 2025 16:43:00 +0200 Subject: [PATCH] implement DynamoDBDerivedTypeAttribute for polymorphism support --- .../DynamoDBv2/Custom/DataModel/Attributes.cs | 30 ++++ .../DynamoDBv2/Custom/DataModel/Configs.cs | 15 ++ .../Custom/DataModel/ContextInternal.cs | 79 +++++++--- .../Custom/DataModel/InternalModel.cs | 42 +++++- .../bc057d5e-a710-458b-ac2e-92e0cf88b741.json | 11 ++ .../IntegrationTests/DataModelTests.cs | 136 ++++++++++++++++-- .../IntegrationTests/DynamoDBTestsBase.cs | 32 +++++ 7 files changed, 311 insertions(+), 34 deletions(-) create mode 100644 sdk/src/Services/DynamoDBv2/Generated/.DevConfigs/bc057d5e-a710-458b-ac2e-92e0cf88b741.json diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Attributes.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Attributes.cs index 77c8231db416..fe667b49c761 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Attributes.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Attributes.cs @@ -240,6 +240,36 @@ public DynamoDBPropertyAttribute(string attributeName, bool storeAsEpoch) public bool StoreAsEpoch { get; set; } } + /// + /// DynamoDB attribute that marks a class for polymorphism support. + /// Specifies the field name to be used as the type discriminator and the derived types. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = true)] + public sealed class DynamoDBDerivedTypeAttribute : DynamoDBPropertyAttribute + { + /// + /// Unique name discriminator of the derived type. + /// + public string TypeDiscriminator { get; } + + /// + /// Derived type of the Property type. + /// Type must be a subclass of the Property type. + /// + public Type DerivedType{ get; } + + /// + /// Construct an instance of DynamoDBPolymorphicAttribute + /// + /// Name of the field to be used as the type discriminator. + /// Derived type names and their corresponding types. + public DynamoDBDerivedTypeAttribute(string typeDiscriminator, Type derivedType) + { + TypeDiscriminator = typeDiscriminator; + DerivedType = derivedType; + } + } + /// /// DynamoDB property that marks up current member as a hash key element. /// Exactly one member in a class must be marked with this attribute. diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Configs.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Configs.cs index 9eb5dad92d64..58229dd5d1eb 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/Configs.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/Configs.cs @@ -140,6 +140,11 @@ public DynamoDBContextConfig() /// /// This setting is only applicable to the high-level library. Service calls made via will always return attributes in UTC. public bool? RetrieveDateTimeInUtc { get; set; } + + /// + /// Property indicating the name of the attribute used for derived types conversion. + /// + public string DerivedTypeAttributeName { get; set; } } /// @@ -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; @@ -380,6 +388,7 @@ public DynamoDBFlatConfig(DynamoDBOperationConfig operationConfig, DynamoDBConte MetadataCachingMode = metadataCachingMode; DisableFetchingTableMetadata = disableFetchingTableMetadata; RetrieveDateTimeInUtc = retrieveDateTimeInUtc; + DerivedTypeAttributeName = derivedTypeAttributeName; State = new OperationState(); } @@ -481,6 +490,12 @@ public DynamoDBFlatConfig(DynamoDBOperationConfig operationConfig, DynamoDBConte // State of the operation using this config internal OperationState State { get; private set; } + /// + /// Property indicating the name of the attribute used for derived types conversion. + /// Default value is "$type" if not set in the config. + /// + public string DerivedTypeAttributeName { get; set; } + public class OperationState { private CircularReferenceTracking referenceTracking; diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextInternal.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextInternal.cs index c6d94e93d6e7..a45913ea5d7f 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextInternal.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/ContextInternal.cs @@ -404,7 +404,7 @@ private ItemStorage ObjectToItemStorage(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) @@ -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; } @@ -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 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 derivedTypeKeysDictionary, out object output) { if ((!Utils.ImplementsInterface(targetType, typeof(ICollection<>)) && !Utils.ImplementsInterface(targetType, typeof(IList))) || @@ -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) @@ -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 derivedTypeKeysDictionary, out object output) { if (!Utils.CanInstantiateArray(targetType)) { @@ -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++) { @@ -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 derivedTypeKeysDictionary, out object output) { output = null; @@ -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) { @@ -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) { @@ -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 derivedTypesDictionary, out Document output) { output = null; @@ -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) { @@ -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 derivedTypesDictionary, out DynamoDBList output) { if (!Utils.ImplementsInterface(type, typeof(ICollection<>))) { @@ -695,7 +718,9 @@ 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) { @@ -703,7 +728,9 @@ private bool TryToList(object value, Type type, DynamoDBFlatConfig flatConfig, o if (item == null) entry = DynamoDBNull.Null; else + { entry = ToDynamoDBEntry(propertyStorage, item, flatConfig); + } output.Add(entry); } @@ -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 { } @@ -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; } @@ -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; diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/InternalModel.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/InternalModel.cs index e722e9e968f0..edd908e803a6 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/InternalModel.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/InternalModel.cs @@ -54,6 +54,18 @@ internal class SimplePropertyStorage // Converter, if one is present public IPropertyConverter Converter { get; protected set; } + public void AddDerivedType(string typeDiscriminator, Type type) + { + DerivedTypesDictionary[type] = typeDiscriminator; + DerivedTypeKeysDictionary[typeDiscriminator] = type; + } + + // derived type information used for polymorphic serialization + public Dictionary DerivedTypesDictionary { get; private set; } + + // derived type information used for polymorphic deserialization + public Dictionary DerivedTypeKeysDictionary { get; private set; } + public SimplePropertyStorage(MemberInfo member) : this(Utils.GetType(member)) { @@ -63,6 +75,18 @@ public SimplePropertyStorage(MemberInfo member) public SimplePropertyStorage(Type memberType) { MemberType = memberType; + DerivedTypesDictionary = new Dictionary(); + DerivedTypeKeysDictionary = new Dictionary(); + } + public SimplePropertyStorage(Type memberType, Dictionary derivedTypesDictionary) + { + MemberType = memberType; + DerivedTypesDictionary = derivedTypesDictionary; + } + public SimplePropertyStorage(Type memberType, Dictionary derivedTypeKeysDictionary) + { + MemberType = memberType; + DerivedTypeKeysDictionary = derivedTypeKeysDictionary; } public override string ToString() @@ -94,6 +118,9 @@ internal class PropertyStorage : SimplePropertyStorage // whether to store DateTime as epoch seconds integer public bool StoreAsEpoch { get; set; } + // whether to store Type Name + public bool TypeNameHandling { get; set; } + // corresponding IndexNames, if applicable public List IndexNames { get; set; } @@ -164,6 +191,9 @@ public void Validate(DynamoDBContext context) if (ConverterType != null) { + if (TypeNameHandling) + throw new InvalidOperationException("Converter for " + PropertyName + " must not be set at the same time as derived types."); + if (StoreAsEpoch) throw new InvalidOperationException("Converter for " + PropertyName + " must not be set at the same time as StoreAsEpoch is set to true"); @@ -189,6 +219,7 @@ public PropertyStorage(MemberInfo member) IndexNames = new List(); Indexes = new List(); } + } /// @@ -787,7 +818,7 @@ private static void PopulateConfigFromType(ItemStorageConfig config, Type type) if (propertyAttribute != null) { propertyStorage.StoreAsEpoch = propertyAttribute.StoreAsEpoch; - + if (propertyAttribute.Converter != null) propertyStorage.ConverterType = propertyAttribute.Converter; @@ -809,7 +840,7 @@ private static void PopulateConfigFromType(ItemStorageConfig config, Type type) { propertyStorage.IsGSIRangeKey = true; propertyStorage.AddIndex(gsiRangeAttribute); - } + } else propertyStorage.IsRangeKey = true; } @@ -820,6 +851,13 @@ private static void PopulateConfigFromType(ItemStorageConfig config, Type type) propertyStorage.IsLSIRangeKey = true; propertyStorage.AddIndex(lsiRangeKeyAttribute); } + + DynamoDBDerivedTypeAttribute polymorphicAttribute = attribute as DynamoDBDerivedTypeAttribute; + if (polymorphicAttribute != null) + { + propertyStorage.TypeNameHandling = true; + propertyStorage.AddDerivedType(polymorphicAttribute.TypeDiscriminator, polymorphicAttribute.DerivedType); + } } } diff --git a/sdk/src/Services/DynamoDBv2/Generated/.DevConfigs/bc057d5e-a710-458b-ac2e-92e0cf88b741.json b/sdk/src/Services/DynamoDBv2/Generated/.DevConfigs/bc057d5e-a710-458b-ac2e-92e0cf88b741.json new file mode 100644 index 000000000000..55ab5e6ff6ba --- /dev/null +++ b/sdk/src/Services/DynamoDBv2/Generated/.DevConfigs/bc057d5e-a710-458b-ac2e-92e0cf88b741.json @@ -0,0 +1,11 @@ +{ + "services": [ + { + "serviceName": "DynamoDBv2", + "type": "patch", + "changeLogMessages": [ + "Implement DynamoDBDerivedTypeAttribute for polymorphism support on save and load data." + ] + } + ] +} \ No newline at end of file diff --git a/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs b/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs index 5763cf7631ae..16b930c28f81 100644 --- a/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs +++ b/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs @@ -10,6 +10,7 @@ using Amazon.DynamoDBv2.Model; using Amazon.DynamoDBv2.DocumentModel; using Amazon.DynamoDBv2.DataModel; +using System.Threading.Tasks; namespace AWSSDK_DotNet.IntegrationTests.Tests.DynamoDB { @@ -390,6 +391,74 @@ public void TestContext_RetrieveDateTimeInUtc_OperationConfig(bool retrieveDateT Assert.AreEqual(employee.Age, storedEmployee.Age); } + /// + /// Tests that the DynamoDB operations can read and write polymorphic items. + /// + /// + [TestMethod] + [TestCategory("DynamoDBv2")] + public async Task TestContext_SaveAndLoad_WithDerivedTypeItems() + { + CleanupTables(); + TableCache.Clear(); + + var a1 = new A { Name = "A1", MyPropA = 1}; + var b1 = new B { Name = "B1", MyPropA = 2,MyPropB = 3}; + + Guid id = Guid.NewGuid(); + + var model = new ModelA + { + Id = id, + MyType = b1, + MyClasses = new List { a1, b1 }, + DictionaryClasses = new Dictionary() + { + {"A",a1}, + {"B",b1} + } + }; + + await Context.SaveAsync(model); + + var storedModel = await Context.LoadAsync(id); + Assert.AreEqual(model.Id, storedModel.Id); + Assert.AreEqual(model.MyType.GetType(), storedModel.MyType.GetType()); + Assert.AreEqual(model.MyClasses.Count, storedModel.MyClasses.Count); + Assert.AreEqual(model.MyClasses[0].GetType(), storedModel.MyClasses[0].GetType()); + Assert.AreEqual(model.MyClasses[1].GetType(), storedModel.MyClasses[1].GetType()); + Assert.AreEqual(model.DictionaryClasses["A"].GetType(), storedModel.DictionaryClasses["A"].GetType()); + Assert.AreEqual(model.DictionaryClasses["B"].GetType(), storedModel.DictionaryClasses["B"].GetType()); + } + + /// + /// Tests that the DynamoDB operations can read and write polymorphic items. + /// + /// + [TestMethod] + [TestCategory("DynamoDBv2")] + public async Task TestContext_TransactWriteAndLoad_WithDerivedTypeItems() + { + CleanupTables(); + TableCache.Clear(); + + var model = CreateNestedTypeItem(out var id); + + var transactWrite = Context.CreateTransactWrite(); + transactWrite.AddSaveItem(model); + await transactWrite.ExecuteAsync(); + + var storedModel = await Context.LoadAsync(id); + Assert.AreEqual(model.Id, storedModel.Id); + Assert.AreEqual(model.MyType.GetType(), storedModel.MyType.GetType()); + Assert.AreEqual(model.MyClasses.Count, storedModel.MyClasses.Count); + Assert.AreEqual(model.MyClasses[0].GetType(), storedModel.MyClasses[0].GetType()); + Assert.AreEqual(model.MyClasses[1].GetType(), storedModel.MyClasses[1].GetType()); + Assert.AreEqual(model.DictionaryClasses["A"].GetType(), storedModel.DictionaryClasses["A"].GetType()); + Assert.AreEqual(model.DictionaryClasses["B"].GetType(), storedModel.DictionaryClasses["B"].GetType()); + } + + /// /// Runs the same object-mapper integration tests as , /// but using table definitions created by instead of the internal call @@ -1723,25 +1792,45 @@ private void TestOtherContextOperations() Assert.AreEqual(employee1.Data.Length, doc["Data"].AsByteArray().Length); } + private ModelA CreateNestedTypeItem(out Guid id) + { + var a1 = new A { Name = "A1", MyPropA = 1 }; + var b1 = new B { Name = "B1", MyPropA = 2, MyPropB = 3 }; + + id = Guid.NewGuid(); + + var model = new ModelA + { + Id = id, + MyType = b1, + MyClasses = new List { a1, b1 }, + DictionaryClasses = new Dictionary() + { + {"A",a1}, + {"B",b1} + } + }; + return model; + } #region OPM definitions public enum Status : long { - Active = 256, - Inactive = 1024, - Upcoming = 9999, - Obsolete = -10, - Removed = 42 + Active = 256, + Inactive = 1024, + Upcoming = 9999, + Obsolete = -10, + Removed = 42 } [Flags] public enum Support { - Windows = 1 << 0, - iOS = 1 << 1, - Unix = 1 << 2, - Abacus = 1 << 3, + Windows = 1 << 0, + iOS = 1 << 1, + Unix = 1 << 2, + Abacus = 1 << 3, } public class StatusConverter : IPropertyConverter @@ -2063,6 +2152,35 @@ public object FromEntry(DynamoDBEntry entry) } } + public class A + { + public string Name { get; set; } + + public int MyPropA { get; set; } + } + + public class B : A + { + public int MyPropB { get; set; } + } + + [DynamoDBTable("TestTable")] + public class ModelA + { + [DynamoDBHashKey] + public Guid Id { get; set; } + + [DynamoDBDerivedType("B", typeof(B))] + public A MyType { get; set; } + + [DynamoDBDerivedType("B", typeof(B))] + [DynamoDBProperty("test")] + public List MyClasses { get; set; } + + + [DynamoDBDerivedType("B", typeof(B))] + public Dictionary DictionaryClasses { get; set; } + } #endregion } } diff --git a/sdk/test/Services/DynamoDBv2/IntegrationTests/DynamoDBTestsBase.cs b/sdk/test/Services/DynamoDBv2/IntegrationTests/DynamoDBTestsBase.cs index 742411330d32..812e03453ae6 100644 --- a/sdk/test/Services/DynamoDBv2/IntegrationTests/DynamoDBTestsBase.cs +++ b/sdk/test/Services/DynamoDBv2/IntegrationTests/DynamoDBTestsBase.cs @@ -86,6 +86,7 @@ private static void ClientBeforeRequestEvent(object sender, Amazon.Runtime.Reque public void CleanupTables() { ClearTable(hashTableName); + ClearTable(nestedTableName); ClearTable(hashRangeTableName); ClearTable(numericHashRangeTableName); } @@ -102,6 +103,7 @@ public static void CreateContext(DynamoDBEntryConversion conversion, bool isEmpt Context = new DynamoDBContext(Client, config); } + public static string nestedTableName; public static string hashTableName; public static string hashRangeTableName; public static string numericHashRangeTableName; @@ -148,15 +150,22 @@ public static void ClearTable(string tableName) public static void CreateTestTables() { + nestedTableName = TableNamePrefix + "NestedTable"; hashTableName = TableNamePrefix + "HashTable"; hashRangeTableName = TableNamePrefix + "HashRangeTable"; numericHashRangeTableName = TableNamePrefix + "NumericHashRangeTable"; + bool createTestTable = true; bool createHashTable = true; bool createHashRangeTable = true; bool createNumericHashRangeTable = true; if (ReuseTables) { + if (GetStatus(nestedTableName) != null) + { + WaitForTableStatus(nestedTableName, TableStatus.ACTIVE); + createTestTable = false; + } if (GetStatus(hashTableName) != null) { WaitForTableStatus(hashTableName, TableStatus.ACTIVE); @@ -174,6 +183,28 @@ public static void CreateTestTables() } } + if (createTestTable) + { + // Create hash-key table with global index + Client.CreateTable(new CreateTableRequest + { + TableName = nestedTableName, + AttributeDefinitions = new List + { + new AttributeDefinition { AttributeName = "Id", AttributeType = ScalarAttributeType.S }, + }, + KeySchema = new List + { + new KeySchemaElement { KeyType = KeyType.HASH, AttributeName = "Id" } + }, + BillingMode = BillingMode.PAY_PER_REQUEST + }); + CreatedTables.Add(nestedTableName); + + // Wait for table to be ready + WaitForTableStatus(nestedTableName, TableStatus.ACTIVE); + } + if (createHashTable) { // Create hash-key table with global index @@ -294,6 +325,7 @@ public static void CreateTestTables() // Make sure TTL is enabled for the tables and is on the correct attribute + EnsureTTL(nestedTableName); EnsureTTL(hashTableName); EnsureTTL(hashRangeTableName); EnsureTTL(numericHashRangeTableName);