I'm aware of two different scenarios in which exceptions can be produced when working with an Entity Framework DbContext:
Enumerating a query (could throw a EntityCommandExecutionException)
Calling SaveChanges (could throw a DbUpdateException)
Within a single instance of DbContext, I'm wanting to catch these exceptions, try to recover if applicable, and then repeat the operation.
Specifically, if a call to SaveChanges throws an exception because of a deadlock, I would like to retry the call to SaveChanges. I already know how to detect this situation and perform the retry.
I saw this answer here, which indicates that an SQL connection shouldn't be used after a deadlock. This indicates that I should restart the entire DbContext and higher-level operation to recover from such exceptions.
What I'm not sure about is whether it's safe to continue using the DbContext after it has thrown an exception such as this. Will it enter an unusable state? Will it still work but not function correctly? Will SaveChanges no longer occur transactionally?
If you don't supply the DbContext with an already opened SQL connection, the DbContext will open and close the connection for you when you call SaveChanges. In that case there is no danger in keeping the DbContext around, except of course that the entities the DbContext holds on to might be in an invalid state (because this could be the reason that the SQL exception was thrown).
Here's an example of a DbContext that is suppied by an opened SQL connection and transaction:
using (var connection = new SqlConnection("my connection"))
{
connection.Open();
using (var transaction = connection.BeginTransaction())
{
using (var context = new DbContext(connection))
{
// Do useful stuff.
context.SaveChanges();
}
transaction.Commit();
}
}
If you supply the DbContext with a SqlConnection that runs in the context of a transaction, this answer holds.
Note that Entity Framework will not create a nested transaction. It simply checks whether the connection is "enlisted in user transaction". If SaveChanges already runs in a transaction, no transaction is started. Entity Framework however is unable to detect if the database has aborted the transaction because of a severe failure (such as a database deadlock). So if a first call to SaveChanges fails with something like a deadlock and you catch and recall SaveChanges, Entity Framework still thinks it is running inside a transaction.
This means that this second call is executed without a transaction and this means that when the operation fails halfway, the already executed statements will NOT be rolled back since there is no transaction to rollback.
The problem of the torn SaveChanges operation could have been prevented if Entity Framework used nested transactions, but it still wouldn't solve the general problem of consistency.
Entity Framework creates connections and transactions for us when we do not supply them explicitly. We only need/want to supply a connection and transaction explicitly when the call to SaveChanges is part of a bigger overall transaction. So even if EF created a nested transaction for us and committed this before returning from SaveChanges, we're in trouble if we call SaveChanges a second time, since this 'nested' transaction actually isn't nested at all. When EF commits this 'nested' transaction, it actually commits the only transaction there is, which means that the entire operation we needed to be atomic is torn; all changes done by SaveChanges are committed, while the operations that might came after this call didn't run. Obviously this is not a good place to be.
So moral of the story is that either you let Entity Framework handle connections and transactions for you and you can redo calls to SaveChanges without risk, or you handle transactions yourself and will have to fail fast when the database throws an exception; you shouldn't call SaveChanges again.
Related
If you call SaveChanges and it throws an exception (say, because of a concurrency exception), and then you call it a 2nd time, will SaveChanges try to "replay" the same database commands, or will it just "pick up where it left off" or will it just be all messed up and you shouldn't call it again?
And what if it was in an explicit transaction when it threw, and you exited that transaction and then entered an new one and called SaveChanges again?
I'm not talking about EF Core, but you can give me the answer for that as well.
If I made multiple operations with the same Entity Framework DbContext (add and update)
with one call to SaveChanges, are those changes will be done as a Transaction or not?
using (MyContext context = new MyContext())
{
context.Table1.Add(entity1);
context.Table2.Add(entity2);
context.SaveChanges();
}
or is there a chance to execute just one of them without executing the other?
Yes, it's wrapped in a transaction:
In all versions of Entity Framework, whenever you execute
SaveChanges() to insert, update or delete on the database the
framework will wrap that operation in a transaction. This transaction
lasts only long enough to execute the operation and then completes.
When you execute another such operation a new transaction is started.
https://learn.microsoft.com/en-us/ef/ef6/saving/transactions
You can't do any partial save, otherwise, your DbContext could get into inconsistent state. You can only call SaveChanges multiple times after each change operation.
I am chasing an issue with MySql / EF Core where I randomly have an exception thrown saying Nested transactions are not supported. This exception does not occur when my system is only used by one user. But when I run my tests in parallel or when I have multiple users, the exception occurs. I looked at all the code and their is nothing that could create nested transactions.
The only piece of codes that scares me so far is something like the following:
using (var transaction = _context.Database.BeginTransaction())
{
// Create a few entities and add them to the EF context
...
// Insert the rows: hopefully at this point, my transaction is not commited yet.
_context.SaveChanges();
// I then want to update a few rows with a raw sql statement without
// having to load the entities in memory.
_context.Database.ExecuteSqlCommand("UPDATE ...");
// Now that I have inserted and inserted some data, I want to commit all these
// changes atomically.
transaction.Commit();
}
Is this safe? Am I guaranteed that my SaveChanges and ExecuteSqlCommand will be executed on the same MySqlConnection? I have the feeling that when I call SaveChanges, it closes my connection and puts it back on the connection pool. Then, my ExecuteSqlCommand takes a new connection from the pool (it may be the same one or another one). So my initial connection (the one where I opened the transaction) is put back in the pool and it could be reused by another thread.
This is just a hunch and I am totally not sure if this could cause the problem.
My real question in the end is:
is it safe to use SaveChanges and ExecuteSqlCommand within a transaction?
I upgraded from MySql.Data.EntityFrameworkCore/MySql.Data 6.10.1-beta to 6.10.3-rc and it looks like the problem is gone. Since the problem was random I can't be totally sure that the problem is indeed fixed, but so far so good.
EDIT:
3 years later, the problem was never observed anymore.
public void whyEntityExistsExceptionisnotthrown(){
EntityManager em=getEntityManager();
try{
Partner partnerOne=em.find(Partner.class, 1L); // from the database
System.out.println("Partner partnerOne information-----------> "+partnerOne.getName());
Partner partnerTwo =new Partner();
partnerTwo.setIdpartner(1L);
partnerTwo.setName("Partner 200");
em.persist(partnerTwo);
Partner partnerThree=em.find(Partner.class, 1L);
// the method find has two entities with the id 1L. I think this could be a problem.
if(em.contains(partnerOne))
System.out.println("PartnerOne managed");
if(em.contains(partnerTwo))
System.out.println("PartnerTwo managed");
System.out.println("Partner partnerTwo information-----------> "+partnerTwo.getName());
System.out.println("Partner partnerThree information-----------> "+partnerThree.getName());
}catch(EntityExistsException e){
System.out.println("The entity already exist");
}
}
Through this post I try to see that problems can arise by allowing two entities have the same id in a persistence context.
The question is:
Is there a way to avoid that there may be two managed entities with the same id in a persistence context before calling the flush method or commit?
Why not throw the exception persist?
If I call this method the result is:
First call to whyEntityExistsExceptionisnotthrown
partnerOne: the information from the database (I've got a partner in database with id=1)
partnerTwo: Name=Partner 200
partnerThree: Name=Partner 200 (but could have been the information of partnerOne.
Next Call
partnerOne ------> Name=Partner 200
partnerTwo ------> Name=Partner 200
partnerThree ----> Name=Partner 200
According to the documentation of the persist() method, it throws an
EntityExistsException - if the entity already exists. (If the entity
already exists, the EntityExistsException may be thrown when the
persist operation is invoked, or the EntityExistsException or another
PersistenceException may be thrown at flush or commit time.)
So to be sure that your persist() reaches the DB, you should call em.flush() right after persisting the entity and you will get one exception (either EntityExistsException on calling persist or EntityExistsException/PersistenceException flush). Yes, one could ask himself why the JPA specification is not very clear in this point, but I am pretty sure there is good reason for that, like performance (trying to make a single I/O operation to DB). So, if you want portable/working code, call the flush() operation. You probably do not understand that most operations like remove, merge, persist are not guaranteed to reach to DB until the transaction commit. So it is your duty to call flush() when needed.
So to answer your questions:
Is there a way to avoid that there may be two managed entities with
the same id in a persistence context before calling the flush method
or commit?
I doubt that, and also I haven't found so far the need for that (!call flush!). Also as far as I know, the JPA spec. does not require that.
Why not throw the exception persist?
Because the persist is not guaranteed to be synchronized to DB in that moment. The documentation says that pretty clear.
Some important notes:
As I understand from your code, your ID field is NOT autogenerated. Usually the applications lets the DB to generate the ID, so that calling persist gets synchronized to DB.
Also, in your code (without testing it) I am sure that partnerOne==partnerThree is evaluated as true, meaning they are actually EXACTLY THE SAME object. The only problem is with the partnerTwo, which you must flush.
Hi All,
I am not able to understand the exact behavior of #Post callback methods. As mention in ProJPA book "When the SQL for deletion of an entity finally does get sent to the database, the PostRemove event will get fired. As with the PostPersist lifecycle event, the PostRemove event does not guarantee success. The enclosing transaction may still be rolled back".
My concern here is, if SQL DELETE statement is already fire then how transaction will be rolled back? If SQL DELETE statement is not able to delete the object then exception will be thrown and #PostDelete will not be executed. So, under what scenario transaction can be rolled back??
Thanks all for your time!!
According JPA specifications: the #PostRemove callback is executed after the remove operation on the EntityManager.
The key point to understand is that the remove operation on the EntityManager won't throw an exception if the remove fail. The transaction may be marked as "Rollback-Only" (i.e. it means that the transaction will be roll-backed when the transaction ends... and the exception will be thrown at the end of the transaction).
The JPA specifications indicates that the #PostRemove callback will be executed (in the same transaction as the remove operation of course) regardless of the flag "Rollback-Only".
It means that you can perform additional database operation in the #PostRemove : those operations will be part of the same transaction (and so will also be rollbacked if something went wrong). And the corollary : if something flag the transaction as rollback-only during the #PostRemove : the remove operation will not be executed on your database.