/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ using System; using System.Collections.Generic; using System.Linq; using System.Threading; #if AWS_ASYNC_API using System.Threading.Tasks; #endif using Amazon.DynamoDBv2.Model; namespace Amazon.DynamoDBv2.DocumentModel { /// /// Class for retrieving multiple Documents from a single DynamoDB table in a transaction. /// public partial class DocumentTransactGet { #region Internal properties internal Table TargetTable { get; private set; } internal List Items { get; private set; } #endregion #region Public properties /// /// List of results retrieved from DynamoDB. /// Populated after Execute is called. /// public List Results { get; internal set; } #endregion #region Constructor /// /// Constructs a DocumentTransactGet instance for a specific table. /// /// Table to get items from. public DocumentTransactGet(Table targetTable) { TargetTable = targetTable; Items = new List(); } #endregion #region Public methods /// /// Add a single item to get, identified by its hash primary key, /// using the specified expression to identify the attributes to retrieve. /// /// Hash key element of the item to get. /// /// An expression that identifies one or more attributes of the specified item to retrieve from the table. /// public void AddKey(Primitive hashKey, Expression projectionExpression) { AddKey(hashKey, new TransactGetItemOperationConfig { ProjectionExpression = projectionExpression }); } /// /// Add a single item to get, identified by its hash primary key, using the specified config. /// /// Hash key element of the item to get. /// Configuration to use. public void AddKey(Primitive hashKey, TransactGetItemOperationConfig operationConfig = null) { AddKey(hashKey, rangeKey: null, operationConfig); } /// /// Add a single item to get, identified by its hash-and-range primary key, /// using the specified expression to identify the attributes to retrieve. /// /// Hash key element of the item to get. /// Range key element of the item to get. /// /// An expression that identifies one or more attributes of the specified item to retrieve from the table. /// public void AddKey(Primitive hashKey, Primitive rangeKey, Expression projectionExpression) { AddKey(hashKey, rangeKey, new TransactGetItemOperationConfig { ProjectionExpression = projectionExpression }); } /// /// Add a single item to get, identified by its hash-and-range primary key, using the specified config. /// /// Hash key element of the item to get. /// Range key element of the item to get. /// Configuration to use. public void AddKey(Primitive hashKey, Primitive rangeKey, TransactGetItemOperationConfig operationConfig = null) { AddKeyHelper(TargetTable.MakeKey(hashKey, rangeKey), operationConfig); } /// /// Add a single item to get, identified by its key, /// using the specified expression to identify the attributes to retrieve. /// /// Key of the item to get. /// /// An expression that identifies one or more attributes of the specified item to retrieve from the table. /// public void AddKey(IDictionary key, Expression projectionExpression) { AddKey(key, new TransactGetItemOperationConfig { ProjectionExpression = projectionExpression }); } /// /// Add a single item to get, identified by its key, using the specified config. /// /// Key of the item to get. /// Configuration to use. public void AddKey(IDictionary key, TransactGetItemOperationConfig operationConfig = null) { AddKeyHelper(TargetTable.MakeKey(key), operationConfig); } /// /// Creates a MultiTableDocumentTransactGet object that is a combination /// of the current DocumentTransactGet and the specified DocumentTransactGet. /// /// Other DocumentTransactGet object. /// /// MultiTableDocumentTransactGet consisting of the two DocumentTransactGet objects. /// public MultiTableDocumentTransactGet Combine(DocumentTransactGet otherTransactionPart) { return new MultiTableDocumentTransactGet(this, otherTransactionPart); } #endregion #region Internal/private methods internal void ExecuteHelper() { var items = GetMultiTransactGet().GetItems(); Results = items.Values.SingleOrDefault() ?? new List(); } #if AWS_ASYNC_API internal async Task ExecuteHelperAsync(CancellationToken cancellationToken) { var items = await GetMultiTransactGet().GetItemsAsync(cancellationToken).ConfigureAwait(false); Results = items.Values.SingleOrDefault() ?? new List(); } #endif internal void AddKeyHelper(Key key, TransactGetItemOperationConfig operationConfig = null) { Items.Add(new TransactGetRequestItem { Key = key, TransactionPart = this, OperationConfig = operationConfig }); } private MultiTransactGet GetMultiTransactGet() { return new MultiTransactGet { Items = Items.ToList() }; } #endregion } /// /// Class for retrieving multiple Documents from multiple DynamoDB tables in a transaction. /// public partial class MultiTableDocumentTransactGet { #region Properties /// /// List of DocumentTransactGet objects to include in the multi-table /// transaction request. /// public List TransactionParts { get; private set; } #endregion #region Constructor /// /// Constructs a MultiTableDocumentTransactGet object from a number of /// DocumentTransactGet objects. /// /// Collection of DocumentTransactGet objects. public MultiTableDocumentTransactGet(params DocumentTransactGet[] transactionParts) { if (transactionParts == null) throw new ArgumentNullException(nameof(transactionParts)); TransactionParts = new List(transactionParts); } #endregion #region Public methods /// /// Add a DocumentTransactGet object to the multi-table transaction request. /// /// DocumentTransactGet to add. public void AddTransactionPart(DocumentTransactGet transactionPart) { TransactionParts.Add(transactionPart); } #endregion #region Internal/private methods internal void ExecuteHelper() { var items = GetMultiTransactGet().GetItems(); foreach (var transactionPart in TransactionParts) { items.TryGetValue(transactionPart, out var results); transactionPart.Results = results ?? new List(); } } #if AWS_ASYNC_API internal async Task ExecuteHelperAsync(CancellationToken cancellationToken) { var items = await GetMultiTransactGet().GetItemsAsync(cancellationToken).ConfigureAwait(false); foreach (var transactionPart in TransactionParts) { items.TryGetValue(transactionPart, out var results); transactionPart.Results = results ?? new List(); } } #endif private MultiTransactGet GetMultiTransactGet() { return new MultiTransactGet { Items = TransactionParts.SelectMany(x => x.Items).ToList() }; } #endregion } internal class MultiTransactGet { #region Properties public List Items { get; set; } #endregion #region Public methods public Dictionary> GetItems() { return GetItemsHelper(); } #if AWS_ASYNC_API public Task>> GetItemsAsync(CancellationToken cancellationToken) { return GetItemsHelperAsync(cancellationToken); } #endif #endregion #region Private helper methods private Dictionary> GetItemsHelper() { if (Items == null || !Items.Any()) return new Dictionary>(); var request = ConstructRequest(isAsync: false); var dynamoDbClient = Items[0].TransactionPart.TargetTable.DDBClient; var response = dynamoDbClient.TransactGetItems(request); return GetDocuments(response.Responses); } #if AWS_ASYNC_API private async Task>> GetItemsHelperAsync(CancellationToken cancellationToken) { if (Items == null || !Items.Any()) return new Dictionary>(); var request = ConstructRequest(isAsync: true); var dynamoDbClient = Items[0].TransactionPart.TargetTable.DDBClient; var response = await dynamoDbClient.TransactGetItemsAsync(request, cancellationToken).ConfigureAwait(false); return GetDocuments(response.Responses); } #endif private TransactGetItemsRequest ConstructRequest(bool isAsync) { var transactItems = Items.Select(item => item.GetRequest()).ToList(); var request = new TransactGetItemsRequest { TransactItems = transactItems }; Items[0].TransactionPart.TargetTable.AddRequestHandler(request, isAsync); return request; } private Dictionary> GetDocuments(List responses) { var docs = new Dictionary>(); foreach (var entry in responses.Select((response, idx) => new { response.Item, Items[idx].TransactionPart })) { var item = entry.Item; var transactionPart = entry.TransactionPart; if (item == null || // If the Item property is set to a non-null value in the response, // its type will always be AlwaysSendDictionary, not simply Dictionary. // Therefore, if the type is Dictionary, we can infer it was not set or was set to null. item.GetType() == typeof(Dictionary)) { continue; } if (!docs.TryGetValue(transactionPart, out var partDocs)) { partDocs = new List(); docs[transactionPart] = partDocs; } var document = transactionPart.TargetTable.FromAttributeMap(item); partDocs.Add(document); } return docs; } #endregion } internal class TransactGetRequestItem { #region Properties public Key Key { get; set; } public DocumentTransactGet TransactionPart { get; set; } public TransactGetItemOperationConfig OperationConfig { get; set; } #endregion #region Methods public TransactGetItem GetRequest() { var currentConfig = OperationConfig ?? new TransactGetItemOperationConfig(); var get = new Get { Key = Key, TableName = TransactionPart.TargetTable.TableName }; currentConfig.ProjectionExpression?.ApplyExpression(get, TransactionPart.TargetTable); return new TransactGetItem { Get = get }; } #endregion } }