ExecuteReader yêu cầu một Kết nối mở và có sẵn. Trạng thái hiện tại của kết nối là Đang kết nối


114

Khi cố gắng kết nối với cơ sở dữ liệu MSSQL qua ASP.NET trực tuyến, tôi sẽ nhận được những điều sau đây khi hai hoặc nhiều người kết nối đồng thời:

ExecuteReader yêu cầu một Kết nối mở và có sẵn. Trạng thái hiện tại của kết nối là Đang kết nối.

Trang web hoạt động tốt trên máy chủ localhost của tôi.

Đây là mã thô.

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

Tôi có thể biết những gì có thể đã xảy ra sai và làm thế nào để tôi sửa chữa nó?

Chỉnh sửa: Đừng quên, chuỗi kết nối và kết nối của tôi đều ở trạng thái tĩnh. Tôi tin rằng đây là lý do. Xin hãy tư vấn.

public static string conString = ConfigurationManager.ConnectionStrings["dbConnection"].ConnectionString;
public static SqlConnection conn = null;

24
Không sử dụng kết nối chia sẻ / tĩnh trong môi trường đa luồng như ASP.NET vì bạn đang tạo khóa hoặc ngoại lệ (quá nhiều kết nối mở, v.v.). Hãy ném DB-Class của bạn vào thùng rác và tạo, mở, sử dụng, đóng, xử lý các đối tượng ado.net ở nơi bạn cần. Hãy xem kỹ câu lệnh using.
Tim Schmelter

2
bạn có thể cho tôi biết chi tiết về SqlOpenConnection (); và sql.ExecuteReader (); chức năng ..?
Ankit Rajput

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

@GuoHongLim: Tôi đã quên đề cập rằng ngay cả một tĩnh cũng conStringkhông bổ sung gì về mặt hiệu suất vì dù sao nó cũng được lưu vào bộ nhớ cache theo mặc định (như mọi giá trị cấu hình cho ứng dụng hiện tại).
Tim Schmelter

... và chỉ để làm cho nó trở thành một ẩn số: Việc đảm bảo bạn cũng xử lý giao dịch cơ sở dữ liệu / đơn vị công việc chính xác được coi là một bài tập cho người đọc.
mwardm

Câu trả lời:


226

Xin lỗi vì chỉ nhận xét ở vị trí đầu tiên, nhưng hầu như ngày nào tôi cũng đăng một nhận xét tương tự vì nhiều người nghĩ rằng sẽ rất thông minh nếu gói chức năng ADO.NET vào một DB-Class (tôi cũng vậy 10 năm trước). Hầu hết họ quyết định sử dụng các đối tượng tĩnh / được chia sẻ vì nó có vẻ nhanh hơn so với việc tạo một đối tượng mới cho bất kỳ hành động nào.

Đó không phải là một ý tưởng tốt về mặt hiệu suất cũng như về mặt an toàn thất bại.

Đừng săn trộm trên lãnh thổ của Connection-Pool

Có một lý do chính đáng tại sao ADO.NET quản lý nội bộ các Kết nối cơ bản với DBMS trong Nhóm kết nối ADO-NET :

Trong thực tế, hầu hết các ứng dụng chỉ sử dụng một hoặc một vài cấu hình khác nhau cho các kết nối. Điều này có nghĩa là trong quá trình thực thi ứng dụng, nhiều kết nối giống nhau sẽ được mở và đóng liên tục. Để giảm thiểu chi phí mở kết nối, ADO.NET sử dụng một kỹ thuật tối ưu hóa được gọi là gộp kết nối.

Việc gộp kết nối làm giảm số lần các kết nối mới phải được mở. Pooler duy trì quyền sở hữu kết nối vật lý. Nó quản lý các kết nối bằng cách duy trì một tập hợp các kết nối đang hoạt động cho mỗi cấu hình kết nối nhất định. Bất cứ khi nào người dùng gọi Mở trên một kết nối, trình tổng hợp sẽ tìm kiếm một kết nối khả dụng trong nhóm. Nếu một kết nối tổng hợp khả dụng, nó sẽ trả về cho người gọi thay vì mở một kết nối mới. Khi ứng dụng gọi Đóng trên kết nối, trình tổng hợp trả nó về tập hợp các kết nối hoạt động được gộp chung thay vì đóng nó. Sau khi kết nối được quay trở lại nhóm, nó đã sẵn sàng để được sử dụng lại trong lần gọi Mở tiếp theo.

Vì vậy, rõ ràng là không có lý do gì để tránh tạo, mở hoặc đóng các kết nối vì thực sự chúng không được tạo, mở và đóng. Đây là "chỉ" một cờ để nhóm kết nối biết khi nào một kết nối có thể được sử dụng lại hay không. Nhưng đó là một cờ rất quan trọng, bởi vì nếu một kết nối "đang được sử dụng" (nhóm kết nối giả định), thì một kết nối vật lý mới phải được hoạt động với DBMS, điều rất tốn kém.

Vì vậy, bạn không cải thiện hiệu suất mà ngược lại. Nếu đạt đến kích thước nhóm tối đa được chỉ định (100 là mặc định), bạn thậm chí sẽ nhận được ngoại lệ (quá nhiều kết nối đang mở ...). Vì vậy, điều này sẽ không chỉ ảnh hưởng lớn đến hiệu suất mà còn là nguồn gốc cho các lỗi khó chịu và (không sử dụng Giao dịch) một khu vực kết xuất dữ liệu.

Nếu bạn thậm chí đang sử dụng các kết nối tĩnh, bạn đang tạo một khóa cho mọi luồng đang cố gắng truy cập đối tượng này. Bản chất ASP.NET là một môi trường đa luồng. Vì vậy, có một cơ hội lớn cho những khóa này gây ra các vấn đề về hiệu suất tốt nhất. Trên thực tế, sớm hay muộn bạn sẽ nhận được nhiều ngoại lệ khác nhau (như ExecuteReader của bạn yêu cầu một Kết nối mở và có sẵn ).

Kết luận :

  • Không sử dụng lại các kết nối hoặc bất kỳ đối tượng ADO.NET nào.
  • Đừng làm cho chúng tĩnh / được chia sẻ (trong VB.NET)
  • Luôn tạo, mở (trong trường hợp có Kết nối), sử dụng, đóng và hủy chúng ở nơi bạn cần (fe trong một phương thức)
  • sử dụng using-statementđể hủy và đóng (trong trường hợp Kết nối) một cách ẩn ý

Điều đó đúng không chỉ với Connections (mặc dù đáng chú ý nhất). Mọi đối tượng triển khai IDisposablenên được xử lý (đơn giản nhất là using-statement), tất cả những thứ khác trong System.Data.SqlClientkhông gian tên.

Tất cả những điều trên phản ánh một Lớp DB tùy chỉnh đóng gói và sử dụng lại tất cả các đối tượng. Đó là lý do tại sao tôi bình luận để thùng rác nó. Đó chỉ là một nguồn có vấn đề.


Chỉnh sửa : Đây là một triển khai có thể có của retrievePromotion-method của bạn :

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

điều này thực sự hữu ích để cung cấp cho mô hình công việc kết nối. Cảm ơn vì lời giải thích này.
aminvincent,

được viết tốt, một lời giải thích cho điều mà nhiều người vô tình phát hiện ra và tôi ước nhiều người biết điều này hơn. (+1)
Andrew Hill

1
Cảm ơn bạn, tôi nghĩ rằng đây là lời giải thích hay nhất về chủ đề này mà tôi từng đọc, một chủ đề rất quan trọng và nhiều người mới đã nhầm lẫn. Tôi phải khen bạn vì khả năng viết xuất sắc của bạn.
Sasinosoft

@Tim Schmelter làm cách nào để tôi có thể thực hiện các truy vấn của mình chạy trên các chuỗi khác nhau bằng cách sử dụng một giao dịch duy nhất để cam kết / quay lại bằng cách sử dụng phương pháp được đề xuất của bạn?
geeko

1

Tôi đã gặp lỗi này một vài ngày trước.

TRONG trường hợp của tôi, đó là vì tôi đang sử dụng Giao dịch trên Singleton.

.Net không hoạt động tốt với Singleton như đã nêu ở trên.

Giải pháp của tôi là:

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

Tôi đã sử dụng HttpContext.Current.Items cho trường hợp của mình. Lớp DbHelper và DbHelperCore này là lớp của riêng tôi

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.