当尝试通过ASP.NET在线连接到MSSQL数据库时,当两个或多个人同时连接时,我将得到以下信息:


ExecuteReader需要一个开放且可用的Connection。该连接的当前状态为“正在连接”。


该站点在我的本地主机服务器上正常运行。

这是粗糙的代码。

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;


评论

不要在像ASP.NET这样的多线程环境中使用共享/静态连接,因为这会生成锁或异常(打开的连接过多等)。将您的DB类扔到垃圾桶中,并在需要它们的地方创建,打开,使用,关闭,放置ado.net对象。还要查看使用声明。

您能给我有关SqlOpenConnection();和sql.ExecuteReader();的详细信息吗?功能?..

私有无效SqlOpenConnection(){试试{conn = new SqlConnection(); conn.ConnectionString = conString; conn.Open(); } catch(SqlException ex){throw ex; }}

@GuoHongLim:我忘了提一句,即使是静态conString也不会对性能产生任何影响,因为它还是默认情况下被缓存的(作为当前应用程序的每个配置值)。

...只是为了使它成为众所周知的未知数:确保您还获得数据库事务处理/正确的工作单元,这是读者的一项练习。

#1 楼

抱歉,我只发表评论,但我几乎每天都发表类似的评论,因为许多人认为将ADO.NET功能封装到DB-Class中是很明智的(我也是10年前)。大多数情况下,他们决定使用静态/共享对象,因为它似乎比为任何操作创建新对象要快。

就性能和故障安全性而言,这都不是一个好主意。 。

不要在Connection-Pool的领土上偷猎

有充分的理由为什么ADO.NET在ADO-NET Connection-中内部管理与DBMS的基础连接。池:


实际上,大多数应用程序仅使用一种或几种不同的
配置进行连接。这意味着在应用程序执行期间,许多相同的连接将被重复打开和关闭。为了最大程度地减少打开连接的成本,ADO.NET使用称为连接池的优化技术。连接池减少了必须打开新连接的次数。池管理者维护物理连接的所有权。它通过为每个给定的连接配置保留一组活动的
连接来管理连接。每当用户
在连接上调用“打开”时,池化程序就会在池中寻找可用的
连接。如果有池连接,则它
将其返回给调用方,而不是打开新连接。当
应用程序在连接上调用“关闭”时,池化程序将其返回到活动连接的池化集中,而不是将其关闭。一旦将
连接返回到池中,就可以在下一个Open调用中重新使用它了。


因此,显然没有理由避免创建,打开或关闭连接,因为实际上根本没有创建,打开和关闭连接。这是“仅”一个标志,供连接池知道何时可以重新使用连接。但这是一个非常重要的标志,因为如果“正在使用”连接(连接池假定),则必须向DBMS开放新的物理连接,这是非常昂贵的。

因此,您并没有获得任何性能上的改善,反之亦然。如果达到指定的最大池大小(默认为100),您甚至会收到异常(打开的连接过多...)。因此,这不仅会极大地影响性能,而且还会成为令人讨厌的错误的源头,并且(不使用事务处理)将成为数据转储区域。

如果您甚至在使用静态连接,就在创建每个尝试访问此对象的线程的锁。 ASP.NET本质上是一个多线程环境。因此,这些锁极有可能导致性能问题。实际上,迟早会有很多不同的异常(例如ExecuteReader需要一个开放且可用的Connection)。

结论:


不要重用连接或任何ADO.NET对象。
不要将它们设置为静态/共享(在VB.NET中)
始终创建,打开(在使用Connections的情况下),使用,关闭并将它们放置在需要的位置(方法中的fe)
使用using-statement隐式处置和关闭(在Connections的情况下)

这不仅对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 implicitely in an using-statement
                throw;
            }
        }
    }
    return promo;
}


评论


这对于提供连接工作范式非常有用。感谢您的解释。

–amin vincent
18年7月31日,3:22

写得很好,是许多人偶然发现的解释,我希望更多的人知道这一点。 (+1)

–安德鲁·希尔(Andrew Hill)
19年1月10日23:53

谢谢您,先生,我认为这是我读过的关于该主题的最好的解释,这是非常重要的主题,很多新手都会犯错。我必须赞扬你出色的写作能力。

– Sasinosoft
1月22日20:51

@Tim Schmelter如何使用您建议的方法使在不同线程上运行的查询利用单个事务进行提交/回退?

–geeko
3月12日14:02

#2 楼

我几天前发现此错误。

我的情况是因为我在Singleton上使用了事务。

.Net不能与Singleton一起很好地运行

我的解决方案是这样的:

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是我自己的类