Видалення певних рядків з DataTable


85

Я хочу видалити кілька рядків з DataTable, але це видає таку помилку,

Колекція була змінена; операція перерахування може не виконуватися

Я використовую для видалення цього коду,

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

Отже, в чому проблема і як її виправити? Який метод ви радите?

Відповіді:


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]:-)
Відор,

11
Це неправильно. Ви можете використовувати foreach для прокручування таблиці під час видалення рядків. Дивіться відповідь Стіва .
Олександрівський сад

3
Ця відповідь також повинна містити відповідь @ bokkie. Якщо ми використаємо DataTableпізніше, це призведе до винятку. Правильним способом буде звернення Remove()до джерела DataTable- dtPerson.Rows.Remove(dr).
Code.me

Якщо ви використовуєте DataTable для оновлення таблиці на сервері баз даних, @Steve має кращу відповідь. Ви можете позначити рядки як видалені, оновити рядки та додати нові рядки в одному циклі. Ви можете використовувати SqlAdapter для внесення змін до таблиці Db. Беручи до уваги, як часто виникає проблема, весь процес набагато складніший, ніж ви думаєте, що це має бути, але він працює. Якби я не збирався скористатися транзакційним характером DataTable, я б просто використовував колекцію об’єктів та підхід namco або Widor.
BH

Чи не Delete()потрібно використовувати виклик для AcceptChanges()того, щоб видалення набрало чинності?
Broots Waymb

128

Перш ніж усі перейдуть до набору " Ви не можете видалити рядки в переліченні ", спочатку потрібно усвідомити, що таблиці даних є транзакційними , і технічно не очищати зміни, доки ви не викликаєте AcceptChanges ()

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

АЛЕ! АЛЕ!

Якщо ви завантажуєте рядки з бази даних і викликаєте функцію ' AcceptChanges () ', усі ці очікувані зміни фіксуються в DataTable. Тепер ви можете переглядати список рядків, що викликають Delete (), не доглядаючи у всьому світі, оскільки він просто позначає вухо рядком для Deletion, але не виконується, поки ви знову не зателефонуєте 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, це мене врятувало під час розбудови кошмарних вихідних. Ви заслуговуєте на все пиво!
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 ()
Метью Лок

"Ви також можете викликати метод Delete класу DataRow, щоб просто позначити рядок для видалення. Виклик Remove - це те саме, що викликати Delete, а потім викликати AcceptChanges. Видалення не повинно викликатися у циклі foreach під час ітерації через об'єкт DataRowCollection. Видалити змінює стан колекції ". Див. Msdn.microsoft.com/de-de/library/… Cheers.
Андреас Крон,

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

Сподіваюся, це вам допоможе


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

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 у список:

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"]лише для читання. Тоді я знайшов це рішення.

Це програма wpf C#,

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. Спроба прочитати видалений запис

Оскільки таблиці даних є колекціями транзакцій , записи можна позначити для видалення, але все одно відображатимуться в переліку. Це означає, що якщо ви запитаєте видалений запис для стовпця, "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.