1. ホーム
  2. c#

[解決済み] ExecuteReader は、開いていて利用可能な Connection を必要とします。コネクションの現在の状態は、「接続中」です。

2022-02-10 17:53:11

質問

ASP.NET onlineで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;

解決方法は?

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

それは、パフォーマンスの面でも、フェイルセーフの面でも、良いアイデアではありません。

Connection-Poolの領域を密猟しない

ADO.NETがDBMSへの接続を内部で管理するのには、それなりの理由があります。 ADO-NETコネクションプール :

実際には、ほとんどのアプリケーションは、1つまたはいくつかの異なるものを使用するだけです。 接続のためのコンフィギュレーション。つまり、アプリケーションの を実行すると、多くの同一の接続が繰り返し開かれることになります。 閉じた。ADO.NETでは、コネクションを開くコストを最小化するために コネクションプーリングと呼ばれる最適化技術。

接続プーリングは、新しい接続の回数を減らすことができます。 を開かなければならない。プーラーは物理的な所有権を維持します。 コネクションを使用します。接続を管理するために、アクティブな接続のセットを維持します。 接続の構成に応じた ユーザーが プーラーは、接続のオープンを呼び出すと、利用可能な接続を探します。 のコネクションをプール内に保持する。プールされた接続が利用可能な場合、プールされた接続が利用可能な場合 は、新しい接続を開く代わりに、その接続を呼び出し元に返します。呼び出した側が アプリケーションは、接続のクローズを呼び出すと、プーラーはその接続を を閉じるのではなく、プールされた有効な接続の集合を閉じる。一度 接続がプールに戻されると、その接続を再利用する準備が整います。 次の Open 呼び出し

つまり、コネクションの作成、オープン、クローズを避ける理由は何もないのです。これは、コネクションプールがコネクションを再利用できるかどうかを知るためのフラグに過ぎません。しかし、これは非常に重要なフラグです。なぜなら、もし接続が使用中であれば(接続プールはそう仮定します)、新しい物理接続をDBMSにオープンしなければならず、これは非常にコストがかかるからです。

つまり、パフォーマンスが向上するどころか、その逆になってしまうわけです。もし、指定された最大プールサイズ(デフォルトは100)に達すると、例外(開いている接続が多すぎる...)が発生することさえあります。つまり、これはパフォーマンスに多大な影響を与えるだけでなく、厄介なエラーの原因となり、(トランザクションを使用しない場合)データダンプエリアとなります。

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

結論 :

  • コネクションやADO.NETオブジェクトを一切再利用しないこと。
  • 静的/共有にしない(VB.NETの場合)
  • 作成、オープン(コネクションの場合)、使用、クローズ、ディスポは必ず必要な場所(メソッド内など)で行ってください。
  • を使用します。 using-statement 暗黙のうちに廃棄・閉鎖すること。

これは、Connectionsに限ったことではありません(もっとも顕著ですが)。を実装しているすべてのオブジェクトは IDisposable を廃棄する必要があります(最もシンプルな方法は using-statement ) の中で、さらに System.Data.SqlClient という名前空間があります。

以上のことから、すべてのオブジェクトをカプセル化して再利用するカスタムDB-Classに反対していることがわかります。それが、私がゴミ箱に入れるようにコメントした理由です。あくまで問題発生源です。


編集 : 以下は、あなたの retrievePromotion -メソッドです。

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;
}