2

オブジェクト内のキャッシュ (エンタープライズ ライブラリ経由) に格納された情報持つ従来の ASP.NET コード ベースに取り組んでいDataTableます。これは、.NET 4.0 で実行される複数ユーザーのイントラネット環境です。可能性のある根本原因として以前の「修正済み」の問題を指摘する問題が本番環境に存在します:KeyNotFoundExceptionの呼び出しで発生しますDataView.ToTable()。この特定のコードは、アプリケーションの大部分のページからページをロードする際に発生する検証の一部です。

var table = GetSomeDataTableFromCache();
var view = table.DefaultView;
view.RowFilter = "Foo = 'Bar'";
var filteredTable = view.ToTable();

これはコードの簡略化です。何が起こったのかというと、何日も経ってから、このコードは明らかに前述の例外をスローしていました。以前の開発者は、例外をキャッチして飲み込み、null を返すことで「修正」しました。この動作は、明るみに出た他の問題の原因と推定されています。

元の例外を再現しようとしましたが、あまり成功しませんでした。もしそれを再現できれば、それを理解し、それを無視するよりも優れた元の問題に対する解決策を形成しようと試みることができると感じています。

DataView.ToTable() + KeyNotFoundExceptionGoogle は生成された結果を検索して、列名が存在することを検証していることに注意してください。例は次の場所にあります。

http://forums.asp.net/t/1695676.aspx/1

ただし、 をRowFilter無効な列名に設定すると、EvaluateExceptionその行に が発生することがわかりました。また、空のテーブルを使用し、空の結果を生成するフィルターを使用して、エラーのトラブルシューティングを試みました。どのシナリオも例外ではないことが証明されています。

KeyNotFoundExceptionでは、提案された無効な列名でない場合、どこで発生するのでしょうか? そして、それがどのように発生するかがわかったら、どうすればそれを回避できるでしょうか?

4

1 に答える 1

4

ASP.NET は本質的にマルチスレッド システムであることに注意してください。DataView読み取りはスレッドセーフであると文書化されていますが、書き込みはそうではありません。これを考慮することが重要です。

DataTable提示されたスニペットでは、キャッシュからa のインスタンスをプルし、DefaultViewフィルタリングして新しいものに書き込むためにそのインスタンスにアクセスするDataTableと、安全でないことが証明される状態が発生する可能性があります。これは、フィルター内の列が原因ではなく、複数の同時に同じコードを実行できるスレッド。

実装の詳細として、DataView内部状態のいくつかのビットを利用します。実行中、ToTable()複数の非ローカル状態が関係します。特に、DataRow をキーとするディクショナリ フィールドと、キーとして使用される DataRow フィールドがあります。このディクショナリはクリアされ、追加され、行を参照する値で行が上書きされ、次に null に設定されます。これはすべてプロセスの一部です。複数のスレッドが同時にこれを実行している場合、あるスレッドが別のスレッドが依存している状態を上書きして無効にすることは考えられません。これは、質問に記載されている例外や、その他の潜在的に有害な結果につながる可能性があります。

とにかく、複数の実行を利用できる環境でも、コード スニペットを出発点として使用して問題を再現してみましょう。

static void Main()
{
    var table = new DataTable();
    table.Columns.Add("Foo");
    table.Columns.Add("ID", typeof(int));

    for (int i = 0; i < 100; i++)
    {
        table.Rows.Add(i.ToString(), i);
    }

    for (int j = 0; j < 100; j++) 
    {
        Enumerable
           .Range(0,100)
           .AsParallel()
           .ForAll(item => ExecuteToTable(table, item));
    }
}

static void ExecuteToTable(DataTable table, int item)
{
    var view = table.DefaultView;
    view.RowFilter = string.Format("Foo = '{0}'", item);
    var filteredTable = view.ToTable();
}

これは例外を生成しますか? 実行して確認してください。複数回の実行が必要になる場合がありますが、コードを実行しているマシンが私のものと似ていれば、それほど多くはかかりません。(並列クエリのループでは、実際には 1 回だけかかります。)

このコードを LinqPad で実行したところ、「望ましい」例外が生成されました。これは並列クエリ実行であるため、AggregatedException にラップされますが、InnerException が物語を語ります。

KeyNotFoundException: 指定されたキーがディクショナリに存在しませんでした。

at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at System.Data.DataView.CopyTo(DataRowView[] array, Int32 index)
at System.Data.DataView.GetEnumerator()
at System.Data.DataView.ToTable(String tableName, Boolean distinct, String[] columnNames)
at System.Data.DataView.ToTable()

それで、私たちはそれを再現し、うまくいけばそれを理解しました. 避けてみてはどうですか?可能な解決策はいくつかありますが、ユースケースによっては、他のものよりも口当たりの良いものもあり、大規模な書き直しからわずかな変更までさまざまです。

あなたはできる

DefaultView にアクセスするtable.Copy() 前に、DataTableを使用してコピーします。これにより、各リクエストに独自のテーブルが与えられます (上記のスニペット)。ただし、テーブルが大きい場合、コピーのコストが高くなる可能性があります。を使用して上記の再現コードを実行しCopy()、例外が回避されるかどうかを確認してください。

DataView を完全に避けてください。Linq は、DataTable に対しても役立ちます。以下のスニペットを使用して、フィルター処理された DataTable 出力を生成できます。ただし、フィルターを通過する行がない場合は、独自のCopyToDataTable()例外がスローされる可能性があることにも注意してください。その可能性がある場合は、コードを分割し、最後の部分を呼び出す前に (.Any() を使用して) 結果を確認してください。に対するもう 1 つの欠点は、利用可能なオーバーロードを利用する場合、出力テーブルに含める列をa を使用して指定できることです。DataViewDataViewToTable

var filteredTable
        = table.AsEnumerable()
               .Where(row => string.Equals(row.Field<string>("Foo"), item.ToString(), StringComparison.InvariantCultureIgnoreCase))
               .CopyToDataTable();

もちろん、コードをさらに再設計したり、スレッド セーフのための独自の手段をキャッシュ戦略に追加したりすることもできますが、実際のテーブルがフィルター処理される方法を変更する実装には時間がかかります。

于 2013-04-04T03:17:23.017 に答える