using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net.WebSockets; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Amazon.S3.Model; using Amazon.DynamoDBv2.DocumentModel; using Amazon.DynamoDBv2.Model; using Amazon.DynamoDBv2; using Amazon.Runtime.Internal.Transform; using Amazon.DynamoDBv2.DataModel; using Microsoft.AspNetCore.Components.Web; using ServerCore; using ServerBase; using DYNAMO_DB_TABLE_NAME = System.String; using DYNAMO_DB_TABLE_FULL_NAME = System.String; using DB_TIMESTAMP = System.String; namespace ServerBase; public static class DynamoDBDocBaseHelper { public static async Task<(Result, PrimaryKey?)> makePrimaryKey(string PK, string SK = DynamoDbClient.SK_EMPTY) where TDoc : DynamoDbDocBase, new() { await Task.CompletedTask; var result = new Result(); var err_msg = string.Empty; var doc = new TDoc(); doc.setCombinationKeyForPKSK(PK, SK); var error_code = doc.onApplyPKSK(); if (error_code.isFail()) { err_msg = $"Failed to onApplyPKSK() !!! : {doc.getPrimaryKey().toBasicString()}, Params(PK:{PK}, SK:{SK}), Doc:{typeof(TDoc).Name}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return (result, null); } return (result, doc.getPrimaryKey()); } public static Dictionary toKeyWithAttributeValue(this PrimaryKey primaryKey) { var key = new Dictionary { { PrimaryKey.PK_Define , new AttributeValue { S = primaryKey.PK } } , { PrimaryKey.SK_Define , new AttributeValue { S = primaryKey.SK } } }; return key; } public static Primitive toPartitionKey(this PrimaryKey primaryKey) { ConditionValidCheckHelper.throwIfFalseWithCondition( () => false == primaryKey.PK.isNullOrWhiteSpace() , () => $"Invalid PK, Null or WhiteSpace !!!" ); return new Primitive(primaryKey.PK); } public static Primitive? toSortKey(this PrimaryKey primaryKey) { if (true == primaryKey.SK.isNullOrWhiteSpace()) return null; return new Primitive(primaryKey.SK); } public static Result tryFillupUpdateItemRequest( this Amazon.DynamoDBv2.DocumentModel.Document document , UpdateItemRequest updateItemRequest , bool isUpsert = false ) { var result = new Result(); var err_msg = string.Empty; if (false == document.isValid()) { err_msg = $"DynamoDbDocument invalid !!! : {document.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentIsInvalid, err_msg); return result; } var update_expression = "SET "; var expression_attribute_names = new Dictionary(); var expression_attribute_values = new Dictionary(); var expression_idx = 0; foreach ( var each in document.ToAttributeMap() ) { if( each.Key == PrimaryKey.PK_Define || each.Key == PrimaryKey.SK_Define ) { continue; } if (expression_idx > 0) { update_expression += ", "; } var placeholder_key = $"#{each.Key}"; var placeholder_value = $":{each.Key}"; update_expression += $"{placeholder_key} = {placeholder_value}"; expression_idx++; expression_attribute_names.Add(placeholder_key, each.Key); expression_attribute_values.Add(placeholder_value, each.Value); } updateItemRequest.UpdateExpression = update_expression; updateItemRequest.ExpressionAttributeNames = expression_attribute_names; updateItemRequest.ExpressionAttributeValues = expression_attribute_values; updateItemRequest.ReturnValues = "ALL_NEW"; if( false == isUpsert && null != updateItemRequest.ConditionExpression && true == updateItemRequest.ConditionExpression.isNullOrWhiteSpace() ) { updateItemRequest.ConditionExpression = $"attribute_exists({PrimaryKey.PK_Define}) AND attribute_exists({PrimaryKey.SK_Define})"; } return result; } public static async Task tryFillupTimestampsForUpsert( this Document document , DynamoDbClient dynamoDbClient, DYNAMO_DB_TABLE_NAME tableName) { var result = new Result(); (result, bool is_found) = await dynamoDbClient.checkIfDocumentExists(tableName, document); if(result.isFail()) { return result; } // 데이터가 존재하는 경우 if(true == is_found) { result = document.tryFillupTimestamps(QueryType.Update); if (result.isFail()) { return result; } } // 데이터가 없는 경우 else { result = document.tryFillupTimestamps(QueryType.Insert); if (result.isFail()) { return result; } } return result; } public static void fillupTimestamps(this Amazon.DynamoDBv2.Model.Put put) { var attribute_values = put.Item; NullReferenceCheckHelper.throwIfNull(attribute_values, () => $"attributes is null !!!"); fillupAttributeValueWithTimestampsForInsert(attribute_values, DateTimeHelper.Current); } public static Result tryFillupTimestamps(this Amazon.DynamoDBv2.Model.Update update, bool isUpsert = false) { var result = new Result(); var err_msg = string.Empty; var statement = update.UpdateExpression; NullReferenceCheckHelper.throwIfNull(statement, () => $"attribute_names is null !!!"); var attribute_names = update.ExpressionAttributeNames; NullReferenceCheckHelper.throwIfNull(attribute_names, () => $"attribute_names is null !!!"); var attribute_values = update.ExpressionAttributeValues; NullReferenceCheckHelper.throwIfNull(attribute_values, () => $"attribute_values is null !!!"); result = tryFillupTimestamps( ref statement , attribute_names, attribute_values , DateTimeHelper.Current , isUpsert ); if (result.isFail()) { return result; } update.UpdateExpression = statement; return result; } public static Result tryFillupTimestamps(this Amazon.DynamoDBv2.DocumentModel.Expression expression, bool isUpsert = false) { var result = new Result(); var err_msg = string.Empty; var statement = expression.ExpressionStatement; NullReferenceCheckHelper.throwIfNull(statement, () => $"statement is null !!!"); var attribute_names = expression.ExpressionAttributeNames; NullReferenceCheckHelper.throwIfNull(attribute_names, () => $"attribute_names is null !!!"); var attribute_values = expression.ExpressionAttributeValues; NullReferenceCheckHelper.throwIfNull(attribute_values, () => $"attribute_values is null !!!"); result = tryFillupTimestamps( ref statement , attribute_names, attribute_values , DateTimeHelper.Current , isUpsert ); if (result.isFail()) { return result; } expression.ExpressionStatement = statement; return result; } public static void fillupTimestamps(this PutItemRequest putItemRequest) { var attributes = putItemRequest.Item; NullReferenceCheckHelper.throwIfNull(attributes, () => $"attributes is null !!!"); fillupAttributeValueWithTimestampsForInsert(attributes, DateTimeHelper.Current); } public static Result tryFillupTimestamps(this UpdateItemRequest updateItemRequest, bool isUpsert = false) { var result = new Result(); var err_msg = string.Empty; var statement = updateItemRequest.UpdateExpression; NullReferenceCheckHelper.throwIfNull(statement, () => $"statement is null !!! - {updateItemRequest.toBasicString()}"); var attribute_names = updateItemRequest.ExpressionAttributeNames; NullReferenceCheckHelper.throwIfNull(attribute_names, () => $"attribute_names is null !!! - {updateItemRequest.toBasicString()}"); var attribute_values = updateItemRequest.ExpressionAttributeValues; NullReferenceCheckHelper.throwIfNull(attribute_values, () => $"attribute_values is null !!! - {updateItemRequest.toBasicString()}"); result = tryFillupTimestamps( ref statement , attribute_names, attribute_values , DateTimeHelper.Current , isUpsert ); if(result.isFail()) { return result; } updateItemRequest.UpdateExpression = statement; return result; } private static Result tryFillupTimestamps( ref string statement , Dictionary attributeNames, Dictionary attributeValues , DateTime currentTime , bool isUpsert = false ) { var result = new Result(); var err_msg = string.Empty; result = fillupStatementForTimestamps(ref statement, isUpsert); if (result.isFail()) { return result; } fillupAttributeNameForTimestamps(attributeNames, isUpsert); fillupAttributeValueWithTimestamps(attributeValues, currentTime, isUpsert); return result; } private static Result tryFillupTimestamps( ref string statement , Dictionary attributeNames, Dictionary attributeValues , DateTime currentTime , bool isUpsert = false ) { var result = new Result(); var err_msg = string.Empty; result = fillupStatementForTimestamps(ref statement, isUpsert); if (result.isFail()) { return result; } fillupAttributeNameForTimestamps(attributeNames, isUpsert); fillupDynamoDBEntryWithTimestamps(attributeValues, currentTime, isUpsert); return result; } private static Result fillupStatementForTimestamps(ref string statement, bool isUpsert = false) { var result = new Result(); var err_msg = string.Empty; if (statement.isNullOrWhiteSpace()) { return result; } if (false == statement.Contains("SET")) { err_msg = $"Not found SET keyword in ExpressionStatement !!!"; result.setFail(ServerErrorCode.DynamoDbExpressionError, err_msg); Log.getLogger().error(err_msg); return result; } // Expression 설정: UpdatedTime을 설정하고, 항상 UpdatedTime 갱신 한다. if (false == statement.Contains($"#{DynamoDbDocBase.UpdatedDateTime}")) { if (true == statement.Contains("#") || true == statement.Contains(":")) { statement += ","; } statement += $" #{DynamoDbDocBase.UpdatedDateTime} = :{DynamoDbDocBase.UpdatedDateTime}"; } if (true == isUpsert) { // Expression 설정: 아이템이 없을 때 CreatedTime을 설정하고, 항상 UpdatedTime 갱신 한다. if (false == statement.Contains($"#{DynamoDbDocBase.CreatedDateTime}")) { statement += $", #{DynamoDbDocBase.CreatedDateTime} = if_not_exists(#{DynamoDbDocBase.CreatedDateTime}, :{DynamoDbDocBase.CreatedDateTime})"; } } return result; } private static void fillupAttributeValueWithTimestampsForInsert( Dictionary attributeValues, DateTime currentTime ) { // 현재 시간 설정 var current_time = currentTime.toStringWithUtcIso8601(); attributeValues[DynamoDbDocBase.CreatedDateTime] = new AttributeValue { S = current_time }; attributeValues[DynamoDbDocBase.UpdatedDateTime] = new AttributeValue { S = current_time }; } private static void fillupAttributeNameForTimestamps( Dictionary attributeNames , bool isUpsert = false ) { attributeNames[$"#{DynamoDbDocBase.UpdatedDateTime}"] = $"{DynamoDbDocBase.UpdatedDateTime}"; if (true == isUpsert) { attributeNames[$"#{DynamoDbDocBase.CreatedDateTime}"] = $"{DynamoDbDocBase.CreatedDateTime}"; } } private static void fillupAttributeValueWithTimestamps( Dictionary attributeValues , DateTime currentTime , bool isUpsert = false ) { // 현재 시간 설정 var current_time = currentTime.toStringWithUtcIso8601(); attributeValues[$":{DynamoDbDocBase.UpdatedDateTime}"] = new AttributeValue { S = current_time }; if (true == isUpsert) { attributeValues[$":{DynamoDbDocBase.CreatedDateTime}"] = new AttributeValue { S = current_time }; } } private static void fillupDynamoDBEntryWithTimestamps( Dictionary attributeValues , DateTime currentTime , bool isUpsert = false ) { attributeValues[$":{DynamoDbDocBase.CreatedDateTime}"] = currentTime; if (true == isUpsert) { attributeValues[$":{DynamoDbDocBase.CreatedDateTime}"] = currentTime; } } public static async Task<(Result, bool)> checkIfDocumentExists( this DynamoDbClient dbClient , DYNAMO_DB_TABLE_NAME tableName , Document toCheckDocument , bool isConsistentRead = false) { ArgumentNullReferenceCheckHelper.throwIfNull(toCheckDocument, () => $"toCheckDocument is null !!!"); var db_connector = dbClient.getDbClient(); NullReferenceCheckHelper.throwIfNull(db_connector, () => $"db_connector is null !!! - {toCheckDocument.toBasicString()}, tableName:{tableName}"); var result = new Result(); var err_msg = string.Empty; var projection_field = DynamoDbDocBase.CreatedDateTime; (result, var primary_key) = toCheckDocument.toPrimaryKey(); if(result.isFail()) { return (result, false); } NullReferenceCheckHelper.throwIfNull(primary_key, () => $"primary_key is null !!! - {toCheckDocument.toBasicString()}, tableName:{tableName}"); // GetItemRequest 설정 var get_request = new GetItemRequest { TableName = tableName, Key = primary_key.toKeyWithAttributeValue(), ProjectionExpression = projection_field, ConsistentRead = isConsistentRead // 일관된 읽기를 사용하지 않음 (성능 최적화) }; try { var response = await db_connector.GetItemAsync(get_request); if(false == response.IsItemSet) { err_msg = $"Not found PrimaryKey in DynamoDb !!! - DbTable:{tableName}, {primary_key.toBasicString()}"; Log.getLogger().warn(err_msg); return (result, false); } if (true == response.Item.TryGetValue(projection_field, out var found_date_time)) { toCheckDocument[projection_field] = found_date_time.S; } } catch (Exception e) { err_msg = $"Exception !!!, Failed to perform in checkIfPrimaryKeyExists() !!! : exception:{e} - {primary_key.toBasicString()}, tableName:{tableName}"; result.setFail(ServerErrorCode.DynamoDbException, err_msg); Log.getLogger().error(result.toBasicString()); return (result, false); } return (result, true); } public static DynamoDbDocumentQueryContext createDocumentQueryContext( this Amazon.DynamoDBv2.DocumentModel.Document document , DYNAMO_DB_TABLE_FULL_NAME tableFullName , QueryType queryType , DynamoDbQueryExceptionNotifier.ExceptionHandler? exceptionHandler = null ) { return new DynamoDbDocumentQueryContext(tableFullName, document, queryType, exceptionHandler); } public static Result tryFillupTimestamps(this Amazon.DynamoDBv2.DocumentModel.Document document, QueryType queryType) { var result = new Result(); var err_msg = string.Empty; var currenct_time = DateTimeHelper.Current; if (QueryType.Insert == queryType) { document[DynamoDbDocBase.CreatedDateTime] = currenct_time; document[DynamoDbDocBase.UpdatedDateTime] = currenct_time; } else if(QueryType.Update == queryType) { document[DynamoDbDocBase.UpdatedDateTime] = currenct_time; } else { err_msg = $"Invalid DynamoDbDocumentQueryContext.QueryType !!! : queryType:{queryType} - {document.toPKSK()}"; result.setFail(ServerErrorCode.DynamoDbDocumentQueryContextTypeInvalid, err_msg); return result; } return result; } public static string getPK(this Amazon.DynamoDBv2.DocumentModel.Document document) { var doc_type = "Empty !!!"; if (false == document.TryGetValue(PrimaryKey.PK_Define, out var db_entry)) { Log.getLogger().fatal($"Not found PrimaryKey.PK_Define !!! : {document.toBasicString()}"); return doc_type; } return db_entry.AsString(); } public static string getSK(this Amazon.DynamoDBv2.DocumentModel.Document document) { var doc_type = "Empty !!!"; if (false == document.TryGetValue(PrimaryKey.SK_Define, out var db_entry)) { Log.getLogger().fatal($"Not found PrimaryKey.SK_Define !!! : {document.toBasicString()}"); return doc_type; } return db_entry.AsString(); } public static string toPKSK(this Amazon.DynamoDBv2.DocumentModel.Document document) { return $"PK:{document.getPK()}, SK:{document.getSK()}"; } public static string getDocType(this Amazon.DynamoDBv2.DocumentModel.Document document) { var doc_type = "Empty !!!"; if(false == document.TryGetValue("DocType", out var db_entry)) { Log.getLogger().fatal($"Not found DocType !!! : {document.toBasicString()}"); return doc_type; } return db_entry.AsString(); } public static bool isValid(this Amazon.DynamoDBv2.DocumentModel.Document document) { if (document.getPK() == string.Empty || document.getSK() == string.Empty || document.getDocType() == string.Empty) { Log.getLogger().error($"PK or SK or DocType is empty !!! : {document.toBasicString()}"); return false; } return true; } public static bool fillupAttribObject(this Amazon.DynamoDBv2.DocumentModel.Document document, out TAttrib? fillupAttrib) where TAttrib : AttribBase, new() { fillupAttrib = null; var attrib_type_name = typeof(TAttrib).Name; try { var dynamo_db_entry = document.findAttribObjectWithCaseInsensitive(); if (null == dynamo_db_entry) { Log.getLogger().fatal($"Failed to findAttribObjectWithCaseInsensitive() !!!, Not found AttribType in Document of DynamoDbTable !!! - AttribType:{attrib_type_name}"); return false; } TAttrib? attrib_object = null; if (dynamo_db_entry.isJsonEncoded()) { attrib_object = JsonConvert.DeserializeObject(dynamo_db_entry.AsString()); if (null == attrib_object) { Log.getLogger().fatal($"Failed to JsonConvert.DeserializeObject in DynamoDBEntry of DynamoDbTable !!! - AttribType:{attrib_type_name}"); return false; } } else { attrib_object = DynamoDbClientHelper.dynamoDbDocumentToClass(dynamo_db_entry); if (null == attrib_object) { Log.getLogger().fatal($"Failed to DynamoDbClientHelper.dynamoDbDocumentToClass in DynamoDBEntry of DynamoDbTable !!! - AttribType:{attrib_type_name}"); return false; } } fillupAttrib = attrib_object; return true; } catch (Exception e) { var error_code = ServerErrorCode.TryCatchException; Log.getLogger().fatal($"Exception !!!, Failed to get AttribBase in DynamoDBEntry of DynamoDbTable !!! : errorCode:{error_code}, exception:{e} - AttribType:{attrib_type_name}"); return false; } } public static Amazon.DynamoDBv2.DocumentModel.DynamoDBEntry? findAttribObjectWithCaseInsensitive(this Amazon.DynamoDBv2.DocumentModel.Document document) where TAttrib : AttribBase { var attrib_type_name = typeof(TAttrib).Name; var attribute_map = document.ToAttributeMap(); foreach(var each in attribute_map) { if(true == each.Key.Equals(attrib_type_name, StringComparison.OrdinalIgnoreCase)) { return document[each.Key]; } } return null; } public static bool setAttribObject(this Amazon.DynamoDBv2.DocumentModel.Document document, TAttrib fillupAttrib) where TAttrib : AttribBase { var attrib_type_name = typeof(TAttrib).Name; var is_save_to_json_in_db = fillupAttrib.isSaveToJsonInDb(); try { var dynamo_db_entry = document.findAttribObjectWithCaseInsensitive(); if (null == dynamo_db_entry) { if(true == is_save_to_json_in_db) { document[attrib_type_name] = fillupAttrib.toJsonString(); } else { document[attrib_type_name] = fillupAttrib.toDocument(); } } else { if (true == is_save_to_json_in_db) { dynamo_db_entry = fillupAttrib.toJsonString(); } else { dynamo_db_entry = fillupAttrib.toDocument(); } } return true; } catch (Exception e) { var error_code = ServerErrorCode.TryCatchException; Log.getLogger().fatal($"Exception !!!, Failed to set AttribBase in DynamoDBEntry of DynamoDbTable !!! : errorCode:{error_code}, exception:{e} - AttribType:{attrib_type_name}"); return false; } } public static bool getTimestampByDB_TIMESTAMP(this Amazon.DynamoDBv2.DocumentModel.Document document, DB_TIMESTAMP timestampName, out DateTime createdDateTime) { createdDateTime = DateTime.MinValue; try { if(false == document.TryGetValue(timestampName, out var datetime)) { return false; } createdDateTime = datetime.AsString().toUtcTime(); return true; } catch (Exception e) { var error_code = ServerErrorCode.TryCatchException; Log.getLogger().fatal($"Exception !!!, Failed to get document[{timestampName}] in DynamoDbTable !!! : errorCode:{error_code}, exception:{e} - {document.toBasicString()}"); } return false; } public static async Task<(Result, TDoc?)> simpleQueryDocTypeWithQueryOperationConfig( this DynamoDbClient dbClient , QueryOperationConfig queryOperationConfig , bool isFailOnEmptyDoc = true , string eventTid = "" ) where TDoc : DynamoDbDocBase, new() { var result = new Result(); var err_msg = string.Empty; var to_copy_doc = new TDoc(); var table = dbClient.getTableByName(to_copy_doc.TableName); var search = table.queryWithStopwatch(queryOperationConfig, eventTid); if (search == null) { err_msg = $"Failed to Table.Query !!! : Doc:{typeof(TDoc).Name} - {table.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbQueryFailed, err_msg); Log.getLogger().error(result.toBasicString()); return (result, null); } var next_documents = await search.GetNextSetAsync(); if ( null == next_documents || 0 >= next_documents.Count) { if(true == isFailOnEmptyDoc) { err_msg = $"No match Doc !!! : Doc:{typeof(TDoc).Name} - {table.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbQueryNoMatchAttribute, err_msg); Log.getLogger().error(result.toBasicString()); } return (result, null); } var source_document = next_documents[0]; result = await to_copy_doc.onCopyFromDocument(source_document); if (result.isFail()) { err_msg = $"Failed to onCopyFromDocument() !!! : {result.toBasicString()}, TgtDoc:{typeof(TDoc).Name} <= SrcDoc:{source_document.toBasicString()} - {table.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentCopyFailedToDoc, err_msg); Log.getLogger().error(result.toBasicString()); return (result, null); } return (result, to_copy_doc); } public static async Task<(Result, List)> simpleQueryDocTypesWithQueryOperationConfig( this DynamoDbClient dbClient , QueryOperationConfig queryOperationConfig , bool isFailOnEmptyDoc = false , string eventTid = "" ) where TDoc : DynamoDbDocBase, new() { var result = new Result(); var err_msg = string.Empty; var table = dbClient.getTableByDoc(); var doc_bases = new List(); var search = table.queryWithStopwatch(queryOperationConfig, eventTid); while (search.IsDone == false) { var next_documents = await search.GetNextSetAsync(); if(null == next_documents) { break; } foreach (var document in next_documents) { var doc = new TDoc(); result = await doc.onCopyFromDocument(document); if (result.isFail()) { err_msg = $"Failed to onCopyFromDocument() !!! : {result.toBasicString()}, TgtDoc:{typeof(TDoc).Name} <= SrcDoc:{document.toBasicString()} - {table.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentCopyFailedToDoc, err_msg); Log.getLogger().error(result.toBasicString()); return (result, doc_bases); } doc_bases.Add(doc); } } if( 0 >= doc_bases.Count && true == isFailOnEmptyDoc) { err_msg = $"No match Doc !!! : Doc:{typeof(TDoc).Name} - {table.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbQueryNoMatchAttribute, err_msg); Log.getLogger().error(result.toBasicString()); } return (result, doc_bases); } public static async Task<(Result, TDoc?)> simpleQueryDocTypeWithScanOperationConfig( this DynamoDbClient dbClient , ScanOperationConfig scanOperationConfig) where TDoc : DynamoDbDocBase, new() { var result = new Result(); var err_msg = string.Empty; var table = dbClient.getTableByDoc(); var search = table.Scan(scanOperationConfig); if (search == null) { err_msg = $"Failed to Table.Query !!! : Doc:{typeof(TDoc).Name} - {table.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbQueryFailed, err_msg); Log.getLogger().error(result.toBasicString()); return (result, null); } var next_documents = await search.GetNextSetAsync(); if ( null == next_documents || 0 >= next_documents.Count) { err_msg = $"No match Doc !!! : Doc:{typeof(TDoc).Name} - {table.TableName}"; result.setFail(ServerErrorCode.DynamoDbQueryNoMatchAttribute, err_msg); Log.getLogger().warn(result.toBasicString()); return (result, null); } var source_document = next_documents[0]; var to_copy_doc = new TDoc(); result = await to_copy_doc.onCopyFromDocument(source_document); if (result.isFail()) { err_msg = $"Failed to onCopyFromDocument !!! : {result.toBasicString()}, TgtDoc:{typeof(TDoc).Name} <= SrcDoc:{source_document.toBasicString()} - {table.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbDocumentCopyFailedToDoc, err_msg); Log.getLogger().error(result.toBasicString()); return (result, null); } return (result, to_copy_doc); } public static async Task<(Result, List)> simpleQueryDocTypesWithScanOperationConfig( this DynamoDbClient dbClient , ScanOperationConfig scanOperationConfig , bool isFailOnCriticalError = false ) where TDoc : DynamoDbDocBase, new() { var result = new Result(); var err_msg = string.Empty; var table = dbClient.getTableByDoc(); var doc_bases = new List(); var search = table.Scan(scanOperationConfig); while (search.IsDone == false) { var next_documents = await search.GetNextSetAsync(); if(null == next_documents) { return (result, doc_bases); } foreach (var document in next_documents) { var doc = new TDoc(); result = await doc.onCopyFromDocument(document); if (result.isFail()) { if (true == isFailOnCriticalError) { err_msg = $"Failed to onCopyFromDocument() !!!, by Critical Error !!! : {result.toBasicString()}, TgtDoc:{typeof(TDoc).Name} <= SrcDoc:{document.toBasicString()} - {table.toBasicString()}"; Log.getLogger().error(err_msg); return (result, doc_bases); } else { err_msg = $"Failed to onCopyFromDocument() !!! : {result.toBasicString()}, TgtDoc:{typeof(TDoc).Name} <= SrcDoc:{document.toBasicString()} - {table.toBasicString()}"; Log.getLogger().warn(err_msg); } continue; } doc_bases.Add(doc); } } return (result, doc_bases); } public static async Task<(Result, TDoc?)> simpleQueryTransactReadDocWithItemRequest( this DynamoDbClient dbClient , TransactGetItem toReadTransactQuery , bool isFailOnEmptyData = false , UInt16 retryCount = 3 ) where TDoc : DynamoDbDocBase, new() { var result = new Result(); var err_msg = string.Empty; var get_items = new List() { toReadTransactQuery }; (result, var read_documents) = await dbClient.simpleTransactReadOfItemRequestWithItemRequest(get_items, isFailOnEmptyData, retryCount); if(result.isFail()) { return (result, null); } foreach (var each in read_documents) { var table_name = each.Key; var documents = each.Value; foreach (var document in documents) { var doc = new TDoc(); result = await doc.onCopyFromDocument(document); if (result.isFail()) { err_msg = $"Failed to onCopyFromDocument() !!! : {result.toBasicString()}, TgtDoc:{typeof(TDoc).Name} <= SrcDoc:{document.toBasicString()} - {table_name}"; result.setFail(ServerErrorCode.DynamoDbDocumentCopyFailedToDoc, err_msg); Log.getLogger().error(result.toBasicString()); return (result, null); } return (result, doc); } } return (result, null); } public static async Task<(Result, Dictionary>)> simpleQueryTransactReadDocsWithItemRequest( this DynamoDbClient dbClient , List toReadTransactQueries , bool isFailOnEmptyData = false , UInt16 retryCount = 3 ) where TDoc : DynamoDbDocBase, new() { var result = new Result(); var err_msg = string.Empty; var doc_bases = new Dictionary>(); (result, var read_documents) = await dbClient.simpleTransactReadOfItemRequestWithItemRequest(toReadTransactQueries, isFailOnEmptyData, retryCount); if(result.isFail()) { return (result, doc_bases); } foreach (var each in read_documents) { var table_name = each.Key; var documents = each.Value; if (false == read_documents.TryGetValue(table_name, out var has_documents)) { has_documents = new List(); read_documents.Add(table_name, has_documents); } foreach (var document in documents) { var doc = new TDoc(); result = await doc.onCopyFromDocument(document); if (result.isFail()) { err_msg = $"Failed to onCopyFromDocument() !!! : {result.toBasicString()}, TgtDoc:{typeof(TDoc).Name} <= SrcDoc:{document.toBasicString()} - {table_name}"; result.setFail(ServerErrorCode.DynamoDbDocumentCopyFailedToDoc, err_msg); Log.getLogger().error(result.toBasicString()); return (result, doc_bases); } has_documents.Add(document); } } return (result, doc_bases); } public static async Task simpleInsertDocumentWithDocType( this DynamoDbClient dynamoDbClient, TDoc docBase , string eventTid = "") where TDoc : DynamoDbDocBase { var result = new Result(); var err_msg = string.Empty; var table = dynamoDbClient.getTableByName(docBase.TableName); if (QueryType.Insert != docBase.getQueryType()) { result = await docBase.newDoc4Query(); if (result.isFail()) { err_msg = $"Failed to newDoc4Query() !!! : {result.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"; Log.getLogger().error(err_msg); return result; } } (result, var copied_doc) = await docBase.onCopyToDocument(); if (result.isFail()) { err_msg = $"Failed to onCopyToDocument() !!! : {result.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"; Log.getLogger().error(err_msg); return result; } result = await table.simpleInsertDocument(copied_doc, eventTid); if (result.isFail()) { err_msg = $"Failed to simpleInsertDocument() !!! : {result.toBasicString()}, {copied_doc.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"; Log.getLogger().error(result.toBasicString()); return result; } Log.getLogger().debug($"Db insert from simpleInsertDocument() !!! : {copied_doc.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"); return result; } public static async Task simpleUpsertDocumentWithDocType( this DynamoDbClient dynamoDbClient, TDoc docBase , string eventTid = "") where TDoc : DynamoDbDocBase { var result = new Result(); var err_msg = string.Empty; var table = dynamoDbClient.getTableByName(docBase.TableName); if (QueryType.Upsert != docBase.getQueryType()) { result = await docBase.upsertDoc4Query(); if (result.isFail()) { err_msg = $"Failed to upsertDoc4Query() !!! : {result.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"; Log.getLogger().error(err_msg); return result; } } (result, var copied_doc) = await docBase.onCopyToDocument(); if (result.isFail()) { err_msg = $"Failed to onCopyToDocument() !!! : {result.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"; Log.getLogger().error(err_msg); return result; } result = await copied_doc.tryFillupTimestampsForUpsert(dynamoDbClient, table.TableName); if(result.isFail()) { err_msg = $"Failed to tryFillupTimestampsForUpsert() !!! : {result.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"; Log.getLogger().error(err_msg); return result; } (result, _) = await table.simpleUpsertDocument(copied_doc, eventTid); if (result.isFail()) { err_msg = $"Failed to simpleUpsertDocument() !!! : {result.toBasicString()}, {copied_doc.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"; Log.getLogger().error(result.toBasicString()); return result; } Log.getLogger().debug($"Db upsert from simpleUpsertDocument() !!! : {copied_doc.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"); return result; } public static async Task simpleUpdateDocumentWithDocType( this DynamoDbClient dynamoDbClient, TDoc docBase , string eventTid = "" ) where TDoc : DynamoDbDocBase { var result = new Result(); var err_msg = string.Empty; var table = dynamoDbClient.getTableByName(docBase.TableName); if (QueryType.Update != docBase.getQueryType()) { result = await docBase.updateDoc4Query(); if (result.isFail()) { err_msg = $"Failed to updateDoc4Query() !!! : {result.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"; Log.getLogger().error(err_msg); return result; } } (result, var copied_doc) = await docBase.onCopyToDocument(); if (result.isFail()) { err_msg = $"Failed to onCopyToDocument() !!! : {result.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"; Log.getLogger().error(err_msg); return result; } (result, _) = await table.simpleUpdateDocument(copied_doc, eventTid); if (result.isFail()) { err_msg = $"Failed to simpleUpdateDocument() !!! : {result.toBasicString()}, {copied_doc.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"; Log.getLogger().error(err_msg); return result; } Log.getLogger().debug($"Db update from simpleUpdateDocument() !!! : {copied_doc.toBasicString()} - {typeof(TDoc).Name}, {table.toBasicString()}"); return result; } public static async Task simpleDeleteDocumentWithDocType( this DynamoDbClient dynamoDbClient, TDoc docBase , string eventTid = "" ) where TDoc : DynamoDbDocBase { var result = new Result(); var err_msg = string.Empty; var table = dynamoDbClient.getTableByName(docBase.TableName); if (QueryType.Delete != docBase.getQueryType()) { result = await docBase.deleteDoc4Query(); if (result.isFail()) { err_msg = $"Failed to deleteDoc4Query() !!! : {result.toBasicString()} - {typeof(TDoc).Name}"; Log.getLogger().error(err_msg); return result; } } (result, var copied_doc) = await docBase.onCopyToDocument(); if (result.isFail()) { err_msg = $"Failed to onCopyToDocument() !!! : {result.toBasicString()} - {typeof(TDoc).Name}"; Log.getLogger().error(err_msg); return result; } result = await table.simpleDeleteDocument(copied_doc, eventTid); if (result.isFail()) { err_msg = $"Failed to simpleDeleteDocument() !!! : {result.toBasicString()}, {copied_doc.toBasicString()} - {typeof(TDoc).Name}"; Log.getLogger().error(err_msg); return result; } Log.getLogger().debug($"Db delete from simpleDeleteDocument() !!! : {copied_doc.toBasicString()} - {typeof(TDoc).Name}"); return result; } public static async Task<(Result, DynamoDbDocumentQueryContext?)> toDocumentQueryContext( this TDoc dbBaseDoc ) where TDoc : DynamoDbDocBase { var result = new Result(); var err_msg = string.Empty; var server_logic = ServerLogicApp.getServerLogicApp(); var dynamo_db_connector = server_logic.getDynamoDbClient(); (result, var copied_doc) = await dbBaseDoc.onCopyToDocument(); if (result.isFail()) { err_msg = $"Failed to onCopyToDocument() from TDoc !!! : {result.toBasicString()} - {typeof(TDoc).Name}"; Log.getLogger().error(err_msg); return (result, null); } return (result, new DynamoDbDocumentQueryContext(dynamo_db_connector.getTableFullName(dbBaseDoc.TableName), copied_doc, dbBaseDoc.getQueryType(), dbBaseDoc.getExceptionHandler())); } public static async Task<(Result, TAttrib?)> simpleQueryDocTypeToAttrib( this DynamoDbClient dynamoDbClient , string pk, string sk = DynamoDbClient.SK_EMPTY , string eventTid = "") where TDoc : DynamoDbDocBase, new() where TAttrib : AttribBase { var result = new Result(); var err_msg = string.Empty; (result, var make_primary_key) = await makePrimaryKey(pk, sk); if (result.isFail()) { return (result, null); } NullReferenceCheckHelper.throwIfNull(make_primary_key, () => $"make_primary_key is null !!!"); var query_config = dynamoDbClient.makeQueryConfigForReadByPKSK(make_primary_key.PK, make_primary_key.SK); (result, var found_base_doc) = await dynamoDbClient.simpleQueryDocTypeWithQueryOperationConfig(query_config, eventTid:eventTid); if (result.isFail()) { err_msg = $"Failed to simpleQueryDocTypeWithQueryOperationConfig() !!! : {result.toBasicString()}, {make_primary_key.toBasicString()}"; Log.getLogger().error(err_msg); return (result, null); } NullReferenceCheckHelper.throwIfNull(found_base_doc, () => $"found_base_doc is null !!! - {make_primary_key.toBasicString()}"); var found_attrib = found_base_doc.getAttrib(); NullReferenceCheckHelper.throwIfNull(found_attrib, () => $"found_attrib is null !!! - {make_primary_key.toBasicString()}"); return (result, found_attrib); } public static async Task<(Result, List?)> simpleQueryDocTypesToAttrib( this DynamoDbClient dynamoDbClient , string pk, string sk = DynamoDbClient.SK_EMPTY , string eventTid = "") where TDoc : DynamoDbDocBase, new() where TAttrib : AttribBase { var result = new Result(); var err_msg = string.Empty; (result, var make_primary_key) = await makePrimaryKey(pk, sk); if (result.isFail()) { return (result, null); } NullReferenceCheckHelper.throwIfNull(make_primary_key, () => $"make_primary_key is null !!!"); var query_config = dynamoDbClient.makeQueryConfigForReadByPKSK(make_primary_key.PK, make_primary_key.SK); (result, var found_base_doc_list) = await dynamoDbClient.simpleQueryDocTypesWithQueryOperationConfig(query_config, eventTid:eventTid); if (result.isFail()) { err_msg = $"Failed to simpleQueryDocTypesWithQueryOperationConfig() !!! : {result.toBasicString()}, {make_primary_key.toBasicString()}"; Log.getLogger().error(err_msg); return (result, null); } var found_attrib_list = new List(); foreach(var found_base_doc in found_base_doc_list) { var found_attrib = found_base_doc.getAttrib(); NullReferenceCheckHelper.throwIfNull(found_attrib, () => $"found_attrib is null !!! - {make_primary_key.toBasicString()}"); found_attrib_list.Add(found_attrib); } return (result, found_attrib_list); } public static (Result, PrimaryKey?) toPrimaryKey(this Amazon.DynamoDBv2.DocumentModel.Document document) { var result = new Result(); var err_msg = string.Empty; if(false == document.TryGetValue(PrimaryKey.PK_Define, out var found_pk)) { err_msg = $"Not found PK in Document !!! : {document.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbPrimaryKeyNotFound, err_msg); return (result, null); } var pk = found_pk.AsString(); if (false == document.TryGetValue(PrimaryKey.SK_Define, out var found_sk)) { err_msg = $"Not found SK in Document !!! : {document.toBasicString()}"; result.setFail(ServerErrorCode.DynamoDbPrimaryKeyNotFound, err_msg); return (result, null); } var sk = found_sk.AsString(); return (result, new PrimaryKey(pk, sk)); } public static async Task<(Result, TDoc?)> simpleQueryDocTypesWithUpdateItemRequest( this DynamoDbClient dbClient , UpdateItemRequest updateItemRequest , string eventTid = "") where TDoc : DynamoDbDocBase, new() { var result = new Result(); var err_msg = string.Empty; (result, var updated_doc) = await dbClient.simpleQueryWithUpdateItemRequest(updateItemRequest, false, eventTid); if(result.isFail()) { return (result, null); } NullReferenceCheckHelper.throwIfNull(updated_doc, () => $"updated_doc is null !!!"); var doc = new TDoc(); result = await doc.onCopyFromDocument(updated_doc); if (result.isFail()) { err_msg = $"Failed to onCopyFromDocument() !!! : {result.toBasicString()}, TgtDoc:{typeof(TDoc).Name} <= SrcDoc:{updated_doc.toBasicString()}"; Log.getLogger().error(err_msg); return (result, null); } return (result, doc); } public static async Task createIfNotExist( this DynamoDbClient dbClient , TDoc toInsertDoc, Action initFunc) where TDoc : DynamoDbDocBase, new() { var result = new Result(); var make_primary_key = toInsertDoc.getPrimaryKey(); NullReferenceCheckHelper.throwIfNull(make_primary_key, () => $"make_primary_key is null !!!"); (result, var is_exist) = await dbClient.checkIfAttributeExists(dbClient.getTableFullName(toInsertDoc.TableName), make_primary_key); if(result.isFail()) { return result; } if(false == is_exist) { if(null != initFunc) { initFunc(toInsertDoc); } return await dbClient.simpleInsertDocumentWithDocType(toInsertDoc); } return result; } public static async Task<(Result, bool)> checkIfAttributeExists( this DynamoDbClient dbClient , DYNAMO_DB_TABLE_FULL_NAME tableFullName , PrimaryKey targetPrimaryKey , bool isConsistentRead = false) { var db_connector = dbClient.getDbClient(); NullReferenceCheckHelper.throwIfNull(db_connector, () => $"db_connector is null !!! - {targetPrimaryKey.toBasicString()}"); ConditionValidCheckHelper.throwIfFalseWithCondition(() => false == tableFullName.isNullOrWhiteSpace(), () => $"Invalid TableFullName !!! - {targetPrimaryKey.toBasicString()}"); var result = new Result(); var err_msg = string.Empty; var projection_field = DynamoDbDocBase.CreatedDateTime; // GetItemRequest 설정 var get_request = new GetItemRequest { TableName = tableFullName, Key = targetPrimaryKey.toKeyWithAttributeValue(), ProjectionExpression = projection_field, ConsistentRead = isConsistentRead // 일관된 읽기를 사용하지 않음 (성능 최적화) }; try { var response = await db_connector.GetItemAsync(get_request); if(false == response.IsItemSet) { err_msg = $"Not found PrimaryKey in DynamoDb !!! : {get_request.toBasicString()}"; Log.getLogger().warn(err_msg); return (result, false); } } catch (Exception e) { var error_code = ServerErrorCode.DynamoDbException; err_msg = $"Exception !!!, Failed to perform in checkIfPrimaryKeyExists() !!! : errorCode:{error_code}, exception:{e}, {get_request.toBasicString()}"; result.setFail(error_code, err_msg); Log.getLogger().error(result.toBasicString()); return (result, false); } return (result, true); } public static bool setAttribObjectWithJsonString(this JObject jobject, TAttrib fillupAttrib) where TAttrib : AttribBase { var attrib_type_name = typeof(TAttrib).Name; try { jobject[attrib_type_name] = JObject.Parse(fillupAttrib.toJsonString()); return true; } catch (Exception e) { var error_code = ServerErrorCode.TryCatchException; Log.getLogger().fatal($"Exception !!!, Failed to perform in setAttribObjectWithJsonString<{attrib_type_name}>() !!! : errorCode:{error_code}, exception:{e} - AttribType:{attrib_type_name}"); return false; } } }