Eliminar filas específicas de DataTable

85

Quiero eliminar algunas filas de DataTable, pero da un error como este,

Se modificó la colección; Es posible que la operación de enumeración no se ejecute

Uso para borrar este código,

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

Entonces, ¿cuál es el problema y cómo solucionarlo? ¿Qué método aconsejas?

Namco
fuente

Respuestas:

169

Si elimina un elemento de una colección, esa colección se ha modificado y no puede continuar enumerándola.

En su lugar, use un bucle For, como:

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

Tenga en cuenta que está iterando a la inversa para evitar saltarse una fila después de eliminar el índice actual.

Widor
fuente
¡@Slugster se me adelantó! (Te cambié [ii]a [i], sin embargo :-)
Widor
11
Esto es incorrecto. Usted puede utilizar un foreach para recorrer una mesa mientras eliminar filas. Vea la respuesta de Steve .
Alexander Garden
3
Esta respuesta también debe incluir la respuesta de @ bokkie. Si usamos el DataTableúltimo, lanzará una excepción. La forma correcta sería llamar Remove()a la fuente DataTable- dtPerson.Rows.Remove(dr).
Code.me
Si está utilizando DataTable para actualizar una tabla en un servidor de base de datos, @Steve tiene una mejor respuesta. Puede marcar filas como eliminadas, actualizar filas y agregar nuevas filas, todo en un solo ciclo. Puede usar un SqlAdapter para confirmar los cambios en la tabla Db. Teniendo en cuenta la frecuencia con la que surge el problema, todo el proceso es mucho más complicado de lo que crees que debería ser, pero funciona. Si no fuera a aprovechar la naturaleza transaccional de DataTable, solo usaría una colección de objetos y el enfoque namco o Widor.
BH
¿El uso no Delete()requiere una llamada a AcceptChanges()para que la eliminación surta efecto?
Broots Waymb
128

Antes de que todos se suban al tren de ' No puede eliminar filas en una enumeración ', primero debe darse cuenta de que las tablas de datos son transaccionales y no depurar técnicamente los cambios hasta que llame a AcceptChanges ()

Si está viendo esta excepción mientras llama a Delete , ya se encuentra en un estado de datos de cambios pendientes . Por ejemplo, si acaba de cargar desde la base de datos, llamar a Delete lanzaría una excepción si estuviera dentro de un bucle foreach.

¡PERO! ¡PERO!

Si carga filas de la base de datos y llama a la función ' AcceptChanges () ', confirma todos los cambios pendientes en DataTable. Ahora puede iterar a través de la lista de filas que llaman a Delete () sin ninguna preocupación en el mundo, porque simplemente marca la fila para la eliminación, pero no se confirma hasta que llame nuevamente a AcceptChanges ()

Me doy cuenta de que esta respuesta está un poco anticuada, pero tuve que lidiar con un problema similar recientemente y espero que esto le ahorre algo de dolor a un futuro desarrollador que trabaja en un código de 10 años :)


Ps Aquí hay un ejemplo de código simple agregado por 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()
Steve
fuente
para la versión c # solo necesita usar {y} en lugar de ()
BugLover
2
también es más útil (creo) cambiar object row_loopVariable in ds.Tables(0).RowsaDataRow row in ds.Tables(0).Rows
BugLover
2
Ffs, esto me ha salvado durante una implementación de fin de semana de pesadilla. ¡Te mereces todas las cervezas!
James Love
Consulte los documentos en msdn.microsoft.com/de-de/library/…
Andreas Krohn
Buen código. Una cosa, en C # la forma típica de incrementar en uno es en counter++lugar de counter+= 1.
MQuiggGeorgia
18

con esta solución:

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

si va a utilizar la tabla de datos después de eliminar la fila, obtendrá un error. Entonces, lo que puede hacer es: reemplazar dr.Delete();condtPerson.Rows.Remove(dr);

bokkie
fuente
16

Esto funciona para mi

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
fuente
tan fácil pasar por alto dt.AcceptChanges ()
Matthew Lock
"También puede llamar al método Delete de la clase DataRow para marcar una fila para su eliminación. Llamar a Remove es lo mismo que llamar a Delete y luego llamar a AcceptChanges. No se debe llamar a Remove en un bucle foreach mientras se itera a través de un objeto DataRowCollection. modifica el estado de la colección ". Consulte msdn.microsoft.com/de-de/library/… Saludos.
Andreas Krohn
9
DataRow[] dtr=dtPerson.select("name=Joe");
foreach(var drow in dtr)
{
   drow.delete();
}
dtperson.AcceptChanges();

Espero que te ayude

Karthik
fuente
1
el comando drow.Delete();no es, los drow.delete();métodos distinguen entre mayúsculas y minúsculas en .net por cierto
MethodMan
5

Para eliminar toda la fila de DataTable , haga esto

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
fuente
4

O simplemente convierta una colección DataTable Row en una lista:

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

Dónde está el problema: está prohibido eliminar elementos de la colección dentro de un bucle foreach.

Solución: hazlo como escribió Widor o usa dos bucles. En la primera pasada sobre DataTable, solo almacena (en una lista temporal) las referencias a las filas que desea eliminar. Luego, en la segunda pasada sobre su lista temporal, borra esas filas.

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

Sé que esta es una pregunta muy antigua y tengo una situación similar hace unos días.

El problema era que en mi mesa hay aprox. 10000 filas, por lo que hacer un bucle en las DataTablefilas fue muy lento.

Finalmente, encontré una solución mucho más rápida, donde hago una copia de la fuente DataTablecon los resultados deseados, la fuente clara DataTabley los mergeresultados de temporal DataTablea la fuente uno.

nota : en su lugar, busque Joeen DataRowllamado nameDebe buscar todos los registros que no tengan nombre Joe(forma de búsqueda poco opuesta)

Hay ejemplo ( 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

Espero que esta solución más corta ayude a alguien.

Hay un c#código (no estoy seguro de si es correcto porque usé el convertidor en línea :():

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

Por supuesto, utilicé Try/Catchen caso de que no haya resultado (por ejemplo, si Tu dtPersonno contiene name Joe, arrojará una excepción), por lo que no haces nada con Tu tabla, permanece sin cambios.

Nelek
fuente
0

Tengo un conjunto de datos en mi aplicación y fui a establecer cambios (eliminar una fila), pero ds.tabales["TableName"]es de solo lectura. Entonces encontré esta solución.

Es una C#aplicación 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
fuente
0

Intenta esto para obtener y eliminar la columna de identificación de la tabla de datos

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

Veo varios fragmentos de la respuesta correcta aquí, pero permítanme reunirlos todos y explicar un par de cosas.

En primer lugar, AcceptChangessolo debe usarse para marcar la transacción completa en una tabla como validada y confirmada. Lo que significa que si está utilizando DataTable como una fuente de datos para vincularse a, por ejemplo, un servidor SQL, llamar AcceptChangesmanualmente garantizará que los cambios nunca se guarden en el servidor SQL .

Lo que hace que este problema sea más confuso es que en realidad hay dos casos en los que se lanza la excepción y tenemos que evitarlos.

1. Modificar la colección de un IEnumerable

No podemos agregar o eliminar un índice a la colección que se está enumerando porque hacerlo puede afectar la indexación interna del enumerador. Hay dos formas de evitar esto: o haga su propia indexación en un ciclo for, o use una colección separada (que no esté modificada) para la enumeración.

2. Intentar leer una entrada eliminada

Dado que las tablas de datos son colecciones transaccionales , las entradas se pueden marcar para su eliminación, pero aún aparecen en la enumeración. Lo que significa que si solicita una entrada eliminada para la columna "name", arrojará una excepción. Lo que significa que debemos verificar si dr.RowState != DataRowState.Deletedantes de consultar una columna.

Poniendolo todo junto

Podríamos ensuciarnos y hacer todo eso manualmente, o podemos dejar que DataTable haga todo el trabajo por nosotros y hacer que la declaración se parezca más a una llamada SQL haciendo lo siguiente:

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

Al llamar a la Selectfunción de DataTable , nuestra consulta evita automáticamente las entradas ya eliminadas en DataTable. Y dado que la Selectfunción devuelve una matriz de coincidencias, la colección sobre la que estamos enumerando no se modifica cuando llamamos dr.Delete(). También he condimentado la expresión Seleccionar con interpolación de cadenas para permitir la selección de variables sin hacer que el código sea ruidoso.

Rhaokiel
fuente
0

la forma más fácil de usar este botón en:

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

ejemplo1 = tabla de identificación. yesmediasec = id del botón en la fila

úsalo y todo estará bien

Salim Fh
fuente