从DataTable删除特定的行


85

我想从DataTable中删除一些行,但它给出了这样的错误,

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

我用来删除此代码,

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

那么,问题出在哪里,如何解决?您建议哪种方法?

Answers:


169

如果您从集合中删除项目,则该集合已被更改,无法继续枚举它。

而是使用For循环,例如:

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

请注意,您要反向进行迭代,以避免在删除当前索引后跳过行。


@Slugster击败了我!(但是,我将您更改[ii][i]:-)
Widor

11
这是不正确的。您可以通过一个表,同时删除行使用foreach循环。参见史蒂夫的回答
亚历山大花园

3
此答案还应包括@bokkie的答案。如果我们使用DataTable后者,它将抛出异常。正确的方法是调用Remove()DataTable- dtPerson.Rows.Remove(dr)
Code.me,2015年

如果您正在使用DataTable更新数据库服务器中的表,则@Steve会有更好的答案。您可以在单个循环中将行标记为已删除,更新行以及添加新行。您可以使用SqlAdapter将更改提交到Db表。考虑到问题出现的频率,整个过程比您想象的要复杂得多,但是确实可以。如果我不打算利用DataTable的事务性质,那么我只会使用对象集合和namco或Widor方法。
BH

使用Delete()不需要调用AcceptChanges()才能使删除生效吗?
Broots Waymb

128

在每个人都跳入“您无法删除Enumeration中”潮流之前,您需要首先意识到DataTables是事务性的,并且从技术上讲,不要清除更改,直到您调用AcceptChanges()

如果在调用Delete时看到此异常,则您已经处于等待更改数据状态。例如,如果您刚从数据库中加载,则如果您在foreach循环中,则调用Delete会引发异常。

但!但!

如果从数据库加载行并调用函数“ AcceptChanges() ”,则会将所有这些未完成的更改提交到DataTable。现在您可以遍历调用Delete()的行列表,而无需担心,因为它只是将行标记为Deletion,但直到再次调用AcceptChanges()才提交。

我意识到这个回应有些过时了,但是最近我不得不处理一个类似的问题,希望这可以为将来使用10年代码的开发人员节省一些痛苦:)


Ps这是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()

C#版本只需要使用{和}而不是()
BugLover 2014年

2
也更有用(我认为),将其更改object row_loopVariable in ds.Tables(0).RowsDataRow row in ds.Tables(0).Rows
BugLover

2
FFS,这为我在噩梦般的周末部署期间节省了时间。您应得的所有啤酒!
James Love


好的代码。一件事,在C#中,典型的递增方法是counter++代替counter+= 1
MQuiggGeorgia

18

使用此解决方案:

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

如果删除行后要使用数据表,则会出现错误。因此,您可以做的是:替换dr.Delete();dtPerson.Rows.Remove(dr);


16

这对我有用

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();

很容易错过dt.AcceptChanges()
马修·洛克

“您还可以调用DataRow类的Delete方法以仅标记要删除的行。调用Remove与调用Delete然后调用AcceptChanges相同。在迭代DataRowCollection对象时,不应在foreach循环中调用Remove。修改集合的状态。” 请参阅msdn.microsoft.com/de-de/library/… 干杯。
Andreas Krohn'3

9
DataRow[] dtr=dtPerson.select("name=Joe");
foreach(var drow in dtr)
{
   drow.delete();
}
dtperson.AcceptChanges();

希望对您有帮助


1
该命令drow.Delete();不是drow.delete();方法在.net btw中区分大小写
MethodMan 2016年

5

要删除整个行数据表中,做这样的

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);

4

或者只是将DataTable Row集合转换为列表:

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

1

问题出在哪里:禁止在foreach循环内从集合中删除项目。

解决方案:要么像Widor所写的那样做,要么使用两个循环。在第一次传递DataTable时,您仅存储(在临时列表中)对要删除的行的引用。然后在临时列表的第二遍中删除这些行。


1
<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();
}

1

我知道这是一个非常老的问题,几天前我也有类似情况。

问题是,在我的桌子上大约。10000行,因此循环槽DataTable行非常慢。

最后,我找到了更快的解决方案,在其中复制DataTable具有所需结果的源,清除源DataTablemerge临时结果DataTable到源代码一。

注意:不是搜索JoeDataRowname你要搜索其没有名称的所有记录Joe(搜索的一点相反的方式)

有示例(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

我希望如此简短的解决方案能对某人有所帮助。

c#代码(不确定是否正确,因为我使用了在线转换器:():

//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;

当然,我使用Try/Catch了万一没有结果的情况(例如,如果dtPerson不包含name Joe它会抛出异常),那么您对表不执行任何操作,则它保持不变。


0

我的应用程序中有一个数据集,我去设置了更改(删除一行),但是 ds.tabales["TableName"]是只读的。然后我找到了解决方案。

这是一个wpfC#应用

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);                 
    }           
}

0

您可以尝试从数据表中获取和删除ID列

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");
}

0

我在这里看到了正确答案的各种点滴,但让我将它们汇总并解释几件事。

首先,AcceptChanges仅应将标记在表上的整个事务标记为已验证并已提交。这意味着,如果您将DataTable用作绑定到例如SQL Server的数据源,则AcceptChanges手动调用将确保这些更改永远不会保存到SQL Server中

使这个问题更令人困惑的是,实际上在两种情况下会引发异常,因此我们必须防止这两种情况。

1.修改IEnumerable的集合

我们无法在要枚举的集合中添加或删除索引,因为这样做可能会影响枚举器的内部索引。解决该问题的方法有两种:要么在for循环中进行自己的索引编制,要么对枚举使用单独的集合(未经修改)。

2.尝试读取已删除的条目

由于DataTables是事务性集合,因此可以将条目标记为删除,但仍会出现在枚举中。这意味着,如果您要求删除该列的条目,"name"则它将引发异常。这意味着我们必须dr.RowState != DataRowState.Deleted在查询列之前检查是否。

放在一起

我们可能会变得凌乱并手动完成所有这些操作,或者通过执行以下操作,让DataTable为我们完成所有工作,并使语句看起来更像是SQL调用:

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

通过调用DataTable的Select函数,我们的查询将自动避免DataTable中已删除的条目。而且由于该Select函数返回的是一个匹配数组,因此我们在调用时不会修改正在枚举的集合dr.Delete()。我还使用字符串插值对Select表达式进行了加分,以允许在不使代码嘈杂的情况下进行变量选择。


0

在按钮中使用此方法的简单方法:

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

example1 = id表。yesmediasec =行中按钮的ID

用它,一切都会好的

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.