当尝试通过ASP.NET在线连接到MSSQL数据库时,当两个或更多人同时连接时,我将得到以下信息:
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年前)。通常,他们决定使用静态/共享对象,因为它似乎比为任何操作创建新对象都快。
从性能或故障安全角度来讲,这都不是一个好主意。
ADO.NET内部管理ADO-NET Connection-Pool中与DBMS的基础连接是有充分的理由的:
实际上,大多数应用程序仅使用一种或几种不同的配置进行连接。这意味着在应用程序执行期间,许多相同的连接将被反复打开和关闭。为了最大程度地减少打开连接的成本,ADO.NET使用了一种称为连接池的优化技术。 连接池减少了必须打开新连接的次数。池管理者维护物理连接的所有权。它通过为每个给定的连接配置保留一组活动的连接来管理连接。每当用户在连接上调用“打开”时,池管理器就会在池中寻找可用的连接。如果池化连接可用,它将把它返回给调用者,而不是打开一个新连接。当应用程序在连接上调用“关闭”时,池化程序将其返回到活动连接的池化集中,而不是将其关闭。一旦将连接返回到池,就可以在下一个Open调用中重用该连接。
实际上,大多数应用程序仅使用一种或几种不同的配置进行连接。这意味着在应用程序执行期间,许多相同的连接将被反复打开和关闭。为了最大程度地减少打开连接的成本,ADO.NET使用了一种称为连接池的优化技术。
连接池减少了必须打开新连接的次数。池管理者维护物理连接的所有权。它通过为每个给定的连接配置保留一组活动的连接来管理连接。每当用户在连接上调用“打开”时,池管理器就会在池中寻找可用的连接。如果池化连接可用,它将把它返回给调用者,而不是打开一个新连接。当应用程序在连接上调用“关闭”时,池化程序将其返回到活动连接的池化集中,而不是将其关闭。一旦将连接返回到池,就可以在下一个Open调用中重用该连接。
因此,显然没有理由避免创建,打开或关闭连接,因为实际上根本没有创建,打开和关闭连接。这是“仅”标志,供连接池知道何时可以重新使用连接。但这是一个非常重要的标志,因为如果“正在使用”连接(连接池假定),则必须向DBMS开放新的物理连接,这是非常昂贵的。
因此,您没有获得任何性能改进,反而相反。如果达到指定的最大池大小(默认为100),您甚至会收到异常(打开的连接过多…)。因此,这不仅会极大地影响性能,而且还会成为令人讨厌的错误和(不使用事务)数据转储区域的来源。
如果您甚至在使用静态连接,您都将为尝试访问该对象的每个线程创建一个锁。ASP.NET本质上是一个多线程环境。因此,这些锁极有可能导致性能问题。实际上,迟早您会收到许多不同的异常(例如 ExecuteReader需要一个开放且可用的Connection )。
结论 :
using-statement
这不仅适用于Connections(尽管最值得注意)。每个实现的对象IDisposable都应using- statement在System.Data.SqlClient命名空间中处理(由简化)。
IDisposable
using- statement
System.Data.SqlClient
上面所有这些都针对定制DB-Class,该DB-Class封装和重用所有对象。这就是为什么我评论要丢弃它的原因。那只是一个问题源。
编辑 :这是您的retrievePromotion-方法的可能实现:
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; }