I'm currently having a problem with optimistic concurrency in my ASP.NET MVC application.
Essentially, this following controller works fine when called upon once. However, I have a page that is made to modify residents in six different groups at the same time. This is where the problem occurs. Basically, I post up the list of residents one at a time for each list so there are essentially six concurrent ajax calls that hit the server at the same time. (This may be what I need to change, but I am not sure!)
The javascript ajax call posts up the id of the group along with a list of the resident IDs. The residents are then added to the groups references. I have been looking into refreshing the state, but this isn't working as it seems only one of the six groups is updated. I have tried also letting the client win. I really just need some guidance and tips on resolving this issue!
Here is my current code in my controller:
var group = _context.TherapyGroups.Include(r => r.Residents)
.Where(x => x.ID.ToString() == groupid).FirstOrDefault();
if(group == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
group.Residents.Clear();
foreach(var id in residents)
{
var resident = _context.Residents.Where(x => x.ID.ToString() == id).FirstOrDefault();
if(resident == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
group.Residents.Add(resident);
}
bool saveFailed;
do
{
saveFailed = false;
try
{
_context.SaveChanges();
}
catch (DbUpdateException e)
{
saveFailed = true;
((IObjectContextAdapter)_context).ObjectContext.Refresh(RefreshMode.StoreWins, _context.Residents);
}
} while (saveFailed);
Wasn't sure how to fix the issue you are having with the code you have, so tried simplifying it a bit. See if this helps:
var groupId = _context.TherapyGroups.FirstOrDefault(x => x.ID.ToString() == groupid).Select(x => x.ID);
if(groupId == "") // or if(groupId == 0), not sure type of x.ID
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
// Get all of the residents whose Id is in the residents list
var residentObjects = _context.Residents.Where(x => residents.Contains( x.ID.ToString()));
// Update the groupId for each resident
foreach(var resident in residentObjects)
{
resident.GroupId = groupId;
}
var saveFailed = false;
try
{
_context.SaveChanges();
}
catch (DbUpdateException e)
{
saveFailed = true;
}
Related
In my application from the view someone pressed the Approve button, controller will collect the Main Id of the request. Here I want to update 3rd table Approval_Status column to true. I passed the main Id and got the 3rd table Id which I want to update record to the variable.
int PartyId = db.ApprovalProcess.Where(x => x.Req_Id == id).ToList().First().Id;
and then I wrote this code to pass the value. But it wont work. Can I get a help for this (question will seems like easy to you, but i want to tell you that I'm self learning ASP.NET MVC these days. So some stuff still I couldn't get)
Here is my database structure. Main table name is AppRequest, 2nd table is ApprovalProcess and the 3rd one is Approval_Parties.
This is my current code:
public ActionResult ApproveRequest(int? id)
{
int PartyId = db.ApprovalProcess.Where(x => x.Req_Id == id).ToList().First().Id;
if (ModelState.IsValid)
{
// model.Approved_Date = DateTime.Now;
ApprovalParty approvalParty = new ApprovalParty();
approvalParty.Approve_Status = true;
db.SaveChanges();
return RedirectToAction("Index");
}
}
I think I'm missing the code that which record should update in the table that already assigned that Id to the PartyId.
Something like this would work:
public ActionResult ApproveRequest(int? id)
{
ApprovalProcess approvalProcess = db.ApprovalProcess.FirsOrDefault(x => x.Req_Id == id);
if (approvalProcess != null)
{
ApprovalParty approvalParty = db.Approval_Parties.FirsOrDefault(x => x.ApprovalProcess_Id == approvalProcess.Id);
if (approvalParty != null)
{
approvalParty.Approve_Status = true;
approvalParty.Approved_Date = DateTime.Now;
db.SaveChanges();
return RedirectToAction("Index");
}
}
}
I have an angular 8 app with .net core web api 2.1 and MS SQL SERVER.
I have login and logout implementation for users.
After the user logins, he can add or delete programming langugages.
When I perform a delete operation, I get the error
database operation expected to affect 1 row(s) but actually effected
5 rows
But the code works properly for finding that particular user and the ID of the programming language that he wants to delete.
Here is the image from the table: https://imgur.com/a/taZtJ7d
As you can see there are no duplicates.
I think it is something about Concurrency Conflicts. That's why I added a try-catch block with DbUpdateConcurrencyException.
It works sometimes and sometimes not.
Here, you can find the definiton for the table https://imgur.com/a/YMCYkNy
When I try the following method to delete Users from MS SQL, it works as it should:
public async Task<IActionResult> DeleteProgrammingLanguage(string userId, int plId)
{
UserPL ps = new UserPL();
try
{
ps = await _context.PlUsers.Where(x => x.UserId == userId && x.ProgrammingLanguageId == plId).SingleAsync();
_context.PlUsers.Remove(ps);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
throw ex;
}
return Ok(ps);
}
I solved my problem with this solution.
public IActionResult DeleteProgrammingLanguage(string userId, int plId)
{
using (_context)
{
// Fetch a person from database and change phone number
var ps = _context.PlUsers.FirstOrDefault(x => x.UserId == userId && x.ProgrammingLanguageId == plId);
// Change the person's name in the database to simulate a concurrency conflict
_context.PlUsers.Remove(ps);
var saved = false;
while (!saved)
{
try
{
// Attempt to save changes to the database
_context.SaveChanges();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is UserPL)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
// TODO: decide which value should be written to database
// proposedValues[property] = <value to be saved>;
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
}
return NoContent();
}
I am implementing CoinPayments IPN in my application and I have trouble with passing data to the method that take their Callback. I have tried like everything I could find: TempData, SessionVariables, tried to implement it somewhere in forms and maybe Request it but that didn`t work for me. So I also tried to implement it with Global static variables. And it worked! But then came another issue: if more than one user were to buy something from website at the same time or even in between any callbacks their data will get mixed. So here I am, trying again to make Session Variables work and have no clue why they are not working as I used them before. Probably what I can think of is that because its a callback from CoinPayments and I handle something wrongly.
Here is the code I have right now: Tho I tried different variations like implementing Session in Get Payment method. Now I ended up with it and it still comes out as null in POST METHOD.
Class for handling Session Variables:
public static class MyGlobalVariables
{
public static int TokenChoice
{
get
{
if (System.Web.HttpContext.Current.Session["TokenChoice"] == null)
{
return -1;
}
else
{
return (int)System.Web.HttpContext.Current.Session["TokenChoice"];
}
}
set
{
System.Web.HttpContext.Current.Session["TokenChoice"] = value;
}
}
public static int PaymentChoice
{
get
{
if (System.Web.HttpContext.Current.Session["PaymentChoice"] == null)
{
return -1;
}
else
{
return (int)System.Web.HttpContext.Current.Session["PaymentChoice"];
}
}
set
{
System.Web.HttpContext.Current.Session["PaymentChoice"] = value;
}
}
public static string CurrentUser
{
get
{
System.Web.HttpContext.Current.Session["CurrentUser"] = System.Web.HttpContext.Current.User.Identity.Name;
return (string)System.Web.HttpContext.Current.Session["CurrentUser"];
}
}
}
Class that returns view where you click on CoinPayments button:
public ActionResult Payment(int tokenChoice, int paymentChoice)
{
ViewBag.Payment = paymentChoice;
MyGlobalVariables.PaymentChoice = paymentChoice;
MyGlobalVariables.TokenChoice = tokenChoice;
return View();
}
Callback class that handles Callback from CoinPayments:
[HttpPost]
public ActionResult Payment()
{
NameValueCollection nvc = Request.Form;
var merchant_id = id;
var ipn_secret = secret;
var order_total = MyGlobalVariables.PaymentChoice;
if (String.IsNullOrEmpty(nvc["ipn_mode"]) || nvc["ipn_mode"] != "hmac")
{
Trace.WriteLine("IPN Mode is not HMAC");
return View();
}
if (String.IsNullOrEmpty(HTTP_HMAC))
{
Trace.WriteLine("No HMAC signature sent");
return View();
}
if (String.IsNullOrEmpty(nvc["merchant"]) || nvc["merchant"] != merchant_id.Trim())
{
Trace.WriteLine("No or incorrect Merchant ID passed");
return View();
}
//var hmac = hash_hmac("sha512", request, ipn_secret.Trim());
var txn_id = nvc["txn_id"];
var item_name = nvc["item_name"];
var item_number = nvc["item_number"];
var amount1 = nvc["amount1"];
var amount2 = float.Parse(nvc["amount2"], CultureInfo.InvariantCulture.NumberFormat);
var currency1 = nvc["currency1"];
var currency2 = nvc["currency2"];
var status = Convert.ToInt32(nvc["status"]);
var status_text = nvc["status_text"];
Trace.WriteLine(status);
if (currency1 != "USD") {
Trace.WriteLine("Original currency mismatch!");
return View();
}
if (Convert.ToInt32(amount1) < Convert.ToInt32(order_total))
{
Trace.WriteLine("Amount is less than order total!");
return View();
}
if (status >= 100 || status == 2) {
using (MyDatabaseEntities1 dc = new MyDatabaseEntities1())
{
var account = dc.Users.Where(a => a.Username == MyGlobalVariables.CurrentUser).FirstOrDefault();
if (account != null && account.Paid == 0)
{
Trace.WriteLine("Payment Completed");
Trace.WriteLine("Tokens to add: " + MyGlobalVariables.TokenChoice);
account.Tokens += MyGlobalVariables.TokenChoice;
account.Paid = 1;
dc.Configuration.ValidateOnSaveEnabled = false;
dc.SaveChanges();
}
}
} else if (status < 0)
{
Trace.WriteLine(
"payment error, this is usually final but payments will sometimes be reopened if there was no exchange rate conversion or with seller consent");
} else {
using (MyDatabaseEntities1 dc = new MyDatabaseEntities1())
{
var account = dc.Users.Where(a => a.Username == MyGlobalVariables.CurrentUser).FirstOrDefault();
if (account != null)
{
account.Paid = 0;
dc.Configuration.ValidateOnSaveEnabled = false;
dc.SaveChanges();
}
}
Trace.WriteLine("Payment is pending");
}
return View();
}
As you can see there are only 3 variables I need to handle.
Also someone might ask why I use Session Variable for Current.User?
Well for some reason Callback method can not read Current.User as it return null. And well... nothing really changed as for now.
If you have ever experienced something like that or can find an issue I would be so thankful since I wasted already over 2 days on that issue.
EDIT:
After some testing I found out variables works fine if I run Post method on my own. So the problem is with handling callback from CoinPayments. Is there a specific way to deal with this?
public bool IsUserGroupMember(string user, string unit)
{
bool member = false;
try
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
string[] groups = unit.Split(',');
foreach (string word in groups)
{
GroupPrincipal grp = GroupPrincipal.FindByIdentity(ctx, IdentityType.Name, word);
if (grp != null)
{
foreach (Principal p in grp.GetMembers(true))
{
if (p.SamAccountName == user)
{
member = true;
grp.Dispose();
ctx.Dispose();
return member;
}
}
}
else
{
grp.Dispose();
ctx.Dispose();
return member;
}
}
}
catch (COMException)
{
return member;
}
return member;
}
I'm using the method above to find if a user is member of a group in Active Directory, recursively. It works well..although sometimes I get a weird exception.
Specified method is not supported.
foreach (Principal p in grp.GetMembers(true)) is red (sorry I can't upload a picture of the exception). The weirdest thing is that it seems to be thrown randomly, and if I refresh the page it works well..
I tried to find a solution on the Internet but no happy news for now..
You should do this the other way around: get the user and then the authorization groups that this user is a member of - this call (.GetAuthorizationGroups on the UserPrincipal) already is searching the groups recursively for you!
public bool IsUserGroupMember(string user, string unit)
{
bool isMember = false;
try
{
// put the PrincipalContext in a using(..) block - then it's
// automatically, safely and properly disposed of at the end
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// get the user
UserPrincipal up = UserPrincipal.FindByIdentity(ctx, user);
if(up != null)
{
// get the authorization groups for the user
// this call is *RECURSIVELY* enumerating all groups
// that this user is a member of
var authGroups = up.GetAuthorizationGroups();
// now that you have the groups - just determine if the user
// is a member of the group you're looking for......
}
}
}
catch (COMException comEx)
{
isMember = false;
}
return isMember;
}
I finally ended up with a solution!
I just had to add my domain name, as follow:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "MyDomain");
It solved the problem right away!
And about the slowness..I used cookies, following this link.
I have the following Asp.Net MVC 4 scaffold code.
//
// POST: /Detail/Edit/5
[HttpPost]
public ActionResult Edit(Detail detail)
{
var dd = Details.FirstOrDefault(d => d.DetailId == detail.DetailId);
if (dd == null)
{
return HttpNotFound();
}
detail.UpdatedBy = User.Identity.Name;
detail.UpdateTime = DateTime.Now;
if (ModelState.IsValid)
{
_db.Entry(detail).State = EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("Index", new { id = detail.MasterId });
}
return View(dealDetail);
}
However, the line _db.Entry(detail).State = EntityState.Modified; raise the following error. What's the correct way to update the detail line of a master/detail editing scenery?
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
This line:
var dd = Details.FirstOrDefault(d => d.DetailId == detail.DetailId);
will cause loading of Detail entity from the database. Now you have two Details with the same Id but only one (the one loaded by that query) can be used for persistence. You can change your code to:
if (!Details.Any(d => d.DetailId == detail.DetailId))
{
return HttpNotFound();
}
or update the attached detail (dd) for example by:
// All values of detail entity must be set in your HTTP post!
_db.Entry(dd).CurrentValues.SetValues(detail);