ExecuteReader membutuhkan Koneksi terbuka dan tersedia. Status koneksi saat ini adalah Menghubungkan

114

Ketika mencoba untuk menyambung ke database MSSQL melalui ASP.NET online, saya akan mendapatkan yang berikut ini ketika dua atau lebih orang terhubung secara bersamaan:

ExecuteReader membutuhkan Koneksi terbuka dan tersedia. Status koneksi saat ini adalah Menghubungkan.

Situs ini berfungsi dengan baik di server localhost saya.

Ini adalah kode kasarnya.

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

Bolehkah saya tahu apa yang mungkin salah dan bagaimana cara memperbaikinya?

Edit: Jangan lupa, string koneksi dan koneksi saya keduanya statis. Saya yakin inilah alasannya. Mohon saran.

public static string conString = ConfigurationManager.ConnectionStrings["dbConnection"].ConnectionString;
public static SqlConnection conn = null;
Guo Hong Lim
sumber
24
Jangan gunakan koneksi bersama / statis di lingkungan multithreading seperti ASP.NET karena Anda membuat kunci atau pengecualian (terlalu banyak koneksi terbuka, dll.). Buang DB-Class Anda ke tong sampah dan buat, buka, gunakan, tutup, buang objek ado.net di tempat yang Anda butuhkan. Lihat juga pernyataan-penggunaan.
Tim Schmelter
2
dapatkah Anda memberi saya detail tentang SqlOpenConnection (); dan sql.ExecuteReader (); fungsi? ..
ankit rajput
private void SqlOpenConnection () {coba {conn = new SqlConnection (); conn.ConnectionString = conString; samb.Open (); } catch (SqlException ex) {throw ex; }}
Guo Hong Lim
@GuoHongLim: Saya lupa menyebutkan bahwa bahkan statis conStringtidak menambahkan apa pun dalam hal kinerja karena tetap di-cache secara default (karena setiap nilai konfigurasi untuk aplikasi saat ini).
Tim Schmelter
... dan hanya untuk membuatnya dikenal-tidak diketahui: Memastikan Anda juga mendapatkan penanganan transaksi database / unit-of-work yang benar tetap dilakukan sebagai latihan bagi pembaca.
mwardm

Jawaban:

226

Maaf karena hanya berkomentar di tempat pertama, tetapi saya memposting komentar serupa hampir setiap hari karena banyak orang berpikir bahwa akan cerdas untuk merangkum fungsionalitas ADO.NET ke dalam DB-Class (saya juga 10 tahun yang lalu). Sebagian besar mereka memutuskan untuk menggunakan objek statis / bersama karena tampaknya lebih cepat daripada membuat objek baru untuk tindakan apa pun.

Itu bukanlah ide yang baik dalam hal kinerja maupun dalam hal keamanan gagal.

Jangan berburu di wilayah Connection-Pool

Ada alasan bagus mengapa ADO.NET secara internal mengelola Koneksi yang mendasari ke DBMS di ADO-NET Connection-Pool :

Dalam praktiknya, sebagian besar aplikasi hanya menggunakan satu atau beberapa konfigurasi berbeda untuk koneksi. Ini berarti bahwa selama eksekusi aplikasi, banyak koneksi identik yang akan dibuka dan ditutup berulang kali. Untuk meminimalkan biaya pembukaan koneksi, ADO.NET menggunakan teknik pengoptimalan yang disebut penggabungan koneksi.

Penggabungan koneksi mengurangi berapa kali koneksi baru harus dibuka. Pooler mempertahankan kepemilikan koneksi fisik. Ia mengelola koneksi dengan tetap menghidupkan satu set koneksi aktif untuk setiap konfigurasi koneksi yang diberikan. Setiap kali pengguna memanggil Buka pada koneksi, pooler mencari koneksi yang tersedia di pool. Jika koneksi gabungan tersedia, itu mengembalikannya ke pemanggil alih-alih membuka koneksi baru. Ketika aplikasi memanggil Tutup pada koneksi, pooler mengembalikannya ke kumpulan koneksi aktif alih-alih menutupnya. Setelah koneksi dikembalikan ke kolam, itu siap untuk digunakan kembali pada panggilan Terbuka berikutnya.

Jadi jelas tidak ada alasan untuk menghindari membuat, membuka atau menutup koneksi karena sebenarnya mereka tidak dibuat, dibuka dan ditutup sama sekali. Ini "hanya" sebuah tanda untuk kumpulan koneksi untuk mengetahui kapan koneksi dapat digunakan kembali atau tidak. Tetapi itu adalah tanda yang sangat penting, karena jika koneksi sedang "digunakan" (asumsi kumpulan koneksi), koneksi fisik baru harus openend ke DBMS yang sangat mahal.

Jadi Anda tidak mendapatkan peningkatan kinerja tetapi sebaliknya. Jika ukuran kumpulan maksimum yang ditentukan (100 adalah default) tercapai, Anda bahkan akan mendapatkan pengecualian (terlalu banyak koneksi terbuka ...). Jadi ini tidak hanya akan berdampak besar pada kinerja tetapi juga menjadi sumber kesalahan yang parah dan (tanpa menggunakan Transaksi) area-dumping-data.

Jika Anda bahkan menggunakan koneksi statis, Anda membuat kunci untuk setiap utas yang mencoba mengakses objek ini. ASP.NET adalah lingkungan multithreading secara alami. Jadi ada peluang besar untuk kunci ini yang paling banyak menyebabkan masalah kinerja. Sebenarnya cepat atau lambat Anda akan mendapatkan banyak pengecualian berbeda (seperti ExecuteReader Anda membutuhkan Koneksi terbuka dan tersedia ).

Kesimpulan :

  • Jangan gunakan kembali koneksi atau objek ADO.NET sama sekali.
  • Jangan membuatnya statis / dibagikan (di VB.NET)
  • Selalu buat, buka (dalam kasus Koneksi), gunakan, tutup dan buang di mana Anda membutuhkannya (fe dalam suatu metode)
  • gunakan using-statementuntuk membuang dan menutup (dalam kasus Koneksi) secara implisit

Itu benar tidak hanya untuk Koneksi (meskipun paling menonjol). Setiap implementasi objek IDisposableharus dibuang (paling sederhana oleh using-statement), terlebih lagi di System.Data.SqlClientnamespace.

Semua hal di atas menentang DB-Class kustom yang merangkum dan menggunakan kembali semua objek. Itulah alasan mengapa saya berkomentar untuk membuangnya. Itu hanya sumber masalah.


Sunting : Berikut kemungkinan penerapan retrievePromotion-method Anda :

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;
}
Tim Schmelter
sumber
Hal ini sangat berguna untuk memberikan paradigma kerja koneksi. Terima kasih atas penjelasannya.
aminvincent
ditulis dengan baik, penjelasan untuk sesuatu yang tidak sengaja ditemukan banyak orang, dan saya harap lebih banyak orang mengetahui hal ini. (+1)
Andrew Hill
1
Terima kasih pak, menurut saya inilah penjelasan terbaik tentang hal ini yang pernah saya baca, pokok bahasan yang sangat penting dan banyak kesalahan pemula itu. Saya harus memuji Anda atas kemampuan menulis Anda yang luar biasa.
Sasinosoft
@Tim Schmelter bagaimana cara membuat kueri saya berjalan di utas yang berbeda menggunakan satu transaksi untuk melakukan / memutar kembali menggunakan pendekatan yang Anda sarankan?
geeko
1

Saya menemukan kesalahan ini beberapa hari yang lalu.

DALAM kasus saya itu karena saya menggunakan Transaksi di Singleton.

.Net tidak berfungsi dengan baik dengan Singleton seperti yang disebutkan di atas.

Solusi saya adalah ini:

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

Saya menggunakan HttpContext.Current.Items untuk contoh saya. Kelas DbHelper dan DbHelperCore ini adalah kelas saya sendiri

Damon Abdiel
sumber