Deep dive - New transaction management in 5.0
If you closely checked the version 5.0, you could notice in our code and documentation that the recommended way how to work with transactions has changed. Read this article about the details ...
Using connections and transactions with Kentico CMS has never been easier!
With all these TransactionScopes around, and also not exactly right connection and transaction management in previous version, we needed to make the things right. There were several reasons for the change:
The code to handle transactions was too heavy and required to exactly follow the patterns, otherwise, it could cause some issues.
If you used transaction, you had to ensure, that all methods within that transaction get the same connection object. It was hard to not only make sure that you are calling the method with connection parameter or inject the connection with the main object parameter, but also some parts of our API didn't even have such options.
Same with some heavy code that could actually use more connections that was really needed
Our transaction handling wasn't compatible with the .NET TransactionScope class
The core of new connection and transaction handling are two classes: CMSConnectionScope
, they provide a way how to ensure an connection (transaction) envelope around your code to make sure that all nested command will use the same connection (transaction).
It is important to explain why we just didn't take the .NET classes but built our own. Again, there are several reasons:
.NET classes are not thread safe enough for our purposes where we for instance fire a thread for logging the web farm of staging task from within the transaction.
We provide a way to connect your own DataProvider which doesn't need to be based on any .NET class compatible with the .NET classes so in such case, you would have to implement much more to make it work.
Still, the usage of these classes is very similar to .NET ones
How to use the CMSConnectionScope?
It is very simple, you just create it before your actions and let it be disposed after, using the using
statement is the best way to do both:
using (CMSConnectionScope cs = new CMSConnectionScope
... do your actions
What happens is that the connection of the scope is put to the context, making all nested connections to use the same backend connection. If you do not put there one using the constructor parameter, one will be automatically created for you. The overriden constructor gets the existing connection and also a parameter which says if the connection should be open within this scope. In case it is, it is also closed automatically, no need for additinal code:
(IDataConnection conn, bool openConnection)
So no more taking care about passing the connection around, create a scope and it's done automatically for you.
It is important to note that when a new thread is created, the connection is not passed to the thread to maintain the thread safety. So the thread should create its own CMSConnectionScope
for its operations.
BTW: I recommend you to always use the class CMSThread
for new threads, it will give you more flexibility with everything since the context values are put to the thread.
How to use TransactionScope
Once again, it is very similar to .NET one, you just create one, commit (complete) is before it is disposed and let the rest be handled by the disposal.
using (CMSTransactionScope tr = new CMSTransactionScope
... do your actions in transaction
tr.Commit(); // Commit the changes, you can also use the Complete() method, it does the same
When there is a transaction, there must a connection, that is why the CMSTransactionScope
can handle both. You just use the CMSTransactionScope
, and you automatically get the new CMSConnectionScope
around it. But still, you have the option to use the existing connection with it if you use the other, overriden constructor:
Nesting connection and transaction scopes
Of course we through about it and it is possible to nest them both in any direction, because basically there is always a CMSConnectionScope
so the outer most code handles the connection. So do may do following things:
Use CMSConnectionScope inside another CMSConnectionScope - this does exactly the same as not having the inner one. The outer one says "use this connection for everything" so the inner one doesn't make sense in this context and it is skipped. No need to do such thing at all (but this happens if you use some of our methods within your scope, yours (outer) will always take priority).
Use CMSTransactionScope inside CMSConnectionScope - The transaction just uses the existing connection from the scope. This can be used if you do some reading and then use the same connection by writing within transaction. Single connection will be always used.
Use CMSTransactionScope inside another CMSTransactionScope - It acts the same way like for the connections. The outer one still needs to commit the change, so the inner one is not used (like it never existed)
Use CMSConnectionScope inside CMSTransactionScope - Even this can happen, it is a situation where you use our reading code in your transaction code. I told you that CMSTransactionScope has always a CMSConnectionScope around (either existing or new) it so it is basically the same scenario as the first one, the inner connection is not used.
Summary: There is no need for you to take care about the nesting itself, you just need to know which are your outer most transaction scopes (borders for the transaction) and connection scopes (borders for the connection).
Combine your code with our code
If you need to combine your code to access external data sources within the transaction with our code, it is possible. Just know that all GeneralConnection
methods and calls to our API nested within the CMSConnectionScope
are using the same connection. Anything custom will work the way you are used to.
If you just simply do not call the Commit (Complete) method, the transaction will be rollbacked at the end automatically (even when some exception is fired). Same with the connection, if it gets open, it is closed automatically.
There is always a way how to make this compatible with standard .NET TransactionScope class, we couldn't use it by default since the Distributed Transaction Coordinator Service is not guaranteed to be enabled on the machine and it would cause installation issues, but you can easily set the web.config key to enable it:
<add key="CMSUseTransactionScope" value="true" />
What happens is that instead of standard BeginTransaction / CommitTransaction it uses the .NET TransactionScope
class. Needless to say that in such case your data provider has to also support it.
That's about it. I do not want to post the original code for handling transactions, you can find it in the documentation archive. It is not obsoleted, you can still use it the same way, but when you compare it to new one, I am sure you will like the new one more ;-)
See you next time