Fehler "Diesem Befehl ist bereits ein offener DataReader zugeordnet, der zuerst geschlossen werden muss", wenn zwei verschiedene Befehle verwendet werden

69

Ich habe diesen Legacy-Code:

 private void conecta()
 {  
     if (conexao.State == ConnectionState.Closed)
         conexao.Open();
 }

 public List<string[]> get_dados_historico_verificacao_email_WEB(string email)
 {
     List<string[]> historicos = new List<string[]>();
     conecta();

     sql = 
         @"SELECT * 
         FROM historico_verificacao_email 
         WHERE nm_email = '" + email + @"' 
         ORDER BY dt_verificacao_email DESC, hr_verificacao_email DESC";

     com = new SqlCommand(sql, conexao);
     SqlDataReader dr = com.ExecuteReader();

     if (dr.HasRows)
     {
         while (dr.Read())
         {
             string[] dados_historico = new string[6];
             dados_historico[0] = dr["nm_email"].ToString();
             dados_historico[1] = dr["dt_verificacao_email"].ToString();
             dados_historico[1] = dados_historico[1].Substring(0, 10);
             dados_historico[2] = dr["hr_verificacao_email"].ToString();
             dados_historico[3] = dr["ds_tipo_verificacao"].ToString();

             sql = 
                 @"SELECT COUNT(e.cd_historico_verificacao_email) QT 
                 FROM emails_lidos e 
                 WHERE e.cd_historico_verificacao_email = 
                     '" + dr["cd_historico_verificacao_email"].ToString() + "'";

             tipo_sql = "seleção";
             conecta();
             com2 = new SqlCommand(sql, conexao);

             SqlDataReader dr3 = com2.ExecuteReader();
             while (dr3.Read())
             {
                 //quantidade de emails lidos naquela verificação
                 dados_historico[4] = dr3["QT"].ToString(); 
             }
             dr3.Close();
             conexao.Close();

             //login
             dados_historico[5] = dr["cd_login_usuario"].ToString();
             historicos.Add(dados_historico);
         }
         dr.Close();
     }
     else
     { 
         dr.Close();
     }

     conexao.Close();
     return historicos;
 }


Ich habe zwei separate Befehle erstellt, um das Problem zu beheben, aber es wird weiterhin fortgesetzt: "Diesem Befehl ist bereits ein offener DataReader zugeordnet, der zuerst geschlossen werden muss."

Eine zusätzliche Info: Der gleiche Code funktioniert in einer anderen App.

Alejandro Carnero
quelle
30
Sie haben eine SQL-Injection-Sicherheitsanfälligkeit.
SLaks
4
Es ist nicht nur auf Befehl, es ist durch Verbindung. Wenn Sie für beide Befehle dieselbe Verbindung verwenden, wird der Fehler angezeigt.
Michael Todd
Warum setzen Sie den internen Befehl nicht in eine zusätzliche Funktion? Es verbessert die Lesbarkeit.
Sockenfd

Antworten:

8

Ich schlage vor, eine zusätzliche Verbindung für den zweiten Befehl zu erstellen, würde es lösen. Versuchen Sie, beide Abfragen in einer Abfrage zu kombinieren. Erstellen Sie eine Unterabfrage für die Zählung.

while (dr3.Read())
{
    dados_historico[4] = dr3["QT"].ToString(); //quantidade de emails lidos naquela verificação
}

Warum immer wieder denselben Wert überschreiben?

if (dr3.Read())
{
    dados_historico[4] = dr3["QT"].ToString(); //quantidade de emails lidos naquela verificação
}

Wäre genug.

Jeroen van Langen
quelle
8
Das Erstellen einer zweiten Verbindung zu derselben Datenbank löst möglicherweise das Problem, hilft jedoch nicht bei der Lesbarkeit und Wartung des Codes
Mauricio Gracia Gutierrez
+1. Auf diese Weise könnte er sogar vom Server herstellerunabhängig sein, obwohl er dafür DbConnection anstelle von SqlConnection und Db * -Klassen verwenden muss, die einer beliebigen Sql * -Klasse entsprechen. Aber das Beste ist, wenn er seine Fragen neu schreibt, so wie Sie (und jede Antwort) sagen.
Csaba Toth
21
  1. Die optimale Lösung könnte darin bestehen, zu versuchen, Ihre Lösung in ein Formular umzuwandeln, in dem nicht zwei Leser gleichzeitig geöffnet sein müssen. Im Idealfall kann es sich um eine einzelne Abfrage handeln. Ich habe jetzt keine Zeit dafür.
  2. Wenn Ihr Problem so speziell ist, dass wirklich mehr Leser gleichzeitig geöffnet sein müssen und Ihre Anforderungen nicht älter als das SQL Server 2005 DB-Backend sind, lautet das Zauberwort MARS (Multiple Active Result Sets) . http://msdn.microsoft.com/en-us/library/ms345109%28v=SQL.90%29.aspx . Die Lösung des verknüpften Themas von Bob Vale zeigt, wie es aktiviert wird: Geben Sie MultipleActiveResultSets=truein Ihrer Verbindungszeichenfolge an. Ich sage dies nur als interessante Möglichkeit, aber Sie sollten Ihre Lösung lieber umwandeln.

    • Um die erwähnte SQL-Injection-Möglichkeit zu vermeiden, setzen Sie die Parameter auf den SQLCommand selbst, anstatt sie in die Abfragezeichenfolge einzubetten. Die Abfragezeichenfolge sollte nur die Verweise auf die Parameter enthalten, die Sie an den SqlCommand übergeben.
Csaba Toth
quelle
Csaba Toth funktioniert die MARS-Einstellung in einer anderen Datenbank, die nicht MS SQL SERVER ist?
Mauricio Gracia Gutierrez
Es ist eine MS SQL Server-Funktion. 2005 und höher. Andere RDBMS haben möglicherweise ähnliche Funktionen, sind jedoch spezifisch für den jeweiligen Servertyp.
Csaba Toth
1
Es kann auch Nachteile geben: stackoverflow.com/questions/374444/…
Csaba Toth
1
Beginnen Sie mit der Arbeit an einer einzelnen Abfrage. Haben Sie eine JOIN- oder eingebettete Abfrage ausprobiert?
Csaba Toth
13

Sie können ein solches Problem bekommen, wenn Sie sich two different commandsin derselben Verbindung befinden - insbesondere wenn Sie den zweiten Befehl in a aufrufenloop . Das heißt, der zweite Befehl wird für jeden Datensatz aufgerufen, der vom ersten Befehl zurückgegeben wird. Wenn vom ersten Befehl etwa 10.000 Datensätze zurückgegeben werden, ist dieses Problem wahrscheinlicher.

Früher habe ich ein solches Szenario vermieden, indem ich es als einzelnen Befehl erstellt habe. Der erste Befehl gibt alle erforderlichen Daten zurück und lädt sie in eine DataTable.

Hinweis: MARSKann eine Lösung sein - aber es kann riskant sein und viele Leute mögen es nicht.

Referenz

  1. Was bedeutet "Beim aktuellen Befehl ist ein schwerwiegender Fehler aufgetreten. Die Ergebnisse, falls vorhanden, sollten verworfen werden." SQL Azure Fehler bedeuten?
  2. Probleme mit Linq-To-Sql und MARS - Beim aktuellen Befehl ist ein schwerwiegender Fehler aufgetreten. Die Ergebnisse, falls vorhanden, sollten verworfen werden
  3. Komplexe GROUP BY auf DataTable
LCJ
quelle
2
Schlüssel: Wenn Sie foreach () auf einem IQueryable aufrufen (Sie haben .Where () ausgeführt, aber nicht mit .ToList () gefolgt) und dann versuchen, den Kontext innerhalb dieser Schleife zu verwenden, kann dies das Problem verursachen.
Don Cheadle
1
Danke @DonCheadle, das war genau das Problem, das ich hatte. Die ToList () hat das Problem gelöst! Vielen Dank!
Mack
8

Ich wette, das Problem wird in dieser Zeile angezeigt

SqlDataReader dr3 = com2.ExecuteReader();

Ich schlage vor, dass Sie den ersten Leser ausführen und a dr.Close();und iterieren historicosmit einer anderen Schleife ausführen com2.ExecuteReader().

public List<string[]> get_dados_historico_verificacao_email_WEB(string email)
    {

        List<string[]> historicos = new List<string[]>();
        conecta();
        sql = "SELECT * FROM historico_verificacao_email WHERE nm_email = '" + email + "' ORDER BY  dt_verificacao_email DESC, hr_verificacao_email DESC"; 
        com = new SqlCommand(sql, conexao);
        SqlDataReader dr = com.ExecuteReader();

        if (dr.HasRows)
        {
            while (dr.Read())
            {
                string[] dados_historico = new string[6];
                dados_historico[0] = dr["nm_email"].ToString();
                dados_historico[1] = dr["dt_verificacao_email"].ToString();
                dados_historico[1] = dados_historico[1].Substring(0, 10);
                //System.Windows.Forms.MessageBox.Show(dados_historico[1]);
                dados_historico[2] = dr["hr_verificacao_email"].ToString();
                dados_historico[3] = dr["ds_tipo_verificacao"].ToString();
                dados_historico[5] = dr["cd_login_usuario"].ToString();
                historicos.Add(dados_historico);
            }

            dr.Close();

            sql = "SELECT COUNT(e.cd_historico_verificacao_email) QT FROM emails_lidos e WHERE e.cd_historico_verificacao_email = '" + dr["cd_historico_verificacao_email"].ToString() + "'";
            tipo_sql = "seleção";
            com2 = new SqlCommand(sql, conexao);

            for(int i = 0 ; i < historicos.Count() ; i++)
            {
                SqlDataReader dr3 = com2.ExecuteReader();
                while (dr3.Read())
                {
                    historicos[i][4] = dr3["QT"].ToString(); //quantidade de emails lidos naquela verificação
                }
                dr3.Close();
            }

        }

        return historicos;
Mauricio Gracia Gutierrez
quelle
@CsabaToth zwei Schleifen eine zum Einlesen der Daten in dados_historico und schließen den Leser und dann eine andere Schleife, um den anderen Leser auszuführen
Mauricio Gracia Gutierrez
+1 Ich weiß. Und anscheinend sammelt er die Daten in einem Array, damit er sie wiederverwenden kann. Ihre ist die wirkliche Lösung, es sei denn, es gibt eine noch bessere Einzelabfrage.
Csaba Toth
3

Versuchen Sie, die Abfrage zu kombinieren. Sie wird viel schneller ausgeführt als die Ausführung einer zusätzlichen Abfrage pro Zeile. Ich mag die Zeichenfolge [], die Sie verwenden, nicht. Ich würde eine Klasse zum Speichern der Informationen erstellen.

    public List<string[]> get_dados_historico_verificacao_email_WEB(string email)
    {
        List<string[]> historicos = new List<string[]>();

        using (SqlConnection conexao = new SqlConnection("ConnectionString"))
        {
            string sql =
                @"SELECT    *, 
                            (   SELECT      COUNT(e.cd_historico_verificacao_email) 
                                FROM        emails_lidos e 
                                WHERE       e.cd_historico_verificacao_email = a.nm_email ) QT
                  FROM      historico_verificacao_email a
                  WHERE     nm_email = @email
                  ORDER BY  dt_verificacao_email DESC, 
                            hr_verificacao_email DESC";

            using (SqlCommand com = new SqlCommand(sql, conexao))
            {
                com.Parameters.Add("email", SqlDbType.VarChar).Value = email;

                SqlDataReader dr = com.ExecuteReader();

                while (dr.Read())
                {
                    string[] dados_historico = new string[6];
                    dados_historico[0] = dr["nm_email"].ToString();
                    dados_historico[1] = dr["dt_verificacao_email"].ToString();
                    dados_historico[1] = dados_historico[1].Substring(0, 10);
                    //System.Windows.Forms.MessageBox.Show(dados_historico[1]);
                    dados_historico[2] = dr["hr_verificacao_email"].ToString();
                    dados_historico[3] = dr["ds_tipo_verificacao"].ToString();
                    dados_historico[4] = dr["QT"].ToString();
                    dados_historico[5] = dr["cd_login_usuario"].ToString();

                    historicos.Add(dados_historico);
                }
            }
        }
        return historicos;
    }

Ungetestet, aber vielleicht gibt eine Idee.

Jeroen van Langen
quelle
2

Fügen Sie MultipleActiveResultSets=truedem Provider einen Teil Ihrer Verbindungszeichenfolge hinzu. Siehe das folgende Beispiel:

<add name="DbContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=dbName;Persist Security Info=True;User ID=userName;Password=password;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
Rousonur Jaman
quelle