Löschen bestimmter Zeilen aus DataTable

82

Ich möchte einige Zeilen aus DataTable löschen, aber es gibt einen Fehler wie diesen:

Sammlung wurde geändert; Aufzählungsoperation wird möglicherweise nicht ausgeführt

Ich benutze zum Löschen dieses Codes,

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

Also, was ist das Problem und wie kann es behoben werden? Welche Methode raten Sie?

namco
quelle

Antworten:

160

Wenn Sie ein Element aus einer Sammlung löschen, wurde diese Sammlung geändert und Sie können nicht weiter durch sie aufzählen.

Verwenden Sie stattdessen eine For-Schleife, z.

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

Beachten Sie, dass Sie in umgekehrter Reihenfolge iterieren, um zu vermeiden, dass nach dem Löschen des aktuellen Index eine Zeile übersprungen wird.

Widor
quelle
@Slugster hat mich geschlagen! (Ich habe deine jedoch geändert [ii]zu [i]:-)
Widor
11
Das ist falsch. Sie können ein foreach verwenden, um eine Tabelle zu durchlaufen, während Sie Zeilen löschen. Siehe Antwort von Steve .
Alexander Garden
3
Diese Antwort sollte auch die Antwort von @ bokkie enthalten. Wenn wir das DataTablespäter verwenden, wird es eine Ausnahme auslösen. Der richtige Weg wäre, Remove()die Quelle aufzurufen DataTable- dtPerson.Rows.Remove(dr).
Code.me
Wenn Sie die DataTable verwenden, um eine Tabelle auf einem Datenbankserver zu aktualisieren, hat @Steve eine bessere Antwort. Sie können Zeilen als gelöscht markieren, Zeilen aktualisieren und neue Zeilen in einer einzigen Schleife hinzufügen. Sie können einen SqlAdapter verwenden, um die Änderungen in die Db-Tabelle zu übernehmen. Wenn man bedenkt, wie oft das Problem auftritt, ist der gesamte Prozess viel komplizierter als man denkt, aber es funktioniert. Wenn ich die Transaktionsnatur der DataTable nicht nutzen würde, würde ich einfach eine Objektsammlung und den Namco- oder Widor-Ansatz verwenden.
BH
Benötigt die Verwendung Delete()keinen Aufruf von AcceptChanges(), damit die Löschung wirksam wird?
Broots Waymb
126

Bevor alle auf den Zug " Sie können keine Zeilen in einer Aufzählung löschen " springen, müssen Sie zunächst erkennen, dass DataTables transaktional sind , und Änderungen erst dann technisch bereinigen, wenn Sie AcceptChanges () aufrufen.

Wenn diese Ausnahme beim Aufrufen von Löschen angezeigt wird , befinden Sie sich bereits in einem Datenstatus mit ausstehenden Änderungen . Wenn Sie beispielsweise gerade aus der Datenbank geladen haben, würde der Aufruf von Löschen eine Ausnahme auslösen, wenn Sie sich in einer foreach-Schleife befinden.

ABER! ABER!

Wenn Sie Zeilen aus der Datenbank laden und die Funktion ' AcceptChanges () ' aufrufen , übernehmen Sie alle ausstehenden Änderungen an der DataTable. Jetzt können Sie die Liste der Zeilen, die Delete () aufrufen, ohne Rücksicht auf die Welt durchlaufen, da die Zeile lediglich zum Löschen vorgesehen ist, aber erst festgeschrieben wird, wenn Sie AcceptChanges () erneut aufrufen.

Mir ist klar, dass diese Antwort etwas veraltet ist, aber ich musste mich kürzlich mit einem ähnlichen Problem befassen und hoffe, dass dies einem zukünftigen Entwickler, der an 10 Jahre altem Code arbeitet, einige Schmerzen erspart :)


Ps Hier ist ein einfaches Codebeispiel, das von Jeff hinzugefügt wurde :

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()
Steve
quelle
Für die C # -Version müssen nur {und} anstelle von () verwendet werden
BugLover
2
auch hilfreicher (glaube ich) zu ändern , object row_loopVariable in ds.Tables(0).RowsumDataRow row in ds.Tables(0).Rows
BugLover
2
Ffs, das hat mich während eines Albtraumwochenendeinsatzes gerettet. Sie verdienen alle Biere!
James Love
Siehe Dokumente unter msdn.microsoft.com/de-de/library/…
Andreas Krohn
Schöner Code. Eine Sache, in C # ist die typische Art, um eins zu erhöhen, counter++anstelle von counter+= 1.
MQuiggGeorgia
17

mit dieser Lösung:

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

Wenn Sie die Datentabelle nach dem Löschen der Zeile verwenden, wird eine Fehlermeldung angezeigt. Was Sie also tun können, ist: Ersetzen dr.Delete();durchdtPerson.Rows.Remove(dr);

Bokkie
quelle
14

Das funktioniert bei mir,

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
quelle
so leicht zu übersehen dt.AcceptChanges ()
Matthew Lock
"Sie können auch die Delete-Methode der DataRow-Klasse aufrufen, um nur eine Zeile zum Entfernen zu markieren. Das Aufrufen von Remove entspricht dem Aufrufen von Delete und dem anschließenden Aufrufen von AcceptChanges. Remove sollte nicht in einer foreach-Schleife aufgerufen werden, während ein DataRowCollection-Objekt durchlaufen wird ändert den Status der Sammlung. " Siehe msdn.microsoft.com/de-de/library/… Prost.
Andreas Krohn
9
DataRow[] dtr=dtPerson.select("name=Joe");
foreach(var drow in dtr)
{
   drow.delete();
}
dtperson.AcceptChanges();

ich hoffe es hilft dir

Karthik
quelle
1
Der Befehl ist drow.Delete();nicht drow.delete();Methoden unterscheiden zwischen Groß- und Kleinschreibung in .net übrigens
MethodMan
4

Oder konvertieren Sie einfach eine DataTable- Zeilensammlung in eine Liste:

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

Gehen Sie folgendermaßen vor , um die gesamte Zeile aus DataTable zu entfernen

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.
quelle
1

Wo liegt das Problem: Es ist verboten, Elemente aus der Sammlung innerhalb einer foreach-Schleife zu löschen.

Lösung: Machen Sie es entweder so, wie Widor es geschrieben hat, oder verwenden Sie zwei Schleifen. Beim ersten Durchlauf von DataTable speichern Sie nur (in einer temporären Liste) die Verweise auf Zeilen, die Sie löschen möchten. Im zweiten Durchgang über Ihre temporäre Liste löschen Sie diese Zeilen.

Al Kepp
quelle
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
quelle
1

Ich weiß, dass dies eine sehr alte Frage ist, und ich habe vor einigen Tagen eine ähnliche Situation.

Problem war, in meiner Tabelle sind ca. 10000 Reihen, so dass das Schleifen durch DataTableReihen sehr langsam war.

Schließlich fand ich eine viel schnellere Lösung, bei der ich eine Kopie der Quelle DataTablemit den gewünschten Ergebnissen, der klaren Quelle DataTableund den mergeErgebnissen von temporär DataTablein die erste Quelle erstelle.

Anmerkung : Statt für die Suche Joein DataRownamens nameSie haben für alle Datensätze , die nicht haben Namen suchen Joe(wenig entgegengesetzte Art und Weise des Suchens)

Es gibt Beispiel ( 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

Ich hoffe, dass diese kürzere Lösung jemandem hilft.

Es gibt c#Code (nicht sicher, ob er korrekt ist, da ich den Online-Konverter verwendet habe :():

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

Natürlich habe ich für den Try/CatchFall, dass es kein Ergebnis gibt (zum Beispiel, wenn Ihr dtPersones nicht enthält name Joe, eine Ausnahme auslöst), verwendet, damit Sie nichts mit Ihrer Tabelle tun, bleibt es unverändert.

nelek
quelle
0

Ich habe einen Datensatz in meiner App und habe Änderungen vorgenommen (eine Zeile löschen), der jedoch ds.tabales["TableName"]schreibgeschützt ist. Dann habe ich diese Lösung gefunden.

Es ist eine 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);                 
    }           
}
Mamad
quelle
0

Sie versuchen dies, um die ID-Spalte abzurufen und aus der Datentabelle zu entfernen

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
quelle
0

Ich sehe hier verschiedene Teile der richtigen Antwort, aber lassen Sie mich alles zusammenbringen und ein paar Dinge erklären.

Zunächst AcceptChangessollte nur verwendet werden, um die gesamte Transaktion in einer Tabelle als validiert und festgeschrieben zu markieren. Wenn Sie also die DataTable als DataSource zum Binden an beispielsweise einen SQL Server verwenden, wird durch AcceptChangesmanuelles Aufrufen sichergestellt, dass die Änderungen niemals auf dem SQL Server gespeichert werden .

Was dieses Problem verwirrender macht, ist, dass es tatsächlich zwei Fälle gibt, in denen die Ausnahme ausgelöst wird und wir beide verhindern müssen.

1. Ändern der Sammlung eines IEnumerable

Wir können der Aufzählung keinen Index hinzufügen oder entfernen, da dies die interne Indizierung des Enumerators beeinträchtigen kann. Es gibt zwei Möglichkeiten, dies zu umgehen: Führen Sie entweder Ihre eigene Indizierung in einer for-Schleife durch oder verwenden Sie eine separate Sammlung (die nicht geändert wird) für die Aufzählung.

2. Versuch, einen gelöschten Eintrag zu lesen

Da es sich bei DataTables um Transaktionssammlungen handelt , können Einträge zum Löschen markiert werden, erscheinen jedoch weiterhin in der Aufzählung. Das heißt, wenn Sie einen gelöschten Eintrag für die Spalte "name"anfordern, wird eine Ausnahme ausgelöst. Das heißt, wir müssen prüfen, ob, dr.RowState != DataRowState.Deletedbevor wir eine Spalte abfragen.

Alles zusammenfügen

Wir könnten chaotisch werden und all das manuell erledigen, oder wir könnten die DataTable die ganze Arbeit für uns erledigen lassen und die Anweisung wie einen SQL-Aufruf aussehen lassen, indem wir Folgendes tun:

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

Durch den Aufruf der DataTable- SelectFunktion vermeidet unsere Abfrage automatisch bereits gelöschte Einträge in der DataTable. Und da die SelectFunktion ein Array von Übereinstimmungen zurückgibt, wird die Sammlung, über die wir aufzählen, beim Aufruf nicht geändert dr.Delete(). Ich habe auch den Select-Ausdruck mit String-Interpolation aufgepeppt, um eine Variablenauswahl zu ermöglichen, ohne den Code verrauschen zu lassen.

Rhaokiel
quelle