Eliminazione di righe specifiche da DataTable


87

Voglio eliminare alcune righe da DataTable, ma dà un errore come questo,

La raccolta è stata modificata; l'operazione di enumerazione potrebbe non essere eseguita

Uso per cancellare questo codice,

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

Allora, qual è il problema e come risolverlo? Quale metodo mi consigliate?

Risposte:


170

Se elimini un elemento da una raccolta, tale raccolta è stata modificata e non puoi continuare a enumerarla.

Utilizza invece un ciclo For, ad esempio:

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

Nota che stai iterando al contrario per evitare di saltare una riga dopo aver eliminato l'indice corrente.


@Slugster mi ha battuto! ( Tuttavia, ho cambiato il tuo [ii]in [i]:-)
Widor

11
Questo non è corretto. È possibile utilizzare un foreach per scorrere un tavolo durante l'eliminazione righe. Vedi risposta di Steve .
Alexander Garden

3
Questa risposta dovrebbe includere anche la risposta di @ bokkie. Se usiamo il DataTablesuccessivo, verrà generata un'eccezione. Il modo corretto sarebbe chiamare Remove()la fonte DataTable- dtPerson.Rows.Remove(dr).
Code.me

Se stai utilizzando DataTable per aggiornare una tabella in un server di database, @Steve ha una risposta migliore. Puoi contrassegnare le righe come eliminate, aggiornare le righe e aggiungere nuove righe in un unico ciclo. È possibile utilizzare un SqlAdapter per eseguire il commit delle modifiche nella tabella Db. Considerando la frequenza con cui si presenta il problema, l'intero processo è molto più complicato di quanto si potrebbe pensare, ma funziona. Se non avessi intenzione di sfruttare la natura transazionale di DataTable, userei semplicemente una raccolta di oggetti e l'approccio namco o Widor.
BH

L'utilizzo non Delete()richiede una chiamata a AcceptChanges()affinché l'eliminazione abbia effetto?
Broots Waymb

128

Prima che tutti saltino sul carrozzone " Non è possibile eliminare righe in un'enumerazione ", è necessario prima rendersi conto che le DataTable sono transazionali e non eliminare tecnicamente le modifiche finché non si chiama AcceptChanges ()

Se visualizzi questa eccezione durante la chiamata a Elimina , sei già in uno stato dati in attesa di modifiche . Ad esempio, se hai appena caricato dal database, la chiamata di Delete genererebbe un'eccezione se ti trovassi all'interno di un ciclo foreach.

MA! MA!

Se carichi righe dal database e chiami la funzione " AcceptChanges () ", esegui il commit di tutte le modifiche in sospeso su DataTable. Ora puoi scorrere l'elenco delle righe che chiamano Delete () senza preoccuparti del mondo, perché contrassegna semplicemente la riga per l'eliminazione, ma non viene eseguito il commit finché non chiami di nuovo AcceptChanges ()

Mi rendo conto che questa risposta è un po 'datata, ma di recente ho dovuto affrontare un problema simile e spero che questo faccia risparmiare un po' di dolore a un futuro sviluppatore che lavora su un codice di 10 anni :)


Ps Ecco un semplice esempio di codice aggiunto da 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()

per la versione c # basta usare {e} invece di ()
BugLover

2
anche più utile (credo) passare object row_loopVariable in ds.Tables(0).RowsaDataRow row in ds.Tables(0).Rows
BugLover

2
Ffs, questo mi ha salvato durante una distribuzione del fine settimana da incubo. Ti meriti tutte le birre!
James Love


Bel codice. Una cosa, in C # il modo tipico per incrementare di uno è counter++invece di counter+= 1.
MQuiggGeorgia

18

con questa soluzione:

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

se hai intenzione di utilizzare il datatable dopo aver eliminato la riga, otterrai un errore. Quindi quello che puoi fare è: sostituire dr.Delete();condtPerson.Rows.Remove(dr);


16

Questo funziona per me

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

così facile perdere dt.AcceptChanges ()
Matthew Lock

"Puoi anche chiamare il metodo Delete della classe DataRow per contrassegnare solo una riga per la rimozione. Chiamare Remove equivale a chiamare Delete e quindi chiamare AcceptChanges. Remove non deve essere chiamato in un ciclo foreach durante l'iterazione di un oggetto DataRowCollection.Remove modifica lo stato della raccolta. " Vedi msdn.microsoft.com/de-de/library/… Cheers.
Andreas Krohn

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

Spero ti possa aiutare


1
il comando drow.Delete();non è i drow.delete();metodi fanno distinzione tra maiuscole e minuscole in .net btw
MethodMan

5

Per rimuovere l' intera riga da DataTable , fai così

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

O semplicemente converti una raccolta di DataTable Row in un elenco:

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

1

Dov'è il problema: è vietato eliminare elementi dalla raccolta all'interno di un ciclo foreach.

Soluzione: fallo come ha scritto Widor o usa due loop. Nel primo passaggio su DataTable si memorizzano (in un elenco temporaneo) solo i riferimenti alle righe che si desidera eliminare. Quindi nel secondo passaggio sul tuo elenco temporaneo elimini quelle righe.


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

So che questa è una domanda molto vecchia e qualche giorno fa ho una situazione simile.

Il problema era che nella mia tabella sono circa. 10000 righe, così facendo un giro di trogoloDataTable file è stato molto lento.

Alla fine, ho trovato una soluzione molto più veloce, in cui creo una copia della fonte DataTablecon i risultati desiderati, chiara la fonte DataTablee i mergerisultati dal temporaneo DataTablealla fonte uno.

nota : cerca invece Joein DataRowchiamato nameDevi cercare tutti i record di cui non hanno nome Joe(modo leggermente opposto di ricerca)

C'è un esempio ( 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

Spero che questa soluzione più breve possa aiutare qualcuno.

C'è un c#codice (non sono sicuro che sia corretto perché ho usato il convertitore 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;

Ovviamente, ho usato Try/Catchnel caso in cui se non ci fosse alcun risultato (ad esempio, se il tuo dtPersonnon lo contiene name Joegenererà un'eccezione), quindi non fai nulla con la tua tabella, rimane invariata.


0

Ho un set di dati nella mia app e sono andato a impostare le modifiche (eliminando una riga) ad esso ma ds.tabales["TableName"]è di sola lettura. Poi ho trovato questa soluzione.

È C#un'app 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

Prova questo per ottenere e rimuovere la colonna id dalla tabella dei dati

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

Sto vedendo vari frammenti della risposta giusta qui, ma lasciami mettere insieme tutto e spiegare un paio di cose.

Prima di tutto, AcceptChangesdovrebbe essere utilizzato solo per contrassegnare l'intera transazione su una tabella come convalidata e impegnata. Ciò significa che se si utilizza DataTable come DataSource per l'associazione, ad esempio, a un server SQL, la chiamata AcceptChangesmanuale garantirà che le modifiche non vengano mai salvate sul server SQL .

Ciò che rende questo problema più confuso è che in realtà ci sono due casi in cui viene lanciata l'eccezione e dobbiamo prevenirli entrambi.

1. Modifica di una raccolta di IEnumerable

Non è possibile aggiungere o rimuovere un indice alla raccolta da enumerare perché ciò potrebbe influire sull'indicizzazione interna dell'enumeratore. Esistono due modi per aggirare questo problema: eseguire la propria indicizzazione in un ciclo for o utilizzare una raccolta separata (che non viene modificata) per l'enumerazione.

2. Tentativo di leggere una voce eliminata

Poiché le DataTable sono raccolte transazionali , le voci possono essere contrassegnate per l'eliminazione ma vengono comunque visualizzate nell'enumerazione. Ciò significa che se chiedi una voce eliminata per la colonna "name", verrà generata un'eccezione. Il che significa che dobbiamo controllare per vedere sedr.RowState != DataRowState.Deleted prima di interrogare una colonna.

Mettere tutto insieme

Potremmo creare confusione e fare tutto questo manualmente, oppure possiamo lasciare che DataTable faccia tutto il lavoro per noi e rendere l'istruzione più simile a una chiamata SQL procedendo come segue:

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

Chiamando la Selectfunzione di DataTable , la nostra query evita automaticamente le voci già eliminate in DataTable. E poiché la Selectfunzione restituisce un array di corrispondenze, la raccolta su cui stiamo enumerando non viene modificata quando la chiamiamo dr.Delete(). Ho anche ravvivato l'espressione Select con l'interpolazione di stringhe per consentire la selezione delle variabili senza rendere il codice rumoroso.


0

il modo più semplice usa questo pulsante in:

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

esempio1 = tabella id. yesmediasec = id del pulsante nella riga

usalo e ogni cosa andrà bene

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.