Skip to content
Open
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/264e45e8-60ef-4e07-8f70-7fb4dc554187.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"services": [
{
"serviceName": "DynamoDBv2",
"type": "patch",
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New public API is being added/changed in this PR (e.g., introducing BaseDeleteItemDocumentOperationRequest and changing DeleteItemDocumentOperationRequest's base type). Per DevConfig guidance, this should likely be a minor bump rather than patch to reflect the public surface area change.

Suggested change
"type": "patch",
"type": "minor",

Copilot uses AI. Check for mistakes.
"changeLogMessages": [
"Replace legacy parameter Expected with ConditionExpression in DeleteItem and DeleteItemAsync flow"
]
}
]
}
54 changes: 28 additions & 26 deletions sdk/src/Services/DynamoDBv2/Custom/DataModel/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -650,49 +650,51 @@ private void ApplyPostUpdate(ItemStorage storage, object value, DynamoDBFlatConf

private void DeleteHelper<[DynamicallyAccessedMembers(InternalConstants.DataModelModeledType)] T>(T value, DynamoDBFlatConfig flatConfig)
{
if (value == null) throw new ArgumentNullException("value");

flatConfig.IgnoreNullValues = true;
ItemStorage storage = ObjectToItemStorage<T>(value, true, flatConfig);
ItemStorage storage = GetItemStorage(value, flatConfig);
if (storage == null) return;

Table table = GetTargetTable(storage.Config, flatConfig);
if (flatConfig.SkipVersionCheck.Value || !storage.Config.HasVersion)
{
table.DeleteHelper(table.MakeKey(storage.Document), null);
}
else
{
Document expectedDocument = CreateExpectedDocumentForVersion(storage);
table.DeleteHelper(
table.MakeKey(storage.Document),
new DeleteItemOperationConfig { Expected = expectedDocument });
}
var operationRequest = CreateInternalDeleteItemOperationRequest(flatConfig, storage, table);
table.DeleteHelper(operationRequest);
}

private static readonly Task CompletedTask = Task.FromResult<object>(null);

private Task DeleteHelperAsync<[DynamicallyAccessedMembers(InternalConstants.DataModelModeledType)] T>(T value, DynamoDBFlatConfig flatConfig, CancellationToken cancellationToken)
{
ItemStorage storage = GetItemStorage(value, flatConfig);
if (storage == null) return CompletedTask;

Table table = GetTargetTable(storage.Config, flatConfig);
var operationRequest = CreateInternalDeleteItemOperationRequest(flatConfig, storage, table);
return table.DeleteHelperAsync(operationRequest, cancellationToken);
}

private ItemStorage GetItemStorage<[DynamicallyAccessedMembers(InternalConstants.DataModelModeledType)] T>(T value, DynamoDBFlatConfig flatConfig)
{
if (value == null) throw new ArgumentNullException("value");

flatConfig.IgnoreNullValues = true;
ItemStorage storage = ObjectToItemStorage(value, true, flatConfig);
if (storage == null) return CompletedTask;
return storage;
}

Table table = GetTargetTable(storage.Config, flatConfig);
if (flatConfig.SkipVersionCheck.Value || !storage.Config.HasVersion)
private static InternalDeleteItemDocumentOperationRequest CreateInternalDeleteItemOperationRequest(DynamoDBFlatConfig flatConfig, ItemStorage storage, Table table)
{
var operationRequest = new InternalDeleteItemDocumentOperationRequest()
{
return table.DeleteHelperAsync(table.MakeKey(storage.Document), null, cancellationToken);
}
else
Key = table.MakeKey(storage.Document)
};

if (!flatConfig.SkipVersionCheck.Value && storage.Config.HasVersion)
{
Document expectedDocument = CreateExpectedDocumentForVersion(storage);
return table.DeleteHelperAsync(
table.MakeKey(storage.Document),
new DeleteItemOperationConfig { Expected = expectedDocument },
cancellationToken);
var conversionConfig = new DynamoDBEntry.AttributeConversionConfig(table.Conversion, table.IsEmptyStringValueEnabled);
var versionExpression = CreateConditionExpressionForVersion(storage, conversionConfig);

operationRequest.ConditionalExpression = versionExpression;
}

return operationRequest;
}

#endregion
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

Expand Down Expand Up @@ -375,30 +376,54 @@ protected override Document PostProcess(UpdateItemDocumentOperationRequest reque
/// invocation, and post-processing of results. The pipeline is intended for internal use within the document model.
/// </remarks>
internal sealed class DeleteItemPipeline :
DocumentOperationPipeline<DeleteItemDocumentOperationRequest, DeleteItemRequest, DeleteItemResponse, Document>
DocumentOperationPipeline<BaseDeleteItemDocumentOperationRequest, DeleteItemRequest, DeleteItemResponse, Document>
{
public DeleteItemPipeline(Table table) : base(table) { }

protected override void Validate(DeleteItemDocumentOperationRequest request)
protected override void Validate(BaseDeleteItemDocumentOperationRequest request)
{
if (request == null) throw new ArgumentNullException(nameof(request));
if (request.Key == null || request.Key.Count == 0)
throw new InvalidOperationException("DeleteItemDocumentOperationRequest.Key cannot be null or empty.");

switch (request)
{
case DeleteItemDocumentOperationRequest deleteItemRequest:
if (deleteItemRequest.Key == null || deleteItemRequest.Key.Count == 0)
throw new InvalidOperationException("DeleteItemDocumentOperationRequest.Key cannot be null or empty.");
break;
case InternalDeleteItemDocumentOperationRequest internalDeleteItemRequest:
if (internalDeleteItemRequest.Key == null || internalDeleteItemRequest.Key.Count == 0)
throw new InvalidOperationException("InternalDeleteItemDocumentOperationRequest.Key cannot be null or empty.");
break;
default:
throw new InvalidOperationException("Unsupported type for BaseDeleteItemDocumentOperationRequest");
}

}

protected override DeleteItemRequest Map(DeleteItemDocumentOperationRequest request)
protected override DeleteItemRequest Map(BaseDeleteItemDocumentOperationRequest request)
{
var req = new DeleteItemRequest
{
TableName = Table.TableName,
Key = Table.MakeKey(request.Key)
};
switch (request)
{
case DeleteItemDocumentOperationRequest deletetemRequest:
req.Key = Table.MakeKey(deletetemRequest.Key);
break;
case InternalDeleteItemDocumentOperationRequest internalDeleteItemRequest:
req.Key = new Dictionary<string, AttributeValue>(internalDeleteItemRequest.Key);
break;
default:
throw new InvalidOperationException("Unsupported type for BaseDeleteItemDocumentOperationRequest");
}

if (request.ReturnValues == ReturnValues.AllOldAttributes)
req.ReturnValues = EnumMapper.Convert(request.ReturnValues);
return req;
}

protected override void ApplyExpressions(DeleteItemDocumentOperationRequest request, DeleteItemRequest lowLevel)
protected override void ApplyExpressions(BaseDeleteItemDocumentOperationRequest request, DeleteItemRequest lowLevel)
{
if (request.ConditionalExpression is { IsSet: true })
request.ConditionalExpression.ApplyExpression(lowLevel, Table);
Expand All @@ -424,7 +449,7 @@ protected override async Task<DeleteItemResponse> InvokeAsync(DeleteItemRequest
await Table.DDBClient.DeleteItemAsync(lowLevel, ct).ConfigureAwait(false);


protected override Document PostProcess(DeleteItemDocumentOperationRequest request, DeleteItemResponse serviceResponse)
protected override Document PostProcess(BaseDeleteItemDocumentOperationRequest request, DeleteItemResponse serviceResponse)
{
var resp = (DeleteItemResponse)serviceResponse;
if (request.ReturnValues == ReturnValues.AllOldAttributes && resp.Attributes != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Amazon.DynamoDBv2.Model;
using System;
using System.Collections.Generic;

namespace Amazon.DynamoDBv2.DocumentModel
Expand Down Expand Up @@ -269,13 +270,22 @@ public class UpdateItemDocumentOperationRequest : DocumentOperationRequest
/// Legacy parameters such as Expected are not supported.
/// Use ConditionalExpression instead.
/// </summary>
public class DeleteItemDocumentOperationRequest : DocumentOperationRequest
public class DeleteItemDocumentOperationRequest : BaseDeleteItemDocumentOperationRequest
{
/// <summary>
/// Gets or sets the key identifying the item in the table.
/// </summary>
public IDictionary<string, DynamoDBEntry> Key { get; set; }
}

/// <summary>
/// Represents a request to delete an item from a DynamoDB table using the Document Model.
/// This class introduces a modern expression-based API that replaces legacy parameter-based approaches.
/// Legacy parameters such as Expected are not supported.
/// Use ConditionalExpression instead.
/// </summary>
public class BaseDeleteItemDocumentOperationRequest : DocumentOperationRequest
{
/// <summary>
/// Gets or sets the conditional expression specifying when the item should be deleted.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Amazon.DynamoDBv2.Model;
using System.Collections.Generic;
Comment thread
aanton-git marked this conversation as resolved.

namespace Amazon.DynamoDBv2.DocumentModel
{
/// <summary>
/// Represents an internal request to delete an item from a DynamoDB table using the Document Model.
/// This class introduces a modern expression-based API that replaces legacy parameter-based approaches.
/// Legacy parameters such as Expected are not supported.
/// Use ConditionalExpression instead.
/// </summary>
internal class InternalDeleteItemDocumentOperationRequest : BaseDeleteItemDocumentOperationRequest
{
/// <summary>
/// Gets or sets the key identifying the item in the table.
/// </summary>
internal Dictionary<string, AttributeValue> Key { get; set; }
}
}
4 changes: 2 additions & 2 deletions sdk/src/Services/DynamoDBv2/Custom/DocumentModel/Table.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1755,13 +1755,13 @@ internal async Task<Document> DeleteHelperAsync(Key key, DeleteItemOperationConf
}


internal Document DeleteHelper(DeleteItemDocumentOperationRequest request)
internal Document DeleteHelper(BaseDeleteItemDocumentOperationRequest request)
{
var pipeline = new DeleteItemPipeline(this);
return pipeline.ExecuteSync(request);
}

internal async Task<Document> DeleteHelperAsync(DeleteItemDocumentOperationRequest request, CancellationToken cancellationToken)
internal async Task<Document> DeleteHelperAsync(BaseDeleteItemDocumentOperationRequest request, CancellationToken cancellationToken)
{
var pipeline = new DeleteItemPipeline(this);
return await pipeline.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;
using Amazon.DynamoDBv2.DocumentModel;
using Amazon.DynamoDBv2.Model;
using AWSSDK_DotNet.IntegrationTests.Tests.DynamoDB.Fixtures;
using static AWSSDK_DotNet.IntegrationTests.Tests.DynamoDB.DataModelContextTestHelpers;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using static AWSSDK_DotNet.IntegrationTests.Tests.DynamoDB.DataModelContextTestHelpers;

namespace AWSSDK_DotNet.IntegrationTests.Tests.DynamoDB
{
Expand Down Expand Up @@ -232,6 +232,54 @@ public async Task Test_AutoGeneratedTimestampAttribute_With_Annotations()
ApproximatelyEqual(createdAt.Value, loadedAfterUpdate.CreatedAt.Value);
}

[Fact]
public async Task TestContext_Delete_With_ExpressionCondition()
{
VersionedAnnotatedEmployee employee1 = new VersionedAnnotatedEmployee
{
Name = "Alan",
Age = 31,
CompanyName = "Big River",
CurrentStatus = Status.Active,
Score = 120,
ManagerName = "Barbara",
InternalId = "Alan@BigRiver",
Aliases = new List<string> { "Al", "Steve" },
Data = Encoding.UTF8.GetBytes("Some binary data")
};

VersionedAnnotatedEmployee employee2 = new VersionedAnnotatedEmployee
{
Name = "Alanee",
Age = 33,
CompanyName = "Big River",
CurrentStatus = Status.Active,
Score = 120,
ManagerName = "Barbara",
InternalId = "Alan@BigRiver",
Aliases = new List<string> { "Al", "Steve" },
Data = Encoding.UTF8.GetBytes("Some binary data")
};

await _context.SaveAsync(employee1);
var loadedItem = await _context.LoadAsync<VersionedAnnotatedEmployee>(employee1.Name, employee1.Age);
Assert.NotNull(loadedItem);

await _context.DeleteAsync(employee1, new DeleteConfig() { SkipVersionCheck = true });

var loadedItemAfterDelete = await _context.LoadAsync<VersionedAnnotatedEmployee>(employee1.Name, employee1.Age);
Assert.Null(loadedItemAfterDelete);

await _context.SaveAsync(employee2);
var loadedItem2 = await _context.LoadAsync<VersionedAnnotatedEmployee>(employee2.Name, employee2.Age);
Assert.NotNull(loadedItem2);

await _context.DeleteAsync(employee2);

var loadedItemAfterDelete2 = await _context.LoadAsync<VersionedAnnotatedEmployee>(employee2.Name, employee2.Age);
Assert.Null(loadedItemAfterDelete2);
}

private DynamoDBContext CreateBuilderTablesContext(DynamoDBEntryConversion conversion)
{
var context = new DynamoDBContext(_fixture.Client, new DynamoDBContextConfig
Expand Down
Loading