Programming Guide > Client-Side Transactions |
DataSource for Entity Framework gives developers a powerful mechanism for canceling changes on the client without involving the server. It is called transaction because it is similar to the common concept of database transaction in that it allows you to ensure that a certain group of changes (unit of work) is either made in its entirety or canceled in its entirety-that your code never makes incomplete or inconsistent changes to entities in memory. It is important to understand that these transactions have no affect in any way and are completely independent from database transactions. To distinguish them from database transactions, we sometimes call them client-side transactions.
Client-side transactions are especially useful in implementing Cancel/Undo buttons/commands. Doing this without C1DataSource requires cancelling all changes in the object context. C1DataSource client-side transactions make partial canceling of changes possible. And the transactions can even be nested, so you can have, for example, a dialog box with a Cancel button (which cancels only the changes made in that dialog box, not all changes in the object context made elsewhere in the application), and from that dialog box you can open another dialog box with its own Cancel button. Using a nested (child) transaction in the child dialog ensures that its Cancel button cancels (rolls back) only the changes made in the child dialog box, so the user can return to editing data in the parent dialog box and then accept or cancel changes in it using the parent transaction.
The easiest way to work with client-side transactions is by associating them with live views. For example, if we have a data grid bound to a live view
var ordersView = from o in customer.Orders.AsLive()
select new OrderInfo
{ OrderID = o.OrderID,
OrderDate = o.OrderDate, };
dataGrid1.ItemsSource = ordersView;
We can create a transaction and associate it with the view like this:
var transaction = _scope.ClientCache.CreateTransaction();
ordersView.SetTransaction(transaction);
To create a child (nested) transaction, instead of calling the method ClientCacheBase,CreateTransaction, use the ClientTransaction constructor by passing it the parent transaction as a parameter:
var transaction = new ClientTransaction(parentTransaction);
Once a transaction is associated with a view by calling View.SetTransaction; it tracks all changes made through that view, via data binding (for example, changes made by the end user in the grid bound to the view as in the example above) as well as programmatically in code. Rolling back the transaction (calling ClientTransaction.Rollback) cancels the changes tracked by that transaction.
It is often necessary to bind GUI controls to a single object (as opposed to binding to a collection of objects). A single object can’t be represented by a live view, so we don’t have the convenience of View.SetTransaction|keyword=SetTransaction method, but in this case we can use the ClientTransaction.ScopeDataContext method. In WPF and Silverlight, you can use this method to set the DataContext so it will be used for data bindings specified in XAML:
DataContext = transaction.ScopeDataContext(order);
The resulting DataContext wraps the original one and performs the same data binding but with the additional benefit of all changes made through that data binding being tracked by the ‘transaction’, so they can be rolled back if necessary.
In WinForms, you can use the same ScopeDataContext and bind to the resulting object, for example, like this:
var dataContext = transaction.ScopeDataContext(order);
textBox.DataBindings.Add(new Binding("Text", dataContext, "OrderDate"));
Finally, sometimes you need to change (or add or delete) some entities in code, not through a live view or data binding, and want those changes to be tracked by a transaction. You can do it using the ClientTransaction.Scope() method. That method opens a scope for a transaction. When you modify entities while in the scope of a transaction, those changes are tracked by that transaction. That method is designed to be used with the 'using' construct that conveniently closes the scope on exit, like this:
using (transaction.Scope())
{
Customer.Orders.Add(order);
}
All three methods of using transactions described above are demonstrated in the Transactions sample project that comes with C1DataSource. It also demonstrates how a form with a Cancel button can be implemented inside another form that also has a Cancel button using child (nested) transactions.