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


114

オンラインで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;

24
ASP.NETのようなマルチスレッド環境では、ロックまたは例外(開いている接続が多すぎるなど)を生成するため、共有/静的接続を使用しないでください。DBクラスをガベージキャンに投入し、必要な場所でado.netオブジェクトを作成、開く、使用、閉じる、破棄します。usingステートメントもご覧ください。
Tim Schmelter 2012年

2
SqlOpenConnection();とsql.ExecuteReader();について詳しく教えてください。functions?..
ankit rajput

private void SqlOpenConnection(){try {conn = new SqlConnection(); conn.ConnectionString = conString; conn.Open(); } catch(SqlException ex){throw ex; }}
Guo Hong Lim

@GuoHongLim:静的であっても、(現在のアプリケーションのすべての構成値として)デフォルトでキャッシュされるconStringため、パフォーマンスの面では何も追加されないことを忘れていました。
Tim Schmelter

...そしてそれを既知/不明にするために:データベースのトランザクション処理/作業単位が正しくなるようにすることは、読者の課題として残されています。
mwardm 2016年

回答:


226

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

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

接続プールの領域を密猟しないでください

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

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

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

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

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

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

結論

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

これは、Connectionsだけではありません(もっとも注目に値します)。実装IDisposableするすべてのオブジェクトを破棄する必要があります(最も単純な方法using-statementSystem.Data.SqlClient

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


編集:-メソッドの可能な実装は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;
}

これは、接続作業のパラダイムを与えるのに非常に役立ちます。この説明をありがとう。
aminvincent 2018

よく書かれていて、多くの人が誤って発見したものの説明です。もっと多くの人にこれを知ってもらいたいです。(+1)
Andrew Hill

1
ありがとうございます、これは私が今まで読んだこのテーマについての最高の説明だと思います。非常に重要なテーマであり、多くの初心者が間違いを犯しています。あなたの優れたライティング能力を称賛しなければなりません。
Sasinosoft

@Tim Schmelterさまざまなスレッドで実行されているクエリで、提案されたアプローチを使用してコミット/ロールバックするために単一のトランザクションを利用するにはどうすればよいですか?
geeko

1

数日前にこのエラーを見つけました。

私の場合は、シングルトンでトランザクションを使用していたためです。

上記のように、.Netはシングルトンではうまく機能しません。

私の解決策はこれでした:

public class DbHelper : DbHelperCore
{
    public DbHelper()
    {
        Connection = null;
        Transaction = null;
    }

    public static DbHelper instance
    {
        get
        {
            if (HttpContext.Current is null)
                return new DbHelper();
            else if (HttpContext.Current.Items["dbh"] == null)
                HttpContext.Current.Items["dbh"] = new DbHelper();

            return (DbHelper)HttpContext.Current.Items["dbh"];
        }
    }

    public override void BeginTransaction()
    {
        Connection = new SqlConnection(Entity.Connection.getCon);
        if (Connection.State == System.Data.ConnectionState.Closed)
            Connection.Open();
        Transaction = Connection.BeginTransaction();
    }
}

私のインスタンスにはHttpContext.Current.Itemsを使用しました。このクラスDbHelperとDbHelperCoreは私自身のクラスです

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.