Compensating transactions with DataSets

When building SO applications you usually need to write code to compensate failing transactions.

When you call two services that can't (or shouldn't) be called in the same transactional context, and the second fails you need to 'rollback' the first one by executing code that undoes the operation.

Suppose you have a Business Logic Layer method that receives an ADO.NET DataSet, and the method does the 'right thing' when you perform any insert/update/deleted operation in the DataSet. In this case, it's possible to automatically compensate the transaction by reversing the operations in the DataSet.

The DataSet knows all the operations you performed on data and all the previous values. You can build a DataSet that perform the opposite operations. For example, if you added a row in the DataSet, then you can have that row as Deleted in the new one. If you changed it, you can have it changed but the reverse way (the old value now is the new one and viceversa). If you deleted a row, you can have it added in the DataSet.

You could write:


CustomerDataSet ds ;

// load it somehow, retrieving it from a webservice or whatever, and change it

CustomerDataSet reverseDs = (CustomerDataSet) DataSetUtil.ReverseDataSet(ds);

customerManager.Update(ds);

...

// if other service fails, compensate the transaction

customerManager.Update(reverseDs);

It seems to work pretty well, but there are a couple of gotchas:

  • As the .Update() method calls .AcceptChanges(), you need to have the reversed dataset before calling Update()
  •  If the Update() method refreshes values in the DataSet (for example, it loads an identity field), then you'll need to set that value in the reversed DataSet after calling .Update(ds) and before calling .Update(reverseDS). It will end up as something like:

CustomerDataSet ds ;

// load it somehow, retrieving it from a webservice or whatever, and change it

CustomerDataSet dsCopy = (CustomerDataSet) ds.Copy();

customerManager.Update(ds);

dsCopy.Customers[0].CustomerId = ds.Customers[0].CustomerId;

customerManager.Update(DataSetUtil.ReverseDataSet(dsCopy));

 

It should also be possible to make the Update() method returns the reverse dataset, relieving the client-side developer from knowing which field is updated by the customerManager. That's probably a good solution if you know the operation will need to be included in a compensating transaction.

This solution will probably don't work for all the scenarios, but if it does, it saves a lot of error-prone coding.

You can find the code for the ReverseDataSet method here. It has zero test in production environments ;).

1 Comment

  • Fortunately, they added a .AcceptChangesDuringUpdate feature in 2.0 that greatly simplifies this situation - it's definitely not the most straightforward thing to deal with now but like you showed, it's doable.

Comments have been disabled for this content.