web-dev-qa-db-ja.com

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

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

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

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

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

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

64
namco

コレクションからアイテムを削除した場合、そのコレクションは変更されているため、アイテムの列挙を続けることはできません。

代わりに、次のようなForループを使用します。

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

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

133
Widor

誰もが「列挙型の行を削除できない」バンドワゴンにジャンプする前に、まずDataTablesがtransactionalであることを認識する必要があります。 、およびAcceptChanges()を呼び出すまで技術的に変更をパージしない

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

しかし!しかし!

データベースから行をロードし、関数「AcceptChanges()」を呼び出すと、保留中のすべての変更がDataTableにコミットされます。 Delete()を呼び出す行のリストを繰り返し処理できるようになりました。これは、削除のために行を単純にマークするだけで、again callAcceptChanges()

私はこの応答が少し古くなっていることを理解していますが、最近同様の問題に対処しなければならなかったし、10年前のコードに取り組んでいる将来の開発者の痛みを軽減することを願っています:)


追伸 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()
105
Steve

このソリューションでは:

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);に置き換えることです

15
bokkie

これは私のために働く、

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();
13
Balaji Birajdar
DataRow[] dtr=dtPerson.select("name=Joe");
foreach(var drow in dtr)
{
   drow.delete();
}
dtperson.AcceptChanges();

私はそれがあなたを助けることを願っています

8
Karthik

または、単にDataTableRow collectionをリストに変換します:

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

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);
3
Karthikeyan P

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

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

1
Al Kepp

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

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

最後に、ソースDataTableのコピーを作成し、ソースDataTableおよびmergeの結果を一時的なDataTableからソース1にクリアする、はるかに高速なソリューションを見つけました。

Joeと呼ばれるDataRownameを検索する代わりに、名前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;

もちろん、結果がない場合(たとえば、dtPersonnameが含まれていない場合、Joeは例外をスローします)、Try/Catchを使用しました。

1
nelek
<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
Arun Prasad E S

データテーブルから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
shubham

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

まず、AcceptChangesは、テーブル上のトランザクション全体を検証およびコミット済みとしてマークするためにのみ使用する必要があります。つまり、DataTableを、たとえばSQLサーバーにバインドするためのDataSourceとして使用している場合、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
Rhaokiel

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

Wpf C#アプリです。

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
Mamad