Entity Framework 6 introduced a new way to support transactions in the DbContext with the BeginTransaction method:
var db = new MyDbContext();
using(var tx = db.Database.BeginTransaction())
{
// update entities
try
{
db.SaveChanges();
tx.Commit();
}
catch(Exception)
{
tx.Rollback();
}
}
Is the Rollback() call in the method necessary? What happens if it is not called on an exception? I know when using TransactionScope it will roll back the transaction automatically when it is disposed and Complete is not called. Does DbContextTransaction behave similarly?
No it is not necessary to explicitly call Rollback. The tx variable will be disposed when the using block finishes, and the transaction will be rolled-back if Commit() has not been called.
I have tested this using SQL Server Activity Monitor, by observing the locks held on the database objects, as well as querying against the database to observe when the data is rolled back, using the nolock hint in my select statement to be able to view uncommitted changes in the database.
E.g. select top 10 * from [tablename] (nolock) order by modifiedDate
To the EF, the database provider is arbitrary and pluggable and the
provider can be replaced with MySQL or any other database that has an
EF provider implementation. Therefore, from the EF point of view,
there is no guarantee that the provider will automatically rollback
the disposed transaction, because the EF does not know about the
implementation of the database provider.
This answer pretty much explains everything and confusion with all msdn docs having that Rollback called explicitly: https://stackoverflow.com/a/28915506/5867244
Related
I am using Entity Framework Core with PostgreSQL. I want to override SaveChanges in my DbContext to both commit the EF changes and send a message with Rebus within the same database transaction. The plan is to also use PostgreSQL for transport etc, but I am having trouble merely enlisting Rebus in a transaction scope using the code below.
public override int SaveChanges()
{
using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
tx.EnlistRebus();
var result = base.SaveChanges();
//TODO: _bus.Send("something happened");
tx.Complete();
return result;
}
}
Running the above results in PostgresException: 55000: prepared transactions are disabled, which is true, since my PostgreSQL configuration has not enabled it. My question is why Rebus needs to cause a 2-phase commit here. Maybe I'm missing something, though I would hope a 2PC is not needed, given both Entity Framework and Rebus will use the same relational database instance.
The call to EnlistRebus is in the Rebus.TransactionScopes package and does not know what transport implementation is in use and might do the safe thing.
Is there another way to do both the database operation and Rebus send transactionally without 2-phase commit? I can of course use a separate table to store my pending message from SaveChanges and have a separate worker pull from that table and send the message using Rebus. I suspect this approach is the most solid and straightforward.
I am using Rebus 6.3.0, Rebus.PostgreAql 7.1.0, Rebus.TransactionScopes 6.0.0, Npgsql 4.1.3.1, Npgsql.EntityframeworkCore.PostgreSQL 3.1.4 and EF Core 3.1.4.
Rebus.PostgreSQL will actually enlist in the ambient transaction by itself without Rebus.TransactionScopes.
Looking at the Rebus.PostgreSQL repo, I saw there was a PR that seems to solve my issue. Quote from the PR at https://github.com/rebus-org/Rebus.PostgreSql/pull/14:
I first tried getting https://github.com/rebus-org/Rebus.TransactionScopes to work, but that does not seem to do anything using the postgresql transport.
To verify that it does indeed do the message publishing in the ambient transaction, I did the following modification:
public override int SaveChanges()
{
using (var tx = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted },
TransactionScopeAsyncFlowOption.Enabled))
{
var result = base.SaveChanges();
_bus.Send(new MyMessage { CurrentDateTime = DateTime.Now}).Wait();
if (ShouldCrash)
{
throw new ArgumentException();
}
tx.Complete();
return result;
}
}
If ShouldCrash is set to true, no message is published and no changes are made to the entities. If ShouldCrash is set to false both message publishing and entity change are performed.
I think this works because of the following from the Npgsql docs:
Note that if you open and close connections to the same database inside an ambient transaction, without ever having two connections open at the same time, Npgsql will internally reuse the same connection, avoiding the escalation to a full-blown distributed transaction.
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.
Within JBOSS 7.1 AS, I'm using container managed transaction. For each request, I do several entity updates. Most of the entities use "insert, merge, refresh" methods from EntityManager to manage the updates. However, there is one entity that uses explicit Query to do "executeUpdate" on the DB (see below for the code snippet). This sql update is immediately commited to the DB and it is not aligned with container managed transaction (like other entity updates). Is there anyway align explicit sql update (the one below) with container-managed-transaction? I'm trying to get rollback to work and this sql update is not being rolledback. All other entity updates and inserts are working fine except this one. Thanks for all your help.
code snippet:
entityManager.createQuery
( "UPDATE Balance a SET a.balanceValue = :newValue WHERE a.balanceId =:balanceId AND a.balanceValue = :currentValue" ) .setParameter("balanceId", cb.getBalanceId()) .setParameter("currentValue", cb.getBalanceValue()).setParameter("newValue", newAmt).executeUpdate();
Additional code: (Code below is using Bean-managed transaction, but i get same behaviour for CMT as well)
ut.begin();
ChargingBalance bal2 = entityManager.find(ChargingBalance.class, 13);
bal2.setResetValue((new Date()).getTime());
String UPDATE_BALANCE_AND_EXPIRYDATE_EQUAL = "UPDATE ChargingBalanceValue a"
+ " SET a.balanceValue = :newValue "
+ " WHERE a.balanceId = :balanceId";
Query query = entityManager.createQuery(UPDATE_BALANCE_AND_EXPIRYDATE_EQUAL)
.setParameter("balanceId", 33)
.setParameter("newValue", 1000l);
/*The executeUpdate command gets committed to DB before ut.commit is executed */
query.executeUpdate();
/* This below only commits changes on ResetValue */
ut.commit();
ut.begin();
ChargingBalance bal = entityManager.find(ChargingBalance.class, 23);
bal.setResetValue(1011l);
query = entityManager.createQuery(UPDATE_BALANCE_AND_EXPIRYDATE_EQUAL)
.setParameter("balanceId", 33)
.setParameter("newValue", 2000l);
query.executeUpdate();
/* This rollback doesn't rollback changes executed by executeUpdate, but it rollbacks ResetValue change */
ut.rollback();
The executeUpdate command gets committed to DB before ut.commit is
executed
It might have probably flushed changes into the database, but not committed as you were in BMT.
You can try roll back & verify if it is really committed & is within transaction.
This below only commits changes on ResetValue
When you execute native or JPQL/HQL query, it will make changes directly into the database & EntityManager might not be aware of those changes.
Therefore managed entities aren't refreshed implicitly by EntityManager & might contain outdated/stale data.
You can go through documentation for more details, below is the exerpt.
JPQL UPDATE queries provide an alternative way for updating entity
objects. Unlike SELECT queries, which are used to retrieve data from
the database, UPDATE queries do not retrieve data from the database,
but when executed, update the content of specified entity objects in
the database.
Updating entity objects in the database using an UPDATE query may be
slightly more efficient than retrieving entity objects and then
updating them, but it should be used cautiously because bypassing the
EntityManager may break its synchronization with the database. For
example, the EntityManager may not be aware that a cached entity
object in its persistence context has been modified by an UPDATE
query. Therefore, it is a good practice to use a separate
EntityManager for UPDATE queries.
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.
Is there a way to implement transactions in code first without having to write stored procedures?
I have some scenarios where multi-table entries need to be created with unique guids before a final table entry can be created. Is this something I can code using EF alone?
DbContext.SaveChanges() method uses a transaction . So it is Atomic and you don't want to use stored procedures. The unitOfWork patter is implemented in EF itself to accomplish this.
But let's say you are using two DbContext instances to d your job , then you need to wrap your work with a transaction scope like this,
using (var scpe=new TransactionScope()){
...
context1.SaveChanges();
....
context.SaveChanges();
scope.Complete();
}
SaveChanges operates within a transaction. SaveChanges will roll back
that transaction and throw an exception if any of the dirty
ObjectStateEntry objects cannot be persisted.
See the documentation