DataTableから特定の行を削除する


85

DataTableからいくつかの行を削除したいのですが、次のようなエラーが発生します。

コレクションが変更されました。列挙操作が実行されない可能性があります

私はこのコードを削除するために使用します、

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

それで、問題は何ですか、そしてそれを修正する方法は何ですか?どの方法をお勧めしますか?

回答:


169

コレクションからアイテムを削除すると、そのコレクションは変更され、列挙を続行できなくなります。

代わりに、次のようなForループを使用してください。

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

現在のインデックスを削除した後に行をスキップしないように、逆に繰り返していることに注意してください。


@Slugsterは私をそれに打ち負かしました!(しかし、私はあなた[ii]をに変更しました[i]:-)
Widor 2011

11
これは正しくありません。あなたはできる行を削除しながら、テーブルをループするforeachを使用しています。スティーブの回答を参照してください。
アレクサンダーガーデン

3
この回答には、@ bokkieの回答も含める必要があります。後者を使用するDataTableと、例外がスローされます。正しい方法はRemove()、ソースを呼び出すことですDataTable- dtPerson.Rows.Remove(dr)
code.me 2015年

DataTableを使用してデータベースサーバーのテーブルを更新している場合は、@ Steveの方が適切です。行を削除済みとしてマークし、行を更新し、新しい行をすべて1つのループで追加できます。SqlAdapterを使用して、Dbテーブルへの変更をコミットできます。問題が発生する頻度を考えると、プロセス全体は、思ったよりもはるかに複雑ですが、機能します。DataTableのトランザクションの性質を利用しない場合は、オブジェクトコレクションとnamcoまたはWidorのアプローチを使用します。
BH 2016年

を使用すると、削除を有効にするためにDelete()への呼び出しが必要ではありませんAcceptChanges()か?
Broots Waymb 2017年

128

全員が「列挙型行を削除することはできません」という時流に乗る前に、まずDataTablesがトランザクションであり、AcceptChanges()を呼び出すまで変更を技術的にパージしないことを理解する必要があります。

Deleteの呼び出し中にこの例外が発生した場合は、すでに変更保留中のデータ状態になっています。たとえば、データベースからロードしたばかりの場合、foreachループ内にいると、Deleteを呼び出すと例外がスローされます。

だが!だが!

データベースから行をロードし、関数 ' AcceptChanges() 'を呼び出すと、保留中の変更をすべてDataTableにコミットします。これで、Delete()を呼び出す行のリストを気にせずに繰り返すことができます。これは、行に削除のマークを付けるだけで、AcceptChanges()再度呼び出すまでコミットされないためです。

この応答は少し古いと思いますが、最近同様の問題に対処する必要がありました。これにより、10年前のコードに取り組んでいる将来の開発者の苦痛が軽減されることを願っています:)


PSこれは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()

C#バージョンの場合、()の代わりに{と}を使用する必要があります
BugLover 2014

2
に変更object row_loopVariable in ds.Tables(0).Rowsするのもより便利だと思いますDataRow row in ds.Tables(0).Rows
BugLover 2014

2
Ffs、これは悪夢のような週末の展開中に私を救ってくれました。あなたはすべてのビールに値する!
James Love


素敵なコード。1つ、C#counter++では、1ずつインクリメントする一般的な方法はの代わりですcounter+= 1
MQuiggGeorgia 2017

18

このソリューションで:

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

行を削除した後にデータテーブルを使用しようとすると、エラーが発生します。それでは、あなたができることは次のとおりです。置き換えるdr.Delete();dtPerson.Rows.Remove(dr);


16

これは私のために働きます、

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

dt.AcceptChanges()を見逃しやすい
Matthew Lock

「DataRowクラスのDeleteメソッドを呼び出して、削除する行をマークすることもできます。Removeを呼び出すことは、Deleteを呼び出してからAcceptChangesを呼び出すことと同じです。DataRowCollectionオブジェクトを反復処理している間、foreachループでRemoveを呼び出さないでください。Removeコレクションの状態を変更します。」msdn.microsoft.com/de-de/library/… 乾杯を参照してください
Andreas Krohn 2017年

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

お役に立てば幸いです


1
コマンドはdrow.Delete();ないdrow.delete();方法がところで.NETで大文字と小文字を区別
メソッド・マン

5

DataTableから行全体を削除するには、次のようにします

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

または、DataTableRow コレクションをリストに変換するだけです。

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

1

問題はどこにありますか:foreachループ内のコレクションからアイテムを削除することは禁止されています。

解決策:Widorが書いたようにするか、2つのループを使用します。DataTableの最初のパスでは、削除する行への参照のみを(一時リストに)格納します。次に、一時リストの2回目のパスで、これらの行を削除します。


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

私はこれが非常に古い質問であることを知っています、そして私は数日前に同様の状況にあります。

問題は、私のテーブルでは約です。10000行なので、トラフDataTable行のループは非常に遅くなりました。

最後に、私ははるかに高速な解決策を見つけました。そこではDataTable、希望する結果でソースのコピーを作成し、ソースDataTablemerge結果を一時的なものからDataTableソース1にクリアします。

注意:代わりにJoeDataRow呼び出さnameれた場所で検索します。名前のないすべてのレコードを検索する必要がありますJoe(検索の少し反対の方法)

例があります(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

この短い解決策が誰かを助けることを願っています。

有る c#コードます(オンラインコンバーターを使用したため、正しいかどうかはわかりません:():

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

もちろん、Try/Catch結果がない場合(たとえば、結果dtPersonが含まれname Joeていない場合は例外がスローされる場合)に使用したので、テーブルに対して何も行わず、変更されません。


0

アプリにデータセットがあり、それに変更を設定(行を削除)しましたが、 ds.tabales["TableName"]が、読み取り専用です。それから私はこの解決策を見つけました。

これはwpfC#アプリです。

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

データテーブルからid列を取得および削除するためにこれを試します

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

私はここで正しい答えのさまざまな断片を見ていますが、それをすべてまとめて、いくつかのことを説明しましょう。

まずAcceptChanges、テーブル上のトランザクション全体を検証およびコミット済みとしてマークするためにのみ使用する必要があります。つまり、たとえばSQLサーバーにバインドするためのデータソースとしてDataTableを使用している場合、AcceptChanges手動で呼び出すと、変更がSQLサーバーに保存されないことが保証されます

この問題をさらに混乱させるのは、例外がスローされるケースが実際には2つあり、両方を防止する必要があることです。

1.IEnumerableのコレクションの変更

列挙するコレクションにインデックスを追加または削除することはできません。そうすると、列挙子の内部インデックスに影響を与える可能性があるためです。これを回避するには、2つの方法があります。forループで独自のインデックスを作成するか、列挙に別のコレクション(変更されていない)を使用します。

2.削除されたエントリを読み込もうとしています

DataTablesはトランザクションコレクションであるため、エントリに削除のマークを付けることができますが、列挙には引き続き表示されます。つまり、列の削除されたエントリを要求する"name"と、例外がスローされます。つまりdr.RowState != DataRowState.Deleted、列をクエリする前に確認する必要があります。

すべてを一緒に入れて

面倒になってすべてを手動で行うことも、DataTableにすべての作業を行わせて、次のようにすることでステートメントをSQL呼び出しのように見せることもできます。

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

DataTableのSelect関数を呼び出すことにより、クエリはDataTableですでに削除されたエントリを自動的に回避します。また、Select関数は一致の配列を返すため、列挙しているコレクションは、を呼び出しても変更されませんdr.Delete()。また、コードを煩わしくすることなく変数を選択できるように、文字列補間を使用してSelect式にスパイスを加えました。


0

ボタンでこれを使用する簡単な方法:

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

example1 = idテーブル。yesmediasec =行のボタンのID

それを使用し、すべてのものが大丈夫になります

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.