从 DataTable 中删除特定行

我想从 DataTable 中删除一些行,但是它会出现这样的错误,

集合; 枚举操作可能无法执行

我用来删除这段代码,

foreach(DataRow dr in dtPerson.Rows){
if(dr["name"].ToString()=="Joe")
dr.Delete();
}

那么,问题是什么,如何解决呢? 你有什么建议?

460478 次浏览

If you delete an item from a collection, that collection has been changed and you can't continue to enumerate through it.

Instead, use a For loop, such as:

for(int i = dtPerson.Rows.Count-1; i >= 0; i--)
{
DataRow dr = dtPerson.Rows[i];
if (dr["name"] == "Joe")
dr.Delete();
}
dtPerson.AcceptChanges();

Note that you are iterating in reverse to avoid skipping a row after deleting the current index.

Where is the problem: It is forbidden to delete items from collection inside a foreach loop.

Solution: Either do it like Widor wrote, or use two loops. In the first pass over DataTable you only store (in a temporary list) the references to rows you want to delete. Then in the second pass over your temporary list you delete those rows.

This works for me,

List<string> lstRemoveColumns = new List<string>() { "ColValue1", "ColVal2", "ColValue3", "ColValue4" };
List<DataRow> rowsToDelete = new List<DataRow>();


foreach (DataRow row in dt.Rows) {
if (lstRemoveColumns.Contains(row["ColumnName"].ToString())) {
rowsToDelete.Add(row);
}
}


foreach (DataRow row in rowsToDelete) {
dt.Rows.Remove(row);
}


dt.AcceptChanges();

with this solution:

for(int i = dtPerson.Rows.Count-1; i >= 0; i--)
{
DataRow dr = dtPerson.Rows[i];
if (dr["name"] == "Joe")
dr.Delete();
}

if you are going to use the datatable after deleting the row, you will get an error. So what you can do is: replace dr.Delete(); with dtPerson.Rows.Remove(dr);

Before everyone jumps on the 'You can't delete rows in an Enumeration' bandwagon, you need to first realize that DataTables are transactional, and do not technically purge changes until you call AcceptChanges()

If you are seeing this exception while calling Delete, you are already in a pending-changes data state. For instance, if you have just loaded from the database, calling Delete would throw an exception if you were inside a foreach loop.

BUT! BUT!

If you load rows from the database and call the function 'AcceptChanges()' you commit all of those pending changes to the DataTable. Now you can iterate through the list of rows calling Delete() without a care in the world, because it simply ear-marks the row for Deletion, but is not committed until you again call AcceptChanges()

I realize this response is a bit dated, but I had to deal with a similar issue recently and hopefully this saves some pain for a future developer working on 10-year-old code :)


P.s. Here is a simple code example added by Jeff:

C#

YourDataTable.AcceptChanges();
foreach (DataRow row in YourDataTable.Rows) {
// If this row is offensive then
row.Delete();
}
YourDataTable.AcceptChanges();

VB.Net

ds.Tables(0).AcceptChanges()
For Each row In ds.Tables(0).Rows
ds.Tables(0).Rows(counter).Delete()
counter += 1
Next
ds.Tables(0).AcceptChanges()

Or just convert a DataTable Row collection to a list:

foreach(DataRow dr in dtPerson.Rows.ToList())
{
if(dr["name"].ToString()=="Joe")
dr.Delete();
}

I have a dataset in my app and I went to set changes (deleting a row) to it but ds.tabales["TableName"] is read only. Then I found this solution.

It's a wpf C# app,

try {
var results = from row in ds.Tables["TableName"].AsEnumerable() where row.Field<string>("Personalid") == "47" select row;
foreach (DataRow row in results) {
ds.Tables["TableName"].Rows.Remove(row);
}
}

To remove entire row from DataTable , do like this

DataTable dt = new DataTable();  //User DataTable
DataRow[] rows;
rows = dt.Select("UserName = 'KarthiK'");  //'UserName' is ColumnName
foreach (DataRow row in rows)
dt.Rows.Remove(row);
<asp:GridView ID="grd_item_list" runat="server" AutoGenerateColumns="false" Width="100%" CssClass="table table-bordered table-hover" OnRowCommand="grd_item_list_RowCommand">
<Columns>
<asp:TemplateField HeaderText="No">
<ItemTemplate>
<%# Container.DataItemIndex + 1 %>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Actions">
<ItemTemplate>
<asp:Button ID="remove_itemIndex" OnClientClick="if(confirm('Are You Sure to delete?')==true){ return true;} else{ return false;}" runat="server" class="btn btn-primary" Text="REMOVE" CommandName="REMOVE_ITEM" CommandArgument='<%# Container.DataItemIndex+1 %>' />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>


**This is the row binding event**


protected void grd_item_list_RowCommand(object sender, GridViewCommandEventArgs e) {


item_list_bind_structure();


if (ViewState["item_list"] != null)
dt = (DataTable)ViewState["item_list"];




if (e.CommandName == "REMOVE_ITEM") {
var RowNum = Convert.ToInt32(e.CommandArgument.ToString()) - 1;


DataRow dr = dt.Rows[RowNum];
dr.Delete();


}


grd_item_list.DataSource = dt;
grd_item_list.DataBind();
}
DataRow[] dtr = dtPerson.Select("name=Joe"); //name is the column in the data table
foreach(var drow in dtr)
{
drow.Delete();
}
dtperson.AcceptChanges();

I know this is, very, old question, and I have similar situation few days ago.

Problem was, in my table are approx. 10000 rows, so looping trough DataTable rows was very slow.

Finally, I found much faster solution, where I make copy of source DataTable with desired results, clear source DataTable and merge results from temporary DataTable into source one.

note : instead search for Joe in DataRow called name You have to search for all records whose not have name Joe (little opposite way of searching)

There is example (vb.net) :

'Copy all rows into tmpTable whose not contain Joe in name DataRow
Dim tmpTable As DataTable = drPerson.Select("name<>'Joe'").CopyToTable
'Clear source DataTable, in Your case dtPerson
dtPerson.Clear()
'merge tmpTable into dtPerson (rows whose name not contain Joe)
dtPerson.Merge(tmpTable)
tmpTable = Nothing

I hope so this shorter solution will help someone.

There is c# code (not sure is it correct because I used online converter :( ):

//Copy all rows into tmpTable whose not contain Joe in name DataRow
DataTable tmpTable = drPerson.Select("name<>'Joe'").CopyToTable;
//Clear source DataTable, in Your case dtPerson
dtPerson.Clear();
//merge tmpTable into dtPerson (rows whose name not contain Joe)
dtPerson.Merge(tmpTable);
tmpTable = null;

Of course, I used Try/Catch in case if there is no result (for example, if Your dtPerson don't contain name Joe it will throw exception), so You do nothing with Your table, it stays unchanged.

You try this for getting and removing id column from data table

if (dt1.Columns.Contains("ID"))
{
for (int i = dt1.Rows.Count - 1; i >= 0; i--)
{
DataRow dr = dt1.Rows[i];


if (dr["ID"].ToString() != "" && dr["ID"].ToString() != null)
{
dr.Delete();
}
}


dt1.Columns.Remove("ID");
}

I'm seeing various bits and pieces of the right answer here, but let me bring it all together and explain a couple of things.

First of all, AcceptChanges should only be used to mark the entire transaction on a table as being validated and committed. Which means if you are using the DataTable as a DataSource for binding to, for example, an SQL server, then calling AcceptChanges manually will guarantee that that the changes never get saved to the SQL server.

What makes this issue more confusing is that there are actually two cases in which the exception is thrown and we have to prevent both of them.

1. Modifying an IEnumerable's Collection

We can't add or remove an index to the collection being enumerated because doing so may affect the enumerator's internal indexing. There are two ways to get around this: either do your own indexing in a for loop, or use a separate collection (that is not modified) for the enumeration.

2. Attempting to Read a Deleted Entry

Since DataTables are transactional collections, entries can be marked for deletion but still appear in the enumeration. Which means that if you ask a deleted entry for the column "name" then it will throw an exception. Which means we must check to see whether dr.RowState != DataRowState.Deleted before querying a column.

Putting it all together

We could get messy and do all of that manually, or we can let the DataTable do all the work for us and make the statement look and at more like an SQL call by doing the following:

string name = "Joe";
foreach(DataRow dr in dtPerson.Select($"name='{name}'"))
dr.Delete();

By calling DataTable's Select function, our query automatically avoids already deleted entries in the DataTable. And since the Select function returns an array of matches, the collection we are enumerating over is not modified when we call dr.Delete(). I've also spiced up the Select expression with string interpolation to allow for variable selection without making the code noisy.

the easy way use this in button :

 var table = $('#example1').DataTable();
table.row($(`#yesmediasec-${id}`).closest('tr')).remove( ).draw();

example1 = id table . yesmediasec = id of the button in the row

use it and every thing will be ok