web-dev-qa-db-ja.com

DataTableの内部インデックスが破損しています

3.5 .NETフレームワークに対して実行しているC#で.NET WinFormsアプリを使用しています。このアプリでは、DataColumnDataTableの.Expressionメンバーを次のように設定しています。

DataColumn column = dtData.Columns["TestColumn"];
column.Expression = "some expression";

2行目で実際にExpressionを設定すると、次の例外が発生することがあります。

FileName=
LineNumber=0
Source=System.Data
TargetSite=Int32 RBInsert(Int32, Int32, Int32, Int32, Boolean)
System.InvalidOperationException: DataTable internal index is corrupted: '5'.
   at System.Data.RBTree`1.RBInsert(Int32 root_id, Int32 x_id, Int32 mainTreeNodeID, Int32 position, Boolean append)
   at System.Data.RBTree`1.RBInsert(Int32 root_id, Int32 x_id, Int32 mainTreeNodeID, Int32 position, Boolean append)
   at System.Data.Index.InitRecords(IFilter filter)
   at System.Data.Index.Reset()
   at System.Data.DataTable.ResetInternalIndexes(DataColumn column)
   at System.Data.DataTable.EvaluateExpressions(DataColumn column)
   at System.Data.DataColumn.set_Expression(String value)

エラーがいつ発生するかについて、知覚できる韻や理由はありません。同じデータセットをロードする場合、正常に動作する可能性がありますが、再ロードは失敗し、その逆も同様です。これは、列の1つを変更しようとしているときにDataTableで別の書き込み操作が発生している競合状態に関連していると思います。ただし、DataTablesに関連するコードはではなくマルチスレッドであり、UIスレッドでのみ実行されます。

私はウェブと Microsoftフォーラム を検索しましたが、この問題については多くの議論と混乱があります。 2006年にこの問題が最初に報告されたとき、それは.NETフレームワークの欠陥であると考えられていました。ただし、現在のフレームワークには適用されなくなった修正プログラムを適用した結果は、さまざまです。

別の一般的な理論は、一見無害であるように見えますが、実際には書き込み操作であるDataTableに対する操作があるということです。たとえば、DataViewに基づいて新しいDataTableを作成することは、後で参照するためにDataTableに内部インデックスを作成するため、実際にはテーブル自体に対する書き込み操作です。これらの書き込み操作はスレッドセーフではないため、競合状態により、DataTableへのアクセスと一致しないスレッドセーフな書き込みが発生することがあります。これにより、DataTableの内部インデックスが破損し、例外が発生します。

lockブロックをコード内の各DataView作成の周りに配置しようとしましたが、前に述べたように、DataTableを利用するコードはスレッド化されておらず、locksは、いずれの場合も効果がありませんでした。

誰かがこれを見て、うまく解決/回避しましたか?


いいえ、残念ながらできません。 DataTableの読み込みは、そのDataColumnの1つに式を適用するためにそれを手に入れるまでに既に行われています。列を削除してから、提案されたコードを使用して列を再度追加することもできますが、それによって内部インデックスが破損する問題を解決する特別な理由はありますか?

27
user30525

行のインポート中に同じ問題が発生したようですが、挿入によって修正される前に_DataTable.BeginLoadData_を呼び出したようです。

編集:結局のところ、これは片側だけを修正しましたが、行を追加するとこの例外がスローされます。

Edit2:Robert Rossney によって提案されたバインディングの一時停止により、追加の問題も修正されました。私は単にDataSourceDataGridViewから削除し、DataTableを使い終わった後でそれを追加しました。

Edit3:まだ修正されていません...木曜日以降、例外がコードのすべての場所で忍び寄っています...これははるかに奇妙で、私がこれまでにフレームワークで遭遇した最もf ****なバグ(そして、.NET 2.0で作業していた3年間で奇妙なことをたくさん見てきましたが、将来の1つではないことを保証するのに十分プロジェクトはその上に構築されます)。しかし、話題に戻って、十分な怒りの言葉です。

私はすべてのディスカッションを読み終えました Microsoftサポートフォーラムで とそれについて簡単に要約します。元の バグレポートは'05年に作成されました

  • 2006年3月:バグが初めて報告され、調査が開始されます。翌年には、さまざまな形や症状で報告されます。
  • 2007年3月:最後に 番号KB 932491のホットフィックス がリリースされました(期待を裏切らないでください)、それは 完全に無関係なホットフィックスのダウンロード にリンクしています、または少なくともそうです。 hotfixが機能しないと今後数か月にわたって報告されている多くの人が、成功を報告しています。
  • 2007年7月:Microsoftからのライブの最後の兆候(完全に役に立たない回答)、これを超えてマイクロソフトからこれ以上の応答はありません。それ以上の確認、サポートの試み、詳細情報の要求はありません...何もありません。この時点を過ぎると、コミュニティに関連する情報しかありません。

真剣に、これは私の意見でそれを要約しません。ディスカッション全体から次の情報を抽出することができました。

  • DataTablenotスレッドセーフです。マルチスレッディングがどこかにある場合は、自分でLock/Synchronizeする必要があります。
  • インデックスの破損は、実際の例外がスローされるのどこかで発生します。
  • 考えられる破損の原因の1つは、適用されたExpressionまたは適用されたSortです。
  • もう1つの可能なソースはDataTable.ListChanged()イベントです。このイベントまたはそれから発生するイベントのデータを変更しないでください。これには、バインドされたコントロールからのさまざまなChangedイベントが含まれます。
  • コントロールに対してDefaultViewをバインドするときに問題が発生する可能性があります。常にDataTable.BeginLoadData()およびDataTable.EndLoadData()を使用してください。
  • DefaultViewの作成と操作は、DataTable(およびそのIndexに対する書き込み操作です。 )、フライングスパゲッティモンスターはその理由を知っています。

この原因として考えられるのは、おそらくソースコードまたはフレームワークのコードのいずれかにおける競合状態です。どうやら、マイクロソフトはこのバグを修正することができないか、修正するつもりはありません。いずれにせよ、コードの競合状態を確認してください。私の意見では、それはDefaultViewと関係があります。ある時点で、変更がInsert全体に適切に伝播されないため、DataTableまたはデータの操作によって内部インデックスが破損しています。

もちろん、さらに情報や追加の修正が見つかった場合は報告します。ここで少し感情的になった場合は申し訳ありませんが、この問題を特定するために3日間費やしてきましたが、ゆっくりと新しい仕事に就く正当な理由に見え始めています。

Edit4:バインディング(_control.DataSource = null;_)を完全に削除し、データのロード後に再度追加することで、このバグを回避できました完成されました。これは、バインドされたコントロールから発生するDefaultViewおよびイベントと関係があるという私の考えを刺激します。

20
Bobby

個人的に、この特定のバグは、さまざまな方法で3週間私の宿敵となっています。私はコードベースの一部でそれを解決しましたが、他の場所で表示されます(今夜、ようやく潰したと思います)。例外情報はあまり役に立たず、再インデックスを強制する方法は、問題を解決するためのMSの欠如を考えると素晴らしい機能でした。

私はMSのホットフィックスを探すつもりはありません-それらにはKB記事があり、それから完全に無関係なASP.Netフィックスにリダイレクトします。

わかりました-十分不満です。私が遭遇したさまざまな場所でこの特定の問題を解決するのに実際に私が役立ったものを見てみましょう。

  • デフォルトビューの使用は避け、可能であればデフォルトビューを変更してください。ところで、.Net 2.0にはビューの作成時に多数のリーダー/ライターロックがあるため、2.0より前の問題ではありません。
  • 可能な場合はAcceptChanges()を呼び出します。
  • このコードにはリーダー/ライターのロックがないため、.Select(expression)に注意してください。これは、唯一の場所です(少なくとも、usenetの人によると、塩の粒と一緒に持っていってください)。 、これはあなたの問題と非常に似ています-したがって、Mutexesを使用すると役立つことがあります)
  • 問題の列にAllowDBNullを設定します(問題のある値ですが、usenetで報告されています-私はそれが意味のある場所でのみ使用しました)
  • DataRowフィールドにnull(C#)/ Nothing(VB)を設定していないことを確認してください。 nullの代わりにDBNull.Valueを使用します。 yourの場合、フィールドがnullでないことを確認する必要があります。式の構文doesは、IsNull(val、alt_val)演算子をサポートしています。
  • これはおそらく私に最も役立ちました(不思議なことに不合理です)。値が変化しない場合は、割り当てないでください。したがって、yourの場合は、完全な割り当ての代わりにこれを使用します。

    if(column.Expression!= "some expression")column.Expression = "some expression";

(角括弧を削除しましたが、なぜそこにあったのかはわかりません)。

編集(5/16/12):この問題に繰り返し遭遇しました(UltraGrid/UltraWinGridを使用)。 DataViewの並べ替えを削除するアドバイスを使用し、DataViewの並べ替えと一致する並べ替え済みの列を追加しました。これにより問題が解決しました。

10
torial

あなたは「スレッドセーフではない」と述べています。異なるスレッドからオブジェクトを操作していますか?もしそうなら、それは非常によく破損の理由かもしれません。

このバグを再現する方法を確認しようとしている人へのメモです。私はかなり頻繁にそのエラーを生成するいくつかのコードを持っています。同時の読み取り/書き込みをロックしますが、DataView.FindRowsの呼び出しはそのロックの外側で行われます。 OPは、creatingデータビューは非表示の書き込み操作であると指摘しました。

//based off of code at http://support.Microsoft.com/kb/932491
using System.Data;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System;
public class GenerateSomeDataTableErrors
{   
    public static void Main()
    {
        DataTable Table = new DataTable("Employee");
        Table.Columns.Add("Id", typeof(int));
        Table.Columns.Add("Name", typeof(string));
        Table.PrimaryKey = new DataColumn[] { Table.Columns["Id"] };

        DataSet Employees = new DataSet();
        Employees.Tables.Add(Table);

        DataRow ManagerB = Table.NewRow();
        ManagerB["ID"] = 392;
        ManagerB["Name"] = "somename";
        Table.Rows.Add(ManagerB);

        DataRow ManagerA = Table.NewRow();
        ManagerA["ID"] = 394;
        ManagerA["Name"] = "somename";
        Table.Rows.Add(ManagerA);

        Employees.AcceptChanges();

        object locker = new object();

        //key = exception string, value = count of exceptions with same text
        ConcurrentDictionary<string, int> exceptions = new ConcurrentDictionary<string, int>();

        DataView employeeNameView = new DataView(Table, string.Empty, "Name", DataViewRowState.CurrentRows);

        Parallel.For(0, 100000, (i, s) =>
        {
            try
            {
                #region do modifications to the table, in a thread-safe fashion
                lock (locker)
                {
                    var row = Table.Rows.Find(392);

                    if (row != null) //it's there, delete it
                    {
                        row.Delete();
                        Employees.AcceptChanges();
                    }
                    else //it's not there, add it
                    {
                        var newRow = Table.NewRow();
                        newRow["ID"] = 392;
                        newRow["Name"] = "somename";
                        Table.Rows.Add(newRow);
                        Employees.AcceptChanges();
                    }
                }
                #endregion

                //Apparently this is the dangerous part, finding rows 
                // without locking on the same object the modification work is using.
                //lock(locker)
                employeeNameView.FindRows("somename");
            }
            catch (Exception e)
            {
                string estring = e.ToString();
                exceptions.TryAdd(estring, 0);
                lock (exceptions)
                { exceptions[estring] += 1; }
            }
        });

        foreach (var entry in exceptions)
        {
            Console.WriteLine("==============The following occurred " + entry.Value + " times");
            Console.WriteLine(entry.Key);
        }
    }//Main
}//class

そのまま実行すると、次のような出力が得られます(出力は実行するたびに多少異なります)。

==============The following occurred 2 times
System.InvalidOperationException: DataTable internal index is corrupted: '13'.
   at System.Data.RBTree`1.GetNodeByIndex(Int32 userIndex)
   at System.Data.DataView.GetRow(Int32 index)
   at System.Data.DataView.GetDataRowViewFromRange(Range range)
   at System.Data.DataView.FindRowsByKey(Object[] key)
   at GenerateSomeDataTableErrors.<>c__DisplayClass9.<Main>b__8(Int32 i, ParallelLoopState s) in Program.cs:line 110
==============The following occurred 3 times
System.IndexOutOfRangeException: Index 1 is either negative or above rows count.
   at System.Data.DataView.GetRow(Int32 index)
   at System.Data.DataView.GetDataRowViewFromRange(Range range)
   at System.Data.DataView.FindRowsByKey(Object[] key)
   at GenerateSomeDataTableErrors.<>c__DisplayClass9.<Main>b__8(Int32 i, ParallelLoopState s) in line 110
==============The following occurred 1 times
System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Data.DataView.GetRow(Int32 index)
   at System.Data.DataView.GetDataRowViewFromRange(Range range)
   at System.Data.DataView.FindRowsByKey(Object[] key)
   at GenerateSomeDataTableErrors.<>c__DisplayClass9.<Main>b__8(Int32 i, ParallelLoopState s) in Program.cs:line 110
Press any key to continue . . .

また、FindRows呼び出しにロックを設定しても、例外はありません。

3
Anssssss

私の理解は、この問題についての長くてつらい議論から、それはスレッドセーフでない書き込み操作のアーティファクトであり、通常は自分が作成していることを知らなかったということです。

私の場合、犯人はBindingSourceのようです。バインドを一時停止し、試行中の操作をすべて実行してから、バインドを再開する必要があることがわかり、問題は解消しました。これは18か月前だったので、詳細はわかりませんが、BindingSourceが独自のスレッドでなんらかの操作を行っているという印象を受けたことを覚えています。 (これは、当時よりも今ではあまり意味がありません。)

問題の別の潜在的な原因は、DataTableのRowChangingイベントです。そのイベントハンドラーでテーブルを変更する何かをする場合、悪いことを期待してください。

2
Robert Rossney

ミューテックスを ここで説明 として適用して、そのような状況でスレッドにスリープを誘導するというアイデアを試してみませんか?

1
Suj

ここで同じ問題があり、別のアプローチを試みました。画面に関連するもの(バインディングなど)にはデータテーブルを使用していません。 (複数のスレッドで)DataRowオブジェクトを作成し、それらをテーブルに追加しています。

私はlock()を使用してみましたが、これも役立つと考えて、行の追加をシングルトンに集中化しようとしました。そうではなかった。参考までに、これが私が使用したシングルトンです。たぶん他の誰かがこれに基づいて何かを理解できるでしょうか?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace EntityToDataSet
{
   public class RowAdder
   {
      #region Data
      private readonly object mLockObject = new object();
      private static RowAdder mInstance;

      public static RowAdder Instance
      {
         get
         {
            if (mInstance == null)
            {
               mInstance = new RowAdder();
            }
            return mInstance;
         }
      }

      object mSync;
      #endregion

      #region Constructor
      private RowAdder()
      {
      }
      #endregion

      public void Add(DataTable table, DataRow row)
      {
         lock (mLockObject)
         {
            table.Rows.Add(row);
         }
      }
   }
}
1
David Catriel

私は同じ問題に遭遇し、これが私のためにそれを修正したものです Stack Overflow-内部インデックスが破損しています

データセットでスレッドを使用している場合、そのエラーが発生します。

私の場合、スレッドで実行されているメソッド内でデータセットの新しい行を作成しようとしました。

1つの方法は、行を作成するメソッドの周りでSyncLockを使用することでした。または別の方法(そしておそらくさらに良い方法)は、スレッドの外部で行を作成することでした。

基本的に私のコードは次のようになります:

    Dim elements As New List(Of element)
    Dim dataRows As New List(Of MyDataSet.Row)

    For i As Integer = 0 To elements.Count - 1
        dataRows.Add(Me.Ds.Elements.NewElementsRow)
    Next

    Parallel.For(0, elements.Count, Sub(i As Integer)
                                        Me.CreateElementRow(elements(i), dataRows(i))
                                    End Sub)

CreateElementRowメソッドでは、スレッドで多くの計算を行っています。

お役に立てれば。

1
mrc

Datagridviewにバインドされているデータセットに行をプログラムで追加すると、同じ問題(テーブルインデックスが5で破損する)が発生しました。 datagridviewのAddRowイベントにイベントハンドラーがあり、ユーザーがUIで新しい行を開始した場合に初期化を行うことを考慮していません。例外スタックトレースでは、何も見られませんでした。イベントを無効にすることで、これをすばやく解決できました。私はここでいくつかのコメントを深く読んだだけでそれに到達しました。そのような問題については2時間はあまり多くありません:-)、私は思います。これは、データセットにリンクされているdatgridviewに割り当てられているすべてのイベントハンドラーにブレークポイントを設定することで確認できます。

1
rgaab

これは、内部インデックスが破損している問題を修正する方法です。

System.Data.DataTable dtNew = new DataTable();
for (int iCol = 0; iCol < dtOriginalData.Columns.Count; iCol++)
{
    dtNew.Columns.Add(dtOriginalData.Columns[iCol].ColumnName, dtOriginalData.Columns[iCol].DataType);
}
for (int iCopyIndex = 0; iCopyIndex < item.Data.Rows.Count; iCopyIndex++)
{
    dtNew.Rows.Add(dtOriginalData.Rows[iCopyIndex].ItemArray);
    //dtNew.ImportRow(dtOriginalData.Rows[iCopyIndex]); 
}
dtOriginalData = dtNew; 

楽しんで、Andrew M

1
Andrew Marais

私は私のdatatable-internal-indexエラーをこの方法で解決しました:

CellEndEditCellBeginEditイベントに変更しました。また... NULLを不必要に使用しないでください:

Private Sub User_role_groupDataGridView_CellBeginEdit(sender As Object, e As DataGridViewCellCancelEventArgs) Handles User_role_groupDataGridView.CellBeginEdit
    Try 
        If Not Me.User_role_groupDataGridView.Rows(e.RowIndex).IsNewRow Then Me.User_role_groupDataGridView.Rows(e.RowIndex).Cells("last_modified_user_group_role").Value = Now
    Catch ex As Exception
        Me.displayUserMessage(ex.ToString, Me.Text, True)
    End Try
End Sub
1
Samuel Darteh

.NET 4.5.2勝利フォームアプリと同じでした。私の場合、理由は、単一のBindingSource列にバインドされた複数のコントロールにあるように見えました。多くのコントロールを1つの値にバインドすると問題が発生することは承知していますが、レイアウトがかなり複雑なため、私にとってはそれほど問題ではありませんでした。

片側から値を変更すると、問題のエラーが発生したBindingSourceに対して同じ操作を実行しようとする複数のOnChangeイベントが発生したようです。

バインドを一時停止しようとしましたが、成功しませんでした。フラグを実装して、コードが並列で何度も実行されるのを防ぎました。

0
Kuba D

スレッドを使用して同じ問題が発生しました。私がしたことは、テーブルをマージする必要があるときに呼び出されるデリゲートを作成することでした。

internal delegate void MergeData (DataTable dataTable1, DataTable dataTable2);

internal static void MergeDataTable (DataTable dataTable1, DataTable dataTable2)
{
    dataTable1.Merge (dataTable2, true);
}

次に、実行中にデリゲートを呼び出しますが、エラーは発生しません。

Delegates.MergeData mergeData = new Delegates.MergeData (Delegates.MergeDataTable);

object [] paramsMerge = {dataTable1, dataTable2};

this.Invoke (mergeData, paramsMerge);
0
Ailton

私の場合、フレームワークのバージョンは2.0です。問題の原因は、DataView ListChangedイベントにありました。以下のコードは、いくつかのデフォルト値で新しい行を初期化します。

private void dataView_ListChanged(object sender, ListChangedEventArgs e)
{
    if (e.ListChangedType == ListChangedType.ItemAdded)
    {
        DataView v = (DataView)sender;
        DataRowView drv = v[e.NewIndex];

        // This "if" works fine
        if (drv["Foo"] == DBNull.Value)
        {
            drv["Foo"] = GetFooDefault();
        }

        // This "if" brakes the internal index     
        if (drv["Bar"] == DBNull.Value && drv["Buz"] != DBNull.Value)
        {
            drv["Bar"] = drv["Buz"];
        }
    }
}

調査の結果、ItemAddedイベントが少なくとも1行に2回呼び出されることが明らかになりました。最初にUIがデータを入力するための新しい行を作成するとき、2回目はよくわかりませんが、DataRowViewがDataViewに追加されるときのように見えます。

最初の「if」は、ItemAddedが初めて呼び出されたときにのみ機能します。 2回目の呼び出しでは、「Foo」列はすでに入力されており、そのままです。

ただし、「Bar」列のデフォルトコードは、両方の呼び出しで実行できます。実際、私の場合は、ユーザーが「Buz」列のデータを入力する機会があったときに、2番目のItemAddedイベントでのみ実行されました(最初は「Buz」にはDBNull値があります)。

だからここに私の発見に基づく推奨事項があります:

  • ListChangedイベントのデータは、e.ListChangedType == ListChangedType.ItemAdded
  • 列の値を設定する前に、これが最初のItemAddedイベントであることを確認するためにチェックを実行する必要があります(たとえば、2番目の呼び出しで値をnullにできない場合は、DBNull.Valueなど)
0
Artemix

同じことが私にも起こりました。 Winforms(.NET 3.5)は、型付きの行に列の1つを設定しようとすると、予期せずこのエラーが発生しました。コードはかなり古く、長い間動作していたので、ちょっと不愉快な驚きでした...

データセットTadaSetの型付きテーブルTadaTableに新しいSortNoを設定する必要がありました。

私を助けたもの、あなたもこれを試すことができます:

int i = 0;
foreach (TadaSet.TadaTableRow row in source)
{
     row.BeginEdit(); //kinda magical operation but it helped :)
     // Also you can make EndEdit() for each row later if you need...
     short newNo = i++;
     if (newNo != row.SortNo) row.SortNo = newNo; //here was the crash
}
0
Schnapz

あなたはただ使うことはできません:

dtData.Columns.Add("TestColumn", typeof(Decimal), "Price * Quantity");
0
balexandre

これが私の同僚のカレンと私にとってうまくいったようです。DataGridViewでこのエラーが発生しましたが、特定の1つの列にデータを入力したときのみです。

グリッド内の列の順序を変更したことがわかりましたが、DataGridView.CellValidatedサブルーチンに、特定の1つの列の値をnullにするコードがあったため、問題が発生したことがわかりません。

そのコードは特定の列番号を参照していました。したがって、元のDataGridView列3が移動されて列1になったが、DataGridView.CellValidatedコードがまだ列3を参照していた場合、エラーが発生しました。正しいe.ColumnIndexを参照するようにコードを変更すると、問題が解決したようです。

(コードでこの1つの番号を変更するのは簡単ではありませんでした。この修正が当てはまることを願っています。)

0
Mike Banach

同時に複数のプロセスで同じデータテーブルを使用している可能性があります。SYNCLOCK...を使用してこの問題を解決しました.

これを試して..

SyncLock your datatable

'''' ----your datatable process

End SyncLock
0
URVISH SUTHAR