DataViews and deleting rows in the DataTable

  • Thread starter Jon Skeet [C# MVP]
  • Start date
J

Jon Skeet [C# MVP]

I'm seeing some behaviour which seems utterly bizarre to me. I'd expect
that a DataView should automatically track the changes in the table, so
that if rows are deleted etc, existing DataRowViews sort themselves
out. Here's a test program I'd expect to work just fine:

using System;
using System.Data;

class Test
{
static void Main()
{
DataTable table = new DataTable();
table.Columns.Add("Name", typeof(string));

table.Rows.Add(new object[]{"Fred"});
table.Rows.Add(new object[]{"Joe"});

DataRowView rowView = table.DefaultView[1];

Console.WriteLine (rowView["Name"]);
table.Rows[0].Delete();
Console.WriteLine (rowView["Name"]);
}
}

I'd expect it to print
Joe
Joe

But instead, it prints the first line and then throws an exception -
the DataRowView is no longer valid, basically.

Is there any decent way round this, or are DataRowViews really meant to
be very transient objects?
 
M

Miha Markic [MVP C#]

Hi Jon,

Jon Skeet said:
I'm seeing some behaviour which seems utterly bizarre to me. I'd expect
that a DataView should automatically track the changes in the table, so
that if rows are deleted etc, existing DataRowViews sort themselves
out. Here's a test program I'd expect to work just fine:

using System;
using System.Data;

class Test
{
static void Main()
{
DataTable table = new DataTable();
table.Columns.Add("Name", typeof(string));

table.Rows.Add(new object[]{"Fred"});
table.Rows.Add(new object[]{"Joe"});

DataRowView rowView = table.DefaultView[1];

Console.WriteLine (rowView["Name"]);
table.Rows[0].Delete();
Console.WriteLine (rowView["Name"]);
}
}

I'd expect it to print
Joe
Joe

But instead, it prints the first line and then throws an exception -
the DataRowView is no longer valid, basically.

Is there any decent way round this, or are DataRowViews really meant to
be very transient objects?

DataView is just a collection of links (DataRowView) to DataRows from
DataTable.
Plus it is a filtered view and in your case it won't contain links to
deleted rows (although it might - depending on settings).
So, when you delete a row from table, the DataView won't contain it anymore
and IMO is correct to let you know through an exception.
Does this explain the behaviour?
 
S

Sijin Joseph

Hi Jon,

To prevent deleted rows from showing up in the DataView, you can use the
following code

//Not sure if null or "" is required for the second and third params
DataRowView rowView = new
DataView(table,null,null,DataViewRowState.CurrentRows);

For automatic resorting, you may have to hook into the DataTable.RowChanged
event and then resort the view.

Sijin Joseph
http://www.indiangeek.net
http://weblogs.asp.net/sjoseph
 
C

C Addison Ritchie

Hey Jon,

It appears that the DataRowView holds just a row indexer into the actual
table's row collection. Your code creates a DataRowView that has an index
set to "1" which points to index position "1" of the table's row collection.
Then you delete a row from the table. There is now no row at index position
"1" in the table's row collection. So when the DataRowView trys to index
position "1" in the table's row collection you get the exception.

The only way I could get this to work was to do the following:
// after call to delete row
rowView = table.DefaultView[0];
// print data
 
J

Jon Skeet [C# MVP]

DataView is just a collection of links (DataRowView) to DataRows from
DataTable.

I wish it were quite that simple.
Plus it is a filtered view and in your case it won't contain links to
deleted rows (although it might - depending on settings).
So, when you delete a row from table, the DataView won't contain it anymore
and IMO is correct to let you know through an exception.
Does this explain the behaviour?

No. I wasn't deleting the row that the DataRowView pointed at - I was
deleting the previous one. Indeed, if you change the last line of code
to:

Console.WriteLine (rowView.Row["Name"]);
or
Console.WriteLine (table.DefaultView[0]["Name"]);

it displays "Joe" with no problem.

The issue is that the DataRowView contains an index into the DataView
which is used for by the indexer. Unfortunately, that index doesn't get
updated when rows get deleted from the underlying table.

Bizarrely enough, if there are still enough rows for the index to be
valid, even if it's incorrect, it seems to get the right answer. Here's
another program:

using System;
using System.Data;

class Test
{
static void Main()
{
DataTable table = new DataTable();
table.Columns.Add("Name", typeof(string));

table.Rows.Add(new object[]{"Fred"});
table.Rows.Add(new object[]{"Joe"});
// table.Rows.Add(new object[]{"Bob"});

DataView view = table.DefaultView;
DataRowView rowView = view[1];

Console.WriteLine (rowView["Name"]);
table.Rows[0].Delete();
Console.WriteLine (rowView["Name"]);
}
}

As above, it crashes. Uncomment the last "add" line and it works
perfectly, which is crazy.
 
J

Jon Skeet [C# MVP]

C Addison Ritchie said:
It appears that the DataRowView holds just a row indexer into the actual
table's row collection.

Not quite - it does also hold a reference to the DataRow.
Your code creates a DataRowView that has an index
set to "1" which points to index position "1" of the table's row collection.
Then you delete a row from the table. There is now no row at index position
"1" in the table's row collection. So when the DataRowView trys to index
position "1" in the table's row collection you get the exception.

The only way I could get this to work was to do the following:
// after call to delete row
rowView = table.DefaultView[0];
// print data

Yes - although interestingly enough, if there's still a position 1, it
uses the correct row (now at position 0).

This all seems entirely bizarre to me - it basically means that after a
table has changed, although the DataView itself is still valid, any
DataRowViews you have quite possibly aren't.
 
M

Miha Markic [MVP C#]

Jon Skeet said:
I wish it were quite that simple.
:)


No. I wasn't deleting the row that the DataRowView pointed at - I was
deleting the previous one. Indeed, if you change the last line of code
to:

Yes, you were. However, the DataView content changed - from 2 rows to 1 row
since it doesn't contain deleted rows.
So, you were holding a reference to the second row which doesn't exist
anymore in the context of the DataView.
Console.WriteLine (rowView.Row["Name"]);
or
Console.WriteLine (table.DefaultView[0]["Name"]);

it displays "Joe" with no problem.

The issue is that the DataRowView contains an index into the DataView
which is used for by the indexer. Unfortunately, that index doesn't get
updated when rows get deleted from the underlying table.

Bizarrely enough, if there are still enough rows for the index to be
valid, even if it's incorrect, it seems to get the right answer. Here's
another program:

using System;
using System.Data;

class Test
{
static void Main()
{
DataTable table = new DataTable();
table.Columns.Add("Name", typeof(string));

table.Rows.Add(new object[]{"Fred"});
table.Rows.Add(new object[]{"Joe"});
// table.Rows.Add(new object[]{"Bob"});

DataView view = table.DefaultView;
DataRowView rowView = view[1];

Console.WriteLine (rowView["Name"]);
table.Rows[0].Delete();
Console.WriteLine (rowView["Name"]);
}
}

As above, it crashes. Uncomment the last "add" line and it works
perfectly, which is crazy.

No, it is not crazy. You are holding reference to the second row which still
exist (if you uncomment Add) though it isn't the same as before.
Jon, if you have messenger, drop me your address on e-mail

--
Miha Markic [MVP C#] - RightHand .NET consulting & development
miha at rthand com
www.rthand.com
 
J

Jon Skeet [C# MVP]

Yes, you were.

No, I really wasn't. I was deleting the first row ("Fred") when the
DataView was pointing to "Joe".
However, the DataView content changed - from 2 rows to 1 row
since it doesn't contain deleted rows.

It does contain a DataRowView to the same DataRow - just not at the
same index as before.
So, you were holding a reference to the second row which doesn't exist
anymore in the context of the DataView.

I was holding a reference to the DataRowView which itself held a
reference to the same Row as a new DataRowView holds:

using System;
using System.Data;

class Test
{
static void Main()
{
DataTable table = new DataTable();
table.Columns.Add("Name", typeof(string));

table.Rows.Add(new object[]{"Fred"});
table.Rows.Add(new object[]{"Joe"});
// table.Rows.Add(new object[]{"Bob"});

DataView view = table.DefaultView;
DataRowView rowView = view[1];

DataRow before = rowView.Row;
table.Rows[0].Delete();

Console.WriteLine (Object.ReferenceEquals(view[0].Row, before);
}
}

That prints

True

So there's a new DataRowView with a different index but the same Row.
Why can't the existing DataRowView just be updated to keep it valid? If
it's not going to be updated, shouldn't it *always* fail?
No, it is not crazy.

I still say it is - there's no way that an exception should be thrown
due solely to an unrelated row being present or not. If the DataRowView
is no longer valid in the DataView, it should *always* throw an
exception. If it's valid in the DataView, it should *never* throw an
exception. Its validity shouldn't be determined by whether other
unrelated rows are present.
You are holding reference to the second row which still
exist (if you uncomment Add) though it isn't the same as before.
Jon, if you have messenger, drop me your address on e-mail

Same as my email address - (e-mail address removed).

Fundamentally, I can't see any reason why the DataRowView shouldn't be
updated to have the new index, rather than a new DataRowView being
created. As it is, DataRowViews are useless once the DataView has been
changed, which is neither clearly documented (as far as I can see) nor
convenient.
 
J

Jon Skeet [C# MVP]

Jon Skeet said:
No, I really wasn't. I was deleting the first row ("Fred") when the
DataView was pointing to "Joe".

Slight correction here: the DataRowView was pointing to "Joe".
 
J

Jon Skeet [C# MVP]

Sijin Joseph said:
To prevent deleted rows from showing up in the DataView, you can use the
following code

//Not sure if null or "" is required for the second and third params
DataRowView rowView = new
DataView(table,null,null,DataViewRowState.CurrentRows);

For automatic resorting, you may have to hook into the DataTable.RowChanged
event and then resort the view.

Views don't contain deleted rows by default, and that's fine - I don't
want them to. What I *do* want is that if I've got a DataRowView, it
stays valid whether or not a different row is deleted. (It should
become invalid if the underlying row that the DataRowView is looking at
is deleted, of course.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top