Удаление определенных строк из DataTable


87

Я хочу удалить несколько строк из DataTable, но выдает такую ​​ошибку:

Коллекция была изменена; операция перечисления может не выполняться

Я использую для удаления этот код,

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

Итак, в чем проблема и как ее исправить? Какой способ посоветуете?

Ответы:


170

Если вы удаляете элемент из коллекции, эта коллекция была изменена, и вы не можете продолжать перечисление через нее.

Вместо этого используйте цикл 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 для циклического просмотра таблицы при удалении строк. См. Ответ Стива .
Alexander Garden

3
Этот ответ также должен включать ответ @bokkie. Если мы воспользуемся DataTableпоследним, это вызовет исключение. Правильный способ - обратиться Remove()к источнику DataTable- dtPerson.Rows.Remove(dr).
Code.me

Если вы используете DataTable для обновления таблицы на сервере базы данных, у @Steve есть лучший ответ. Вы можете пометить строки как удаленные, обновить строки и добавить новые строки за один цикл. Вы можете использовать SqlAdapter для фиксации изменений в таблице Db. Учитывая, как часто возникает проблема, весь процесс намного сложнее, чем вы думаете, но он действительно работает. Если бы я не собирался использовать транзакционный характер DataTable, я бы просто использовал коллекцию объектов и подход namco или Widor.
BH

Разве использование не Delete()требует вызова, чтобы AcceptChanges()удаление вступило в силу?
Broots Waymb

128

Прежде чем все перейдут на подножку `` Вы не можете удалять строки в перечислении '', вам нужно сначала понять, что DataTables являются транзакционными и технически не удаляют изменения, пока вы не вызовете AcceptChanges ()

Если вы видите это исключение при вызове Delete , вы уже находитесь в состоянии данных ожидающих изменений . Например, если вы только что загрузились из базы данных, вызов Delete вызовет исключение, если вы находитесь внутри цикла foreach.

НО! НО!

Если вы загружаете строки из базы данных и вызываете функцию AcceptChanges () , вы фиксируете все эти ожидающие изменения в DataTable. Теперь вы можете перемещаться по списку строк , призывающей Delete () без помощи в мире, потому что это просто ушные метки строки для удаления, но не совершали , пока вы снова не вызовете AcceptChanges ()

Я понимаю, что этот ответ немного устарел, но мне недавно пришлось столкнуться с подобной проблемой, и, надеюсь, это избавит от боли будущего разработчика, работающего над кодом 10-летней давности :)


Ps Вот простой пример кода, добавленный Джеффом :

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

2
также более полезно (я думаю) перейти object row_loopVariable in ds.Tables(0).RowsнаDataRow row in ds.Tables(0).Rows
BugLover

2
Ffs, это спасло меня во время кошмарных выходных. Вы заслужили все пиво!
Джеймс Лав


Хороший код. Во-первых, в 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();
} 

если вы собираетесь использовать datatable после удаления строки, вы получите сообщение об ошибке. Итак, что вы можете сделать: заменить 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 ()
Мэтью Лок

«Вы также можете вызвать метод Delete класса DataRow, чтобы просто пометить строку для удаления. Вызов Remove аналогичен вызову Delete и последующему вызову AcceptChanges. Remove не следует вызывать в цикле foreach во время итерации по объекту DataRowCollection.Remove изменяет состояние коллекции ". См. Msdn.microsoft.com/de-de/library/… Ура.
Андреас Крон

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

Я надеюсь это поможет тебе


1
команда drow.Delete();не drow.delete();методы чувствительны к регистру в .net кстати
MethodMan

5

Чтобы удалить всю строку из DataTable , сделайте следующее

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.

Решение: либо сделайте так, как написал Видор, либо используйте две петли. При первом проходе через 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с желаемыми результатами, очищаю исходный код DataTableи mergeрезультаты из временного DataTableв исходный.

примечание : вместо поиска Joeв DataRowназванном nameВы должны искать все записи, у которых нет имени 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"]он доступен только для чтения. Тогда я нашел это решение.

Это C#приложение WPF ,

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

Вы пробуете это для получения и удаления столбца идентификатора из таблицы данных

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, то вызов AcceptChangesвручную гарантирует, что изменения никогда не будут сохранены на сервере SQL .

Что делает эту проблему более запутанной, так это то, что на самом деле существует два случая, в которых возникает исключение, и мы должны предотвратить их оба.

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

Вызывая Selectфункцию DataTable , наш запрос автоматически избегает уже удаленных записей в DataTable. А поскольку Selectфункция возвращает массив совпадений, перечисляемая нами коллекция не изменяется при вызове dr.Delete(). Я также приправил выражение Select интерполяцией строк, чтобы можно было выбирать переменные, не делая код шумным.


0

простой способ использовать это в кнопке:

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

example1 = таблица идентификаторов. yesmediasec = идентификатор кнопки в строке

используйте это и все будет в порядке

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.