DataTableからいくつかの行を削除したいのですが、次のようなエラーが発生します。
コレクションが変更されました。列挙操作が実行されない可能性があります
私はこのコードを削除するために使用します、
foreach(DataRow dr in dtPerson.Rows){
if(dr["name"].ToString()=="Joe")
dr.Delete();
}
それで、問題は何ですか、そしてそれを修正する方法は何ですか?どの方法をお勧めしますか?
回答:
コレクションからアイテムを削除すると、そのコレクションは変更され、列挙を続行できなくなります。
代わりに、次のようなForループを使用してください。
for(int i = dtPerson.Rows.Count-1; i >= 0; i--)
{
DataRow dr = dtPerson.Rows[i];
if (dr["name"] == "Joe")
dr.Delete();
}
dtPerson.AcceptChanges();
現在のインデックスを削除した後に行をスキップしないように、逆に繰り返していることに注意してください。
DataTableと、例外がスローされます。正しい方法はRemove()、ソースを呼び出すことですDataTable- dtPerson.Rows.Remove(dr)。
Delete()への呼び出しが必要ではありませんAcceptChanges()か?
全員が「列挙型の行を削除することはできません」という時流に乗る前に、まず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()
object row_loopVariable in ds.Tables(0).Rowsするのもより便利だと思いますDataRow row in ds.Tables(0).Rows
counter++では、1ずつインクリメントする一般的な方法はの代わりですcounter+= 1。
これは私のために働きます、
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();
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);
<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();
}
私はこれが非常に古い質問であることを知っています、そして私は数日前に同様の状況にあります。
問題は、私のテーブルでは約です。10000行なので、トラフDataTable行のループは非常に遅くなりました。
最後に、私ははるかに高速な解決策を見つけました。そこではDataTable、希望する結果でソースのコピーを作成し、ソースDataTableとmerge結果を一時的なものからDataTableソース1にクリアします。
注意:代わりにJoe、DataRow呼び出さ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ていない場合は例外がスローされる場合)に使用したので、テーブルに対して何も行わず、変更されません。
アプリにデータセットがあり、それに変更を設定(行を削除)しましたが、 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);
}
}
私はここで正しい答えのさまざまな断片を見ていますが、それをすべてまとめて、いくつかのことを説明しましょう。
まず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式にスパイスを加えました。
[ii]をに変更しました[i]:-)