Entity Framework delete object - concurrency error (how to disable concurrency)? - asp.net

Is there a way to disable concurrency error thrown in EntityFramework?
For example:
using (xEntities5 entities = new xEntities5())
{
entities.Domains.MergeOption = System.Data.Objects.MergeOption.NoTracking;
Domain domain = new Domain() { DomainId = id };
EntityKey key = entities.CreateEntityKey(entities.CreateObjectSet<Domain>().EntitySet.Name, domain);
domain.EntityKey = key;
entities.Attach(domain);
//entities.AttachTo(entities.CreateObjectSet<Domain>().EntitySet.Name, domain);
entities.DeleteObject(domain);
return entities.SaveChanges(); // returns affected rows... must catch error?
}
Is there a way to not have to do try/catch around SaveChanges in order to detect if nothing was deleted?

As I know, you can't turn off concurrency error. Concurrency error is based on number of affected rows so if you want to delete a row and it is not deleted (for example because it doesn't exist any more) concurrency exception is fired. Moreover SaveChanges works in transaction so if you want to delete 5 rows and only 4 rows are deleted the exception is fired and all deletes are rolled back.
Concurrency test can be even more restrictive if you use columns marked as ConcurrencyMode.Fixed. These columns are used in where condition of SQL statements so only unmodified database records can be processed.
Once you get concurrency exception you are supposed to solve it.

SaveChanges() will throw exceptions and doesn't catch them internally, so if you want to continue execution, you have to use try/catch. SaveChanges() also returns a method with the number of entities committed to the database, that is, if no error occurred :-)
HTH.

Related

Added an update operation to InventItemService AIF service and getting exceptions

To my surprise, I didn't find standard update and delete operations on InvenItemService. So in order to fullfill our client's requirements, I ran the AIF update wizard and added these two operations. I thought its easy and found the process of doing that very quick. Before doing that, I set the update property of the AxdItem query to Yes. Later, while debugging the update operations, I figured I had to modify the updateList() and Update() methods on AxdItem class as accordingly to provide method definitions.
public AifResult updateList( AifEntityKeyList _entityKeyList,
AifDocumentXml _xml,
AifEndpointActionPolicyInfo _actionPolicyInfo,
AifConstraintListCollection _constraintListCollection)
{
//throw error(strFmt("#SYS94920"));
return super(_entityKeyList, _xml, _actionPolicyInfo, _constraintListCollection);
}
AifResult update( AifEntityKey _entityKey ,
AifDocumentXml _xml,
AifEndpointActionPolicyInfo _actionPolicyInfo,
AifConstraintList _constraintList)
{
//throw error(strFmt("#SYS94920"));
return super(_entityKey, _xml, _actionPolicyInfo, _constraintList);
}
Now while trying to update an existing item in AX, I am getting following AIF exception.
Cannot edit a record in Item sales order settings (InventItemSalesSetup).
The operation cannot be completed, since the record was not selected for update. Remember TTSBEGIN/TTSCOMMIT as well as the FORUPDATE clause.
Then I changed the update property of all the child data sources on AxdItem Query and re-ran the wizard. Ran CIL again and getting the following exception now.
Cannot edit a record in Item sales order settings (InventItemSalesSetup).
An update conflict occurred due to another user process deleting the record or changing one or more fields in the record.
Any suggestions/ideas?
I have tried several things and spent too much time but failed.
Seems like you trying update the record that already has been deleted in method removeDefaultItemOrderSetup of AxdItem class
This will give you a hint what happens
https://dynamicsuser.net/ax/f/developers/72116/aif-update-cannot-be-run-twice

Handling doctrine transactions on multiple entity managers

Both transactions should be rollback-ed if;
$em1 fails but $em2 succeeds.
$em1 succeeds but $em2 fails.
So, is my example below correct way of dealing with transactions when more than one EMs are involved? I've come up with it after reading Transactions and Concurrency documentation.
$em1->getConnection()->beginTransaction();
$em2->getConnection()->beginTransaction();
try {
$em1->persist($object1);
$em1->flush();
$em1->getConnection()->commit();
$em2->persist($object2);
$em2->flush();
$em2->getConnection()->commit();
} catch (Exception $e) {
$em1->getConnection()->rollback();
$em2->getConnection()->rollback();
}
The reason I'm trying to implement this because I'm getting ....resulted in a Doctrine\ORM\ORMException exception (The EntityManager is closed.) error somewhere along the line in the application. I can probably handle it with the method below but I think using transaction for the business logic above is better.
private function getNewEntityManager($em)
{
if (!$em->isOpen()) {
$em = $em->create($em->getConnection(), $em->getConfiguration());
}
return $em;
}
Your example code actually does work, which surprises me because Francesco Panina is (or should be) correct that $em1->getConnection()->commit()
will commit the first transaction and you will loose [sic] the privilege to rollback such transaction should an error arise from the second transaction.
However, something in the way that Doctrine handles transaction nesting levels means that you actually can still rollback the first transaction when an error arises from the second transaction.
Nonetheless, best practice would be to not depend on this behavior and instead put both commits at the very end of your try block, as so:
$em1->getConnection()->beginTransaction();
$em2->getConnection()->beginTransaction();
try {
$em1->persist($object1);
$em1->flush();
$em2->persist($object2);
$em2->flush();
$em1->getConnection()->commit();
$em2->getConnection()->commit();
} catch (Exception $e) {
$em1->getConnection()->rollback();
$em2->getConnection()->rollback();
throw $e;
}
With this small change, your example does demonstrate the correct way to deal with transactions that span multiple entity managers.
I'd like to point out a couple of things that may clear your mind on the matter:
I'm not aware of the process you use to create the second entity manager, keep in mind that 2 completely different entity manager will not share the same connection. Can you point out your use case for 2 different entity manager?
Consider that the operation:
$em1->getConnection()->commit();
will commit the first transaction and you will loose the privilege to rollback such transaction should an error arise from the second transaction.
Doctrine\ORM\ORMException exception (The EntityManager is closed.)
It's typical when you try to operate any commit/flush operation after a DBAL (database related) exception has been thrown; in this case Doctrine default behaviour is to close the entity manager.
And it is common practice to do so after any rollback:
$em1->getConnection()->rollback();
$em1->close();
Hope it helps,
Regards.

Creating many batches (SysOperation Framework) very quickly doing similar processes - "Cannot edit a record in LastValue (SysLastValue)"?

I have a SysOperation Framework process that creates a ReliableAsynchronous batch to post packing slips and several get created at a time.
Depending on how quickly I click to create them, I get:
Cannot edit a record in LastValue (SysLastValue).
An update conflict occurred due to another user process deleting the record or changing one or more fields in the record.
And
Cannot create a record in LastValue (SysLastValue). User ID: t edit a, Class.
The record already exists.
On a couple of them in the BatchHistory. I have this.parmLoadFromSysLastValue(false); set. I'm not sure how to prevent writing to SysLastValue table.
Any idea what could be going on?
I get this exception a lot too, so I've created the habit of catching DuplicateKeyException in my service operation. When it is thrown, catch it and retry (for a default of 5x).
The error occurs when a lot of processes run simultaneously, like you are doing now.
DupplicateKeyException can be caught inside a transaction so you could improve by putting a try/catch around the code that does the insert in the SysLastValue table if you can find the code.
As far as I can see these are the only to occurrences where a record is inserted in this table (except maybe in kernel):
InventUnusedDimCleanUp.serialize()
SysAutoSemaphore.autoSemaphore()
Put a breakpoint there and see if that code is executed. If so you can add a try/catch with retry and see if that "fixes" it.
You could also use the tracing cockpit and the trace parser to figure out where that record is inserted if it's not one of those two.
My theory about LoadFromSysLastValue: I believe setting this.parmLoadFromSysLastValue(false) does not work since it is only taken into account when the dialog is started, not when your operation is executed. When in batch, no SysLastValue will be used to initialize your data contract as you want it to use the exact parameters you have supplied in your data contract .
It's because of the code calling SysOperationController.savelast() while in batch, my solution is to set loadFromSysLastValue to false in SysOperationController.loadFromSysLastValue() as part of the in batch check:
if (!this.isInBatch())
{
.....
}
//Begin
else
{
loadFromSysLastValue = false;
}
//End

Store update, insert, or delete statement affected an unexpected number of rows (0)" Error in delete function

Each task has a reference to the goal it is assigned to. When I try and delete the tasks, and then the goal I get the error
"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries." on the line _goalRepository.Delete(goalId);
What am I doing wrong?
[HttpPost]
public void DeleteGoal(int goalId, bool deleteTasks)
{
try
{
if (deleteTasks)
{
Goal goalWithTasks = _goalRepository.GetWithTasks(goalId);
foreach (var task in goalWithTasks.Tasks)
{
_taskRepository.Delete(task.Id);
}
goalWithTasks.Tasks = null;
_goalRepository.Update(goalWithTasks);
}
_goalRepository.Delete(goalId);
}
catch (Exception ex)
{
Exception deleteException = ex;
}
}
Most likely the problem is because you're attempting to hold onto and reuse a context across page views. You should create a new context, do your work, and dispose of the context atomically. It's called the Unit Of Work pattern.
The main reason for this is that the context maintains some state information about the database rows it has seen, if that state information becomes stale or out of date then you get exceptions like this.
There are a lot of other reasons to use the Unit of Work pattern, I would suggest you do a web search and do a little reading as an educational exercise.
This may have nothing to do with data access though. You are removing items from a list as you are iterating it, which would cause problems if you were using a normal List. Without knowing much about the internals of EF, my guess is that your delete calls to the repository are changing the same list that you are iterating.
Try iterating the list in one pass and record the Task ids you want to delete in separate list. Then when you have finished iterating, call delete on the Task list. For example:
var tasksToDelete = new List<int>();
foreach (var task in goalWithTasks.Tasks)
{
tasksToDelete.Add(task.Id);
}
foreach (var id in tasksToDelete)
{
_taskRepository.Delete(id);
}
This may not be the cause of your problem but it is good practice to never change the collection you are iterating.
I ran across this issue at work (I am an Intern), I was getting this error when trying to delete a piece of Equipment that was referenced in other data-tables.
I was deleting all references before attempting to delete the Equipment BUT the reference deletion was happening in another Model which had its own database context, the reference deletion would be saved within the Model's context.
But the Equipment Model's context would not know about the changes that just happened in another Model's context which is why when I was trying to delete the Equipment and then save the changes (eg: db.SaveChanges()) the error would happen (the Equipment context still thought there was references to that equipment in other tables).
My solution for this was to re-allocate the context before attempting to delete the Equipment:
db = new DatabaseContext();
Now the newly allocated context has the latest snapshot of the database and is aware of all changes made. Deletion happens without issues.
Hope my experience helps.

Flush separate Castle ActiveRecord Transaction, and refresh object in another Transaction

I've got all of my ASP.NET requests wrapped in a Session and a Transaction that gets commited only at the very end of the request.
At some point during execution of the request, I would like to insert an object and make it visible to other potential threads - i.e. split the insertion into a new transaction, commit that transaction, and move on.
The reason is that the request in question hits an API that then chain hits another one of my pages (near-synchronously) to let me know that it processed, and thus double submits a transaction record, because the original request had not yet finished, and thus not committed the transaction record.
So I've tried wrapping the insertion code with a new SessionScope, TransactionScope(TransactionMode.New), combination of both, flushing everything manually, etc.
However, when I call Refresh on the object I'm still getting the old object state.
Here's some code sample for what I'm seeing:
Post outsidePost = Post.Find(id); // status of this post is Status.Old
using (TransactionScope transaction = new TransactionScope(TransactionMode.New))
{
Post p = Post.Find(id);
p.Status = Status.New; // new status set here
p.Update();
SessionScope.Current.Flush();
transaction.Flush();
transaction.VoteCommit();
}
outsidePost.Refresh();
// refresh doesn't get the new status, status is still Status.Old
Any suggestions, ideas, and comments are appreciated!
I've had a similar problem before, related to isolation levels. The default isolation level was set to "Snapshot", and when running on SQL Server, this meant that the first transaction would not see anything that changed since it started. Maybe it's your isolation level?
If not, try creating a brand-new TransactionScope straight after disposing the one above, and see if you can read it back as New. If you can't, it's probably nothing to do with the outside transaction.
Hope that helps.
Marcus

Resources