web-dev-qa-db-ja.com

ExecuteReaderには、オープンで使用可能な接続が必要です。接続の現在の状態は接続中です

ASP.NETオンライン経由でMSSQLデータベースに接続しようとすると、2人以上が同時に接続したときに次のメッセージが表示されます。

ExecuteReaderには、オープンで使用可能な接続が必要です。接続の現在の状態は接続中です。

このサイトは、私のローカルホストサーバーで正常に動作します。

これは大まかなコードです。

public Promotion retrievePromotion()
{
    int promotionID = 0;
    string promotionTitle = "";
    string promotionUrl = "";
    Promotion promotion = null;
    SqlOpenConnection();
    SqlCommand sql = SqlCommandConnection();

    sql.CommandText = "SELECT TOP 1 PromotionID, PromotionTitle, PromotionURL FROM Promotion";

    SqlDataReader dr = sql.ExecuteReader();
    while (dr.Read())
    {
        promotionID = DB2int(dr["PromotionID"]);
        promotionTitle = DB2string(dr["PromotionTitle"]);
        promotionUrl = DB2string(dr["PromotionURL"]);
        promotion = new Promotion(promotionID, promotionTitle, promotionUrl);
    }
    dr.Dispose();
    sql.Dispose();
    CloseConnection();
    return promotion;
}

何が間違っていたのか、どうすれば修正できるのかを知っていますか?

編集:忘れないでください、私の接続文字列と接続は両方とも静的です。これが理由だと思います。お知らせ下さい。

public static string conString = ConfigurationManager.ConnectionStrings["dbConnection"].ConnectionString;
public static SqlConnection conn = null;
105
Guo Hong Lim

そもそもコメントするだけで申し訳ありませんが、多くの人がADO.NET機能をDBクラスにカプセル化するのが賢明だと思うので、ほぼ毎日同じようなコメントを投稿しています(私も10年前)。ほとんどの場合、静的/共有オブジェクトを使用することを決定します。これは、アクション用に新しいオブジェクトを作成するよりも高速だと思われるためです。

これは、パフォーマンスの観点からもフェイルセーフの観点からも良い考えではありません。

Connection-Poolの領域で密猟しないでください

ADO.NETが ADO-NET Connection-Pool でDBMSへの基礎となる接続を内部的に管理するのには十分な理由があります。

実際には、ほとんどのアプリケーションは、接続に1つまたはいくつかの異なる構成のみを使用します。これは、アプリケーションの実行中に、多くの同一の接続が繰り返し開かれ、閉じられることを意味します。接続を開くコストを最小限に抑えるために、ADO.NETは接続プーリングと呼ばれる最適化手法を使用します。

接続プーリングは、新しい接続を開く必要がある回数を減らします。プーラーは、物理接続の所有権を維持します。特定の接続構成ごとにアクティブな接続のセットを維持することにより、接続を管理します。ユーザーが接続でOpenを呼び出すたびに、プーラーはプールで使用可能な接続を探します。プールされた接続が使用可能な場合、新しい接続を開くのではなく、呼び出し元に返します。アプリケーションが接続でCloseを呼び出すと、プーラーはそれを閉じるのではなく、アクティブな接続のプールされたセットに返します。接続がプールに返されると、次のOpen呼び出しで再利用する準備が整います。

したがって、実際には接続が作成、オープン、クローズされないため、接続の作成、オープン、クローズを回避する理由はありません。これは、接続が再利用可能かどうかを知るための接続プールの「唯一」のフラグです。ただし、接続が「使用中」(接続プールが想定)の場合、非常に高価なDBMSへの新しい物理接続をオープンエンドにする必要があるため、これは非常に重要なフラグです。

そのため、パフォーマンスの改善は得られませんが、逆は得られます。指定された最大プールサイズ(100がデフォルト)に達すると、例外が発生します(開いている接続が多すぎます...)。そのため、これはパフォーマンスに多大な影響を与えるだけでなく、厄介なエラーの原因にもなり(トランザクションを使用しない場合)、データダンプ領域にもなります。

静的接続を使用している場合でも、このオブジェクトにアクセスしようとするすべてのスレッドに対してロックを作成しています。 ASP.NETは本質的にマルチスレッド環境です。そのため、これらのロックがパフォーマンスの問題を引き起こす可能性が高くなります。実際には、遅かれ早かれ多くの異なる例外が発生します(ExecuteReaderには、オープンで使用可能なConnectionが必要です)。

結論

  • 接続やADO.NETオブジェクトを再利用しないでください。
  • それらを静的/共有にしないでください(VB.NETで)
  • 常に作成、オープン(接続の場合)、使用、クローズ、必要な場所への配置(メソッド内など)
  • using-statement を使用して、暗黙的に破棄して接続します(接続の場合)。

これは、Connectionsだけでなく(もっとも注目に値します)。 IDisposable を実装するすべてのオブジェクトは、using-statement名前空間でさらに破棄する必要があります( System.Data.SqlClient で最も簡単です)。

上記はすべて、すべてのオブジェクトをカプセル化して再利用するカスタムDBクラスに反しています。それが私がコメントしてゴミ箱にした理由です。それが唯一の問題の原因です。


Edit:以下はretrievePromotion- methodの可能な実装です:

public Promotion retrievePromotion(int promotionID)
{
    Promotion promo = null;
    var connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["MainConnStr"].ConnectionString;
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        var queryString = "SELECT PromotionID, PromotionTitle, PromotionURL FROM Promotion WHERE PromotionID=@PromotionID";
        using (var da = new SqlDataAdapter(queryString, connection))
        {
            // you could also use a SqlDataReader instead
            // note that a DataTable does not need to be disposed since it does not implement IDisposable
            var tblPromotion = new DataTable();
            // avoid SQL-Injection
            da.SelectCommand.Parameters.Add("@PromotionID", SqlDbType.Int);
            da.SelectCommand.Parameters["@PromotionID"].Value = promotionID;
            try
            {
                connection.Open(); // not necessarily needed in this case because DataAdapter.Fill does it otherwise 
                da.Fill(tblPromotion);
                if (tblPromotion.Rows.Count != 0)
                {
                    var promoRow = tblPromotion.Rows[0];
                    promo = new Promotion()
                    {
                        promotionID    = promotionID,
                        promotionTitle = promoRow.Field<String>("PromotionTitle"),
                        promotionUrl   = promoRow.Field<String>("PromotionURL")
                    };
                }
            }
            catch (Exception ex)
            {
                // log this exception or throw it up the StackTrace
                // we do not need a finally-block to close the connection since it will be closed implicitely in an using-statement
                throw;
            }
        }
    }
    return promo;
}
215
Tim Schmelter