Transactions for C# objects?
Just curious, is there any support for transactions on plain C# objects? Like
using (var transaction = new ObjectTransaction(obj))
{
try
{
obj.Prop1 = value;
obj.Prop2 = value;
obj.Recalculate(); // may fire exception
transaction.Commit(); // now obj is saved
}
except
{
transaction.Rollback(); // now obj properties are restored
}
}
Just to make answers more useful ;-) is there anything similar in other languages?
Update on STM: here's what it claims:
atomic {
x++;
y--;
throw;
}
will leave x/y unchanged, including chained methods calls. Looks like what I ask for. At least it's very interesting. I think that's close enough. Also, there're similar things in other languages, for example Haskell STM. Notice I don't say that it should be used for production ;-)
Solution 1:
Microsoft is working on it. Read about Software Transactional Memory.
- STM.NET
- STM.NET Team Blog
- Channel 9 Video: STM.NET: Who. What. Why.
- Papers on STM
They use a few different syntaxes:
// For those who like arrows
Atomic.Do(() => {
obj.Prop1 = value;
obj.Prop2 = value;
obj.Recalculate();
});
// For others who prefer exceptions
try {
obj.Prop1 = value;
obj.Prop2 = value;
obj.Recalculate();
}
catch (AtomicMarker) {
}
// we may get this in C#:
atomic {
obj.Prop1 = value;
obj.Prop2 = value;
obj.Recalculate();
}
Solution 2:
For what its worth, a full-blown STM is a little ways out, and I would strongly recommend against rolling your own.
Fortunately, you can get the functionality you want by carefully designing your classes. In particular, immutable classes support transaction-like behavior out of the box. Since immutable objects return a new copy of themselves each time a property is set, you always have a full-history changes to rollback on if necessary.
Solution 3:
Juval Lowy has written about this. Here is a link to his original MSDN article (I first heard about the idea in his excellent WCF book). Here's a code example from MSDN, which looks like what you want to achieve:
public class MyClass
{
Transactional<int> m_Number = new Transactional<int>(3);
public void MyMethod()
{
Transactional<string> city = new Transactional<string>("New York");
using(TransactionScope scope = new TransactionScope())
{
city.Value = "London";
m_Number.Value = 4;
m_Number.Value++;
Debug.Assert(m_Number.Value == 5);
//No call to scope.Complete(), transaction will abort
}
}
Solution 4:
You can make a copy of the object prior to executing methods and setting properties. Then, if you don't like the result, you can just "roll back" to the copy. Assuming, of course, that you have no side-effects to contend with.
Solution 5:
No, there isn't currently anything like this built into .net or C#.
However depending on your usage requirements you could implement something that did the job for you.
Your ObjectTransaction
class would serialise (or just duplicate) the object and hold the copy during the transaction. If you called commit
the copy could just be deleted, but if you called rollback you could restore all of the properties on the original object from the copy.
There are lots of caveats to this suggestion.
- if you have to use reflection to get the properties (because you want it to handle any object type) it will be quite slow. Equally for serialisation.
- If you have a tree of objects and not just a simple object with a few properties, handling something like that generically for all object types could be pretty tricky.
- Properties that are lists of data are also tricky.
- If any properties get/set methods trigger changes (or events) that have effects this could cause real issues elsewhere in your app.
- It will only really work for public properties.
All that said, a project I worked on a few years ago did do something exactly like this. Under very strict restrictions it can work really nicely. We only supported our internal business layer data objects. And they all had to inherit from a base interface that provided some additional meta data on property types, and there were rules on what events could be triggered from property setters. We would start the transaction, then bind the object to the GUI. If the user hit ok, the transaction was just closed, but if they hit cancel, the transaction manager unbound it from the GUI and rolled back all the changes on the object.