Exploit DataTable Events as Client Triggers

Use DataTable events to perform validation and business processing based on thechanging of fields and rows.

Brian Noyes

October 30, 2009

9 Min Read
ITPro Today logo

DataStream

LANGUAGES: C#,VB.NET

ASP.NET VERSIONS: 1.0 | 1.1

 

ExploitDataTable Events as Client Triggers

UseDataTable events to perform validation and business processing based on thechanging of fields and rows.

 

By BrianNoyes

 

It'scommon in ASP.NET apps to use server control validation to catch changes beingmade to data at the control level. ASP.NET validation controls performvalidation as close to the data input as possible, using client-side script ifpossible, and server-side code regardless of whether client validation waspossible. This is the right approach for any kind of user input scenario, thatis, catch the problem as soon as you can.

 

However,sometimes you'll want code validation or business processing logic close towhere the data lives in the application server, rather than close to where thechanges originate from the user. Using DataTable events, you can easily performpre- or post-change processing of fields and rows in a data set to write codethat is very much like using triggers in a database.

 

Get in theLoop

TheDataTable class exposes a set of events that you can tap into to getfine-grained control over changes being made to the contained data in thetable. There are three kinds of events, and each kind of event is exposed in pairs.The events are fired when a column changes (ColumnChanging/ColumnChanged), whena row is changed (RowChanging/RowChanged), or when a row is deleted(RowDeleting/RowDeleted). The events named XXChanging are fired just before thechange is applied, and the events named XXChanged are fired just after thechange has been completed.

 

TheColumnChanging and ColumnChanged events include an event argument of typeDataColumnChangeEventArgs, which includes three key properties: Column, Row,and ProposedValue. Column is a reference to the DataColumn that is beingmodified, Row is a reference to the DataRow that is being modified, andProposedValue contains the value that has been assigned to the field.

 

The rowevents all include an event argument of type DataRowChangeEventArgs. Thisobject contains two properties of interest: Action and Row. The Action propertyis an enumeration containing values of Add, Change, Delete, Commit, andRollback. This gives you an indication of why the event is firing for the rowin question. The Row property allows you to inspect the values of the fields ifneeded through the ItemArray property on the DataRow class, or you can inspectthe RowState property to determine what the state of the row was before orafter modification.

 

To understandexactly when each event will fire and what you can do in a handler for theevent, let's step through the three data modification scenarios (insert,update, and delete). I will look at which events fire when for each of theseoperations, and discuss the arguments that come with the event to allow you toperform processing based on the change.

 

To getstarted, you'll need a data set populated with some data and you'll need tosubscribe to the events in which you are interested. In this case, to keep thecode compact, I used the Data Access Application Block (DAAB) SqlHelper classand a typed data set for the Customers table from Northwind (see Figure 1).

 

public voidLoadDataSet()

{

  // Load the data set

  SqlHelper.FillDataset(connString,CommandType.Text,

    "SELECT * FROM Customers",m_ds,

    new string[]{m_ds.Customers.TableName});

 

  // Wire up the events

  m_ds.Customers.ColumnChanging +=

    newDataColumnChangeEventHandler(OnColumnChanging);

  m_ds.Customers.ColumnChanged +=

    new DataColumnChangeEventHandler(OnColumnChanged);

  m_ds.Customers.RowChanging +=

    newDataRowChangeEventHandler(OnRowChanging);

  m_ds.Customers.RowChanged +=

    newDataRowChangeEventHandler(OnRowChanged);

  m_ds.Customers.RowDeleting +=

    new DataRowChangeEventHandler(OnRowDeleting);

  m_ds.Customers.RowDeleted +=

    newDataRowChangeEventHandler(OnRowDeleted);

}

Figure1. Fill theCustomers data set with the SqlHelper class and wire up event handlers for eachof the DataTable events.

 

For now,each of the event handlers that is wired up in Figure 1 simply dumps someinformation to the console so that you can examine the order that the eventsfire. You can modify these handlers in the download code to experiment withdoing other processing based on the values accessible to you when the eventsfire.

 

HandleChange Events

When youadd a new row to a table from code, you typically create the new row, set thevalues, and then add the row to the table, as follows:

 

CustomersDataSet.CustomersRownewrow =

  m_ds.Customers.NewCustomersRow();

// Populate thefields that are non-null

newrow.CompanyName= "FooBros";

newrow.CustomerID= "FOOBR";

// Add it tothe table

m_ds.Customers.AddCustomersRow(newrow);

 

Each ofthe lines of code above where a field value is being set through the propertyon the typed data set is equivalent to setting the value through the DataRowindexer in an un-typed data set. For each of these assignments, theColumnChanging event is fired, followed by the ColumnChanged event. Then, whenAddCustomersRow is called (equivalent to Rows.Add in an un-typed data set), theRowChanging event is fired, followed by RowChanged.

 

Again,each of the column-related events carries an event argument of typeDataColumnChangedEventArgs. You can get to the current value of the field usingthe Column and Row properties, and the argument itself contains the value thatthe field is being changed to as the ProposedValue property:

 

private voidOnColumnChanging(object sender,

  DataColumnChangeEventArgs e)

{

  string colName = e.Column.ColumnName;

  object currVal = e.Row[e.Column.ColumnName];

  object newVal = e.ProposedValue;

  // Output to console...

}

 

Usingthe ColumnName property of the Column property on the event argument, you couldeasily set up a switch..case selection on the ColumnName, cast the current andproposed values for the field to the appropriate type, and do either validationor additional processing based on that change.

 

TriggerValidation or Business Logic

Forvalidation, you would want to do your processing in the ColumnChanging eventhandler, whether the change was triggered by an insert or an update. Forexample, you could check that a data or money value was within an acceptablerange. Or perhaps for a more amorphous field type, such as a string fieldcontaining delimited text or XML, you might transform the input value into someother format to ensure consistent formatting in the underlying data. TheProposedValue property is a read/write property, so you can change the value ofthis property in the ColumnChanging event handler, and the value that youchange the property to will be the one that is used to set the value of thefield.

 

What ifyou want to cancel the change? This gets a little dicier. Unfortunately, theevent arguments do not expose a Cancel property like many event arguments in.NET, which allows you to cancel the firing of the event. So you are left witha couple of options, none of which is completely satisfactory in allsituations. First and easiest, you can just set the value of the ProposedValueproperty to the current value of the field in the ColumnChanging event:

 

e.ProposedValue= e.Row[e.Column.ColumnName];

 

The onlyproblem with this approach is that if you are using the data set for updates tothe database, this will still result in the row being marked as changed,resulting in a round trip to the database and potential triggering of otherchange logic that may be inappropriate since the value did not in fact changein a meaningful way.

 

Anotherpossibility is to throw an exception, which will cancel out event handling andprevent the change from occurring. However, throwing an exception is anexpensive operation, so you wouldn't want to have this occurring often in yourprocessing. You would also have to make sure you have appropriate exceptionhandling in place.

 

Finally,you could call RejectChanges on the row in the RowChanged event handler afterthe change has been applied, but then you would also be throwing away anychanges to other fields in the row, which would be tough to work around in ageneralized way.

 

Anotherway you can use the column events is to trigger post-change processing on otherdata. If you have a dependency between a given field in a table and otherfields in other tables, or simply objects in your code, you could write code ina ColumnChanged event handler to go make the appropriate changes to thedependent objects or fields when a change was completed on a given field.

 

Asmentioned, when the insert is actually performed with the AddCustomerRowmethod, two more events fire: RowChanging and RowChanged. For both of theseevents and an insert operation, the Action property will be set to the Addvalue of the enumeration. In the changing event, the RowState is still set toDetached (the state any row is in when it is created but not yet added to atable) and in the changed event the RowState is set to added so that it can beused for filtering or database updates as desired. If you needed to includelogic that did auditing of changed rows by saving off the changes based on rowstate, this would be where you could do so. By accessing the ItemArray propertyof the row exposed through the event argument Row property, you could performdifferent processing based on the Action and/or the RowState of the row.

 

When youdelete a row, no column events are fired, just the RowDeleting and RowDeletedevents with the Action property of the event arguments set to Delete. TheRowState will be set to whatever its current state is in the RowDeleting event,and will be changed to Deleted in the RowDeleted event.

 

Updatesto a table work a lot like adding a row and setting field values as discussedbefore. The one difference is that when you are updating an existing row, youget the row change events fired after each field is changed immediately afterthe column change events. So for each field that you modify you'll get fourevents: ColumnChanging, ColumnChanged, RowChanging, and RowChanged.

 

Onefinal thing to note is that if you call AcceptChanges on a table, the rowchange events fire for every row in the table with an Action value of Commit.If you call RejectChanges on the table, the row change events will fire foreach row that has changes in it with an Action value of Rollback.

 

Thedownload code for this article has a simple console application that you canuse to experiment with to get used to when these events fire. You can playaround with modifying values in the event handlers to see what the effects are.Using DataTable events gives you a lot of flexibility to be "in the loop" whilechanges are occurring to data in memory, and allows you to validate or formatdata values that are being put into the table, or trigger other processing as aresult of those changes. Using the events of the DataTable, you can design amore event-driven program that can simplify your business logic for manyscenarios.

 

Thefiles accompanying this article are available for download.

 

BrianNoyes is asoftware architect with IDesign, Inc. (http://www.idesign.net),a .NET-focused architecture and design consulting firm. Brian specializes indesigning and building data-driven distributed Windows and Web applications.Brian writes for a variety of publications and is working on a book forAddison-Wesley on building Windows Forms Data Applications with .NET 2.0.Contact him at mailto:[email protected].

 

 

 

 

Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like