Excluindo linhas específicas de DataTable


87

Eu quero excluir algumas linhas de DataTable, mas dá um erro como este,

A coleção foi modificada; operação de enumeração pode não ser executada

Eu uso para excluir este código,

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

Então, qual é o problema e como corrigi-lo? Qual método você aconselha?

Respostas:


170

Se você excluir um item de uma coleção, essa coleção foi alterada e você não pode continuar a enumerá-la.

Em vez disso, use um loop 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();

Observe que você está iterando ao contrário para evitar pular uma linha após excluir o índice atual.


@Slugster chegou antes de mim! ( Porém, mudei o seu [ii]para [i]:-)
Widor

11
Isso está incorreto. Você pode usar um foreach para percorrer uma tabela enquanto exclui linhas. Veja a resposta de Steve .
Alexander Garden,

3
Essa resposta também deve incluir a resposta de @bokkie. Se usarmos o DataTableúltimo, vai lançar uma exceção. A maneira correta seria ligar Remove()para a fonte DataTable- dtPerson.Rows.Remove(dr).
Code.me

Se você está usando o DataTable para atualizar uma tabela em um servidor de banco de dados, @Steve tem uma resposta melhor. Você pode marcar linhas como excluídas, atualizar linhas e adicionar novas linhas em um único loop. Você pode usar um SqlAdapter para confirmar as alterações na tabela Db. Considerando a frequência com que o problema surge, todo o processo é muito mais complicado do que você pensaria que deveria ser, mas funciona. Se eu não fosse tirar proveito da natureza transacional do DataTable, simplesmente usaria uma coleção de objetos e a abordagem namco ou Widor.
BH

O uso de Delete()requer uma chamada para AcceptChanges()para que a exclusão tenha efeito?
Broots Waymb

128

Antes de todos entrarem no movimento ' Você não pode excluir linhas em uma Enumeração ', você precisa primeiro perceber que DataTables são transacionais e não limpar as alterações tecnicamente até que você chame AcceptChanges ()

Se você está vendo esta exceção ao chamar Delete , você está em um estado de dados com alterações pendentes . Por exemplo, se você acabou de carregar do banco de dados, chamar Delete lançaria uma exceção se você estivesse dentro de um loop foreach.

MAS! MAS!

Se você carregar linhas do banco de dados e chamar a função ' AcceptChanges () ', você confirma todas as alterações pendentes na DataTable. Agora você pode iterar através da lista de linhas chamando Delete () sem se importar, porque ela simplesmente marca a linha para exclusão, mas não é confirmada até que você chame de novo AcceptChanges ()

Sei que esta resposta está um pouco desatualizada, mas tive que lidar com um problema semelhante recentemente e espero que isso evite um pouco de dor para um futuro desenvolvedor trabalhando em código de 10 anos :)


PS Aqui está um exemplo de código simples adicionado 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()

para a versão c # só precisa usar {and} em vez de ()
BugLover

2
também mais útil (eu acho) mudar object row_loopVariable in ds.Tables(0).RowsparaDataRow row in ds.Tables(0).Rows
BugLover

2
Ffs, isso me salvou durante uma implantação de fim de semana de pesadelo. Você merece todas as cervejas!
James Love


Belo código. Uma coisa, em C #, a maneira típica de incrementar em um é em counter++vez de counter+= 1.
MQuiggGeorgia

18

com esta solução:

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

se você for usar a tabela de dados após excluir a linha, receberá um erro. Então, o que você pode fazer é: substituir dr.Delete();pordtPerson.Rows.Remove(dr);


16

Isso funciona para mim,

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

tão fácil perder dt.AcceptChanges ()
Matthew Lock,

"Você também pode chamar o método Delete da classe DataRow para apenas marcar uma linha para remoção. Chamar Remove é o mesmo que chamar Delete e, em seguida, chamar AcceptChanges. Remove não deve ser chamado em um loop foreach durante a iteração por meio de um objeto DataRowCollection.Remove modifica o estado da coleção. " Consulte 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();

Espero que te ajude


1
o comando drow.Delete();não é os drow.delete();métodos diferenciam maiúsculas de minúsculas em .net btw
MethodMan

5

Para remover a linha inteira de DataTable , faça assim

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

Ou apenas converta uma coleção de DataTable Row em uma lista:

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

1

Onde está o problema: É proibido excluir itens da coleção dentro de um loop foreach.

Solução: Faça como Widor escreveu ou use dois loops. Na primeira passagem pelo DataTable, você só armazena (em uma lista temporária) as referências às linhas que deseja excluir. Então, na segunda passagem pela sua lista temporária, você exclui essas linhas.


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

Eu sei que esta é uma pergunta muito antiga, e eu tenho uma situação semelhante há alguns dias.

O problema era que na minha mesa são aprox. 10.000 linhas, então o loop de DataTablelinhas de calha era muito lento.

Finalmente, encontrei uma solução muito mais rápida, onde faço uma cópia da fonte DataTablecom os resultados desejados, fonte clara DataTablee mergeresultados do temporário DataTablepara a fonte um.

observação : em vez disso, pesquise Joeem DataRowchamado. nameVocê deve pesquisar todos os registros que não tenham nome Joe(forma oposta de pesquisa)

Há um exemplo ( 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 solução mais curta ajude alguém.

Há um c#código (não tenho certeza se está correto porque usei um conversor 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;

Claro, eu usei Try/Catchno caso se não houver resultado (por exemplo, se Seu dtPersonnão contiver name Joeirá lançar uma exceção), então você não faz nada com Sua tabela, ela permanece inalterada.


0

Tenho um conjunto de dados em meu aplicativo e fui definir alterações (excluir uma linha) para ele, mas ds.tabales["TableName"]é somente leitura. Então eu encontrei essa solução.

É um C#aplicativo 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

Você tenta obter e remover a coluna id da tabela de dados

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

Estou vendo vários pedaços da resposta certa aqui, mas deixe-me juntar tudo e explicar algumas coisas.

Em primeiro lugar, AcceptChangesdeve ser usado apenas para marcar a transação inteira em uma tabela como sendo validada e confirmada. O que significa que se você estiver usando o DataTable como um DataSource para vinculação a, por exemplo, um servidor SQL, chamar AcceptChangesmanualmente garantirá que as alterações nunca serão salvas no servidor SQL .

O que torna esse problema mais confuso é que, na verdade, há dois casos em que a exceção é lançada e temos que evitar os dois.

1. Modificando a coleção de um IEnumerable

Não podemos adicionar ou remover um índice da coleção que está sendo enumerada porque isso pode afetar a indexação interna do enumerador. Há duas maneiras de contornar isso: faça sua própria indexação em um loop for ou use uma coleção separada (que não seja modificada) para a enumeração.

2. Tentativa de ler uma entrada excluída

Como DataTables são coleções transacionais , as entradas podem ser marcadas para exclusão, mas ainda aparecem na enumeração. O que significa que, se você solicitar uma entrada deletada para a coluna "name", será lançada uma exceção. O que significa que devemos verificar se dr.RowState != DataRowState.Deletedantes de consultar uma coluna.

Juntando tudo

Podemos bagunçar e fazer tudo isso manualmente, ou podemos deixar o DataTable fazer todo o trabalho para nós e tornar a instrução mais parecida com uma chamada SQL, fazendo o seguinte:

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

Ao chamar a Selectfunção de DataTable , nossa consulta evita automaticamente entradas já deletadas na DataTable. E como a Selectfunção retorna um array de correspondências, a coleção que estamos enumerando não é modificada quando a chamamos dr.Delete(). Também apurei a expressão Select com interpolação de string para permitir a seleção de variáveis ​​sem tornar o código barulhento.


0

a maneira fácil de usar isso no botão:

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

exemplo1 = tabela de id. yesmediasec = id do botão na linha

use e tudo ficará bem

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.