public async Task <BankUserUpdateModel> Deposit(int id, decimal amount, byte[] accountTimestamp) { var updateStateModel = new BankUserUpdateModel(); if (amount <= 0) { updateStateModel.ErrorList.Add("DepositeAmount", "The amount must be larger than 0."); updateStateModel.ErrorList.Add(string.Empty, "Unable to save changes."); return(updateStateModel); } var accountToUpdate = await this.GetAccountById(id); if (accountToUpdate == null) { updateStateModel.ErrorList.Add(string.Empty, "Unable to save changes. The account was deleted by another user."); return(updateStateModel); } // deposit the amount into Account accountToUpdate.Balance = accountToUpdate.Balance + amount; // set the Timestamp value for the retrieved Account. This Timestamp value will be compared to detech concurrence issue _context.Entry(accountToUpdate).Property("Timestamp").OriginalValue = accountTimestamp; try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException ex) { updateStateModel.ErrorList.Add(string.Empty, "The record you attempted to edit was modified by another user after you got the original value."); var exceptionEntry = ex.Entries.Single(); // Using a NoTracking query means we get the entity but it is not tracked by the context // and will not be merged with existing entities in the context. var modifiedEntityInDatabase = await _context.BankUsers .AsNoTracking() .SingleAsync(d => d.ID == ((BankUser)exceptionEntry.Entity).ID); var latestEntityInDb = _context.Entry(modifiedEntityInDatabase); // get the latest Balance amount of the Account from Database var latestBalance = (Decimal)latestEntityInDb.Property("Balance").CurrentValue; if (latestBalance != accountToUpdate.Balance) { updateStateModel.ErrorList.Add("User.Balance", $"Current value: {latestBalance:c}"); } // re-update the Timestamp value into User.Timestamp. The deposit process can work for the next Submit updateStateModel.EntityTimestamp = (byte[])latestEntityInDb.Property("Timestamp").CurrentValue; } return(updateStateModel); }
public async Task <IActionResult> Deposite(AccountViewModel viewModel) { BankUserUpdateModel result = await _userService.Deposit(viewModel.User.ID, viewModel.DepositeAmount, viewModel.User.Timestamp); if (result.ErrorList.Any()) { foreach (var error in result.ErrorList) { ModelState.AddModelError(error.Key, error.Value); } viewModel.User.Timestamp = null; viewModel.User.Timestamp = result.EntityTimestamp; // remove from ModelState to force User.Timestamp to take new value ModelState.Remove("User.Timestamp"); } else { return(RedirectToAction("Index")); } return(View(viewModel)); }
public async Task <IActionResult> Transfer(TransferViewModel viewModel) { BankUserUpdateModel result = await _userService.Transfer( viewModel.FromUser.ID, viewModel.ToUser.ID, viewModel.TranferAmount, viewModel.FromUser.Timestamp, viewModel.ToUser.Timestamp); if (result.ErrorList.Any()) { foreach (var error in result.ErrorList) { ModelState.AddModelError(error.Key, error.Value); } } else { return(RedirectToAction("Index")); } return(View(viewModel)); }
public async Task <BankUserUpdateModel> Transfer( int fromUserId, int toUserId, decimal amount, byte[] fromAccountTimestamp, byte[] toAccountTimestamp) { var updateStateModel = new BankUserUpdateModel(); if (amount <= 0) { updateStateModel.ErrorList.Add("TranferAmount", "The amount must be larger than 0."); updateStateModel.ErrorList.Add(string.Empty, "Unable to save changes."); return(updateStateModel); } var accountToTransfer = await this.GetAccountById(fromUserId); if (accountToTransfer == null) { updateStateModel.ErrorList.Add(string.Empty, "Unable to save changes. The transfer account was deleted by another user."); return(updateStateModel); } if (accountToTransfer.Balance - amount < 0) { updateStateModel.ErrorList.Add("TranferAmount", "The withdraw amount must be larger or equals the balance amount."); updateStateModel.ErrorList.Add(string.Empty, "Unable to save changes."); return(updateStateModel); } var accountToReceive = await this.GetAccountById(toUserId); if (accountToReceive == null) { updateStateModel.ErrorList.Add(string.Empty, "Unable to save changes. The received account was deleted by another user."); return(updateStateModel); } try { using (var transaction = _context.Database.BeginTransaction()) { // create TransferHistory record var transferHistoryRecord = new TransferHistory { FromUserID = accountToTransfer.ID, ToUserID = accountToReceive.ID, CreatedDate = DateTime.Now, Amount = amount }; _context.TranferHistories.Add(transferHistoryRecord); // transfer the amount into Account accountToTransfer.Balance = accountToTransfer.Balance - amount; accountToReceive.Balance = accountToReceive.Balance + amount; // set the Timestamp value for the retrieved Account. This Timestamp value will be compared to detech concurrence issue _context.Entry(accountToTransfer).Property("Timestamp").OriginalValue = fromAccountTimestamp; _context.Entry(accountToReceive).Property("Timestamp").OriginalValue = toAccountTimestamp; await _context.SaveChangesAsync(); transaction.Commit(); } } catch (DbUpdateConcurrencyException ex) { updateStateModel.ErrorList.Add(string.Empty, "The transfer is failed."); foreach (var exceptionEntry in ex.Entries) { var userId = ((BankUser)exceptionEntry.Entity).ID; // Using a NoTracking query means we get the entity but it is not tracked by the context // and will not be merged with existing entities in the context. var modifiedEntityInDatabase = await _context.BankUsers .AsNoTracking() .SingleAsync(d => d.ID == userId); var latestEntityInDb = _context.Entry(modifiedEntityInDatabase); // get the latest Balance amount of the Account from Database var latestBalance = (Decimal)latestEntityInDb.Property("Balance").CurrentValue; if (userId == accountToTransfer.ID && latestBalance != accountToTransfer.Balance) { updateStateModel.ErrorList.Add("FromUser.Balance", $"Current value: {latestBalance:c}"); } if (userId == accountToReceive.ID && latestBalance != accountToReceive.Balance) { updateStateModel.ErrorList.Add("ToUser.Balance", $"Current value: {latestBalance:c}"); } } } return(updateStateModel); }