September 15, 2009

Transactions and .NETs TransactionScope, what and why?

Transactions are used to add the Rollback functionality to calls, Rollback meaning the if an exception or something is thrown your call will not be executed (Commited is the term used for this).
Another benefit of using Transactions is that you can group calls together, if somethign happens to one call then all of calls in that group can also be rolled back. This type of feature is particularly useful when taking user input and saving to DB, you may not want anything to get written to the DB is some error occurs on the client.

.NET provides us with the System.Transaction assembly, it's not completly straight forward to use but it does hide alot of the underlying complexities.
A couple of things I've learned about using System.Transactions or more specifically the System.Transactions.TransactionScope class:
  • The safest way to use it is with the using clause.
  • Always call Commit() before the end of the using.
  • The optional TransactionScopeOptions enum parameter in the constructor defaults to TransactionScopeOption.Required meaning this new Transaction will join an existing one, the existing one is one that has already been created and is know as the "Ambiant Transaction".

TransactionOptions options = new TransactionOptions();
options.IsolationLevel = IsolationLevel.ReadCommitted;
options.Timeout = TimeSpan.FromMinutes(1);

using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, options))
//do something

The different States of the Transaction
If the Transaction is not Completed it will rollback in code this means if you do not explicitly call TransactionScope.Complete() on the Transaction it will rollback.

So if you detect something wrong has happened in your code then do not call Complete() on it. You must dispose of the Transaction too, if you have your instance wrapped in a Using statement then this is done for you, else you can explicitly call it with the Dispose() method.

If Commit() has not been called before Dispose() then the Transaction goes into a state of Aborted. This indicates something has gone wrong inside the Transaction as Dispose() was called without a Complete().
You may not call Complete() after this, else you'll get a TransactionAbortedException.
So the rule is always either explicitly call Dispose() or let the Using block take care of it and if your code has not thrown an error then call Complete() before Dispose() is called else the functionality inside the Transaction will be rolled back.

What can be put inside a TransactionScope for commital/rollback?
You can put any code you like inside the using of the TransactionScope but only this object that support Transactions will be Rolledback or committed. Objects that support Transactions are usually objects that inherit from System.Transaction e.g.

The official wording from MS indicates that the object must be be able to support transaction promotion i.e. in .NET 2.0 there is something known as the the Lightweight Transaction Manager (LTM) which runs the transactions, it only manages objects that support transaction promotion, I think this actually means the objects implement a set of interfaces or inherit form System.Transaction.Transaction.

from msdn:
"Developers get access to this capability through new classes and interfaces within the System.Transactions namespace. For example, to explicitly start a transaction, an application can instantiate a new TransactionScope. If the application code for that transaction runs inside a single app domain, and if it involves at most a single durable resource, and if that resource supports transaction promotion, then the LTM can coordinate the transaction. For a transaction that involves application code that runs in multiple app domains (including multi-process and multi-machine scenarios), or for any transaction that involves more than one durable resource, even when all application code resides in a single app domain, or when a single transactional resource is involved but the resource does not support transaction promotion, the distributed (OleTx) transaction manager will be used. The application code itself need not concern itself with these optimizations—they just work."

What manages the Transactions?
The Lightweight Transaction Manager (LTM) manages the transactions.

September 03, 2009

Loading Assemblies, Assembly.Load, Assembly.LoadFrom, Assembly.LoadFile, appDomain.Load

I always find this topic a great source of confusion each time I get back to it.
I've just across various Exceptions being thrown in my application, FileNotFoundException and AccessViolationException.
The first caused because the application cannot location a Type that one of my assemblies references and lives in another assembly.
The second is caused by that same file being found but it's locked by another process i.e. something else is reading or has read and not released the assembly.
Both of the above were caused by messy code which used a simple Assembly.LoadFile(...). Looks to me like you should never use this method, I'll explain more below.

There are many ways to create an Assembly instance.

or even

There are some subtelties however, some consequences which may later cause issues e.g. the assembly you've loaded is locked.

Assembly.LoadFile is the simplest but most crude solution, it simple reads an assembly, the assembly should be standalone, no references to Types other than the .NET. The file may have issues later too as it maybe locked until the application is shut down.

Assembly.LoadFrom is a bit more intelligent, it uses the current AppDomains settings, if there's an issue with references then the current AppDomain will try to find the Types using the it's settings.

The most complete solution but not the easiest to debug is the use of the AppDomain.Load. This in my experience is tricky until you get used to what all the properties on the AppDomain mean.
What you do is setup a new AppDomain, until you unload that AppDomain everthing that happens in the meantime is in the context of your new AppDomain's settings.
This will help you resolve references to your assembly in code, you can configure where the AppDomain should look when the Load fails to locate an Type.