This exception usually happens when a batch is being run or alerts are coming into our Salesforce instance too quickly. When inserting a case, we try to lock down the contact and account associated with the case before inserting the case to prevent the 'UNABLE_TO_LOCK_ROW' exception from happening.
Here is the exact exception:
'System.QueryException: Record Currently Unavailable: The record you are attempting to edit, or one of its related records, is currently being modified by another user. Please try again.'
Class.Utility.DoCaseInsertion: line 98, column 1
I've done a lot of research on the 'UNABLE_TO_LOCK_ROW' exception and 'Record Currently Unavailable' exception and I can't seem to find a great solution to this issue.
What I've tried to accomplish is a loop to attempt the insert 10 times, but I'm still getting the 'Record Currently Unavailable' exception. Does anyone else have a suggestion for this?
Below is the code:
Public static void DoCaseInsertion(case myCase) {
try
{
insert myCase;
}
catch (System.DmlException ex)
{
boolean repeat = true;
integer cnt = 0;
while (repeat && cnt < 10)
{
try
{
repeat = false;
List<Contact> contactList = [select id from Contact where id =: myCase.ContactId for update]; // Added for related contact to overcome the 'UNABLE_TO_LOCK_ROW issues'
List<Account> accountList = [select id from Account where id =: myCase.AccountId for update]; // Added for related account to overcome the 'UNABLE_TO_LOCK_ROW issues'
insert myCase;
}
catch (System.DmlException e)
{
repeat = true;
cnt++;
}
}
}
}
This basically happens when there is a conflicting modification being done by other user/process on a particular record that you are trying to access. Mostly it will happen when anykind of batch process is running in the background and locked the particular record you are trying to access(in your case Account). To get rid of this problem, you would need to check if there are any scheduled apex classes running in the background on Accounts/Cases and see if there is anything you can do to optimize the code to avoid conflicting behavior.
Related
I'm trying to update a record in a database through C# code. I found a solution that I think should work using SaveChanges. However, I'm getting an error from my catch statement that says: "An error occurred while starting a transaction on the provider connection. See the inner exception for details." I'm either looking for an answer on how to fix it and/or how to make my catch statement give better details on what the problem actually is.
This is my code.
using var orderContext =
new OrderContext(Resources.SqlAuthenticationConnectionString);
foreach(OrderRecord order in orders)
{
var query =
from o in orderContext.OrderRecords
where o.ID == order.ID
select o;
foreach(OrderRecord record in query)
{
record.HeatLotNumber = order.HeatLotNumber;
record.OrderNumber = order.OrderNumber;
record.ShimCenterMaterial = order.ShimCenterMaterial;
try
{
orderContext.SaveChanges();
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
}
Looks like I didn't look hard enough. Here's what my problem was. The save needs to be outside the foreach loop.
An error occurred while starting a transaction on the provider connection. See the inner exception for details
I have written code for deactivating users who are not logged in last 3 months , but when I am running the program , it is giving me an exception like you can't deactivate this user because of the ownership of the records so then it is not checking for other users.
public class ExampleUser
{
public static void deactivate()
{
List<User> usersToUpdate = new List<User>();
for(User u: [SELECT Id, IsActive FROM User where IsActive= true and LastLoginDate <= :DATE.TODAY()-90])
{
u.IsActive = false;
/*try {
update u;
}
catch(Exception e){
System.debug(e);
}*/
usersToUpdate.add(u);
}
if (usersToUpdate.size()>0)
{
update usersToUpdate;
}
}
}
If I run the commented code(trying to deactivated each user seperately) it is giving an exception with more than 100 SOQL queries.
can anyone please help me in resolving this issue
Use Database.update(usersToUpdate, false) to avoid rolling back the entire transaction if one or more records fails.
That doesn't really solve your problem as such; it just means you do deactivate the users that can be deactivated. You'll need to develop a different solution if you want to review the users that cannot be deactivated for any reason.
You don't need to check the list size. Performing DML on an empty list does nothing.
I am currently working on an API using ASP.NET Core Web API along with Entity Framework Core 2.1 and a SQL Server database. The API is used to transfer money from two accounts A and B. Given the nature of the B account which is an account that accepts payments, a lot of concurrent requests might be executed at the same moment. As you know if it's not well managed, this can result in some users not seeing their payments arrive.
Having spent days trying to achieve concurrency I can't figure out what the best approach is. For the sake of simplicity I created a test project trying to reproduce this concurrency issue.
In the test project, I have two routes: request1 and request2 each one perform a transfer to the same user the first one have an amount of 10 and the second one is 20. I put a Thread.sleep(10000) on the first one as follows:
[HttpGet]
[Route("request1")]
public async Task<string> request1()
{
using (var transaction = _context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
try
{
Wallet w = _context.Wallets.Where(ww => ww.UserId == 1).FirstOrDefault();
Thread.Sleep(10000);
w.Amount = w.Amount + 10;
w.Inserts++;
_context.Wallets.Update(w);
_context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
}
}
return "request 1 executed";
}
[HttpGet]
[Route("request2")]
public async Task<string> request2()
{
using (var transaction = _context.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
try
{
Wallet w = _context.Wallets.Where(ww => ww.UserId == 1).FirstOrDefault();
w.Amount = w.Amount + 20;
w.Inserts++;
_context.Wallets.Update(w);
_context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
}
}
return "request 2 executed";
}
After executing request1 and request2 after in a browser, the first transaction is rolled back due to:
InvalidOperationException: An exception has been raised that is likely due to a transient failure. Consider enabling transient error resiliency by adding 'EnableRetryOnFailure()' to the 'UseSqlServer' call.
I can also retry the transaction but isn't there a better way? using locks ?
Serializable, being the most isolated level and the most costly too is as said in the documentation:
No other transactions can modify data that has been read by the current transaction until the current transaction completes.
Which means no other transaction can update data that has been read by another transaction, which is working as intended here since the update in the request2 route wait for the first transaction (request1) to commit.
The problem here is we need to block read by other transactions once the current transaction has read the wallet row, to solve the problem I need to use locking so that when the first select statement in request1 executes, all the transactions after need to wait for the 1st transaction to finish so they can select the correct value. Since EF Core have no support for locking I need to execute a SQL query directly, so when selecting the wallet I'll add a row lock to the current row selected
//this locks the wallet row with id 1
//and also the default transaction isolation level is enough
Wallet w = _context.Wallets.FromSql("select * from wallets with (XLOCK, ROWLOCK) where id = 1").FirstOrDefault();
Thread.Sleep(10000);
w.Amount = w.Amount + 10;
w.Inserts++;
_context.Wallets.Update(w);
_context.SaveChanges();
transaction.Commit();
Now this works perfectly even after executing multiple request the result of the transfers all combined is correct. In addition to that am using a transaction table that holds every money transfer made with the status to keep a record of each transaction in case something went wrong am able to compute all wallets amount using this table.
Now there are other ways of doing it like:
Stored procedure: but I want my logic to be in the application level
Making a synchronized method to handle the database logic: this way all the database requests are executed in a single thread, I read a blog post that advise about using this approach but maybe we'll use multiple servers for scalability
I don't know if I'm not searching well but I can't find good material for handling pessimistic concurrency with Entity Framework Core, even while browsing Github, most of code I've seen don't use locking.
Which bring me to my question: is this the correct way of doing it?
Cheers and thanks in advance.
My suggestion for you is to catch on DbUpdateConcurrencyException and use entry.GetDatabaseValues(); and entry.OriginalValues.SetValues(databaseValues); into your retry logic. No need to lock the DB.
Here is the sample on EF Core documentation page:
using (var context = new PersonContext())
{
// Fetch a person from database and change phone number
var person = context.People.Single(p => p.PersonId == 1);
person.PhoneNumber = "555-555-5555";
// Change the person's name in the database to simulate a concurrency conflict
context.Database.ExecuteSqlCommand(
"UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1");
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 Person)
{
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);
}
}
}
}
}
You can use distributed lock mechanism with redis for example.
Also, you can lock by userId, it will not lock method for others.
Why don't you handle the concurrency problem in the code, why it needs to be in the DB layer?
You can have a method that updates the value of given wallet with given value and you can use simple lock there. Like this:
private readonly object walletLock = new object();
public void UpdateWalletAmount(int userId, int amount)
{
lock (balanceLock)
{
Wallet w = _context.Wallets.Where(ww => ww.UserId == userId).FirstOrDefault();
w.Amount = w.Amount + amount;
w.Inserts++;
_context.Wallets.Update(w);
_context.SaveChanges();
}
}
So your methods will look like this:
[HttpGet]
[Route("request1")]
public async Task<string> request1()
{
try
{
UpdateWalletAmount(1, 10);
}
catch (Exception ex)
{
// log error
}
return "request 1 executed";
}
[HttpGet]
[Route("request2")]
public async Task<string> request2()
{
try
{
UpdateWalletAmount(1, 20);
}
catch (Exception ex)
{
// log error
}
return "request 2 executed";
}
You don't even need to use a transaction in this context.
When user clicks the link this method takes the user id and other data and writes it to the database. I can't seem to find a way to track what is the user id since the id is automatically generated.
I am using membership base separately from my base. In order to find userID I was trying to compare UserName strings. I get this error: "An unhandled exception was generated during the execution of the current web request." Also "Cannot create an abstract class."
Is there a better way to compare two strings form the two databases? Or is there a better way? This is my first time using lambda expressions.
I tried using Single() and First() instead of Where(), but I still get the same error.
[HttpPost]
public ActionResult AddToList(Review review, int id, HttpContextBase context)
{
try
{
if (ModelState.IsValid)
{
//User user = new User();
//user.UserID = db.Users.Where(c => c.UserName == context.User.Identity.Name);
User user = new User();
Movie movie = db.Movies.Find(id);
review.MovieID = movie.MovieID;
string username = context.User.Identity.Name;
user = (User)db.Users.Where(p => p.UserName.Equals(username));
review.UserID = user.UserID;
db.Reviews.Add(review);
db.SaveChanges();
return RedirectToAction("Index");
};
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View(review);
}
you're problem is likely this line:
user = (User)db.Users.Where(p => p.UserName.Equals(username));
the Where() extension method returns an IEnumerable, not a single instance, so your implicit cast will fail every time. what you're looking for is either First() or FirstOrDefault(). First() will throw an exception if there is no match though, while FirstOrDefault()will return a null if no match is found. the line should be :
user = db.Users.First(p => p.UserName.Equals(username));
you probably have bigger problems than that, but this is the cause of your current error.
EDIT
upon further looking at your code, you're asking for a HttpContextBase in your action result call. you never use it, and it's probably the cause of the Cannot create an abstract class exception. remove that parameter and see if you get any different results.
Say you retrieve a set of records from the datastore (something like: select * from MyClass where reserved='false').
how do i ensure that another user doesn't set the reserved is still false?
I've looked in the Transaction documentation and got shocked from google's solution which is to catch the exception and retry in a loop.
Any solution that I'm missing - it's hard to believe that there's no way to have an atomic operation in this environment.
(btw - i could use 'syncronize' inside the servlet but i think it's not valid as there's no way to ensure that there's only one instance of the servlet object, isn't it? same applies to static variable solution)
Any idea on how to solve?
(here's the google solution:
http://code.google.com/appengine/docs/java/datastore/transactions.html#Entity_Groups
look at:
Key k = KeyFactory.createKey("Employee", "k12345");
Employee e = pm.getObjectById(Employee.class, k);
e.counter += 1;
pm.makePersistent(e);
'This requires a transaction because the value may be updated by another user after this code fetches the object, but before it saves the modified object. Without a transaction, the user's request will use the value of counter prior to the other user's update, and the save will overwrite the new value. With a transaction, the application is told about the other user's update. If the entity is updated during the transaction, then the transaction fails with an exception. The application can repeat the transaction to use the new data'
Horrible solution, isn't it?
You are correct that you cannot use synchronize or a static variable.
You are incorrect that it is impossible to have an atomic action in the App Engine environment. (See what atomic means here) When you do a transaction, it is atomic - either everything happens, or nothing happens. It sounds like what you want is some kind of global locking mechanism. In the RDBMS world, that might be something like "select for update" or setting your transaction isolation level to serialized transactions. Neither one of those types of options are very scalable. Or as you would say, they are both horrible solutions :)
If you really want global locking in app engine, you can do it, but it will be ugly and seriously impair scalability. All you need to do is create some kind of CurrentUser entity, where you store the username of the current user who has a global lock. Before you let a user do anything, you would need to first check that no user is already listed as the CurrentUser, and then write that user's key into the CurrentUser entity. The check and the write would have to be in a transaction. This way, only one user will ever be "Current" and therefore have the global lock.
Do you mean like this:
public void func(Data data2) {
String query = "select from " + objectA.class.getName()
+ " where reserved == false";
List<objectA> Table = (List<objectA>) pm.newQuery(
query).execute();
for (objectA row : Table)
{
Data data1 = row.getData1();
row.setWeight(JUtils.CalcWeight(data1, data2));
}
Collections.sort(Table, new objectA.SortByWeight());
int retries = 0;
int NUM_RETRIES = 10;
for (int i = 0; i < Table.size() ; i++)
{
retries++;
pm.currentTransaction().begin(); // <---- BEGIN
ObjectA obj = pm.getObjectById(Table.get(i).class, Table.get(i).getKey());
if (obj .getReserved() == false) // <--- CHECK if still reserved
obj.setReserved(true);
else
break;
try
{
pm.currentTransaction().commit();
break;
}
catch (JDOCanRetryException ex)
{
if (j == (NUM_RETRIES - 1))
{
throw ex;
}
i--; //so we retry again on the same object
}
}
}