using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.DataModel; using Amazon.DynamoDBv2.DocumentModel; using Amazon.DynamoDBv2.Model; using Amazon.DynamoDb.Wrapper.Interfaces; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Amazon.DynamoDb.Wrapper.Extensions; namespace Amazon.DynamoDb.Wrapper.Implementations { /* * Note: * * ****** Important Methods of DynamoDBContext ******* * 1. Load: Retrieves an item from a table. The method requires only the primary key of the item you want to retrieve. * 2. Query: Queries a table based on query parameters you provide. You can query a table only if it has a composite primary key (partition key and sort key). When querying, you must specify a partition key and a condition that applies to the sort key. * 3. Scan: Performs an entire table scan. You can filter scan results by specifying a scan condition. The condition can be evaluated on any attributes in the table. * 4. Save: Saves the specified object to the table. If the primary key specified in the input object doesn't exist in the table, the method adds a new item to the table. If the primary key exists, the method updates the existing item. * 5. Delete: Deletes an item from the table. The method requires the primary key of the item you want to delete. You can provide either the primary key value or a client-side object containing a primary key value as a parameter to this method. * 6. FromQuery: Runs a Query operation, with the query parameters defined in a QueryOperationConfig object. * 7. FromScan: Runs a Scan operation, with the scan parameters defined in a ScanOperationConfig object. * Reference: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DotNetDynamoDBContext.html */ public class DynamoDBGenericRepository : IDynamoDBGenericRepository where TEntity : class { private readonly IDynamoDBContext _context; private readonly IAmazonDynamoDB _client; private readonly IDynamoDBRepository _repository; public DynamoDBGenericRepository(IDynamoDBContext context, IAmazonDynamoDB client, IDynamoDBRepository repository) { _context = context; _client = client; _repository = repository; } #region High Level API (Object Persistence Model) public async Task GetByPrimaryKeyAsync(object partitionKey) { return await _context.LoadAsync(partitionKey); } public async Task GetByPrimaryKeyAsync(object partitionKey, object sortKey) { return await _context.LoadAsync(partitionKey, sortKey); } public async Task SaveAsync(TEntity entity) { await _context.SaveAsync(entity); } public async Task DeleteAsync(TEntity entity) { await _context.DeleteAsync(entity); } public async Task DeleteAsync(object partitionKey) { await _context.DeleteAsync(partitionKey); } public async Task DeleteAsync(object partitionKey, object sortKey) { await _context.DeleteAsync(partitionKey, sortKey); } public async Task> QueryAsync(QueryOperationConfig queryOperationConfig) { var search = _context.FromQueryAsync(queryOperationConfig); var items = new List(); do { var nextSet = await search.GetNextSetAsync(); items.AddRange(nextSet); } while (!search.IsDone); return items; } public async Task> QueryAsync(QueryFilter filter, bool backwardSearch = false, string indexName = "", List? attributesToGet = null) { var queryConfig = new QueryOperationConfig(); if (attributesToGet != null && attributesToGet.Any()) { queryConfig.Select = SelectValues.SpecificAttributes; queryConfig.AttributesToGet = attributesToGet; } else { queryConfig.Select = SelectValues.AllAttributes; } if (!string.IsNullOrWhiteSpace(indexName)) { queryConfig.IndexName = indexName; } queryConfig.BackwardSearch = backwardSearch; queryConfig.Filter = filter; var search = _context.FromQueryAsync(queryConfig); var items = new List(); do { var nextSet = await search.GetNextSetAsync(); items.AddRange(nextSet); } while (!search.IsDone); return items; } public async Task> ScanAsync(ScanFilter filter, List? attributesToGet = null) { var scanConfig = new ScanOperationConfig(); scanConfig.Filter = filter; if (attributesToGet != null && attributesToGet.Any()) { scanConfig.Select = SelectValues.SpecificAttributes; scanConfig.AttributesToGet = attributesToGet; } else { scanConfig.Select = SelectValues.AllAttributes; } var search = _context.FromScanAsync(scanConfig); var items = new List(); do { var nextSet = await search.GetNextSetAsync(); items.AddRange(nextSet); } while (!search.IsDone); return items; } /// /// A batch get operation can not return more than 100 items in a request, otherwise DynamoDB will reject entire batch operation. /// public async Task> BatchGetAsync(List partitionKeys) { var batchRequest = _context.CreateBatchGet(); // add delete requests in batch if (partitionKeys != null && partitionKeys.Any()) { foreach (var item in partitionKeys) { batchRequest.AddKey(item); } } await batchRequest.ExecuteAsync(); return batchRequest.Results; } /// /// A batch get operation can not return more than 100 items in a request, otherwise DynamoDB will reject entire batch operation. /// public async Task> BatchGetAsync(List> partitionAndSortKeys) { var batchRequest = _context.CreateBatchGet(); // add delete requests in batch if (partitionAndSortKeys != null && partitionAndSortKeys.Any()) { foreach (var item in partitionAndSortKeys) { batchRequest.AddKey(item.Item1, item.Item2); } } await batchRequest.ExecuteAsync(); return batchRequest.Results; } /// /// This method puts or deletes multiple items in DynamoDB table in a batch. /// A batch can not write more than 25 items in a request, otherwise DynamoDB will reject entire batch write operation. /// /// Entities to put /// Entities to delete public async Task BatchWriteAsync(List entitiesToSave, List entitiesToDelete) { var batchRequest = _context.CreateBatchWrite(); // add save requests in batch batchRequest.AddPutItems(entitiesToSave); // add delete requests in batch if (entitiesToDelete != null && entitiesToDelete.Any()) { entitiesToDelete.ForEach(item => { batchRequest.AddDeleteKey(item); }); } await batchRequest.ExecuteAsync(); } #endregion #region Low Level API + High Level API (Hybrid) public async Task SaveAsync(TEntity entity, string conditionExpression = "") { var document = _context.ToDocument(entity); var attributeMaps = document.ToAttributeMap(); // retrieves the target table for the specified type. var tableName = GetTableName(); PutItemRequest putItemRequest = new PutItemRequest { TableName = tableName, Item = attributeMaps, }; if (!string.IsNullOrWhiteSpace(conditionExpression)) { putItemRequest.ConditionExpression = conditionExpression; } var response = await _client.PutItemAsync(putItemRequest); if (response.HttpStatusCode != System.Net.HttpStatusCode.OK) { throw new Exception($"Operation failed with status code {response.HttpStatusCode}"); } } private string GetTableName() { var tableNameAttribute = typeof(T).GetCustomAttributes(typeof(DynamoDBTableAttribute), true).FirstOrDefault() as DynamoDBTableAttribute; if (tableNameAttribute == null) { throw new Exception("DynamoDBTableAttribute not found"); } string prefix = !string.IsNullOrWhiteSpace(StartupExtentions.tableNamePrefix) ? StartupExtentions.tableNamePrefix : string.Empty; return $"{prefix}{tableNameAttribute.TableName}"; } #endregion } }