📜 ⬆️ ⬇️

The Case of Lost Rows in DataView

Debugging a WPF editor for a DB table, I ran into Exception when switching between adjacent cells using the keyboard.
It would seem a common thing - who among us expects did not smell? Yes, and the DataGrid component of the WPF Toolkit is very experimental, so there is nothing surprising. However, something put me on my guard.


IndexOutOfRangeException fell out in DataGrid.cs on line
object nextItem = Items[nextRowIndex];

And yes, nextRowIndex really was out of range, it was -1.

Now let's see where it comes from. He announced a little higher:
int currentRowIndex = Items.IndexOf(CurrentItem);
int nextDisplayIndex = currentDisplayIndex;
int nextRowIndex = currentRowIndex;

and until the moment where we get the exeption, its value (in the case of the “right” button) does not change.
So, set a bryak to initialize nextRowIndex, reproduce actions with the editor ... voila, currentRowIndex == -1!
')
What do we have? Items.IndexOf () tells us that such an item does not exist. But we know that it is not. Check it in Watch:



CurrentItem is equal to the corresponding Item from the collection, everything is fair. Indices for neighboring elements are well defined. But specifically "our" element - not found. Well, we will understand ...

First, the suspicion fell on the actual ItemCollection, an instance of which is Items.
After installing some update on the framework, some of the libraries from WPF (in particular, to the PresentationFramework.dll that we are interested in), the source codes stopped coming up, so part of the way had to be done in assembly listing. However, even in spite of this, we rather quickly found out that the ItemCollection is only a wrapper for the corresponding CollectionView. And in our case, this is the BindingListCollectionView, which is the default view for the DataView.

The BindingListCollectionView itself, however, also turned out to be a wrapper and sent a call to the IList.IndexOf () of the DataView. After he returned the same “minus one”, I abandoned the idea that the problem was in the WPF part.

So, how does IndexOf work on a DataView and why does it not find the right row?

Analysis of the code showed (and even in the debugger you can see the prerequisites for this) that DataTable uses an index for strings (RB Tree). And the DataView refers specifically to the table index in its search for the DataRow associated with the desired DataRowView.
However, if the DataRow is in edit mode, it creates a temporary record inside itself with a copy of all its values.
And it turns out that at this moment DataRowView is associated with a temporary record (otherwise the changes in it would not affect the DataRow itself), respectively, he is trying to look for it in the index!

Here is a small example to reproduce the problem:

static void Main( string [] args)
{
var dataTable = new DataTable();
dataTable.Columns.Add( "Id" , typeof ( int ));
dataTable.Columns.Add( "Name" , typeof ( string ));

var row = dataTable.NewRow();
row[ "Id" ] = 1;
row[ "Name" ] = "John" ;
dataTable.Rows.Add(row);

row = dataTable.NewRow();
row[ "Id" ] = 2;
row[ "Name" ] = "Jack" ;
dataTable.Rows.Add(row);

var view = dataTable.DefaultView;

//row.BeginEdit();

Console .WriteLine(((IList)view).IndexOf(view[0]));
Console .WriteLine(((IList)view).IndexOf(view[1]));
}


* This source code was highlighted with Source Code Highlighter .


In its original form, we obtain the expected 0 and 1.
If you uncomment row.BeginEdit (), then the corresponding line will no longer exist.

By the way, if instead of a DataRow call BeginEdit () on the corresponding DataRowView, then (at first glance) the row index will be displayed correctly. This is due to the fact that DataRowView uses a lazy call - the associated DataRow will be called BeginEdit () only when there is a change in data.

Summary


I do not know how this behavior is correct. Most likely this is a bug, however, the wizards of the .NET Team may well declare its features. In any case, non-core operations should be avoided with the line being edited.
Or use newer data models, like LINQ2SQL or EntityFramework. However, there you are not insured against other surprises :)

As a workout, I added to the DataGrid.cs before Items.IndexOf (CurrentItem) this:
// BUG: item not found if in edit mode
if (IsEditingRowItem)
CommitRowItem();

This case can be considered closed.

Source: https://habr.com/ru/post/53794/


All Articles