public async Task <CreateMockChatsTxn_Output> Execute(CreateMockChatsTxn_Input input, IServiceProvider serviceProvider, MVCDbContext contextFather = null, bool autoCommit = true) { CreateMockChatsTxn_Output output = new CreateMockChatsTxn_Output(); output.ResultConfirmation = resultConfirmation.resultBad(_ResultMessage: "TXN_NOT_STARTED"); // Error handling bool error = false; // To Handle Only One Error try { MVCDbContext contextMGT = (contextFather != null) ? contextFather : new MVCDbContext(); // An using statement is in reality a try -> finally statement, disposing the element in the finally. So we need to take advance of that to create a DBContext inheritance try { // DBContext by convention is a UnitOfWork, track changes and commits when SaveChanges is called // Multithreading issue: so if we use only one DBContext, it will track all entities (_context.Customer.Add) and commit them when SaveChanges is called, // No Matter what transactions or client calls SaveChanges. // Note: Rollback will not remove the entities from the tracker in the context. so better dispose it. //***** 0. Make The Validations - Be careful : Concurrency. Same name can be saved multiple times if called at the exact same time. Better have an alternate database constraint // Company company = await contextMVC.Company.Where(x => x.Id.Equals(input.Company.Id)).FirstOrDefaultAsync(); // Validate user status, user company, user not blocked, etc // One way to do it: Try to save without validations, if constraints fail (inner update db update) then run queries to check what went wrong // Call transaction to create a New Company CreateCompanyTxn_Input createCompanyTxn_Input = new CreateCompanyTxn_Input { Company = new company { name = input.CompanyName, email = input.CompanyName + "@company.com", password = input.CompanyName + "123" } }; CreateCompanyTxn_Output createCompanyTxn = await new GraphQLMutation().CreateCompanyTxn(input: createCompanyTxn_Input, serviceProvider: serviceProvider); if (!createCompanyTxn.ResultConfirmation.resultPassed) { error = true; output.ResultConfirmation = createCompanyTxn.ResultConfirmation; return(output); } // Create Users for (int i = 0; i < input.UsersToCreate; i++) { // Call transaction to create a New User CreateUserTxn_Input createUserTxn_Input = new CreateUserTxn_Input { User = new userApp { userName = i.ToString() + "_" + createCompanyTxn.Company.companyId, email = i.ToString() + "@User.com", password = "******" }, Company = new company { companyId = createCompanyTxn.Company.companyId } }; CreateUserTxn_Output createUserTxn = await new GraphQLMutation().CreateUserTxn(input: createUserTxn_Input, serviceProvider: serviceProvider); if (!createUserTxn.ResultConfirmation.resultPassed) { error = true; output.ResultConfirmation = createUserTxn.ResultConfirmation; return(output); } } // Create chats List <chat> chatsNew = new List <chat>(); for (int i = 0; i < input.ChatsToCreate; i++) { DateTime utcNow = DateTime.UtcNow; // Create the chat chat chat = new chat { chatId = 0, name = "Chat " + i, companyId = createCompanyTxn.Company.companyId, createdAt = utcNow, updatedAt = utcNow, type = "Chat" }; // Add participants List <participant> participants = (from user in contextMGT.UserApp where user.companyId.Equals(createCompanyTxn.Company.companyId) select new participant { participantId = 0, chatId = 0, isAdmin = false, userAppId = user.userAppId }) .OrderBy(x => x.userAppId) .Take(input.UsersToCreate - i) .ToList(); chat.participants = participants; /* * // Add comments Seen * List<Comment> commentsSeen = (from part in participants * select new Comment { CommentId = 0, ChatId = 0, Message = "Message ", CreatedAt = utcNow.AddMinutes(-1), UserAppId = part.UserAppId }) * .ToList(); * * foreach (var comm in commentsSeen) * { * // Create and Save the CommentInfo for each user. * var query = from user in participants * select new CommentInfo { CommentInfoId = 0, CommentId = 0, CreatedAt = utcNow.AddMinutes(-1), Delivered = true, Seen = true, SeenAt = utcNow.AddMinutes(-1), UserAppId = user.UserAppId }; * * comm.CommentsInfo = query.ToList(); * } * * // Add comments Seen * List<Comment> commentsUnseen = (from part in participants * select new Comment { CommentId = 0, ChatId = 0, Message = "Message ", CreatedAt = utcNow, UserAppId = part.UserAppId }) * .ToList(); * * foreach (var comm in commentsUnseen) * { * // Create and Save the CommentInfo for each user. * var query = from user in participants * select new CommentInfo { CommentInfoId = 0, CommentId = 0, CreatedAt = utcNow, Delivered = true, Seen = false, UserAppId = user.UserAppId }; * * comm.CommentsInfo = query.ToList(); * } */ // Add one comment for each participant List <comment> commentsUnseen = (from part in participants select new comment { commentId = 0, chatId = 0, message = "Msg " + part.userAppId, createdAt = utcNow, userAppId = part.userAppId }) .ToList(); foreach (var comm in commentsUnseen) { // Create and Save the CommentInfo for each user. var query = from user in participants select new commentInfo { commentInfoId = 0, commentId = 0, createdAt = utcNow, delivered = true, seen = false, userAppId = user.userAppId }; comm.commentsInfo = query.ToList(); } chat.comments = commentsUnseen; // Call transaction to create a New Chat CreateChatTxn_Input createChatTxn_Input = new CreateChatTxn_Input { Chat = chat }; CreateChatTxn_Output createChatTxn = await new GraphQLMutation().CreateChatTxn(input: createChatTxn_Input); if (!createChatTxn.ResultConfirmation.resultPassed) { error = true; output.ResultConfirmation = createChatTxn.ResultConfirmation; return(output); } chatsNew.Add(createChatTxn.Chat); //***** 4. Save and Commit to the Database (Atomic because is same Context DB) if (!error && autoCommit) { await contextMGT.SaveChangesAsync(); // Call it only once so do all other operations first } }// for (int i = 0; i < input.ChatsToCreate; i++) //***** 4. Save and Commit to the Database (Atomic because is same Context DB) if (!error && autoCommit) { await contextMGT.SaveChangesAsync(); // Call it only once so do all other operations first } //***** 5. Execute Send e-mails or other events once the database has been succesfully saved //***** If this task fails, there are options -> 1. Retry multiple times 2. Save the event as Delay, 3.Rollback Database, Re //***** 6. Confirm the Result (Pass | Fail) If gets to here there are not errors then return the new data from database output.ResultConfirmation = resultConfirmation.resultGood(_ResultMessage: "CHAT_SUCESSFULLY_CREATED"); // If OK output.Chats = chatsNew; } finally { // If the context Father is null the context was created on his own, so dispose it if (contextMGT != null && contextFather == null) { contextMGT.Dispose(); } } } catch (Exception ex) // Main try { System.Diagnostics.Debug.WriteLine("Error: " + ex.Message); string innerError = (ex.InnerException != null) ? ex.InnerException.Message : ""; System.Diagnostics.Debug.WriteLine("Error Inner: " + innerError); output = new CreateMockChatsTxn_Output(); // Restart variable to avoid returning any already saved data output.ResultConfirmation = resultConfirmation.resultBad(_ResultMessage: "EXCEPTION", _ResultDetail: ex.Message); } finally { // Save Logs if needed } return(output); }
public async Task <CreateChatTxn_Output> Execute(CreateChatTxn_Input input, MVCDbContext contextFather = null, bool autoCommit = true) { CreateChatTxn_Output output = new CreateChatTxn_Output(); output.ResultConfirmation = resultConfirmation.resultBad(_ResultMessage: "TXN_NOT_STARTED"); // Error handling bool error = false; // To Handle Only One Error bool chatFound = false; // To see if the chat already exists try { MVCDbContext contextMGT = (contextFather != null) ? contextFather : new MVCDbContext(); // An using statement is in reality a try -> finally statement, disposing the element in the finally. So we need to take advance of that to create a DBContext inheritance try { // DBContext by convention is a UnitOfWork, track changes and commits when SaveChanges is called // Multithreading issue: so if we use only one DBContext, it will track all entities (_context.Customer.Add) and commit them when SaveChanges is called, // No Matter what transactions or client calls SaveChanges. // Note: Rollback will not remove the entities from the tracker in the context. so better dispose it. //***** 0. Make The Validations - Be careful : Concurrency. Same name can be saved multiple times if called at the exact same time. Better have an alternate database constraint // Company company = await contextMVC.Company.Where(x => x.Id.Equals(input.Company.Id)).FirstOrDefaultAsync(); // Validate user status, user company, user not blocked, etc // One way to do it: Try to save without validations, if constraints fail (inner update db update) then run queries to check what went wrong company companyDb = await contextMGT.Company.Where(x => x.companyId.Equals(input.Chat.companyId)).FirstOrDefaultAsync(); if (companyDb == null) { error = true; output.ResultConfirmation = resultConfirmation.resultBad(_ResultMessage: "COMPANY_DOES_NOT_EXIST_ERROR", _ResultDetail: ""); } // Try to find a Chat already crated for the same users. If the chat exists, return the chat if (!error) { DateTime nowUTC = DateTime.UtcNow; //***** 0. Validate if the Chat exists, so return that Chat List <String> userIds = input.Chat.participants .GroupBy(g => g.userAppId) .Select(user => user.Key) // extract unique Ids from users .ToList(); chat chatDb = await contextMGT.Chat.Include(o => o.participants) .Where(company => company.companyId == input.Chat.companyId) // It's the same company .Where(x => x.participants.Count == input.Chat.participants.Count) // Have the same number of users .Where(x => x.participants.All(users => userIds.Contains(users.userAppId))) // Have the same users by Id .FirstOrDefaultAsync(); if (chatDb != null) { chatFound = true; chatDb.updatedAt = nowUTC; input.Chat.comments = null; /* Dont save comments during Create Chat * * * // Save the comments into the Database * foreach (var comment in input.Chat.Comments) * { * comment.CommentId = 0; * comment.ChatId = chatDb.ChatId; * comment.CreatedAt = nowUTC; * contextMGT.Comment.Add(comment); * } */ output.ResultConfirmation = resultConfirmation.resultGood(_ResultMessage: "CHAT_SUCESSFULLY_FOUND"); // If Found output.Chat = chatDb; } //***** 1. Create the Chat (Atomic because is same Context DB) if (!chatFound) { // Create the Chat To Save input.Chat.chatId = 0; input.Chat.createdAt = nowUTC; input.Chat.updatedAt = nowUTC; if (input.Chat.comments != null) { input.Chat.comments.ForEach(x => x.createdAt = nowUTC); } // Save the chat to the context contextMGT.Chat.Add(input.Chat); output.ResultConfirmation = resultConfirmation.resultGood(_ResultMessage: "CHAT_SUCESSFULLY_CREATED"); // If OK output.Chat = input.Chat; } //***** 4. Save and Commit to the Database (Atomic because is same Context DB) if (!error && autoCommit) { await contextMGT.SaveChangesAsync(); // Call it only once so do all other operations first } //***** 5. Execute Send e-mails or other events once the database has been succesfully saved //***** If this task fails, there are options -> 1. Retry multiple times 2. Save the event as Delay, 3.Rollback Database, Re //***** 6. Confirm the Result (Pass | Fail) If gets to here there are not errors then return the new data from database }// if (!error) } finally { // If the context Father is null the context was created on his own, so dispose it if (contextMGT != null && contextFather == null) { contextMGT.Dispose(); } } } catch (Exception ex) // Main try { System.Diagnostics.Debug.WriteLine("Error: " + ex.Message); string innerError = (ex.InnerException != null) ? ex.InnerException.Message : ""; System.Diagnostics.Debug.WriteLine("Error Inner: " + innerError); output = new CreateChatTxn_Output(); // Restart variable to avoid returning any already saved data output.ResultConfirmation = resultConfirmation.resultBad(_ResultMessage: "EXCEPTION", _ResultDetail: ex.Message); } finally { // Save Logs if needed } return(output); }