using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Collections; using System.Diagnostics; using System.Reflection; using System.IO; using System.Net; using Google.Protobuf.WellKnownTypes; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; using Amazon.DynamoDBv2.DocumentModel; using Amazon.Runtime; using StackExchange.Redis; using NLog.LayoutRenderers; using Renci.SshNet.Security; using Amazon.S3.Model; using Amazon.DynamoDBv2.DataModel; using MongoDB.Bson; using Microsoft.IdentityModel.Tokens; using ServerCore; using ServerBase; using DYNAMO_DB_TABLE_FULL_NAME = System.String; using DYNAMO_DB_TABLE_NAME = System.String; namespace ServerBase; public static class DynamoDbClientHelper { public static QueryOperationConfig makeQueryConfigForReadByPKOnly(this DynamoDbClient dbClient, string pk) { QueryFilter filter = new QueryFilter(); filter.AddCondition(PrimaryKey.PK_Define, QueryOperator.Equal, pk); QueryOperationConfig config = new QueryOperationConfig() { Filter = filter, ConsistentRead = true }; return config; } public static QueryOperationConfig makeQueryConfigWithPKOnly(this DynamoDbClient dbClient, string pk, QueryOperationConfig config) { QueryFilter filter = new QueryFilter(); filter.AddCondition(PrimaryKey.PK_Define, QueryOperator.Equal, pk); config.Filter = filter; return config; } public static QueryOperationConfig makeQueryConfigForReadByPKSK(this DynamoDbClient dbClient, string pk, string sk = DynamoDbClient.SK_EMPTY) { QueryFilter filter = new QueryFilter(); filter.AddCondition(PrimaryKey.PK_Define, QueryOperator.Equal, pk); filter.AddCondition(PrimaryKey.SK_Define, QueryOperator.Equal, sk); QueryOperationConfig config = new QueryOperationConfig() { Filter = filter, ConsistentRead = true }; return config; } public static QueryOperationConfig makeQueryConfigWithPKSKBySKBeginWith(this DynamoDbClient dbClient, string pk, string sk, QueryOperationConfig? config = null) { QueryFilter filter = new QueryFilter(); filter.AddCondition(PrimaryKey.PK_Define, QueryOperator.Equal, pk); filter.AddCondition(PrimaryKey.SK_Define, QueryOperator.BeginsWith, (sk == string.Empty ? DynamoDbClient.SK_EMPTY : sk)); if(null == config) { config = new QueryOperationConfig(); } config.Filter = filter; return config; } public static bool isEmptySK(this string _this) { if (_this == null) { return false; } if (_this == DynamoDbClient.SK_EMPTY) { return true; } return false; } public static async Task<(Result, List)> simpleQueryWithQueryOperationConfig(this Table table, QueryOperationConfig queryOperationConfig, string eventTid = "") { var result = new Result(); var err_msg = string.Empty; var documents = new List(); try { var search = table.queryWithStopwatch(queryOperationConfig, eventTid); while (search.IsDone == false) { var next_documents = await search.GetNextSetAsync(); if (null == next_documents) { return (result, documents); } foreach (var document in next_documents) { documents.Add(document); } } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbQueryException; err_msg = $"Exception !!!, Failed to perform in simpleQueryWithQueryOperationConfig() !!! : errorCode:{error_code}, exception:{e} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); return (result, documents); } return (result, documents); } public static async Task simpleInsertDocument(this Table table, Document document, string eventTid = "") { var result = new Result(); var err_msg = string.Empty; try { await table.putItemAsyncWithStopwatch(document, eventTid); } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleInsertDocument() !!! : errorCode:{error_code}, exception:{e}, {document.toBasicString()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return result; } return result; } public static async Task<(Result, Document?)> simpleUpsertDocument(this Table table, Document document, string eventTid = "") { var result = new Result(); var err_msg = string.Empty; Document? upserted_document; try { upserted_document = await table.upsertItemAsyncWithStopwatch(document, eventTid); } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleUpsertDocument() : errorCode:{error_code}, exception:{e}, {document.toBasicString()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return (result, null); } return (result, upserted_document); } public static async Task<(Result, Document?)> simpleUpdateDocument(this Table table, Document document, string eventTid = "") { var result = new Result(); var err_msg = string.Empty; Document? updated_document; try { updated_document = await table.updateItemAsyncWithStopwatch(document, new UpdateItemOperationConfig { ConditionalExpression = new Expression { ExpressionStatement = $"attribute_exists({PrimaryKey.PK_Define}) AND attribute_exists({PrimaryKey.SK_Define})" } }, eventTid); } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleUpdateDocument() !!! : errorCode:{error_code}, exception:{e}, {document.toBasicString()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return (result, null); } return (result, updated_document); } public static async Task simpleDeleteDocument(this Table table, Document document, string eventTid = "") { var result = new Result(); var err_msg = string.Empty; try { await table.deleteItemAsyncWithStopwatch(document, eventTid); } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Failed to perform in simpleDeleteDocument() !!! : : errorCode:{error_code}, exception:{e}, {document.toBasicString()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return result; } return result; } public static async Task simpleUpsertDocumentsWithBatchWrite(this Table table, List toUpsertDocuments, string eventTid = "") { var result = new Result(); var err_msg = string.Empty; try { if (toUpsertDocuments.Count > 0) { var batch = table.CreateBatchWrite(); foreach (var document in toUpsertDocuments) { batch.AddDocumentToPut(document); } await batch.executeAsyncWithStopwatch(eventTid); } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleUpsertDocumentsWithBatchWrite() !!! : errorCode:{error_code}, exception:{e}, {toUpsertDocuments.toBasicStringAll()} - tableName:{table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return result; } return result; } public static async Task simpleDeleteDocumentsWithBatchWrite(this Table table, List toDeleteDocuments, string eventTid = "") { var result = new Result(); var err_msg = string.Empty; try { if (toDeleteDocuments.Count > 0) { var batch = table.CreateBatchWrite(); foreach (var document in toDeleteDocuments) { batch.AddItemToDelete(document); } await batch.executeAsyncWithStopwatch(eventTid); } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleDeleteDocumentsWithBatchWrite() !!! : errorCode:{error_code}, exception:{e}, {toDeleteDocuments.toBasicStringAll()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return result; } return result; } public static async Task<(Result, List)> simpleTransactReadWithDocument( this Table table , List<(PrimaryKey, TransactGetItemOperationConfig?)> toReadTransactQueries , UInt16 retryCount = 3 , string eventTid = "" ) { var result = new Result(); var err_msg = string.Empty; var read_documents = new List(); var transact_get = table.CreateTransactGet(); try { foreach (var read_query in toReadTransactQueries) { var primary_key = read_query.Item1; var transact_config = read_query.Item2 != null ? read_query.Item2 : null; transact_get.AddKey( primary_key.toPartitionKey() , primary_key.toSortKey() , transact_config ); } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleTransactReadWithDocument() !!! : errorCode:{error_code}, exception:{e}, {toReadTransactQueries.toBasicStringAll()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return (result, read_documents); } // 기본(1) + retryCount 만큼 처리 한다. - kangms for (int i = 0; i <= retryCount; ++i) { if (i >= 1) { await Task.Delay(2000); } try { await transact_get.ExecuteAsync(); // 쿼리 순서와 동일하게 반환된 정보가 목록화 된다. !!! - kangms for (var k = 0; k < transact_get.Results.Count; k++) { read_documents.Add(transact_get.Results[k]); } } catch (TransactionConflictException e) { var error_code = ServerErrorCode.DynamoDbTransactionConflictException; err_msg = $"TransactionConflictException !!!, Failed to perform in simpleTransactReadWithDocument() !!! : errorCode:{error_code}, exception:{e}, {toReadTransactQueries.toBasicStringAll()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); continue; } catch (TransactionCanceledException e) { var error_code = ServerErrorCode.DynamoDbTransactionCanceledException; err_msg = $"TransactionCanceledException !!!, Failed to perform in simpleTransactReadWithDocument() !!! : errorCode:{error_code}, exception:{e}, {toReadTransactQueries.toBasicStringAll()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); continue; } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleTransactReadWithDocument() !!! : errorCode:{error_code}, exception:{e}, {toReadTransactQueries.toBasicStringAll()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); continue; } return (result, read_documents); } return (result, read_documents); } public static async Task<(Result, List)> simpleTransactWriteWithDocument( this Table table , List toWriteTransactQueries , UInt16 retryCount = 3 , string eventTid = "" ) { var result = new Result(); var err_msg = string.Empty; var result_documents = new List(); var transact_write = table.CreateTransactWrite(); try { foreach (var write_query in toWriteTransactQueries) { if (false == write_query.isValid()) { err_msg = $"DynamoDbDocumentQueryContext invalid !!! : {write_query.toBasicString()} - {table.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentIsNullInQueryContext, err_msg); Log.getLogger().error(result.toBasicString()); return (result, result_documents); } var document = write_query.getDocument(); NullReferenceCheckHelper.throwIfNull(document, () => $"document is null !!! - {write_query.toBasicString()}, {table.toBasicString()}"); if (false == document.isValid()) { err_msg = $"DynamoDbDocument invalid !!! : {document.toBasicString()} - {table.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentIsInvalid, err_msg); Log.getLogger().error(result.toBasicString()); return (result, result_documents); } var query_type = write_query.getQueryType(); switch (query_type) { case QueryType.Insert: { transact_write.AddDocumentToPut(document, DynamoDbQueryOperationOptionConfig.getTransactWriteConfigForNotExistKey()); break; } case QueryType.Update: { transact_write.AddDocumentToUpdate( document, DynamoDbQueryOperationOptionConfig.getTransactWriteConfigForExistKey()); break; } case QueryType.Upsert: { transact_write.AddDocumentToUpdate( document ); break; } case QueryType.Delete: { transact_write.AddItemToDelete( document ); break; } case QueryType.ExistsDelete: { transact_write.AddItemToDelete( document, DynamoDbQueryOperationOptionConfig.getTransactWriteConfigForExistKey()); break; } default: err_msg = $"Invalid QueryType getQueryType() !!! : queryType:{query_type} - {write_query.toBasicString()}, {document.toBasicString()}, {table.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentQueryContextTypeInvalid, err_msg); Log.getLogger().error(result.toBasicString()); return (result, result_documents); } } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleTransactWriteWithDocument() !!! : errorCode:{error_code}, exception:{e}, {toWriteTransactQueries.toBasicStringAll()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return (result, result_documents); } // 기본(1) + retryCount 만큼 처리 한다. - kangms for (int i = 0; i <= retryCount; ++i) { if (i >= 1) { await Task.Delay(2000); } try { await transact_write.executeAsyncWithStopwatch(eventTid); } catch (TransactionCanceledException e) { var error_code = ServerErrorCode.DynamoDbTransactionCanceledException; err_msg = $"TransactionCanceledException !!!, Failed to perfom in simpleTransactWriteWithDocument() !!! : Transaction Canceled, implies a client issue, fix before retrying !!! : errorCode:{error_code}, exception:{e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (TransactionConflictException e) { var error_code = ServerErrorCode.DynamoDbTransactionConflictException; err_msg = $"TransactionConflictException !!!, Failed to perfom in simpleTransactWriteWithDocument() !!! : Transaction conflict occurred !!! : errorCode:{error_code}, {e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (AmazonDynamoDBException e) { var error_code = ServerErrorCode.DynamoDbAmazonDynamoDbException; err_msg = $"AmazonDynamoDBException !!!, Failed to perfom in simpleTransactWriteWithDocument() !!! : errorCode:{error_code}, {e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (AmazonServiceException e) { var error_code = ServerErrorCode.DynamoDbAmazonServiceException; err_msg = $"AmazonServiceException !!!, Failed to perfom in simpleTransactWriteWithDocument() !!! : errorCode:{error_code}, {e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); continue; } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perfom in simpleTransactWriteWithDocument() !!! : errorCode:{error_code}, exception:{e}, {toWriteTransactQueries.toBasicStringAll()} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } return (result, result_documents); } return (result, result_documents); } public static async Task<(Result, Dictionary>)> simpleTransactReadByItemRequestWithDocument( this DynamoDbClient dynamoDbClient , List toReadTransactQueries , bool isFailOnEmptyData = false , UInt16 retryCount = 3 ) { var result = new Result(); var err_msg = string.Empty; var read_documents = new Dictionary>(); var dynamo_db_connector = dynamoDbClient.getDbClient(); NullReferenceCheckHelper.throwIfNull(dynamo_db_connector, () => $"dynamo_db_connector is null !!! - {toReadTransactQueries.toBasicStringAll()}"); var transact_get = new TransactGetItemsRequest(); try { foreach(var get_item in toReadTransactQueries) { transact_get.TransactItems.Add(get_item); } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleTransactReadByItemRequestWithDocument() !!! : errorCode:{error_code}, exception:{e}, {toReadTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return (result, read_documents); } // 기본(1) + retryCount 만큼 처리 한다. - kangms for (int i = 0; i <= retryCount; ++i) { if (i >= 1) { await Task.Delay(2000); } try { var db_response = await dynamo_db_connector.TransactGetItemsAsync(transact_get); for (var k = 0; k < transact_get.TransactItems.Count; k++) { var table_name = transact_get.TransactItems[k].Get.TableName; var item_response = db_response.Responses[k]; if(false == read_documents.TryGetValue(table_name, out var documents)) { documents = new List(); read_documents.Add(table_name, documents); } documents.Add(Document.FromAttributeMap(item_response.Item)); } if (0 >= read_documents.Count && true == isFailOnEmptyData) { result.setFail(ServerErrorCode.DynamoDbQueryNoMatchAttribute, err_msg); Log.getLogger().error(result.toBasicString()); return (result, read_documents); } } catch (TransactionConflictException e) { var error_code = ServerErrorCode.DynamoDbTransactionConflictException; err_msg = $"TransactionConflictException !!!, Failed to perform in simpleTransactReadByItemRequestWithDocument() !!! : errorCode:{error_code}, exception:{e}, {toReadTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); continue; } catch (TransactionCanceledException e) { var error_code = ServerErrorCode.DynamoDbTransactionCanceledException; err_msg = $"TransactionCanceledException !!!, Failed to perform in simpleTransactReadByItemRequestWithDocument() !!! : errorCode:{error_code}, exception:{e}, {toReadTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); continue; } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleTransactReadByItemRequestWithDocument() !!! : errorCode:{error_code}, exception:{e}, {toReadTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); continue; } return (result, read_documents); } return (result, read_documents); } public static async Task<(Result, Dictionary>)> simpleTransactWriteOfItemRequestWithDocument( this DynamoDbClient dynamoDbClient , List toWriteTransactQueries , UInt16 retryCount = 3 ) { var result = new Result(); var err_msg = string.Empty; var result_documents = new Dictionary>(); var dynamo_db_connector = dynamoDbClient.getDbClient(); NullReferenceCheckHelper.throwIfNull(dynamo_db_connector, () => $"dynamo_db_connector is null !!! - {toWriteTransactQueries.toBasicStringAll()}"); var transact_write = new TransactWriteItemsRequest(); try { foreach (var query_context in toWriteTransactQueries) { if (false == query_context.isValid()) { err_msg = $"DynamoDbDocumentQueryContext invalid !!! - {query_context.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentIsNullInQueryContext, err_msg); Log.getLogger().error(result.toBasicString()); return (result, result_documents); } var document = query_context.getDocument(); NullReferenceCheckHelper.throwIfNull(document, () => $"document is null !!!"); if (false == document.isValid()) { err_msg = $"DynamoDbDocument invalid !!! : {document.toBasicString()} - {query_context.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentIsInvalid, err_msg); Log.getLogger().error(result.toBasicString()); return (result, result_documents); } var table_full_name = query_context.getTableFullName(); var not_exist_transact_config = DynamoDbQueryOperationOptionConfig.getTransactWriteConfigForNotExistKey(); var exist_transact_config = DynamoDbQueryOperationOptionConfig.getTransactWriteConfigForExistKey(); switch (query_context.getQueryType()) { case QueryType.Insert: { var transact_write_item = new TransactWriteItem() { Put = new Put() { TableName = table_full_name, Item = document.ToAttributeMap(), ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD, }, }; transact_write_item.Put.ConditionExpression = not_exist_transact_config.ConditionalExpression.ExpressionStatement; transact_write_item.Put.ExpressionAttributeNames[$"#{PrimaryKey.PK_Define}"] = PrimaryKey.PK_Define; transact_write_item.Put.ExpressionAttributeNames[$"#{PrimaryKey.SK_Define}"] = PrimaryKey.SK_Define; transact_write.TransactItems.Add(transact_write_item); break; } case QueryType.Update: { var keys = document.keysToAttributeMap(); var transact_write_item = new TransactWriteItem() { Update = new Update() { TableName = table_full_name, Key = keys, ExpressionAttributeNames = document.toAttributeNames(), ExpressionAttributeValues = document.toAttributeValues(), ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD, } }; transact_write_item.Update.ConditionExpression = exist_transact_config.ConditionalExpression.ExpressionStatement; transact_write_item.Update.ExpressionAttributeNames[$"#{PrimaryKey.PK_Define}"] = PrimaryKey.PK_Define; transact_write_item.Update.ExpressionAttributeNames[$"#{PrimaryKey.SK_Define}"] = PrimaryKey.SK_Define; transact_write.TransactItems.Add(transact_write_item); break; } case QueryType.Upsert: { var keys = document.keysToAttributeMap(); var transact_write_item = new TransactWriteItem() { Update = new Update() { TableName = table_full_name, Key = keys, ExpressionAttributeNames = document.toAttributeNames(), ExpressionAttributeValues = document.toAttributeValues(), ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD, } }; transact_write.TransactItems.Add(transact_write_item); break; } case QueryType.Delete: { var keys = document.keysToAttributeMap(); var transact_write_item = new TransactWriteItem() { Delete = new Delete() { TableName = table_full_name, Key = keys, ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD, } }; transact_write_item.Delete.ConditionExpression = exist_transact_config.ConditionalExpression.ExpressionStatement; transact_write_item.Delete.ExpressionAttributeNames[$"#{PrimaryKey.PK_Define}"] = PrimaryKey.PK_Define; transact_write_item.Delete.ExpressionAttributeNames[$"#{PrimaryKey.SK_Define}"] = PrimaryKey.SK_Define; transact_write.TransactItems.Add(transact_write_item); break; } default: err_msg = $"Invalid QueryType getQueryType() !!! : {query_context.toBasicString()} - {document.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentQueryContextTypeInvalid, err_msg); Log.getLogger().error(result.toBasicString()); return (result, result_documents); } } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perfom in simpleTransactWriteOfItemRequestWithDocument() !!! : errorCode:{error_code}, exception:{e}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); return (result, result_documents); } // 기본(1) + retryCount 만큼 처리 한다. - kangms for (int i = 0; i <= retryCount; ++i) { if (i >= 1) { await Task.Delay(2000); } try { var db_response = await dynamo_db_connector.TransactWriteItemsAsync(transact_write); if(null != db_response) { var responses = db_response.ItemCollectionMetrics; foreach (var each in responses) { var table_name = each.Key; var collection_metrics = each.Value; foreach (var item_response in collection_metrics) { if (item_response.ItemCollectionKey != null && item_response.ItemCollectionKey.Count > 0) { if (false == result_documents.TryGetValue(table_name, out var documents)) { documents = new List(); result_documents.Add(table_name, documents); } documents.Add(Document.FromAttributeMap(item_response.ItemCollectionKey)); } } } } } catch (TransactionCanceledException e) { var error_code = ServerErrorCode.DynamoDbTransactionCanceledException; err_msg = $"TransactionCanceledException !!!, Failed to perfom in simpleTransactWriteOfItemRequestWithDocument() !!!, Transaction Canceled, implies a client issue, fix before retrying !!! : errorCode:{error_code}, {e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (TransactionConflictException e) { var error_code = ServerErrorCode.DynamoDbTransactionConflictException; err_msg = $"TransactionConflictException !!!, Failed to perfom in simpleTransactWriteOfItemRequestWithDocument() !!!, Transaction conflict occurred !!! : errorCode:{error_code}, {e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (AmazonDynamoDBException e) { var error_code = ServerErrorCode.DynamoDbAmazonDynamoDbException; err_msg = $"AmazonDynamoDBException !!!, Failed to perfom in simpleTransactWriteOfItemRequestWithDocument() !!! : errorCode:{error_code}, {e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (AmazonServiceException e) { var error_code = ServerErrorCode.DynamoDbAmazonServiceException; err_msg = $"AmazonServiceException !!!, Failed to perfom in simpleTransactWriteOfItemRequestWithDocument() !!! : errorCode:{error_code}, {e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); continue; } catch (Exception e) { var error_code = ServerErrorCode.TryCatchException; err_msg = $"Exception !!!, Failed to perfom in simpleTransactWriteOfItemRequestWithDocument() !!! : : errorCode:{error_code}, exception:{e}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } return (result, result_documents); } return (result, result_documents); } public static async Task<(Result, Dictionary>)> simpleTransactReadOfItemRequestWithItemRequest( this DynamoDbClient dynamoDbClient , List toReadTransactQueries , bool isFailOnEmptyData = false , UInt16 retryCount = 3) { var result = new Result(); var err_msg = string.Empty; var dynamo_db_connector = dynamoDbClient.getDbClient(); NullReferenceCheckHelper.throwIfNull(dynamo_db_connector, () => $"dynamo_db_connector is null !!! - {toReadTransactQueries.toBasicStringAll()}"); var read_documents = new Dictionary>(); var transact_get = new TransactGetItemsRequest(); try { foreach (var get_item in toReadTransactQueries) { transact_get.TransactItems.Add(get_item); } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleTransactReadOfItemRequestWithItemRequest() !!! : errorCode:{error_code}, exception:{e}, {toReadTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return (result, read_documents); } // 기본(1) + retryCount 만큼 처리 한다. - kangms for (int i = 0; i <= retryCount; ++i) { if (i >= 1) { await Task.Delay(2000); } try { var db_response = await dynamo_db_connector.TransactGetItemsAsync(transact_get); for (var k = 0; k < transact_get.TransactItems.Count; k++) { var table_name = transact_get.TransactItems[k].Get.TableName; var item_response = db_response.Responses[k]; if (false == read_documents.TryGetValue(table_name, out var documents)) { documents = new List(); read_documents.Add(table_name, documents); } documents.Add(Document.FromAttributeMap(item_response.Item)); } if (0 >= read_documents.Count && true == isFailOnEmptyData) { result.setFail(ServerErrorCode.DynamoDbQueryNoMatchAttribute, err_msg); Log.getLogger().error(result.toBasicString()); return (result, read_documents); } } catch (TransactionCanceledException e) { var error_code = ServerErrorCode.DynamoDbTransactionCanceledException; err_msg = $"TransactionCanceledException !!!, Failed to perform in simpleTransactReadOfItemRequestWithItemRequest() !!!, Transaction Canceled, implies a client issue, fix before retrying !!! : errorCode:{error_code}, {e.toExceptionString()}, {toReadTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (TransactionConflictException e) { var error_code = ServerErrorCode.DynamoDbTransactionConflictException; err_msg = $"TransactionConflictException !!!, Failed to perform in simpleTransactReadOfItemRequestWithItemRequest() !!!, Transaction conflict occurred !!! : errorCode:{error_code}, {e.toExceptionString()}, {toReadTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (AmazonDynamoDBException e) { var error_code = ServerErrorCode.DynamoDbAmazonDynamoDbException; err_msg = $"AmazonDynamoDBException !!!, Failed to perform in simpleTransactReadOfItemRequestWithItemRequest() !!! : errorCode:{error_code}, {e.toExceptionString()}, {toReadTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (AmazonServiceException e) { var error_code = ServerErrorCode.DynamoDbAmazonServiceException; err_msg = $"AmazonServiceException !!!, Failed to perform in simpleTransactReadOfItemRequestWithItemRequest() !!! : errorCode:{error_code}, {e.toExceptionString()}, {toReadTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); continue; } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleTransactReadOfItemRequestWithItemRequest() !!! : errorCode:{error_code}, exception:{e}, {toReadTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } return (result, read_documents); } return (result, read_documents); } public static async Task<(Result, Dictionary>)> simpleTransactWriteOfItemRequestWithItemRequest( this DynamoDbClient dynamoDbClient , List toWriteTransactQueries , UInt16 retryCount = 3) { var result = new Result(); var err_msg = string.Empty; var dynamo_db_connector = dynamoDbClient.getDbClient(); NullReferenceCheckHelper.throwIfNull(dynamo_db_connector, () => $"dynamo_db_connector is null !!! - {toWriteTransactQueries.toBasicStringAll()}"); var result_documents = new Dictionary>(); var transact_write = new TransactWriteItemsRequest(); try { foreach (var request in toWriteTransactQueries) { var transact_write_item = new TransactWriteItem(); if(request is PutItemRequest put_request) { var put = new Put(); put.TableName = put_request.TableName; put.Item = put_request.Item; put.ConditionExpression = put_request.ConditionExpression; put.ExpressionAttributeNames = put_request.ExpressionAttributeNames; put.ExpressionAttributeValues = put_request.ExpressionAttributeValues; put.ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD; put.fillupTimestamps(); transact_write_item.Put = put; } else if (request is UpdateItemRequest update_request) { var update = new Update(); update.TableName = update_request.TableName; update.Key = update_request.Key; update.ExpressionAttributeNames = update_request.ExpressionAttributeNames; update.ExpressionAttributeValues = update_request.ExpressionAttributeValues; update.ConditionExpression = update_request.ConditionExpression; update.UpdateExpression = update_request.UpdateExpression; update.ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD; var timestamps_result = update.tryFillupTimestamps(); if(timestamps_result.isFail()) { Log.getLogger().error($"Failed to tryFillupTimestamps() !!!, in simpleTransactWriteOfItemRequestWithItemRequest() !!! : {timestamps_result.toBasicString()}"); } transact_write_item.Update = update; } else if((request is DeleteItemRequest delete_request)) { var delete = new Delete(); delete.TableName = delete_request.TableName; delete.Key = delete_request.Key; delete.ReturnValuesOnConditionCheckFailure = Amazon.DynamoDBv2.ReturnValuesOnConditionCheckFailure.ALL_OLD; transact_write_item.Delete = delete; } else { err_msg = $"Invalid dbRequest of AmazonDynamoDBRequest !!! : dbRequest:{request.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbRequestInvalid, err_msg); Log.getLogger().error(result.toBasicString()); return (result, result_documents); } transact_write.TransactItems.Add(transact_write_item); } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleTransactWriteOfItemRequestWithItemRequest() !!! : errorCode:{error_code}, exception:{e}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); return (result, result_documents); } // 기본(1) + retryCount 만큼 처리 한다. - kangms for (int i = 0; i <= retryCount; ++i) { if (i >= 1) { await Task.Delay(2000); } try { var db_response = await dynamo_db_connector.TransactWriteItemsAsync(transact_write); if (null != db_response) { var responses = db_response.ItemCollectionMetrics; foreach (var each in responses) { var table_name = each.Key; var collection_metrics = each.Value; foreach (var item_response in collection_metrics) { if (item_response.ItemCollectionKey != null && item_response.ItemCollectionKey.Count > 0) { if (false == result_documents.TryGetValue(table_name, out var documents)) { documents = new List(); result_documents.Add(table_name, documents); } documents.Add(Document.FromAttributeMap(item_response.ItemCollectionKey)); } } } } } catch (TransactionCanceledException e) { var error_code = ServerErrorCode.DynamoDbTransactionCanceledException; err_msg = $"TransactionCanceledException !!!, Failed to perform in simpleTransactWriteOfItemRequestWithItemRequest() !!!, Transaction Canceled, implies a client issue, fix before retrying !!! : errorCode:{error_code}, {e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (TransactionConflictException e) { var error_code = ServerErrorCode.DynamoDbTransactionConflictException; err_msg = $"TransactionConflictException !!!, Failed to perform in simpleTransactWriteOfItemRequestWithItemRequest() !!!, Transaction conflict occurred !!! : errorCode:{error_code}, {e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (AmazonDynamoDBException e) { var error_code = ServerErrorCode.DynamoDbAmazonDynamoDbException; err_msg = $"AmazonDynamoDBException !!!, Failed to perform in simpleTransactWriteOfItemRequestWithItemRequest() !!! : errorCode:{error_code}, {e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } catch (AmazonServiceException e) { var error_code = ServerErrorCode.DynamoDbAmazonServiceException; err_msg = $"AmazonServiceException !!!, Failed to perform in simpleTransactWriteOfItemRequestWithItemRequest() !!! : errorCode:{error_code}, {e.toExceptionString()}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); continue; } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in simpleTransactWriteOfItemRequestWithItemRequest() !!! : errorCode:{error_code}, exception:{e}, {toWriteTransactQueries.toBasicStringAll()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); break; } return (result, result_documents); } return (result, result_documents); } //========================================================================================= // UpdateItemRequest 기반 간단한 쿼리 실행 하기 //========================================================================================= public static async Task<(Result, Document?)> simpleQueryWithUpdateItemRequest( this DynamoDbClient dynamoDbClient , UpdateItemRequest updateItemRequest, bool isUpsert = false, string eventTid ="") { var result = new Result(); var err_msg = string.Empty; Document? updated_doc; try { updateItemRequest.ReturnValues = "ALL_NEW"; var dynamo_db_connector = dynamoDbClient.getDbClient(); NullReferenceCheckHelper.throwIfNull(dynamo_db_connector, () => $"dynamo_db_connector is null !!! - {updateItemRequest.toBasicString()}"); result = updateItemRequest.tryFillupTimestamps(isUpsert); if(result.isFail()) { return (result, null); } var response = await dynamo_db_connector.updateItemAsyncWithStopwatch(updateItemRequest, eventTid); var updated_item = response.Attributes; updated_doc = Document.FromAttributeMap(updated_item); } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbQueryException; err_msg = $"Exception !!!, Failed to perform in simpleQueryWithUpdateItemRequest() !!! : errorCode:{error_code}, exception:{e}, {updateItemRequest.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); return (result, null); } return (result, updated_doc); } //========================================================================================= // 스캔 기반 검색 설정 만들어 반환 하기 //========================================================================================= public static ScanOperationConfig makeScanConfigWithPKByPKBeginWith(this DynamoDbClient dynamoDbClient, string pk) { var filter = new ScanFilter(); filter.AddCondition("PK", ScanOperator.BeginsWith, pk); var config = new ScanOperationConfig() { Filter = filter, ConsistentRead = true }; return config; } //========================================================================================= // 스캔 기반 검색 : 호출시 DB 성능 저하 주의, 서버 테스트 목적으로 사용 고려 //========================================================================================= public static async Task<(Result, List)> simpleScanWithScanOperationConfig(this Table table, ScanOperationConfig scanOperationConfig) { var result = new Result(); var err_msg = string.Empty; var documents = new List(); try { var search = table.Scan(scanOperationConfig); while (search.IsDone == false) { var next_documents = await search.GetNextSetAsync(); if (null == next_documents) { return (result, documents); } foreach (var document in next_documents) { documents.Add(document); } } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbQueryException; err_msg = $"Exception !!!, Failed to perform in simpleScanWithScanOperationConfig() !!! : errorCode:{error_code}, exception:{e} - {table.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(err_msg); return (result, documents); } return (result, documents); } public static async Task<(Result, bool)> isExistPrimaryKey( this DynamoDbClient dynamoDbClient , DYNAMO_DB_TABLE_NAME tableName, PrimaryKey primaryKey , bool isFailOnEmptyData = false , bool isConsistentRead = false ) { var result = new Result(); var err_msg = string.Empty; if (tableName.isNullOrWhiteSpace()) { err_msg = $"Invalid TableName of DynamoDb !!!, Null of WhiteSpace : tableName:{tableName} - {primaryKey.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbTableNameInvalid, err_msg); return (result, false); } var dynamo_db_connector = dynamoDbClient.getDbClient(); NullReferenceCheckHelper.throwIfNull(dynamo_db_connector, () => $"dynamo_db_connector is null !!! - {primaryKey.toBasicString()}, tableName:{tableName}"); // GetItemRequest 설정 var get_request = new GetItemRequest { TableName = tableName, Key = primaryKey.toKeyWithAttributeValue(), ProjectionExpression = $"{PrimaryKey.PK_Define},{PrimaryKey.SK_Define}", ConsistentRead = isConsistentRead // 일관된 읽기를 사용하지 않음 (성능 최적화) }; try { var response = await dynamo_db_connector.GetItemAsync(get_request); if (false == response.IsItemSet) { err_msg = $"Not found PrimaryKey in DynamoDb !!! : {primaryKey.toBasicString()}, tableName:{tableName}"; if (true == isFailOnEmptyData) { result.setFail(ServerErrorCode.DynamoDbQueryNoMatchAttribute, err_msg); Log.getLogger().error(result.toBasicString()); return (result, false); } Log.getLogger().warn(err_msg); return (result, false); } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbQueryException; err_msg = $"Exception !!!, Failed to perform in isExistPrimaryKey() !!! : errorCode:{error_code}, exception:{e}, {primaryKey.toBasicString()}, tableName:{tableName}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return (result, false); } return (result, true); } //========================================================================================= // Document => AmazonDynamoDBRequest 변환해 준다. //========================================================================================= public static (Result, DynamoDbItemRequestQueryContext?) toItemRequestQueryContext( this Amazon.DynamoDBv2.DocumentModel.Document document , DYNAMO_DB_TABLE_FULL_NAME tableFullName, QueryType queryType ) { var result = new Result(); var err_msg = string.Empty; if (tableFullName.isNullOrWhiteSpace()) { err_msg = $"Invalid TableFullName of DynamoDb !!!, Null of WhiteSpace : tableFullName:{tableFullName}, QueryType:{queryType} - {document.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbTableNameInvalid, err_msg); return (result, null); } (result, var primary_key) = document.toPrimaryKey(); if(result.isFail()) { return (result, null); } NullReferenceCheckHelper.throwIfNull(primary_key, () => $"primary_key is null !!! : {document.toBasicString()} - QueryType:{queryType}, tableFullName:{tableFullName}"); DynamoDbItemRequestQueryContext? item_request_query_context; switch (queryType) { case QueryType.Insert: { var to_insert_query_builder = new DynamoDbItemRequestHelper.PutItemRequestBuilder(tableFullName); to_insert_query_builder.withItem(document.ToAttributeMap()); item_request_query_context = new DynamoDbItemRequestQueryContext(to_insert_query_builder.build(), queryType); } break; case QueryType.Update: { var to_update_query_builder = new DynamoDbItemRequestHelper.UpdateItemRequestBuilder(tableFullName); to_update_query_builder.withKeys(primary_key.toKeyWithAttributeValue()); to_update_query_builder.withDocument(document); (result, var update_item_request) = to_update_query_builder.build(); if(result.isFail()) { return (result, null); } NullReferenceCheckHelper.throwIfNull(update_item_request, () => $"update_item_request is null !!! - QueryType:{queryType}, tableFullName:{tableFullName}"); item_request_query_context = new DynamoDbItemRequestQueryContext(update_item_request, queryType); } break; case QueryType.Upsert: { var to_upsert_query_builder = new DynamoDbItemRequestHelper.UpdateItemRequestBuilder(tableFullName, true); to_upsert_query_builder.withKeys(primary_key.toKeyWithAttributeValue()); to_upsert_query_builder.withDocument(document); (result, var update_item_request) = to_upsert_query_builder.build(); if (result.isFail()) { return (result, null); } NullReferenceCheckHelper.throwIfNull(update_item_request, () => $"update_item_request is null !!! - QueryType:{queryType}, tableFullName:{tableFullName}"); item_request_query_context = new DynamoDbItemRequestQueryContext(update_item_request, queryType); } break; case QueryType.Delete: { var to_delete_query_builder = new DynamoDbItemRequestHelper.DeleteItemRequestBuilder(tableFullName); to_delete_query_builder.withKeys(primary_key.toKeyWithAttributeValue()); item_request_query_context = new DynamoDbItemRequestQueryContext(to_delete_query_builder.build(), queryType); } break; default: err_msg = $"Invalid QueryType !!! : QueryType:{queryType} - {document.toBasicString()}, tableFullName:{tableFullName}"; result.setFail(ServerErrorCode.DbQueryTypeInvalid, err_msg); return (result, null); } return (result, item_request_query_context); } //========================================================================================= // DynamoDbDocBase.ATTRIBUTE_PATH Json 정보 => ExpressionAttributeNames 정보로 변환해 준다. //========================================================================================= public static Dictionary toExpressionAttributeNamesFromJson(string jsonString, string targetLeafKey) { (var is_success, var fillup_key_paths) = maekKeyPathsFromJson(jsonString, targetLeafKey); var path_parts = fillup_key_paths.Split('.'); return path_parts.Distinct().ToDictionary(part => "#" + part, part => part); } //========================================================================================= // JsonString => AttributeExpression 정보로 변환해 준다. //========================================================================================= public static (bool, string) toAttributeExpressionFromJson(string jsonString, string targetLeafKey) { (var is_success, var fillup_key_paths) = maekKeyPathsFromJson(jsonString, targetLeafKey); if(is_success) { var parts = fillup_key_paths.Split('.'); for (var i = 0; i < parts.Length; i++) { parts[i] = "#" + parts[i]; } return (true, string.Join(".", parts)); } return (false, string.Empty); } //========================================================================================= // JsonString => AttributePath 정보로 변환해 준다. (AttributePath : Path.Path.Path ...) //========================================================================================= public static (bool, string) maekKeyPathsFromJson(string jsonString, string targetLeafKey) { if(true == JsonHelper.fillupKeyPaths(jsonString, targetLeafKey, out var fillup_key_paths)) { return (true, fillup_key_paths); } return (false, string.Empty); } public static Dictionary keysToAttributeMap(this Amazon.DynamoDBv2.DocumentModel.Document document) { var key = new Dictionary(); foreach (var kvp in document) { if ( PrimaryKey.PK_Define == kvp.Key || PrimaryKey.SK_Define == kvp.Key ) { key[kvp.Key] = new AttributeValue { S = kvp.Value.AsString() }; } } return key; } public static Dictionary toAttributeNames(this Amazon.DynamoDBv2.DocumentModel.Document document, string? parentPath = null) { var attribute_names = new Dictionary(); foreach (var kvp in document) { if ( kvp.Key != PrimaryKey.PK_Define && kvp.Key != PrimaryKey.SK_Define ) { string attribute_path = parentPath != null ? $"{parentPath}.{kvp.Key}" : kvp.Key; string placeholder_name = $"#{attribute_path.Replace('.', '_')}"; string placeholder_value = $":{attribute_path.Replace('.', '_')}"; if (kvp.Value is Document nestedDoc) { toAttributeNames(nestedDoc, attribute_path); } else if (kvp.Value is Primitive primitiveValue) { attribute_names[placeholder_name] = kvp.Key; } } } return attribute_names; } public static Dictionary toAttributeValues(this Amazon.DynamoDBv2.DocumentModel.Document document, string? parentPath = null) { var attribute_values = new Dictionary(); foreach (var kvp in document) { if (kvp.Key != PrimaryKey.PK_Define && kvp.Key != PrimaryKey.SK_Define) { string attribute_path = parentPath != null ? $"{parentPath}.{kvp.Key}" : kvp.Key; string placeholder_value = $":{attribute_path.Replace('.', '_')}"; if (kvp.Value is Document nested_doc) { toAttributeValues(nested_doc, attribute_path); } else if (kvp.Value is Primitive primitiveValue) { attribute_values[placeholder_value] = convertToAttributeValue(placeholder_value); } } } return attribute_values; } public static AttributeValue convertToAttributeValue(Primitive primitive) { if (primitive.Type == DynamoDBEntryType.String) { return new AttributeValue { S = primitive.AsString() }; } if (primitive.Type == DynamoDBEntryType.Numeric) { return new AttributeValue { N = primitive.AsString() }; } if (primitive.Type == DynamoDBEntryType.Binary) { return new AttributeValue { B = new System.IO.MemoryStream(primitive.AsByteArray()) }; } throw new InvalidOperationException("Unsupported Primitive type"); } public static Amazon.DynamoDBv2.DocumentModel.Document classToDynamoDbDocument(T classObject) { NullReferenceCheckHelper.throwIfNull(classObject, () => $"classObject is null !!!"); var document = new Amazon.DynamoDBv2.DocumentModel.Document(); var type = classObject.GetType(); foreach (var property in type.GetProperties()) { string attribute_name = string.Empty; var json_property = property.GetCustomAttribute(); if ( null != json_property && null != json_property.PropertyName && false == json_property.PropertyName.isNullOrWhiteSpace() ) { attribute_name = json_property.PropertyName; } else { attribute_name = property.Name; } var value = property.GetValue(classObject); if (value == null) { var err_msg = $"Return null is property.GetValue() !!! - attributeName:{attribute_name}"; Log.getLogger().warn(err_msg); continue; } document[attribute_name] = convertToDynamoDBEntry(value); } return document; } public static Amazon.DynamoDBv2.DocumentModel.DynamoDBEntry convertToDynamoDBEntry(object value) { var type = value.GetType(); if (type.isPrimitiveType() && false == type.isBool()) { return new Primitive(value.ToString(), true); } else if(type.isBool() || type.isString() || type.isEnum()) { return new Primitive(value.ToString()); } else if (type.isTimestamp()) { Timestamp timestamp = (Timestamp)value; DateTime dateTime = timestamp.ToDateTime(); return new Primitive(dateTime.toStringWithUtcIso8601()); } else if (type.isDateTime()) { DateTime dateTime = (DateTime)value; return new Primitive(dateTime.toStringWithUtcIso8601()); } else if (type.isTimeSpan()) { TimeSpan timeSpan = (TimeSpan)value; return new Primitive(timeSpan.ToString("G")); } else if (type.isByteArray()) { return new Primitive((byte[])value); } else if (type.isList()) { var list = value as IList; NullReferenceCheckHelper.throwIfNull(list, () => $"list is null !!!"); var dynamo_db_list = new DynamoDBList(); foreach (var element in list) { dynamo_db_list.Add(convertToDynamoDBEntry(element)); } return dynamo_db_list; } else if (type.isDictionary()) { var dictionary = value as IDictionary; NullReferenceCheckHelper.throwIfNull(dictionary, () => $"dictionary is null !!!"); var dynamo_db_dictionary = new Amazon.DynamoDBv2.DocumentModel.Document(); foreach (DictionaryEntry entry in dictionary) { NullReferenceCheckHelper.throwIfNull(entry.Value, () => $"entry.Value is null !!!"); dynamo_db_dictionary[entry.Key.ToString()] = convertToDynamoDBEntry(entry.Value); } return dynamo_db_dictionary; } // Class 타입인 경우 !!! else { return classToDynamoDbDocument(value); } } public static T dynamoDbDocumentToClass(Amazon.DynamoDBv2.DocumentModel.DynamoDBEntry dbEntry) where T : class, new() { return (T) dynamoDbDocumentToObject(dbEntry, typeof(T)); } public static object dynamoDbDocumentToObject(Amazon.DynamoDBv2.DocumentModel.DynamoDBEntry dbEntry, System.Type toCreateType) { // targetType이 string인 경우 빈 문자열로 초기화, 그렇지 않으면 Activator.CreateInstance 사용 object? created_object; if (toCreateType == typeof(string)) { created_object = string.Empty; } else { created_object = Activator.CreateInstance(toCreateType); NullReferenceCheckHelper.throwIfNull(created_object, () => $"created_object is null !!! - toCreateType:{toCreateType.getTypeName()}"); } var type = created_object.GetType(); if ( type.isPrimitiveType() || type.isString() || type.isBool() || type.isEnum() || type.isDateTime() || type.isTimeSpan() || type.isByteArray()) { return dynamoDbEntryToValue(dbEntry, created_object); } else if (toCreateType.isList()) { var argument_type = type.GetGenericArguments()[0]; var list = (IList?)Activator.CreateInstance(typeof(List<>).MakeGenericType(argument_type)); NullReferenceCheckHelper.throwIfNull(list, () => $"list is null !!!"); var list_in_dynamodb = dbEntry as Amazon.DynamoDBv2.DocumentModel.DynamoDBList; if (null == list_in_dynamodb) { var err_msg = $"Failed to cast DynamoDBList from DynamoDB !!!, for List : DynamoDBEntry:{dbEntry.ToString()} - createdObject:{created_object.getTypeName()}"; Log.getLogger().fatal(err_msg); return list; } foreach (var db_entry in list_in_dynamodb.Entries) { list.Add(dynamoDbDocumentToObject(db_entry, argument_type)); } return list; } else if (type.isDictionary()) { var key_Type = type.GetGenericArguments()[0]; var value_type = type.GetGenericArguments()[1]; var dictionary = (IDictionary?)Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(key_Type, value_type)); NullReferenceCheckHelper.throwIfNull(dictionary, () => $"dictionary is null !!!"); var dictionary_in_dynamodb = dbEntry as Amazon.DynamoDBv2.DocumentModel.Document; if (null == dictionary_in_dynamodb) { var err_msg = $"Failed to cast Document from DynamoDB !!!, for Dictionary : DynamoDBEntry:{dbEntry.ToString()} - createdObject:{created_object.getTypeName()}"; Log.getLogger().fatal(err_msg); return dictionary; } foreach (var kvp in dictionary_in_dynamodb) { object element_key_document = kvp.Key; if (key_Type.isEnum()) { try { element_key_document = System.Enum.Parse(key_Type, kvp.Key); } catch (Exception e) { var err_msg = $"Exception !!!, Failed to perform in dynamoDbDocumentToObject() !!! : exception:{e}, KeyOfDictionary:{key_Type.Name} == KeyOfDocument:{element_key_document.getTypeName()} - createdObject:{created_object.getTypeName()}"; Log.getLogger().error(err_msg); continue; } } dictionary.Add(element_key_document, dynamoDbDocumentToObject(kvp.Value, value_type)); } return dictionary; } else { var document_in_dynamodb = dbEntry as Amazon.DynamoDBv2.DocumentModel.Document; if (null == document_in_dynamodb) { var err_msg = $"Failed to cast Document from DynamoDB !!!, for Class : DynamoDBEntry:{dbEntry.ToString()} - createdObject:{created_object.getTypeName()}"; Log.getLogger().fatal(err_msg); return created_object; } var has_properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var property in has_properties) { var json_property = property.GetCustomAttribute(); string attribute_name = string.Empty; if (null != json_property && null != json_property.PropertyName) { attribute_name = json_property.PropertyName; } else { attribute_name = property.Name; } if (false == document_in_dynamodb.TryGetValue(attribute_name, out var child_db_entry)) { var err_msg = $"Not found Child DynamoDBEntry from DynamoDB !!!, for Property : PropertyName:{attribute_name} - createdObject:{created_object.getTypeName()}"; Log.getLogger().warn(err_msg); continue; } property.SetValue(created_object, dynamoDbDocumentToObject(child_db_entry, property.PropertyType)); } } return created_object; } public static object dynamoDbEntryToValue(Amazon.DynamoDBv2.DocumentModel.DynamoDBEntry dbEntry, object toValue) { var targetType = toValue.GetType(); if (dbEntry is Amazon.DynamoDBv2.DocumentModel.Primitive primitive) { if (targetType.isPrimitiveType() && !targetType.isBool()) { if (primitive.Type == DynamoDBEntryType.Numeric) { return primitive.toNumericValue(targetType); } } else if (targetType.isString()) { if (primitive.Type == DynamoDBEntryType.String) { return primitive.Value; } } else if (targetType.isBool()) { if (primitive.Type == DynamoDBEntryType.String) { var bool_string = primitive.AsString(); if (bool.TryParse(bool_string, out var bool_value)) { return bool_value; } } } else if (targetType.isEnum()) { if (primitive.Type == DynamoDBEntryType.String) { var enum_string = primitive.AsString(); if (true == System.Enum.TryParse(targetType, enum_string, out var enum_value)) { return enum_value; } } } else if (targetType.isDateTime()) { if (primitive.Type == DynamoDBEntryType.String) { var date_string = primitive.AsString(); if (true == DateTime.TryParse(date_string, null, System.Globalization.DateTimeStyles.RoundtripKind, out var date_value)) { return date_value; } } } else if (targetType.isTimeSpan()) { if (primitive.Type == DynamoDBEntryType.String) { var time_span_string = primitive.AsString(); if (true == TimeSpan.TryParse(time_span_string, out var time_span_value)) { return time_span_value; } } } else if (targetType.isByteArray()) { if (primitive.Type == DynamoDBEntryType.Binary) { return primitive.AsByteArray(); } } } var err_msg = $"Invalid Primitive of DynamoDbClientHelper.dynamoDbEntryToValue() !!! : targetType:{targetType.getTypeName()}, DynamoDBEntry:{dbEntry.ToString()}"; Log.getLogger().fatal(err_msg); throw new TypeAccessException(err_msg); } public static bool setNumericValue(this PropertyInfo property, object targetObject, Primitive primitive) { var type = property.PropertyType; if(type.isShort() || type.isInt16()) { property.SetValue(targetObject, primitive.AsShort()); } else if (type.isInt() || type.isInt32()) { property.SetValue(targetObject, primitive.AsInt()); } else if (type.isLong()) { property.SetValue(targetObject, primitive.AsLong()); } else if(type.isUShort() || type.isUInt16()) { property.SetValue(targetObject, primitive.AsUShort()); } else if (type.isUInt() || type.isUInt32()) { property.SetValue(targetObject, primitive.AsUInt()); } else if (type.isULong()) { property.SetValue(targetObject, primitive.AsULong()); } else if (type.isFloat()) { property.SetValue(targetObject, primitive.AsSingle()); } else if(type.isDouble()) { property.SetValue(targetObject, primitive.AsDouble()); } else { var err_msg = $"Invalid PrimitiveType !!!, setNumericValue() : primitive:{type.Name}"; Log.getLogger().warn(err_msg); return false; } return true; } public static object toNumericValue( this Primitive primitive, System.Type typeOfValue ) { var type = typeOfValue; if(type.isShort() || type.isInt16()) { return primitive.AsShort(); } else if (type.isInt() || type.isInt32()) { return primitive.AsInt(); } else if (type.isLong()) { return primitive.AsLong(); } else if(type.isUShort() || type.isUInt16()) { return primitive.AsUShort(); } else if (type.isUInt() || type.isUInt32()) { return primitive.AsUInt(); } else if (type.isULong()) { return primitive.AsULong(); } else if (type.isFloat()) { return primitive.AsSingle(); } else if(type.isDouble()) { return primitive.AsDouble(); } else { var err_msg = $"Invalid System.Type !!!, toNumericValue() : System.Type:{type.Name}"; Log.getLogger().error(err_msg); return 0; } } public static bool isJsonEncoded(this DynamoDBEntry dbEntry) { if ( dbEntry is Primitive primitive && primitive.Type == DynamoDBEntryType.String ) { var value = primitive.AsString(); try { JToken.Parse(value); return true; } catch (JsonReaderException) { return false; } } return false; } public static bool isNumeric(this DynamoDBEntryType type) { if (DynamoDBEntryType.Numeric == type) { return true; } return false; } public static bool isString(this DynamoDBEntryType type) { if (DynamoDBEntryType.String == type) { return true; } return false; } public static bool isBinary(this DynamoDBEntryType type) { if (DynamoDBEntryType.Binary == type) { return true; } return false; } public static bool isPrimitive(this Amazon.DynamoDBv2.DocumentModel.DynamoDBEntry dbEntry) { var primitive = dbEntry as Amazon.DynamoDBv2.DocumentModel.Primitive; if (null == primitive) { return false; } return true; } public static bool isDocument(this Amazon.DynamoDBv2.DocumentModel.DynamoDBEntry dbEntry) { var document = dbEntry as Amazon.DynamoDBv2.DocumentModel.Document; if (null == document) { return false; } return true; } public static Int64 makeTTLTimeForDynamoDB(DateTime expireDate) { // DateTime 객체를 DateTimeOffset 객체로 변환 한다. var dateTimeOffset = new DateTimeOffset(expireDate); // 주의 : DynamoDb TTL 요구 사항 !!! // DateTimeOffset 객체를 현재시간에 Unix 기준시까지 경과된 시간을 초로 변환 (현재시간 ~ 1970년 1월 1일 00:00:00 UTC 까지의 경과시간) return dateTimeOffset.ToUnixTimeSeconds(); } public static DYNAMO_DB_TABLE_NAME getTableName(this AmazonDynamoDBRequest dbRequest) { var table_name = string.Empty; switch (dbRequest) { case PutItemRequest put: table_name = put.TableName; break; case UpdateItemRequest update: table_name = update.TableName; break; case DeleteItemRequest delete: table_name = delete.TableName; break; case QueryRequest query: table_name = query.TableName; break; case ScanRequest scan: table_name = scan.TableName; break; } ConditionValidCheckHelper.throwIfFalseWithCondition(() => false == table_name.isNullOrWhiteSpace(), () => $"Invalid TableName !!! - {dbRequest.getTypeName()}"); var err_msg = $"Invalid AmazonDynamoDBRequest of DynamoDbClientHelper.getTableName() !!! : dbRequest:{dbRequest.getTypeName()}"; Log.getLogger().fatal(err_msg); throw new TypeAccessException(err_msg); } public static async Task logDetail(this TransactWriteItemsResponse response) { await Task.CompletedTask; var sb = new StringBuilder(); sb.AppendLine($"HTTP Status Code: {response.HttpStatusCode}"); sb.AppendLine($"Request ID: {response.ResponseMetadata.RequestId}"); if (response.HttpStatusCode == HttpStatusCode.OK) { sb.AppendLine($"TransactWriteItemsResponse succeeded."); Log.getLogger().debug(sb.ToString()); } else { sb.AppendLine($"TransactWriteItems did not fully succeed !!!"); Log.getLogger().error(sb.ToString()); } sb = new StringBuilder(); if (response.ConsumedCapacity != null && response.ConsumedCapacity.Count > 0) { sb.AppendLine($"Consumed Capacity:"); foreach (var capacity in response.ConsumedCapacity) { sb.AppendLine($"Table: {capacity.TableName}, CapacityUnits: {capacity.CapacityUnits}"); } } if (response.ItemCollectionMetrics != null && response.ItemCollectionMetrics.Count > 0) { sb.AppendLine($"Item Collection Metrics:"); foreach (var metrics in response.ItemCollectionMetrics) { sb.AppendLine($"Table: {metrics.Key}"); foreach (var metric in metrics.Value) { sb.AppendLine($"SizeEstimateRangeGB: {string.Join(", ", metric.SizeEstimateRangeGB)}"); } } } Log.getLogger().debug(sb.ToString()); } public static async Task logDetail(this TransactGetItemsResponse response, TransactGetItemsRequest request) { await Task.CompletedTask; var sb = new StringBuilder(); sb.AppendLine($"HTTP Status Code: {response.HttpStatusCode}"); sb.AppendLine($"Request ID: {response.ResponseMetadata.RequestId}"); if (response.HttpStatusCode == HttpStatusCode.OK) { sb.AppendLine("TransactGetItemsResponse succeeded."); } else { sb.AppendLine("TransactGetItemsResponse did not fully succeed !!!"); } sb = new StringBuilder(); if (response.ConsumedCapacity != null && response.ConsumedCapacity.Count > 0) { sb.AppendLine("Consumed Capacity:"); foreach (var capacity in response.ConsumedCapacity) { sb.AppendLine($"Table: {capacity.TableName}, CapacityUnits: {capacity.CapacityUnits}"); } } sb.AppendLine("Item Collection Responses:"); if (request.TransactItems != null && request.TransactItems.Count > 0 && response.Responses != null) { for (int i = 0; i < response.Responses.Count; i++) { var request_item = request.TransactItems[i].Get; var table_name = request_item.TableName; var item_response = response.Responses[i]; sb.AppendLine($"Table: {table_name}"); sb.AppendLine(Document.FromAttributeMap(item_response.Item).ToJsonPretty()); } } else { sb.AppendLine("No items returned."); } Log.getLogger().debug(sb.ToString()); } public static async Task logDetail(this UpdateItemResponse response) { await Task.CompletedTask; var sb = new StringBuilder(); sb.AppendLine($"HTTP Status Code: {response.HttpStatusCode}"); sb.AppendLine($"Request ID: {response.ResponseMetadata.RequestId}"); if (response.HttpStatusCode == HttpStatusCode.OK) { sb.AppendLine($"UpdateItemResponse succeeded."); if (response.Attributes != null && response.Attributes.Count > 0) { sb.AppendLine($"Updated Attributes:"); foreach (var attribute in response.Attributes) { sb.AppendLine($"{attribute.Key}: {attribute.Value.S ?? attribute.Value.N}"); } } Log.getLogger().debug(sb.ToString()); } else { sb.AppendLine($"UpdateItemResponse did not succeed !!!"); Log.getLogger().error(sb.ToString()); } } public static async Task logDetail(this BatchWriteItemResponse response) { await Task.CompletedTask; var sb = new StringBuilder(); sb.AppendLine($"HTTP Status Code: {response.HttpStatusCode}"); sb.AppendLine($"Request ID: {response.ResponseMetadata.RequestId}"); if (response.HttpStatusCode == System.Net.HttpStatusCode.OK) { sb.AppendLine($"BatchWriteItemResponse succeeded."); Log.getLogger().debug(sb.ToString()); } else { sb.AppendLine($"BatchWriteItemResponse did not fully succeed !!!"); Log.getLogger().error(sb.ToString()); } if (response.UnprocessedItems != null && response.UnprocessedItems.Count > 0) { sb.AppendLine($"BatchWriteItemResponse Unprocessed Items:"); foreach (var table in response.UnprocessedItems) { sb.AppendLine($"Table: {table.Key}"); foreach (var item in table.Value) { if (item.PutRequest != null) { sb.AppendLine($"Unprocessed PutRequest:"); sb.AppendLine(Document.FromAttributeMap(item.PutRequest.Item).ToJsonPretty()); } if (item.DeleteRequest != null) { sb.AppendLine($"Unprocessed DeleteRequest:"); sb.AppendLine(Document.FromAttributeMap(item.DeleteRequest.Key).ToJsonPretty()); } } } Log.getLogger().error(sb.ToString()); } } public static async Task logDetail(this BatchGetItemResponse response) { await Task.CompletedTask; var sb = new StringBuilder(); sb.AppendLine($"HTTP Status Code: {response.HttpStatusCode}"); sb.AppendLine($"Request ID: {response.ResponseMetadata.RequestId}"); if (response.Responses != null && response.Responses.Count > 0) { sb.AppendLine($"BatchGetItemResponse succeeded."); foreach (var table in response.Responses) { sb.AppendLine($"Table: {table.Key}"); foreach (var item in table.Value) { sb.AppendLine(Document.FromAttributeMap(item).ToJsonPretty()); } } Log.getLogger().debug(sb.ToString()); } else { sb.AppendLine("BatchGetItemResponse did not return any items !!!"); Log.getLogger().error(sb.ToString()); } if (response.UnprocessedKeys != null && response.UnprocessedKeys.Count > 0) { sb.AppendLine("BatchGetItemResponse Unprocessed Keys:"); foreach (var table in response.UnprocessedKeys) { sb.AppendLine($"Table: {table.Key}"); foreach (var key in table.Value.Keys) { sb.AppendLine(Document.FromAttributeMap(key).ToJsonPretty()); } } Log.getLogger().error(sb.ToString()); } } public static string toBasicStringAll(this TransactGetItemsRequest request) { var sb = new StringBuilder(); sb.AppendLine("TransactGetItemsRequest : "); // TransactItems의 각 항목에 대해 로그 작성 if (request.TransactItems != null && request.TransactItems.Count > 0) { int itemCount = 1; foreach (var transactItem in request.TransactItems) { sb.AppendLine($"Item {itemCount++}:"); sb.AppendLine($"TableName: {transactItem.Get.TableName}"); // Key 정보를 AttributeMap 형태로 출력 if (transactItem.Get.Key != null) { sb.AppendLine("Key:"); foreach (var key in transactItem.Get.Key) { sb.AppendLine($"{key.Key}: {key.Value.ToJson()}"); } } else { sb.AppendLine("Key: No key provided."); } if (transactItem.Get.ProjectionExpression != null) { sb.AppendLine($"ProjectionExpression: {transactItem.Get.ProjectionExpression}"); } else { sb.AppendLine("ProjectionExpression: None."); } } } else { sb.AppendLine("No TransactItems found in the request."); } return sb.ToString(); } public static string toBasicStringAll(this List toStringGetItemRequests) { var sb = new StringBuilder(); sb.AppendLine($"List : "); foreach (var db_request in toStringGetItemRequests) { sb.AppendLine(db_request.toBasicString()); } return sb.ToString(); } public static string toBasicString(this AmazonDynamoDBRequest dbRequest) { var sb = new StringBuilder(); sb.AppendLine($"AmazonDynamoDBRequest: {dbRequest.getTypeName()}"); // 각 ItemRequest 별 상세 정보를 구성 한다. switch (dbRequest) { case PutItemRequest putItemRequest: sb.AppendLine($"TableName: {putItemRequest.TableName}"); sb.AppendLine("Item:"); foreach (var kvp in putItemRequest.Item) { sb.AppendLine($" {kvp.Key}: {kvp.Value.S ?? kvp.Value.N ?? kvp.Value.B?.ToString() ?? "null"}"); } break; case UpdateItemRequest updateItemRequest: sb.AppendLine($"TableName: {updateItemRequest.TableName}"); sb.AppendLine($"Key: {getKeyString(updateItemRequest.Key)}"); sb.AppendLine($"ExpressionAttributeNames: {getAttributeNamesString(updateItemRequest.ExpressionAttributeNames)}"); sb.AppendLine($"ExpressionAttributeValues: {getAttributeValuesString(updateItemRequest.ExpressionAttributeValues)}"); sb.AppendLine($"UpdateExpression: {updateItemRequest.UpdateExpression}"); break; case DeleteItemRequest deleteItemRequest: sb.AppendLine($"TableName: {deleteItemRequest.TableName}"); sb.AppendLine($"Key: {getKeyString(deleteItemRequest.Key)}"); break; case QueryRequest queryRequest: sb.AppendLine($"TableName: {queryRequest.TableName}"); sb.AppendLine($"KeyConditionExpression: {queryRequest.KeyConditionExpression}"); break; case ScanRequest scanRequest: sb.AppendLine($"TableName: {scanRequest.TableName}"); sb.AppendLine($"Limit: {scanRequest.Limit}"); break; default: sb.AppendLine($"Unknown ItemRequest Type !!! : AmazonDynamoDBRequest:{dbRequest.getTypeName()}"); break; } return sb.ToString(); } public static string toBasicStringAll(this List toStringDbRequests) { var sb = new StringBuilder(); sb.AppendLine($"List : "); foreach (var db_request in toStringDbRequests) { sb.AppendLine(db_request.toBasicString()); } return sb.ToString(); } public static string toBasicStringAll(this List toStringQueryContexts) { var sb = new StringBuilder(); sb.AppendLine($"List : "); foreach (var query_context in toStringQueryContexts) { sb.AppendLine(query_context.toBasicString()); } return sb.ToString(); } public static string toBasicStringAll(this List<(PrimaryKey, TransactGetItemOperationConfig?)> toQueryDatas) { var sb = new StringBuilder(); sb.AppendLine($"List<(PrimaryKey, TransactGetItemOperationConfig?)> : "); foreach (var query_data in toQueryDatas) { sb.AppendLine(query_data.Item1.toBasicString()); sb.AppendLine(query_data.Item2 != null ? query_data.Item2.toBasicString() : ""); } return sb.ToString(); } public static string toBasicString(this TransactGetItemOperationConfig toStringOperationConfig) { var sb = new StringBuilder(); sb.AppendLine("TransactGetItemOperationConfig : "); var value = toStringOperationConfig.ProjectionExpression != null ? toStringOperationConfig.ProjectionExpression.toBasicString() : ""; sb.AppendLine(value); return sb.ToString(); } public static string toBasicStringAll(this List toStringGetItems) { var sb = new StringBuilder(); sb.AppendLine($"List : "); foreach (var get_item in toStringGetItems) { sb.AppendLine(get_item.Get.toBasicString()); } return sb.ToString(); } public static string toBasicString(this Get get) { var sb = new StringBuilder(); sb.AppendLine($"TableName: {get.TableName}"); if (get.Key != null && get.Key.Count > 0) { sb.AppendLine("Key:"); sb.AppendLine($" {getAttributeValuesString(get.Key)}"); } if (false == get.ProjectionExpression.isNullOrWhiteSpace()) { sb.AppendLine($"ProjectionExpression: {get.ProjectionExpression}"); } if (null != get.ExpressionAttributeNames) { sb.AppendLine($"ExpressionAttributeNames: {getAttributeNamesString(get.ExpressionAttributeNames)}"); } return sb.ToString(); } public static string toBasicStringAll(this List documents) { var sb = new StringBuilder(); sb.AppendLine($"List : "); foreach (var document in documents) { sb.AppendLine(document.toBasicString()); } return sb.ToString(); } public static string toBasicString(this Document document) { return $"document:{document?.ToJsonPretty()}"; } public static string toBasicString(this Expression expression) { var sb = new StringBuilder(); if (expression is Expression projectionExpression) { sb.AppendLine("Projection Expression: "); sb.AppendLine($"Expression: {projectionExpression.ToString()}"); } else { sb.AppendLine("Unknown Expression Type: "); sb.AppendLine(expression.ToString()); } return sb.ToString(); } private static string getKeyString(Dictionary key) { var sb = new StringBuilder(); foreach (var kvp in key) { sb.Append($"{kvp.Key}:{kvp.Value.S ?? kvp.Value.N ?? "null"} "); } return sb.ToString(); } private static string getAttributeNamesString(Dictionary attributeNames) { var sb = new StringBuilder(); foreach (var name in attributeNames) { sb.Append($"{name.Key}:{name.Value ?? "null"} "); } return sb.ToString(); } private static string getAttributeValuesString(Dictionary attributeValues) { var sb = new StringBuilder(); foreach (var value in attributeValues) { sb.Append($"{value.Key}:{value.Value.S ?? value.Value.N ?? "null"} "); } return sb.ToString(); } public static string toBasicString(this Table table) { return $"TableName:{table.TableName}"; } }