ExecuteReader açık ve kullanılabilir bir Bağlantı gerektirir. Bağlantının mevcut durumu Bağlanıyor


114

MSSQL veritabanına ASP.NET üzerinden çevrimiçi bağlanmaya çalışırken, iki veya daha fazla kişi aynı anda bağlandığında aşağıdakileri alacağım:

ExecuteReader açık ve kullanılabilir bir Bağlantı gerektirir. Bağlantının mevcut durumu Bağlanıyor.

Site localhost sunucumda iyi çalışıyor.

Bu kaba koddur.

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

Neyin yanlış gittiğini ve nasıl düzeltebileceğimi öğrenebilir miyim?

Düzenleme: Unutmamak gerekir ki, bağlantı dizilim ve bağlantımın ikisi de statik. Bunun nedeni olduğuna inanıyorum. Tavsiye lütfen.

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

24
Kilit veya istisna oluşturduğunuz için (çok fazla açık bağlantı vb.) ASP.NET gibi çoklu okuma ortamında paylaşılan / statik bağlantılar kullanmayın. DB-Class'ınızı çöp kutusuna atın ve ihtiyaç duyduğunuz yerde ado.net nesneleri oluşturun, açın, kullanın, kapatın, atın. Kullanarak ifadesine de bir göz atın.
Tim Schmelter

2
bana SqlOpenConnection () ve sql.ExecuteReader () hakkında ayrıntılı bilgi verebilir misiniz? functions? ..
ankit rajput

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

@GuoHongLim: Bir statik bile varsayılan olarak önbelleğe alındığı içinconString performans açısından hiçbir şey eklemediğini söylemeyi unuttum (mevcut uygulama için her yapılandırma değeri gibi).
Tim Schmelter

... ve sadece bilinmeyen bir hale getirmek için: Veritabanı işlemlerinizi / iş biriminizi doğru şekilde almanızı sağlamak okuyucu için bir alıştırma olarak bırakılır.
mwardm

Yanıtlar:


227

İlk başta yorum yaptığım için özür dilerim, ancak neredeyse her gün benzer bir yorum yayınlıyorum çünkü birçok kişi ADO.NET işlevselliğini bir DB Sınıfına yerleştirmenin akıllıca olacağını düşünüyor (ben de 10 yıl önce). Çoğunlukla, herhangi bir eylem için yeni bir nesne oluşturmaktan daha hızlı göründüğü için statik / paylaşılan nesneleri kullanmaya karar verirler.

Bu ne performans açısından ne de arıza güvenliği açısından iyi bir fikir.

Bağlantı Havuzu bölgesinde kaçak avlanma

ADO.NET'in, ADO-NET Bağlantı Havuzundaki DBMS'ye temeldeki Bağlantıları dahili olarak yönetmesinin iyi bir nedeni vardır :

Pratikte çoğu uygulama, bağlantılar için yalnızca bir veya birkaç farklı konfigürasyon kullanır. Bu, uygulamanın yürütülmesi sırasında birçok aynı bağlantının tekrar tekrar açılıp kapanacağı anlamına gelir. ADO.NET, bağlantı açma maliyetini en aza indirmek için bağlantı havuzu adı verilen bir optimizasyon tekniği kullanır.

Bağlantı havuzu oluşturma, yeni bağlantıların açılması gereken sayısını azaltır. Pooler, fiziksel bağlantının sahipliğini korur. Verilen her bağlantı yapılandırması için bir dizi etkin bağlantıyı canlı tutarak bağlantıları yönetir. Bir kullanıcı bir bağlantıda Aç'ı aradığında, saldırgan havuzda uygun bir bağlantı arar. Havuzlanmış bir bağlantı varsa, yeni bir bağlantı açmak yerine arayana geri gönderir. Uygulama bağlantıda Kapat'ı çağırdığında, pooler onu kapatmak yerine havuzlanmış etkin bağlantılar kümesine geri döndürür. Bağlantı havuza geri döndüğünde, bir sonraki Açık çağrıda yeniden kullanılmaya hazırdır.

Açıkçası, bağlantı oluşturmaktan, açmaktan veya kapatmaktan kaçınmak için hiçbir neden yok çünkü bunlar aslında oluşturulmuyor, açılmıyor ve kapanmıyor. Bu, bağlantı havuzunun bir bağlantının ne zaman yeniden kullanılıp kullanılamayacağını bilmesi için "yalnızca" bir işarettir. Ancak bu çok önemli bir bayrak, çünkü bir bağlantı "kullanımdaysa" (bağlantı havuzu varsayılır), çok pahalı olan yeni bir fiziksel bağlantının DBMS'ye açık olması gerekir.

Yani performans artışı elde etmiyorsunuz, tam tersi. Belirtilen maksimum havuz boyutuna ulaşılırsa (100 varsayılandır), istisnalar bile alabilirsiniz (çok fazla açık bağlantı ...). Bu nedenle, bu yalnızca performansı muazzam bir şekilde etkilemekle kalmayacak, aynı zamanda kötü hatalar için bir kaynak ve (İşlemleri kullanmadan) bir veri boşaltma alanı olacaktır.

Statik bağlantılar kullanıyorsanız bile, bu nesneye erişmeye çalışan her iş parçacığı için bir kilit oluşturuyorsunuz. ASP.NET, doğası gereği çok iş parçacıklı bir ortamdır. Bu nedenle, en iyi ihtimalle performans sorunlarına neden olan bu kilitler için büyük bir şans var. Aslında er ya da geç birçok farklı istisna ile karşılaşacaksınız ( ExecuteReader'ınızın açık ve kullanılabilir bir Bağlantı gerektirmesi gibi ).

Sonuç :

  • Bağlantıları veya herhangi bir ADO.NET nesnesini yeniden kullanmayın.
  • Bunları statik / paylaşımlı yapmayın (VB.NET'te)
  • Her zaman oluşturun, açın (Bağlantılar olması durumunda), kullanın, kapatın ve ihtiyaç duyduğunuz yerde imha edin (bir yöntemde fe)
  • using-statementimha etmek ve kapatmak için kullanın (Bağlantılar durumunda) dolaylı olarak

Bu sadece Bağlantılar için geçerli değil (en dikkat çekici olmasına rağmen). Gerçekleştirilen her nesne , ad alanında daha fazlası olmak üzere, IDisposableelden çıkarılmalıdır (en basit şekilde using-statement) System.Data.SqlClient.

Yukarıdakilerin tümü, tüm nesneleri kapsülleyen ve yeniden kullanan özel bir DB Sınıfına karşı konuşuyor. Onu çöpe atmak için yorum yapmamın nedeni bu. Bu sadece bir sorun kaynağı.


Düzenleme : İşte- retrievePromotionyönteminizin olası bir uygulaması :

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

bu, bağlantı çalışması paradigması vermek için gerçekten yararlıdır. Bu açıklama için teşekkürler.
aminvincent

iyi yazılmış, birçok insanın kazara keşfettiği bir şey için bir açıklama ve keşke bunu daha fazla insanın bildiği bir şey. (+1)
Andrew Hill

1
Teşekkür ederim efendim, bence bu konu hakkında okuduğum en iyi açıklama, çok önemli bir konu ve pek çok yeniliğin hata yaptığı bir konu. Mükemmel yazma yeteneğiniz için sizi tebrik etmeliyim.
Sasinosoft

@Tim Schmelter, farklı iş parçacıkları üzerinde çalışan sorgularımın önerilen yaklaşımınızı kullanarak taahhüt / geri alma için tek bir işlem kullanmasını nasıl sağlayabilirim?
geeko

1

Bu hatayı birkaç gün önce yakaladım.

Benim durumumda bunun nedeni, bir Singleton'da İşlem kullanıyor olmamdı.

.Net, yukarıda belirtildiği gibi Singleton ile iyi çalışmaz.

Benim çözümüm şuydu:

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

Örneğim için HttpContext.Current.Items kullandım. Bu sınıf DbHelper ve DbHelperCore benim kendi sınıfım

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.