WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

SqlCommand の Dispose は呼ぶべきか?

by WebSurfer 2013年4月23日 13:58

結論は呼ぶべきなのですが、そのあたりのことについて調べて新発見があったので、備忘録として書いておきます。

SqlConnection クラスの Close メソッド(Dispose と機能的に同じ)を明示的に呼び出して接続を閉じることは、リソースリーク防止のため重要であることはよく知られていると思います。

詳しくは、.NETの例外処理 Part.2 を見てください。特に、そのページに書かれている、"GC の仕組みで防がれるリークはメモリリークである、という点です。実は、アプリケーションにおけるリークには大別してメモリリークとリソースリークがあり、リソースリークは GC のみで防止することができません。" という点に注目です。

では、SqlCommand クラスや SqlDataReader クラスの場合はどうでしょうか? MSDN ライブラリのサンプルコードでも Dispose メソッドを呼び出してないケースが多々見られます。SqlConnection クラスと違ってリソースリークの問題はなさそうですが、メモリリーク防止のため Dispose メソッドを呼び出す必要はないのでしょうか?

MSDN ライブラリの Dispose メソッドの実装 を見ると、"マネージリソースのみを使用する型は、ガベージコレクターによって自動的にクリアされるため、このような型で Dispose メソッドを実装しても、パフォーマンス上の利点はありません。" とのことです。(.NET 4.6 / 4.5 の記事にはその説明はありませんが同じことかと思います)

ということは、アンマネージリソースを使用していない SqlCommand などでは、Dispose メソッドを呼び出してもパフォーマンス上の利点はないということになってしまいます。

しかし、実は、Dispose メソッドには、メモリ開放の機能以外に、GC.SuppressFinalize メソッド を実装することにより、冗長なファイナライザーの呼び出しを防ぐことができるという利点があるそうです。

そのあたりは、MSDN フォーラムの Should I call Dispose on a SQLCommand object? というページで、Microsoft (MSFT) の方が "To prevent a finalizer from running, most well written Dispose implementations call a special method called GC.SuppressFinalize, which indicates to the GC that its finalizer shouldn't be run when it falls out of scope (as the Dispose method did the clean up). The component class (which remember the SqlCommand indirectly inherits from), implements such a Dispose method. Therefore to prevent the finalizer from running (and even though it does nothing in the case of SqlCommand), you should always call Dispose." と述べてられている通りだと思います。

また、Dispose メソッドの実装 の説明にも、"Dispose メソッドは、破棄するオブジェクトの SuppressFinalize メソッドを呼び出す必要があります。 SuppressFinalize を呼び出すと、オブジェクトが終了キューに置かれている場合は、そのオブジェクトの Finalize メソッドの呼び出しは行われません。Finalize メソッドの実行は、パフォーマンスに影響を与えることを覚えておいてください。" と書かれています。(.NET 4.6 / 4.5 の記事にはその説明はありませんが同じことかと思います)

という訳で、結論は、IDisposable インターフェイス を継承して Dispose メソッドを実装しているクラスは、そのオブジェクトが使用されなくなった時点で Dispose メソッドを呼び出すべきということのようです。

そのために良く使われるのが、上に紹介した .NETの例外処理 Part.2 にある try/finally パターンや using ステートメントですね。

using ステートメントを使用すると、以下の例のようになると思います。

using(SqlConnection conn = new SqlConnection("接続文字列"))
{

  conn.Open();

  using(SqlCommand cmd = new SqlCommand("クエリ", conn))
  {
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
      if (reader != null)
      {
        while (reader.Read())
        {
          // 何らかの処置
        }
      }
    } // SqlDataReader の Dispose(注1)

  } // SqlCommand の Dispose

} // SqlConnection の Dispose(注2)

注1:Dispose メソッドは、SqlDataReader によって使用されているリソースを解放し、Close メソッドを呼び出します。

注2:SqlConnection クラスの Close メソッドと Dispose メソッドは、機能的に同じです。

------ 2014/9/25 追記 ------

クラスによってはコンストラクタに GC.SuppressFinalize メソッドが実装されており、冗長な Finalize メソッドの呼び出しを防ぐという意味では Dispose() メソッドを呼ぶ必要はないものもあります。

実は、SqlCommand クラスの場合も、現時点のソースコードを見る限り、コンストラクタに GC.SuppressFinalize メソッドが実装されています。

ただし、コンストラクタでの GC.SuppressFinalize の実装は MSDN ライブラリなどにはドキュメント化されてない(ソースコードを見ないと分からない)、ソースコードは変更される可能性がある、将来ネイティブリソースが含まれる可能性はゼロではない(ゼロに近いとは思いますが)・・・ということを考えるべきです。

なので、IDisposable を継承するクラスは Dispose() を呼ぶべきというのが基本ルールであると思っています。

------ 2015/9/11 追記 ------

リンク先の MSDN ライブラリの記事を .NET Framework 4 のものに固定しました。URL にバージョンを指定しないと .NET 4.5 / 4.6 の MSDN ライブラリにリンクされますが、その記述が .NET 4 のものかなり異なっていて、.NET 4 をベースに書いた上の記事とミスマッチが生じましたので。

Tags: ,

ADO.NET

接続文字列のエラーメッセージ

by WebSurfer 2012年7月24日 23:20

データベースに接続しようとして "初期化文字列の形式が使用に適合しません。index x で始まっています。"(実際には x には数字が入ります)というエラーメッセージが出ることがあります。

接続文字列のエラーメッセージ

「使用」って何?、「index」って何?・・・って感じで意味不明ですが、原文(英文)は次のようになっていて、これなら意味が分ります。

"Format of the initialization string does not conform to specification starting at index x"

つまり、接続文字列の x 文字目(0 から数えて)以降が、仕様(使用ではなくて)に適合しないということです。要するに接続文字列が間違っているということです。

例えば、接続文字列で Initial Catalog=Northwind の設定を間違えて以下のようにしたとします。

Data Source=.\SQLEXPRESS;Initial Catalog='Northwind ...

そうすると、上の画像のように "初期化文字列の形式が使用に適合しません。index 25 で始まっています。" というエラーがでます。'Northwind は間違いで、その ' は最初の文字 Data の D を 0 から数えて 25 番目です。

.NET 4 になって、「使用」の間違いぐらいは「仕様」に直したかと思って、調べてみましたが、間違ったままでした。

ただし、接続文字列の間違いが問題ではなくて、レジストリキーの修正が必要という話もありますので注意してください。

データベースの接続が出来ません

Tags:

ADO.NET

パラメータ化クエリ

by WebSurfer 2012年2月2日 22:47

パラメータ化クエリについて少々(かなり?)誤解してました。どのように誤解していたかは恥ずかしいので秘密です。(笑) 調べたことを備忘録として書いておきます。

パラメータ化クエリの説明図

パラメータ化の説明で、パラメータの入力はリテラルとして扱われるから SQL インジェクション攻撃を防ぐことができると言われています。

そこがキーポイントですが、そのリテラルというのが何だか分らないと話が始まりませんので、まずクエリの要素の名前の説明をします。以下の SELECT クエリを例に取ります。

SELECT id, Product FROM TableA WHERE Category='server'

クエリを構成する要素には、キーワード、演算子、識別子、リテラルなどがあり、上記の SELECT クエリではそれぞれ以下のようになります。

キーワード SELECT FROM WHERE
演算子 =
識別子 id Product Category TableA
リテラル 'server'

クエリをパラメータ化するというのはリテラルの部分をプレースホルダを使って記述することです。上記の SELECT クエリの例では以下のようになります。

SELECT id, Product FROM TableA WHERE Category=@Category

プレースホルダの構文はデータソースに依存します。SQL Server の場合は @parametername 形式の名前付きパラメータが使用されます。(頭に @ を付けるのは単なる命名規則です)

パラメータ名は必ずしも識別子と同じ名前にする必要はありませんが、同じにしておいた方が混乱がなさそうです。(例えば、識別子の名前が Category ならパラメータ名は @Category とする)

ADO.NET のコマンドオブジェクトは、パラメータを使用して SQL ステートメントまたはストアドプロシージャに値を渡すことを可能にしています。SQL Server の場合で、上記 SELECT クエリを例に取ると以下のようになります。

string connectionString = 
  WebConfigurationManager.
  ConnectionStrings["MyDB"].ConnectionString;

string query = 
  "SELECT id, Product FROM TableA WHERE Category=@Category";

using (SqlConnection connection =
           new SqlConnection(connectionString))
{
    SqlCommand command =
        new SqlCommand(query, connection);
    SqlParameter param = 
        new SqlParameter("@Category", SqlDbType.VarChar, 50);
    command.Parameters.Add(param);

    command.Parameters["@Category"].Value = TextBox1.Text;

    connection.Open();

    SqlDataReader reader = command.ExecuteReader();

    // 中略
}

SQL Server の場合、Parameters コレクションに追加したパラメータの名前は、クエリのパラメータ名前と一致している必要があります。(Access 等に使われる OleDb プロバイダの場合は、疑問符 (?) で指定される位置パラメータマーカーが使用されますがその話は割愛します。詳しくは、パラメータおよびパラメータのデータ型の構成 (ADO.NET) の「パラメーターのプレースホルダーの使用」セクションを参照してください)

プレースホルダの方式には静的と動的があって、SQL Server の場合は静的プレースホルダを使用します。静的/動的の違いの説明については安全なSQLの呼び出し方 - IPA 独立行政法人 情報処理推進機構を参照してください。

動作は次のとおりです(上の図も参照ください)。まず、プレースホルダのままのクエリをデータベースエンジン側にあらかじめ送信して、実行前にクエリのコンパイルなどの準備をしておきます。クエリの実行の段階で、Parameters コレクションに追加したパラメータの値をデータベースに送信し、データベースがバインド処理します。

ユーザー入力から直接クエリを組み立ててコマンドテキストとして渡すのとは異なり、パラメータの入力は実行可能なコードとしてではなく、リテラル値として扱われます。これにより、攻撃者がサーバーのセキュリティを侵害するコマンドを "注入" しても、注入した値はリテラルの外にはみ出すことはない(上の SELECT クエリの例で言うと、WHERE 句の条件が true または false になるだけ)ので、SQL インジェクション攻撃を防ぐことができます。

なお、パラメータ化するとエスケープ処理されるという話を時々聞きますが、少なくとも SQL Server の場合はそれは誤解です(エスケープ処置はされません。静的プレースホルダ方式なので処置する必要がありません)。

パラメータ化のもう一つのメリットにパフォーマンスの向上があります。それについては 第 4 回 アドホック クエリのパラメータ化 が参考になると思います。このページには、SqlParameter クラスを利用した場合、SQL Server の内部的にどのようにパラメータ化クエリが実行されるかも書いてあって(sp_executesql に変換されて実行される)、興味深いと思いました。

-------- 2016/6/5 追記 --------

上に述べたように、SQL インジェクションの防止とパフォーマンスの向上がパラメータ化クエリを使う主な目的ですが、それ以外にも照合順序の違いによって文字化けに悩むことがなくなるという副次的な効用もあります。

詳しくは別の記事「パラメータ化の副次的な効用」に書きましたので、興味がありましたら見てください。

Tags:

ADO.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar