Usuwanie określonych wierszy z DataTable

85

Chcę usunąć niektóre wiersze z DataTable, ale powoduje to taki błąd,

Kolekcja została zmodyfikowana; operacja wyliczenia może nie zostać wykonana

Używam do usunięcia tego kodu,

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

Na czym więc polega problem i jak go naprawić? Którą metodę radzisz?

namco
źródło

Odpowiedzi:

169

Jeśli usuniesz element z kolekcji, ta kolekcja została zmieniona i nie możesz kontynuować wyliczania za jej pośrednictwem.

Zamiast tego użyj pętli For, takiej jak:

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

Zwróć uwagę, że wykonujesz iterację w odwrotnej kolejności, aby uniknąć pominięcia wiersza po usunięciu bieżącego indeksu.

Widor
źródło
@Slugster mnie pokonał! (I zmieniłaś [ii]TO [i]jednak :-)
Widor
11
To jest niepoprawne. Państwo może używać foreach pętli stole podczas usuwania wierszy. Zobacz odpowiedź Steve'a .
Ogród Aleksandra,
3
Ta odpowiedź powinna również zawierać odpowiedź @ bokkie. Jeśli użyjemy DataTablepóźniejszego, wyrzuci wyjątek. Prawidłowym sposobem byłoby odwołanie Remove()się do źródła DataTable- dtPerson.Rows.Remove(dr).
Code.me
Jeśli używasz DataTable do aktualizowania tabeli na serwerze bazy danych, @Steve ma lepszą odpowiedź. Możesz oznaczać wiersze jako usunięte, aktualizować wiersze i dodawać nowe wiersze w jednej pętli. Możesz użyć SqlAdapter, aby zatwierdzić zmiany w tabeli Db. Biorąc pod uwagę, jak często pojawia się problem, cały proces jest o wiele bardziej zagmatwany, niż mogłoby się wydawać, że powinien, ale działa. Gdybym nie zamierzał skorzystać z transakcyjnego charakteru DataTable, użyłbym po prostu kolekcji obiektów i podejścia namco lub Widor.
BH
Czy używanie nie Delete()wymaga wywołania, aby AcceptChanges()usunięcie odniosło skutek?
Broots Waymb
128

Zanim wszyscy przejdą na modęNie możesz usunąć wierszy w wyliczeniu ”, musisz najpierw zdać sobie sprawę, że DataTables są transakcyjne i nie czyść zmian technicznie, dopóki nie wywołasz AcceptChanges ()

Jeśli widzisz ten wyjątek podczas wywoływania funkcji Delete , oznacza to, że jesteś już w stanie danych oczekujących na zmiany . Na przykład, jeśli właśnie załadowałeś z bazy danych, wywołanie Delete zgłosi wyjątek, jeśli jesteś w pętli foreach.

ALE! ALE!

Jeśli załadujesz wiersze z bazy danych i wywołasz funkcję „ AcceptChanges () ”, zatwierdzasz wszystkie te oczekujące zmiany w DataTable. Teraz możesz iterować listę wierszy wywołując Delete () bez troski w świecie, ponieważ po prostu zaznacza wiersz do usunięcia, ale nie jest zatwierdzany, dopóki nie wywołasz ponownie AcceptChanges ()

Zdaję sobie sprawę, że ta odpowiedź jest nieco przestarzała, ale ostatnio miałem do czynienia z podobnym problemem i mam nadzieję, że zaoszczędzi to trochę bólu przyszłemu programistowi pracującemu nad 10-letnim kodem :)


Ps Oto prosty przykład kodu dodany przez Jeffa :

DO#

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()
Steve
źródło
dla wersji C # wystarczy użyć {i} zamiast ()
BugLover
2
również bardziej pomocne (myślę), aby przejść object row_loopVariable in ds.Tables(0).RowsdoDataRow row in ds.Tables(0).Rows
BugLover
2
Ffs, to uratowało mnie podczas koszmarnego weekendowego wdrożenia. Zasługujesz na wszystkie piwa!
James Love
Zobacz dokumentację pod adresem msdn.microsoft.com/de-de/library/ ...
Andreas Krohn
Niezły kod. Jedna rzecz, w C # typowym sposobem zwiększania o jeden jest counter++zamiast counter+= 1.
MQuiggGeorgia
18

z tym rozwiązaniem:

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

jeśli zamierzasz używać datatable po usunięciu wiersza, pojawi się błąd. Więc co można zrobić, to zamienić dr.Delete();zdtPerson.Rows.Remove(dr);

bokkie
źródło
16

To działa dla mnie,

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();
Balaji Birajdar
źródło
tak łatwo przegapić dt.AcceptChanges ()
Matthew Lock
Można również wywołać metodę Delete klasy DataRow, aby po prostu zaznaczyć wiersz do usunięcia. Wywołanie funkcji Remove jest takie samo, jak wywołanie metody Delete, a następnie wywołanie AcceptChanges. Remove nie powinien być wywoływany w pętli foreach podczas iteracji przez obiekt DataRowCollection.Remove modyfikuje stan kolekcji ”. Zobacz msdn.microsoft.com/de-de/library/ ... Pozdrawiam.
Andreas Krohn
9
DataRow[] dtr=dtPerson.select("name=Joe");
foreach(var drow in dtr)
{
   drow.delete();
}
dtperson.AcceptChanges();

Mam nadzieję, że ci to pomoże

Karthik
źródło
1
polecenie drow.Delete();nie drow.delete();metody rozróżnia
wielkość
5

Aby usunąć cały wiersz z DataTable , wykonaj następujące czynności

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);
Karthikeyan P
źródło
4

Lub po prostu przekonwertuj kolekcję DataTable Row na listę:

foreach(DataRow dr in dtPerson.Rows.ToList())
{
    if(dr["name"].ToString()=="Joe")
    dr.Delete();
}
Milos
źródło
1

Gdzie jest problem: Zabrania się usuwania elementów z kolekcji wewnątrz pętli foreach.

Rozwiązanie: Albo zrób to tak, jak napisał Widor, albo użyj dwóch pętli. W pierwszym przejściu przez DataTable przechowujesz (na liście tymczasowej) tylko odwołania do wierszy, które chcesz usunąć. Następnie w drugim przejściu nad tymczasową listą usuwasz te wiersze.

Al Kepp
źródło
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();
}
Arun Prasad ES
źródło
1

Wiem, że to bardzo stare pytanie i mam podobną sytuację kilka dni temu.

Problem w tym, że w moim stoliku są ok. 10000 rzędów, więc zapętlanie DataTablerzędów koryta było bardzo powolne.

W końcu znalazłem znacznie szybsze rozwiązanie, w którym wykonuję kopię źródła DataTablez pożądanymi wynikami, czystym źródłem DataTablei mergewynikami z tymczasowego DataTablena źródłowe.

uwaga : zamiast szukać Joew DataRownazwanym, namemusisz wyszukać wszystkie rekordy, które nie mają nazwy Joe(trochę odwrotny sposób wyszukiwania)

Oto przykład ( 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

Mam nadzieję, że to krótsze rozwiązanie komuś pomoże.

Jest c#kod (nie jestem pewien, czy jest poprawny, ponieważ użyłem konwertera online :():

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

Oczywiście użyłem Try/Catchw przypadku, gdy nie ma wyniku (na przykład, jeśli Twój dtPersonnie zawiera name Joe, wyrzuci wyjątek), więc nic nie robisz ze swoim stołem, pozostaje niezmieniony.

nelek
źródło
0

Mam zestaw danych w mojej aplikacji i poszedłem ustawić zmiany (usunięcie wiersza), ale ds.tabales["TableName"]jest tylko do odczytu. Wtedy znalazłem to rozwiązanie.

To C#aplikacja 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);                 
    }           
}
Mamad
źródło
0

Próbujesz tego w celu pobrania i usunięcia kolumny id z tabeli danych

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");
}
shubham
źródło
0

Widzę tutaj różne fragmenty właściwej odpowiedzi, ale pozwólcie, że połączę to wszystko razem i wyjaśnię kilka rzeczy.

Przede wszystkim AcceptChangespowinno być używane tylko do oznaczania całej transakcji w tabeli jako sprawdzonej i zatwierdzonej. Oznacza to, że jeśli używasz DataTable jako źródła danych do tworzenia powiązań, na przykład z serwerem SQL, AcceptChangesręczne wywołanie zagwarantuje, że zmiany nigdy nie zostaną zapisane na serwerze SQL .

To, co sprawia, że ​​ta kwestia jest bardziej zagmatwana, to fakt, że w rzeczywistości istnieją dwa przypadki, w których wyjątek jest zgłaszany i musimy im zapobiegać.

1. Modyfikowanie kolekcji IEnumerable

Nie możemy dodać ani usunąć indeksu do wyliczanej kolekcji, ponieważ może to wpłynąć na wewnętrzne indeksowanie modułu wyliczającego. Istnieją dwa sposoby obejścia tego problemu: albo wykonaj własne indeksowanie w pętli for, albo użyj oddzielnej kolekcji (która nie jest modyfikowana) do wyliczenia.

2. Próba odczytania usuniętego wpisu

Ponieważ DataTables są kolekcjami transakcyjnymi , wpisy można oznaczać do usunięcia, ale nadal pojawiają się w wyliczeniu. Co oznacza, że ​​jeśli poprosisz o usunięty wpis w kolumnie "name", zgłosi wyjątek. Co oznacza, że ​​musimy sprawdzić, czy dr.RowState != DataRowState.Deletedprzed zapytaniem o kolumnę.

Kładąc wszystko razem

Moglibyśmy zrobić bałagan i zrobić to wszystko ręcznie, lub możemy pozwolić DataTable wykonać całą pracę za nas i sprawić, by instrukcja wyglądała bardziej jak wywołanie SQL, wykonując następujące czynności:

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

Wywołując Selectfunkcję DataTable , nasze zapytanie automatycznie unika już usuniętych wpisów w DataTable. A ponieważ Selectfunkcja zwraca tablicę dopasowań, wyliczana przez nas kolekcja nie jest modyfikowana podczas wywołania dr.Delete(). Doprawiłem również wyrażenie Select za pomocą interpolacji ciągów, aby umożliwić wybór zmiennych bez hałaśliwego kodu.

Rhaokiel
źródło
0

w łatwy sposób użyj tego w przycisku:

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

example1 = tabela id. yesmediasec = identyfikator przycisku w wierszu

użyj go i wszystko będzie dobrze

Salim Fh
źródło