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;
ベストアンサー1
最初にコメントしただけで申し訳ありませんが、多くの人が ADO.NET 機能を DB クラスにカプセル化するのが賢明だと考えているため、私はほぼ毎日同様のコメントを投稿しています (10 年前は私もそうでした)。ほとんどの場合、アクションごとに新しいオブジェクトを作成するよりも速いと思われるため、静的/共有オブジェクトを使用することに決めています。
それは、パフォーマンスの観点からも、フェイルセーフの観点からも、良い考えではありません。
接続プールの領域を侵害しないでください
ADO.NETがDBMSへの基礎となる接続を内部的に管理するのには十分な理由があります。ADO-NET 接続プール:
実際には、ほとんどのアプリケーションは、接続に 1 つまたはいくつかの異なる構成のみを使用します。つまり、アプリケーションの実行中、多数の同一の接続が繰り返し開かれ、閉じられることになります。接続を開くコストを最小限に抑えるために、ADO.NET は接続プールと呼ばれる最適化手法を使用します。
接続プールにより、新しい接続を開く回数が減ります。 プーラーは物理接続の所有権を保持します。 プーラーは、指定された接続構成ごとにアクティブな接続のセットを維持することで接続を管理します。 ユーザーが接続に対して Open を呼び出すたびに、プーラーはプール内で使用可能な接続を検索します。 プールされた接続が使用可能な場合は、新しい接続を開く代わりに、呼び出し元に返します。 アプリケーションが接続に対して Close を呼び出すと、プーラーは接続を閉じる代わりに、プールされたアクティブな接続のセットに返します。 接続がプールに戻されると、次の Open 呼び出しで再利用できるようになります。
したがって、実際には接続はまったく作成、オープン、クローズされないので、接続の作成、オープン、クローズを避ける理由がないことは明らかです。これは、接続が再利用できるかどうかを知るための接続プールの「単なる」フラグです。ただし、これは非常に重要なフラグです。接続が「使用中」の場合 (接続プールが想定)、DBMS への新しい物理接続をオープンする必要があり、非常にコストがかかるためです。
つまり、パフォーマンスは向上せず、むしろその逆になります。指定された最大プール サイズ (デフォルトは 100) に達すると、例外 (開いている接続が多すぎるなど) も発生します。したがって、これはパフォーマンスに多大な影響を与えるだけでなく、厄介なエラーの原因となり、(トランザクションを使用しない場合) データ ダンプ領域にもなります。
静的接続を使用している場合でも、このオブジェクトにアクセスしようとするすべてのスレッドに対してロックを作成することになります。ASP.NETは本質的にマルチスレッド環境です。そのため、これらのロックが発生する可能性が高く、パフォーマンスの問題が発生します。実際には、遅かれ早かれ、さまざまな例外が発生します(ExecuteReader にはオープンで利用可能な接続が必要です)。
結論:
- 接続や ADO.NET オブジェクトを再利用しないでください。
- 静的/共有にしないでください(VB.NET)
- 常に、必要な場所(メソッド内など)で作成、オープン(接続の場合)、使用、クローズ、破棄します。
- 使用
using-statement
暗黙的に破棄して閉じる(接続の場合)
これはConnectionsに限ったことではありません(もっとも顕著ですが)。IDisposable
処分すべきである(最も簡単な方法はusing-statement
)、名前空間内でさらに多く発生しますSystem.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 implicitly in an using-statement
throw;
}
}
}
return promo;
}