8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C#でSqlConnectionの後片付けをする時のusingの使い分け

Posted at

背景

保守してたら、try-finallyでDBのコネクションの接続をクローズして管理しているコードを発見しました。
ぱっと見usingを使った方が良さげに思え書き換えたけど、例外が発生することに…。

※. usingはJavaでいうtry-with-resourcesという認識

結論

  • コネクションの再利用する前提なのにusingで破棄していたことが原因
  • コネクションの再利用しないならusing、再利用するならtry-finallyするなり自分で管理しよう!

調査

テストコードで検証してみる

try-finallyとusingのそれぞれを使用した場合のサンプルコードで実験してみました。
長いので詳細はこちらをご参照ください。

public class SampleDao
{
    // 省略

    /// <summary>
    /// try-finallyでCloseする
    /// </summary>
    public void execute()
    {
        SqlCommand command = new SqlCommand();
        command.CommandText = "SELECT GETDATE()";
        command.Connection = this.Connection;

        try { 
            command.Connection.Open();
            command.ExecuteReader();
        } finally { 
            command.Connection.Close();
        }
    }

    /// <summary>
    /// usingでCloseする
    /// </summary>
    public void executeWithUsing()
    {
        SqlCommand command = new SqlCommand();
        command.CommandText = "SELECT GETDATE()";
        command.Connection = this.Connection;

        command.Connection.Open();

        using (command.Connection)
        { 
            command.ExecuteReader();
        }
    }
    // 省略
}

上記のサンプルを用いて以下のようなユニットテストで検証してみます。

[TestClass]
public class UnitTest1
{
    // 省略

    [TestMethod]
    public void 複数利用で再利用する場合()
    {
        SampleDao dao1 = new SampleDao();

        // 初回
        try
        {
            dao1.execute();
        }
        catch (Exception e)
        {
            Assert.Fail();
        }
        Assert.AreEqual(ConnectionState.Closed, dao1.Connection.State);
        Assert.AreNotEqual("", dao1.Connection.ConnectionString);

        // 2回目
        try
        {
            dao1.execute();
        }
        catch (Exception e)
        {
            Assert.Fail();
        }
        Assert.AreEqual(ConnectionState.Closed, dao1.Connection.State);
        Assert.AreNotEqual("", dao1.Connection.ConnectionString);
    }

    // 省略

    [TestMethod]
    public void using_複数利用で再利用する場合()
    {
        SampleDao dao1 = new SampleDao();

        // 初回
        try
        {
            dao1.executeWithUsing();
        }
        catch (Exception e)
        {
            Assert.Fail();
        }
        Assert.AreEqual(ConnectionState.Closed, dao1.Connection.State);
        Assert.AreEqual("", dao1.Connection.ConnectionString);

        // 2回目
        try
        {
            dao1.executeWithUsing();
            Assert.Fail();
        }
        catch (Exception e)
        {
            Assert.AreEqual("", dao1.Connection.ConnectionString);
        }
    }
}

結果は以下のようになり、usingを使用したケースではコネクションの再利用はできないことになりました。

usingを使用しない

初回 2回目
単一利用で再利用しない場合 正常終了 N/A
複数利用で再利用しない場合 正常終了 正常終了
複数利用で再利用する場合 正常終了 正常終了

usingを使用

初回 2回目
単一利用で再利用しない場合 正常終了 N/A
複数利用で再利用しない場合 正常終了 正常終了
複数利用で再利用する場合 正常終了 InvalidOperationException

そもそもusingってどういうもの?

Microsoftのusing ステートメント (C# リファレンス)を引用すると以下のように説明されていました。

IDisposable オブジェクトの正しい使用を保証する簡易構文を提供します。 C# 8.0 以降は、using ステートメントによって IAsyncDisposable オブジェクトが適切に使用されるようになります。

上記の参考サイトから詳細として紹介されているステートメント > using ステートメントでは

ステートメントは、 using 1 つまたは複数のリソースを取得し、ステートメントを実行してから、リソースを破棄します。

と説明されていました。

私の言葉でまとめると、リソースは必ずDisposeメソッドによって破棄されるよ、ということが肝のようです。

※. ただし、MicrosoftSqlConnection.Close メソッド > 注釈では以下のように説明されておりちょっと紛らわしい

Close と Dispose は機能的に同等です

今回のケースにおけるusingの使用有無での違い

こんな感じになります。

usingを使用しない

  1. コネクションをオープン
  2. SQL実行
  3. コネクションをクローズ
  4. コネクションは破棄されていないので 1. に戻る

usingを使用

  1. コネクションをオープン
  2. SQL実行
  3. コネクションをクローズ
  4. Disposeでコネクションが破棄
  5. コネクションは破棄されているので 1. に戻ろうとすると例外が発生

まとめ

今回は書き換えたusingによってコネクションが破棄されたことが、既存のコードとの違いであり、例外発生の原因でした。
そのため、実装によっては必ずしもusingがベストではない場合もあることがわかりました。
便利ですが仕様を理解しつつ注意して使っていきたいです!

参考

8
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?