I have a fairly complex WinForms app that uses data binding to tie strongly typed DataTables to controls (not sure if this fact matters here). An exception gets raised when a change to a column triggers logic that updates another column on that same row (or at least this is my theory).
An example: a Customer record is bound to a ComboBox with a list of contacts to Customer::MarketingContactId. When the contact changes there are several textboxes and checkboxes that will update to allow editing of that contact's details. One of these checkboxes has an event handler on CheckChanged
void MarketingContactIsFulltimeChangedHandler(object sender, EventArgs e)
{
var contact = m_row.MarketingContact;
if (contact != null && contact.IsFullTimeEmployee)
{
var i = m_row.Institution;
if (i.CreditLevel > m_row.CreditLevel)
m_row.CreditLevel = i.CreditLevel;
}
}
m_row being the Customer getting updated. The error does not happen in this code though, but is actually raised from the setting of the MarketingContactId field. Here is the call stack, which confirms this:
System.Data.dll!System.Data.DataView.OnListChanged(System.ComponentModel.ListChangedEventArgs e = {System.ComponentModel.ListChangedEventArgs}) Line 1433 + 0x2c bytes C#
System.Data.dll!System.Data.DataView.IndexListChanged(object sender, System.ComponentModel.ListChangedEventArgs e = {System.ComponentModel.ListChangedEventArgs}) Line 1299 C#
System.Data.dll!System.Data.DataView.IndexListChangedInternal(System.ComponentModel.ListChangedEventArgs e) Line 1324 C#
System.Data.dll!System.Data.DataViewListener.IndexListChanged(System.ComponentModel.ListChangedEventArgs e) Line 76 + 0x7 bytes C#
System.Data.dll!System.Data.Index.OnListChanged.AnonymousMethod(System.Data.DataViewListener listener, System.ComponentModel.ListChangedEventArgs args, bool arg2, bool arg3) Line 803 + 0x7 bytes C#
System.Data.dll!System.Data.Listeners<System.Data.DataViewListener>.Notify<System.ComponentModel.ListChangedEventArgs,bool,bool>(System.ComponentModel.ListChangedEventArgs arg1 = {System.ComponentModel.ListChangedEventArgs}, bool arg2 = false, bool arg3 = false, System.Data.Listeners<System.Data.DataViewListener>.Action<System.Data.DataViewListener,System.ComponentModel.ListChangedEventArgs,bool,bool> action = {Method = {Void <OnListChanged>b__2(System.Data.DataViewListener, System.ComponentModel.ListChangedEventArgs, Boolean, Boolean)}}) Line 1029 + 0x1a bytes C#
System.Data.dll!System.Data.Index.OnListChanged(System.ComponentModel.ListChangedEventArgs e) Line 805 C#
System.Data.dll!System.Data.Index.OnListChanged(System.ComponentModel.ListChangedType changedType, int newIndex, int oldIndex) Line 788 C#
System.Data.dll!System.Data.Index.RecordStateChanged(int oldRecord, System.Data.DataViewRowState oldOldState, System.Data.DataViewRowState oldNewState, int newRecord, System.Data.DataViewRowState newOldState, System.Data.DataViewRowState newNewState) Line 903 + 0x10 bytes C#
System.Data.dll!System.Data.DataTable.RecordStateChanged(int record1 = 0, System.Data.DataViewRowState oldState1 = Added, System.Data.DataViewRowState newState1 = None, int record2 = 1, System.Data.DataViewRowState oldState2 = None, System.Data.DataViewRowState newState2 = Added) Line 3473 + 0x18 bytes C#
System.Data.dll!System.Data.DataTable.SetNewRecordWorker(System.Data.DataRow row = "CustomerRow: Id:-1", int proposedRecord, System.Data.DataRowAction action = Change, bool isInMerge, int position, bool fireEvent = true, out System.Exception deferredException = null) Line 3935 + 0x16 bytes C#
System.Data.dll!System.Data.DataTable.SetNewRecord(System.Data.DataRow row, int proposedRecord, System.Data.DataRowAction action, bool isInMerge, bool fireEvent) Line 3824 C#
System.Data.dll!System.Data.DataRow.SetNewRecord(int record) Line 1160 C#
System.Data.dll!System.Data.DataRow.EndEdit() Line 631 + 0xa bytes C#
System.Data.dll!System.Data.DataRow.this[System.Data.DataColumn].set(System.Data.DataColumn column, object value) Line 343 C#
The line numbers are there since I have the symbols loaded for System.Data.dll from the MS Symbol Server feature of Visual Studio. Having this data lets me further track the error to this part of the OnListChanged method:
// snippet from System.Data.DataView::OnListChanged(ListChangedEventArgs e)
if (onListChanged != null) {
if ((col != null) && (e.NewIndex == e.OldIndex)) {
ListChangedEventArgs newEventArg = new ListChangedEventArgs(e.ListChangedType, e.NewIndex, new DataColumnPropertyDescriptor(col));
/*Here is where VS breaks for the exception*/ onListChanged(this, newEventArg);
}
else {
onListChanged(this, e);
}
}
Clearly something has gone wrong if just after a check for onListChanged != null it is. My suspicion is that updating a DataRow during the EndEdit method triggers this. Of course a simple sandbox to isolate this problem did not reporduce the error for me. Has anyone else on SO come across this bug and if so, how did you fix it?
My kludge for now is to call BeginInvoke in the MarketingContactIsFulltimeChangedHandler to another method that performs the CreditLevel logic there.