I know NHibernate supports ambient transactions, because NHibernate sessions enlists in the ambient transactions while inside a transaction scope. However, there are some oddities, consider the following test:
[Test]
public void Transaction_RollsBackTransactionInsideOfAmbientTransaction_AmbientTransactionAborted()
{
// arrange
ISessionFactory sessionFactory = SessionFactoryOneTimeInitializer.GetTestSessionFactory();
ISession session = sessionFactory.OpenSession();
SessionFactoryOneTimeInitializer.CreateDataBaseSchemaIfRequiredByConfiguration(session);
using (new TransactionScope())
{
using (ITransaction transaction = session.BeginTransaction())
{
// act
transaction.Rollback();
}
// assert
Assert.AreEqual(TransactionStatus.Aborted, Transaction.Current.TransactionInformation.Status);
}
}
This test fails. How will NHibernate ensure that the ambient transaction is not persisted to the database?
I know relatively well how Hibernate work with JTA in the Java world, but I am not a .NET expert. Your question nevertheless caught my attention.
In Java, you need to configure Hibernate with either JDBC or JTA transaction. In which case, the Transaction object returned by Hibernate wraps either a transaction that is bound to one database connection (JDBC) or the global transaction that is thread-local. The global thread-local transaction context can be invalidated using UserTransaction#setRollbackOnly, which ensures it will never commit succesfully. It's however preferable to not manage transaction through Hibernate but to use solely the UserTransaction object provided by JTA.
This still seems to be the same in NHibernate and there is two transaction factories. One for distributed transactions and one for local transactions. But both return an AdoTransaction:
public ITransaction CreateTransaction(ISessionImplementor session)
{
return new AdoTransaction(session);
}
This doesn't seem to be consistent in case of distributed / ambient transactions. I don't see how rollback would work in this case given that the global transaction context can not be invalided in .NET (so far I understand), and AdoTransaction seems to represent a transaction on database connection.
So I feel like the answer to your question is "it won't" which would explain that your test fails. This means that you should not manage transaction through NHiberate if you use ambient transaction. Just like it's not a recommended practice with Hibernate and JTA.
EDIT
See also this question: How does TransactionScope roll back transactions?
Related
I have many classes all associated to a specific table. On a higher level i have parent transactions that call various methods so the child methods must be able to work inside a transient transaction and often it is not known beforehand if a method at some stage will be part of a transaction or not since we try to keep them generic.
Then in some cases we need Dapper queries e.g. to turn identity on / off . I understood that Dapper requires passing a Transaction as parameter otherwise it will not be enlisted in the transaction (turns out i was wrong see below).
The DbContext(Pooling) is set per "component/dll" so since a connection is only enlisted when it its opened inside a transaction a scope is used of context to ensure it is opened for this transaction. Furthmore that helps when calling these same methods from e.g. HealthChecks who otherwise will complain about too many open connections when many of them call the same connections opened by services. Have this scope in methods helps also with calling these methods in parallel work so that they are more nicely run in parallel threads.
In other words in this way these methods can be called from these parents which can be parallel job parents or singletons requiring a service scope or parent transactions that require a transients hierarchy.
The problem was: For some reason transaction in the following transaction is always null.
try {
using TransactionScope scope = new TransactionScope(TransactionScopeOption.Required,
System.TimeSpan.FromMinutes(10), TransactionScopeAsyncFlowOption.Enabled);
using Context localcontext = new Context(new DbContextOptionsBuilder<Context>()
.UseSqlServer(_options.ConnectionString).Options);
// just for safety:
localcontext.Database.GetDbConnection().Open();
// the following line is only for dapper input:
IDbContextTransaction transaction = localcontext.Database.CurrentTransaction;
await localcontext.Database.GetDbConnection()
.ExecuteAsync("SET IDENTITY_INSERT [dbo].[Whatever] ON",
null, (System.Data.IDbTransaction)transaction);
}
(which i took from here: Pass current transaction to DbCommand and here https://github.com/zzzprojects/Dapper.Transaction )
UPDATE / SOLUTION:
Ok. So... when using transaction scope the transaction parameter does not have to be passed to Dapper to ensure it enlists in the transaction. That was the clue.
Remove this line
IDbContextTransaction transaction = localcontext.Database.CurrentTransaction;
If there's an active TrasnactionScope your SqlConnection will be automatically enlisted in it. The whole point of TransactionScope is that your data access methods can be completely free of transaction handling. Then in some outer business layer or controller method, the transaction is orchestrated.
The reason CurrentTransaction is null is that there are two different ways to handle transactions. If you want the current System.Transactions.Transaction, you get it with System.Transactions.Transaction.Current.
Stepping back, there are 3 separate ways to manage transactions with SqlConnection.
TSQL Transactions: You can use TSQL API directly issuing BEGIN TRAN, COMMIT TRAN, etc.
ADO.NET Transactions: SqlConnection.BeginTrasaction, IDbTransaction , SqlTransaction, etc. This is a wrapper over the TSQL API, and is a PITA because it introduces a useless requirement to pass the SqlTransaction to each SqlCommand that you want to enlist in the Transaction. But enlisting TSQL commands in the current transaction is not optional, and never has been. And that's a pain because methods that user SqlCommand may not know whether there is a transaction. Dapper and EF both wrap this API in their transaction handling methods.
System.Transactions Transactions: Partly because of this System.Transactions was introduced in .NET 2.0 as a new and unified way to handle transactions in .NET, and SqlClient added support for it. The main innovation of System.Transactions was adding "ambient" transactions. So code could be agnositc about whether there's a transaction and the right thing will just happen. When opening a SqlConnection if there is a current Transaction, the SqlConnection will be enlisted in it, and the changes made using the SqlConnection will not be committed until the Transaction is committed. And there is no need for your ADO.NET code to know about the Transaction. Dapper and EF are both built on top of ADO.NET and SqlClient, so this all just works.
It's easier to explain what's wrong by showing what the code should be:
using(var connection=new SqlConnection(_connectionString))
{
await connection.ExecuteAsync("SET IDENTITY_INSERT [dbo].[Whatever] ON");
}
Where ExecuteAsync comes from Dapper.
There's no reason to create a transaction, much less a transaction scope, to execute a single command.
There's no reason to create a DbContext just to open a connection to the database either, or to execute raw SQL commands. DbContext isn't a database connection, it's job is to Map Objects to Relational data. There are no objects involved here.
To execute multiple commands there's no reason to use multiple connections. Just execute the commands one after the other. If it's really necessary, use an explicit database transaction around those commands. Or create the connection inside a single transaction scope.
Let's say you have an array with those commands, eg something read from a script file :
string[] commands=new[]{...};
using(var connection=new SqlConnection(_connectionString))
{
await connection.OpenAsync();
using (var transaction = connection.BeginTransaction())
{
foreach(var sql in commands)
{
await connection.ExecuteAsync(sql,transaction:transaction);
}
transaction.Commit();
}
}
Doing the same thing using a TransactionScope only requires opening the connection inside the transaction scope.
string[] commands=new[]{...};
using( var scope = new TransactionScope(TransactionScopeOption.Required,
System.TimeSpan.FromMinutes(10), TransactionScopeAsyncFlowOption.Enabled)
using(var connection=new SqlConnection(_connectionString))
{
await connection.OpenAsync();
foreach(var sql in commands)
{
await connection.ExecuteAsync(sql);
}
scope.Complete();
}
Good day,
I have a method which contains several database commits (about 15) in a different classes, I need a way to make all the db changes only if the the method did not throw any exception, I am thinking about using a Transaction Scope, and my question is weather i can use a single instance of that Transaction Scope in all the different classes and if not what is the best practice to perform a rollback in case of an exception?
Thanks!
You usually don't need to perform rollback explicitly, methods that perform database operations might not even be aware of ambient transaction (that is - you don't need to pass TransactionScope to them). Just do:
using (var tran = new TransactionScope()) {
FirstDatabaseOperation();
SecondDatabaseOperation();
// etc
tran.Complete();
}
If exception happens in any operation - transaction will be rolled back for you, because TransactionScope will be disposed.
I'm using Nhibernate for the first time and I 've noticed that when I call BeginTransaction method it lock my Database.
Instead, entity framework (ObjectContext or DbContext too) keeps all changes in memory and SaveChange method work perfectly if no error occurs without lock anything on db.
Has Nhibernate some feautres like EF?
If you are using optimistic concurrency, then you could do something like this:
MyEntity myEntity;
using(var scope = new TransactionScope(TransactionScopeOption.Suppress))
using(var session = sessionFactory.OpenSession())
{
myEntity = session.Get<MyEntity>(id);
scope.Complete();
}
// No longer in a transaction...
myEntity.Add(something);
myEntity.Update(somethingElse);
// Later, possibly in another request...
using(var scope = new TransactionScope(TransactionScopeOption.Required))
using(var session = sessionFactory.OpenSession())
{
session.Update(myEntity);
scope.Complete();
}
As long as a transaction is open (depdending on your isolation level as noted above) you will likely have shared locks on the tables and keys involved in the initial selects, which will block updates to those tables until the transaction completes. If you want to avoid having those locks, you can suppress the transaction for the read, perform modifications, and then attempt to update the object later. The version number on the entity should protect you from lost updates.
Note that you don't have to suppress the read transaction. If you want to block until all writes are committed, you can still require a transaction around the read as long as it is separate from the update transaction and is completed as quickly as possible.
I'm using TransactionScope to manage transactions in EF, i need a ReadCommited behavior but it doesn't works as expected :
using (var trans = new TransactionScope(TransactionScopeOption.Required,
new TransactionOptions()
{ IsolationLevel = IsolationLevel.ReadCommitted}))
{
var c1 = customerRepository.Get(1);
c1.FirstName = "Modified";
customerRepository.Save();
var c2 = customerRepository.Get(1);
Assert.AreNotEqual("Modified", c2.FirstName);
trans.Complete();
}
while i still didn't committed the transaction when getting the second instance, it's FirstName is already modified.
You're inside the same transaction. The transaction isolation level refers to different transactions.
You can't isolate a translation from itself, but of other different transactions.
Try opening two different transaction scopes (i.e. with two apps running at the same time) and you'll see the effect os isolation between them. You can do this debugging two different apps at the same time, and pausing them before commiting the scope.
Look SET TRANSACTION ISOLATION LEVEL (Transact-SQL)
As you can see, when each transaction isolation level is explained, it always refers to other transactions:
READ UNCOMMITTED
Specifies that statements can read rows that have been modified by other transactions but not yet committed.
READ COMMITTED
Specifies that statements cannot read data that has been modified but not committed by other transactions.
REPEATABLE READ
Specifies that statements cannot read data that has been modified but not yet committed by other transactions and ...
and so on.
There is a selection sql query(select * from table1 e.g.) by design in a transactionscope in c#. Normally there is an ambient transaction but If I suppress the ambient transaction when executing this sql query, is there a performance gain or not?
using (TransactionScope scope1 = new TransactionScope())
{
// here there are some business processes
using (TransactionScope scope1 = new TransactionScope(TransactionScopeOption.Suppressed)) //Is this suppressed transaction scope useful in terms of performance?
{
//Here there is a select sql query with no lock table hint.
}
}
Yes- since TransactionScope (as you've used it in your sample) uses the Serializable isolation level, suppressing it for certain queries that don't need the protection of that isolation level will prevent read locks from being taken on the DB server (especially if you're using READ COMMITTED SNAPSHOT as your default isolation level). Also, if other things you've done would promote the transaction to the DTC (ie, multiple connections, etc), you'll save the time to coordinate DTCs, which can be slow.