5

以下のような仕組みが必要な場合はありますか?

using (Something something = new Something())
{
    try
    {
    }
    finally
    {
        something.SomeCleanup();
    }
}

または、すべてのクリーンアップタスクを暗黙のsomething.Dispose()呼び出しで実行する必要がありますか?


問題のあるコードは次のとおりです。

public static DataTable GetDataTable(string cmdText, IEnumerable<Parameter> parameters)
{
    // Create an empty memory table.
    DataTable dataTable = new DataTable();

    // Open a connection to the database.
    using (SqlConnection connection = new SqlConnection(ConfigurationTool.ConnectionString))
    {
        connection.Open();

        // Specify the stored procedure call and its parameters.
        using (SqlCommand command = new SqlCommand(cmdText, connection))
        {
            command.CommandType = CommandType.StoredProcedure;

            SqlParameterCollection parameterCollection = command.Parameters;
            foreach (Parameter parameter in parameters)
                parameterCollection.Add(parameter.SqlParameter);

            try
            {
                // Execute the stored procedure and retrieve the results in the table.
                using (SqlDataAdapter dataAdapter = new SqlDataAdapter(command))
                    try
                    {
                        dataAdapter.Fill(dataTable);
                    }
                    catch
                    {
                        dataTable.Dispose();
                        dataTable = null;
                    }
            }
            finally
            {
                //parameterCollection.Clear();
            }
        }
    }

    return dataTable;
}

注:この関数のユーザーがsの作成を直接Parameter処理する必要がないように、クラスを定義しました。SqlParameterクラスのSqlParameterプロパティをParameter使用して、を取得できますSqlParameter

ある時点で、私のプログラムは次のことを行います(多くのクラスが含まれるため、コードを投稿できません。基本的に、多くのオブジェクトを作成するミニフレームワークがあります)。

  1. の配列を作成しますParameter
  2. GetDataTable('sp_one', parameters)
  3. GetDataTable('sp_two', parameters)
4

3 に答える 3

5

usingキーワードはメソッドを呼び出すだけです。IDisposable オブジェクトの dispose メソッドの外部でクリーンアップが必要.Dispose()な場合は、独自の finally ブロックでそれを行う必要があります。これにより、次の 2 つのポイントが生じます。

  1. この時点で、using ブロックをスキップして、finally ブロック内で Dispose() を呼び出すこともできます。using個人的にはまだブロックでいいと思います。IDisposable インスタンス用に常に1 つ用意するのは良い習慣です。
  2. 上記の条件を満たしている場合は、クラスを再設計して IDisposable パターンを利用する必要があることをお勧めします。

投稿したコードに基づいて、問題はパラメーターがまだどこかにルートされていることです (おそらくそれらを再利用していますか?)。パラメータはまだルート化されているため、収集できません。これらには、関連付けられているコマンドへの参照も含まれているため、SqlCommand オブジェクトもすぐには収集できません。これは、まだルート化されているためです。

重要な点は、.Net フレームワークが管理されていないリソース用にDispose () パターンを予約していることです。SqlParameters と SqlParameterCollection はマネージ型であるため、収集されるまで処理されません。収集は、破棄とは完全に別個に行われます。SqlCommand が最終的に収集されると、その SqlParameter コレクションも処理されます。収集、廃棄、およびそれらの目的を混同しないでください。

やりたいことは、既存のパラメーターをコレクションに追加するのではなく、追加するときに各パラメーターのコピーを作成することです。

public static DataTable GetDataTable(string cmdText, IEnumerable<Parameter> parameters)
{
    // Create an empty memory table.
    DataTable dataTable = new DataTable();

    // Prepare a connection to the database and command to execute.
    using (SqlConnection connection = new SqlConnection(ConfigurationTool.ConnectionString))
    using (SqlCommand command = new SqlCommand(cmdText, connection))
    {
        command.CommandType = CommandType.StoredProcedure;

        SqlParameterCollection parameterCollection = command.Parameters;
        foreach (Parameter parameter in parameters)
            parameterCollection.Add(CloneParameter(parameter.SqlParameter));

        // Execute the stored procedure and retrieve the results in the table.
        using (SqlDataAdapter dataAdapter = new SqlDataAdapter(command))
        {
             dataAdapter.Fill(dataTable);
        }
    }

    return dataTable;
}

ここで注意すべき点:すべての try ブロックを取り除くことができました。それらのいずれも必要ありませんでした。また、 SqlDataAdapter.Fill() メソッドが接続を開いたり閉じたりするので、その部分は必要ありません。

それでは、その CloneParameter() 関数について話しましょう。パラメータを再利用しようとするというコードの目的に反しているように感じます。ここでパラメータを再利用するのは悪い考えだと約束します。パフォーマンスの低下は、特にストアドプロシージャの実行と比較して、存在しないという点で無視できます。私が CloneParameter() の実装をあなたに任せた理由は 2 つあります。1 つはそれが些細なことであり、もう 1 つは、既に通常のデータ アクセス パターンから外れていることです。パラメーターを追加するために通常行うことは、列挙可能なパラメーターではなく、Action<SqlParameterCollection> デリゲートを受け入れることです。関数は次のように宣言されます。

public IEnumerable<IDataRecord>GetData(string cmdText, Action<SqlParameterCollection> addParameters)

次のように呼び出されます。

foreach(var record in GetData("myprocedurename", p => 
  {
      p.Add( /*new parameter here*/ );
      p.Add( /*new parameter here*/ );
    //...
  })
 .Select( /*Returning a IEnumerable rather than a datatable allows me to use it with linq to objects.*/
          /* For example, you could use this spot to convert from DataRecords returned by ADO.Net to business objects */ 
        ))
{
   // use the results here...
}

2 つのテーブルを続けて入力しているので、DataReader/IEnumerable アプローチに対してそれを正当化する可能性のあるクライアント側で作業を行う必要があるようですが、ほとんどの場合、コードに基づいているため、これについて言及したいと思います。 DataReader の方が適しています。

あなたの場合、既存のアクションデリゲートベースのパターンを使用して、パラメーターの重複セットを可能な限り再利用したい場合、パラメーターを追加する方法を知っていて、アクションデリゲートと一致する実際の名前付きメソッドを用意することです. 次に、そのメソッドを渡すだけで、必要なパラメーターを再利用できます。

于 2011-03-31T14:32:09.503 に答える
4

興味深い質問です。

Somethingそれはすべてあなたのクラスに依存します。設計が不十分で、多段階のクリーンアップが必要な場合、その特異性をクライアントに強制します。

クラスをそのように設計するべきではありません。中間クリーンアップを行う必要がある場合は、それらを独自のクラスにカプセル化し、次のようなコードを使用します。

using (Something something = new Something()) {
  // ...
  using (SomethingElse somethingElse = something.GiveMeSomethingElse()) {
  }
  // ...
} 

アップデート:

あなたの例では、次のようになります。

using (SqlConnection connection = new SqlConnection(connectionString)) {
  connection.Open();

  using (SqlCommand command = new SqlCommand("select * from MyTable where id = @id", connection)) {

    // to "reuse" the parameters collection population, just extract this to a separate method      
    command.Parameters.Add(new SqlParameter("id", id));

    // ... execute the command

  }

}

更新 2:

これを行うだけです:

GetDataTable('sp_one', CreateParameters());
GetDataTable('sp_two', CreateParameters());
于 2011-03-31T14:33:59.303 に答える
1

Dispose は、すべての管理されていないリソースをクリーンアップする必要があります。たとえば、いくつかの機能アクションまたはデータベースアクションを実行するために、別のクリーンアップ方法を用意することは完全に可能です。

于 2011-03-31T14:32:20.443 に答える