diff --git a/generator/.DevConfigs/8e546afe-27ad-4b11-8400-e0f3c33f0a4a.json b/generator/.DevConfigs/8e546afe-27ad-4b11-8400-e0f3c33f0a4a.json new file mode 100644 index 000000000000..06ad294598d4 --- /dev/null +++ b/generator/.DevConfigs/8e546afe-27ad-4b11-8400-e0f3c33f0a4a.json @@ -0,0 +1,11 @@ +{ + "services": [ + { + "serviceName": "DynamoDBv2", + "type": "patch", + "changeLogMessages": [ + "Fixed issue with TransactWrite in the DataModel where it wasn't correctly handling cases where only keys were being saved." + ] + } + ] +} \ No newline at end of file diff --git a/sdk/src/Services/DynamoDBv2/Custom/DataModel/TransactWrite.cs b/sdk/src/Services/DynamoDBv2/Custom/DataModel/TransactWrite.cs index e5d69a3f4f9b..0340288caf8a 100644 --- a/sdk/src/Services/DynamoDBv2/Custom/DataModel/TransactWrite.cs +++ b/sdk/src/Services/DynamoDBv2/Custom/DataModel/TransactWrite.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; #if AWS_ASYNC_API using System.Threading; using System.Threading.Tasks; @@ -134,11 +135,8 @@ public void AddSaveItem(T item) Expression conditionExpression = CreateConditionExpressionForVersion(storage); SetNewVersion(storage); - DocumentTransaction.AddDocumentToUpdate(storage.Document, new TransactWriteItemOperationConfig - { - ConditionalExpression = conditionExpression, - ReturnValuesOnConditionCheckFailure = DocumentModel.ReturnValuesOnConditionCheckFailure.None - }); + AddDocumentTransaction(storage, conditionExpression); + var objectItem = new DynamoDBContext.ObjectWithItemStorage { OriginalObject = item, @@ -437,6 +435,45 @@ private Expression CreateConditionExpressionForVersion(ItemStorage storage) DocumentTransaction.TargetTable.IsEmptyStringValueEnabled); return DynamoDBContext.CreateConditionExpressionForVersion(storage, conversionConfig); } + + + private void AddDocumentTransaction(ItemStorage storage, Expression conditionExpression) + { + var hashKeyPropertyNames = storage.Config.HashKeyPropertyNames; + var rangeKeyPropertyNames = storage.Config.RangeKeyPropertyNames; + + var attributeNames = storage.Document.Keys.ToList(); + + foreach (var keyPropertyName in hashKeyPropertyNames) + { + attributeNames.Remove(keyPropertyName); + } + + foreach (var rangeKeyPropertyName in rangeKeyPropertyNames) + { + attributeNames.Remove(rangeKeyPropertyName); + } + + // If there are no attributes left, we need to use PutItem + // as UpdateItem requires at least one data attribute + if (attributeNames.Any()) + { + DocumentTransaction.AddDocumentToUpdate(storage.Document, new TransactWriteItemOperationConfig + { + ConditionalExpression = conditionExpression, + ReturnValuesOnConditionCheckFailure = DocumentModel.ReturnValuesOnConditionCheckFailure.None + }); + } + else + { + + DocumentTransaction.AddDocumentToPut(storage.Document, new TransactWriteItemOperationConfig + { + ConditionalExpression = conditionExpression, + ReturnValuesOnConditionCheckFailure = DocumentModel.ReturnValuesOnConditionCheckFailure.None + }); + } + } private void SetNewVersion(ItemStorage storage) { diff --git a/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs b/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs index 5763cf7631ae..ec52570f756f 100644 --- a/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs +++ b/sdk/test/Services/DynamoDBv2/IntegrationTests/DataModelTests.cs @@ -183,6 +183,76 @@ public void TestContext_DisableFetchingTableMetadata_KeyWithPropertyConverter() Assert.AreEqual(employee.Name, storedEmployee.Name); } + + /// + /// Tests that disabling fetching table metadata works with a key that has a property converter. + /// + [TestMethod] + [TestCategory("DynamoDBv2")] + public void TestTransactWrite_AddSaveItem_DocumentTransaction() + { + TableCache.Clear(); + CleanupTables(); + TableCache.Clear(); + + CreateContext(DynamoDBEntryConversion.V2, true, true); + + { + + var hashRangeOnly = new AnnotatedRangeTable + { + Name = "Bob", + Age = 10 + }; + + var transactWrite = Context.CreateTransactWrite(); + transactWrite.AddSaveItem(hashRangeOnly); + transactWrite.Execute(); + + var storedHashOnly = Context.Load(hashRangeOnly.Name, hashRangeOnly.Age); + Assert.IsNotNull(storedHashOnly); + Assert.AreEqual(hashRangeOnly.Name, storedHashOnly.Name); + } + + { + var hashRangeOnly = new IgnoreAnnotatedRangeTable + { + Name = "Bob", + Age = 10, + IgnoreAttribute = 100 + }; + + var transactWrite = Context.CreateTransactWrite(); + transactWrite.AddSaveItem(hashRangeOnly); + transactWrite.Execute(); + + var storedHashOnly = Context.Load(hashRangeOnly.Name, hashRangeOnly.Age); + Assert.IsNotNull(storedHashOnly); + Assert.AreEqual(hashRangeOnly.Name, storedHashOnly.Name); + Assert.AreEqual(hashRangeOnly.Age, storedHashOnly.Age); + } + + { + var hashRangeOnly = new AnnotatedRangeTable2 + { + Name = "Bob", + Age = 10, + NotAnnotatedAttribute = 100 + }; + + var transactWrite = Context.CreateTransactWrite(); + transactWrite.AddSaveItem(hashRangeOnly); + transactWrite.Execute(); + + var storedHashOnly = Context.Load(hashRangeOnly.Name, hashRangeOnly.Age); + Assert.IsNotNull(storedHashOnly); + Assert.AreEqual(hashRangeOnly.Name, storedHashOnly.Name); + Assert.AreEqual(hashRangeOnly.Age, storedHashOnly.Age); + Assert.AreEqual(hashRangeOnly.NotAnnotatedAttribute, storedHashOnly.NotAnnotatedAttribute); + } + + } + /// /// Tests that the DynamoDB operations can retrieve attributes in UTC and local timezone. /// @@ -2039,6 +2109,34 @@ public class PropertyConverterEmployee public Status Name { get; set; } } + [DynamoDBTable("HashRangeTable")] + public class AnnotatedRangeTable + { + // Hash key + [DynamoDBHashKey] + public string Name { get; set; } + + // Range key + [DynamoDBRangeKey] + internal int Age { get; set; } + } + + [DynamoDBTable("HashRangeTable")] + public class IgnoreAnnotatedRangeTable : AnnotatedRangeTable + { + [DynamoDBIgnore] + internal int IgnoreAttribute { get; set; } + } + + + [DynamoDBTable("HashRangeTable")] + public class AnnotatedRangeTable2 : AnnotatedRangeTable + { + internal int NotAnnotatedAttribute { get; set; } + } + + + public class DateTimeUtcConverter : IPropertyConverter { public DynamoDBEntry ToEntry(object value) => (DateTime)value;