c# multipleactiveresultsets=true - Erreur «Il existe déjà un DataReader ouvert associé à cette commande qui doit être fermé en premier» lors de l'utilisation de 2 commandes distinctes



d'abord this (7)

Vous pouvez avoir un tel problème lorsque vous utilisez two different commands sur la même connexion, en particulier lorsque vous appelez la deuxième commande dans une loop . Cela appelle la deuxième commande pour chaque enregistrement renvoyé par la première commande. Si la première commande renvoie environ 10 000 enregistrements, le problème sera probablement plus grave.

J'avais l'habitude d'éviter un tel scénario en le transformant en une seule commande. La première commande renvoie toutes les données requises et les charge dans un DataTable.

Remarque: MARS peut être une solution - mais cela peut être risqué et beaucoup de gens ne l'aiment pas.

Référence

  1. "Une erreur grave s'est produite sur la commande en cours. Les résultats, le cas échéant, doivent être ignorés." Erreur SQL Azure signifie?
  2. Malheurs Linq-To-Sql et MARS - Une erreur grave s'est produite sur la commande en cours. Les résultats, le cas échéant, doivent être écartés
  3. GROUP BY complexe sur DataTable

J'ai ce code hérité:

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


J'ai créé deux commandes distinctes pour corriger le problème, mais le problème persiste: "Un DataReader ouvert associé à cette commande doit déjà être fermé".

Une information supplémentaire: le même code fonctionne dans une autre application.


Je suggère de créer une connexion supplémentaire pour la deuxième commande, le résoudre. Essayez de combiner les deux requêtes en une requête. Créez une sous-requête pour le compte.

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

Pourquoi remplacer la même valeur encore et encore?

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

Serait assez.


  1. La solution optimale pourrait être d’essayer de transformer votre solution en une forme dans laquelle vous n’auriez pas besoin d’avoir deux lecteurs ouverts à la fois. Idéalement, il pourrait s'agir d'une requête unique. Je n'ai pas le temps de le faire maintenant.
  2. Si votre problème est si spécial que vous avez réellement besoin d'ouvrir plus de lecteurs simultanément et que vos exigences n'autorisent pas le backend SQL antérieur à DB Server 2005, le mot magique est MARS (Multiple Active Result Set) . http://msdn.microsoft.com/en-us/library/ms345109%28v=SQL.90%29.aspx . La solution de la rubrique liée de Bob Vale montre comment l'activer: spécifiez MultipleActiveResultSets=true dans votre chaîne de connexion. Je dis simplement que c'est une possibilité intéressante, mais vous devriez plutôt transformer votre solution.

    • afin d'éviter la possibilité d'injection SQL mentionnée, définissez les paramètres sur la SQLCommand elle-même au lieu de les incorporer dans la chaîne de requête. La chaîne de requête ne doit contenir que les références aux paramètres que vous transmettez à la commande SqlCommand.

Essayez de combiner la requête, cela s'exécutera beaucoup plus rapidement que d'exécuter une requête supplémentaire par ligne. Ik n'aime pas la chaîne [] que vous utilisez, je créerais une classe pour contenir les informations.

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

Non testé, mais peut-être donne une idée.


Ajoutez simplement ce qui suit dans votre chaîne de connexion:

MultipleActiveResultSets=True;

Je parie que le problème est montré dans cette ligne

SqlDataReader dr3 = com2.ExecuteReader();

Je suggère que vous exécutiez le premier lecteur et faites un dr.Close(); et iterate historicos , avec une autre boucle, en exécutant la 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;

Le deuxième test comporte deux tâches imbriquées et vous attendez la plus externe, pour résoudre ce problème, vous devez utiliser t.Result.Wait() . t.Result est la tâche intérieure.

La seconde méthode est à peu près équivalente à ceci:

public void TestAwait()
{
  var t = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("Start");
                return Task.Factory.StartNew(() =>
                {
                    Task.Delay(5000).Wait(); Console.WriteLine("Done");
                });
            });
            t.Wait();
            Console.WriteLine("All done");
}

En appelant t.Wait() vous attendez la tâche la plus externe qui revient immédiatement.

La façon la plus correcte de gérer ce scénario consiste à renoncer à utiliser Wait du tout et à await . Wait peut provoquer des problèmes d'interblocage une fois que vous avez attaché une interface utilisateur à votre code asynchrone.

    [Test]
    public async Task TestCorrect() //note the return type of Task. This is required to get the async test 'waitable' by the framework
    {
        await Task.Factory.StartNew(async () =>
        {
            Console.WriteLine("Start");
            await Task.Delay(5000);
            Console.WriteLine("Done");
        }).Unwrap(); //Note the call to Unwrap. This automatically attempts to find the most Inner `Task` in the return type.
        Console.WriteLine("All done");
    }

Encore mieux, utilisez Task.Run pour lancer votre opération asynchrone:

    [TestMethod]
    public async Task TestCorrect()
    {
        await Task.Run(async () => //Task.Run automatically unwraps nested Task types!
        {
            Console.WriteLine("Start");
            await Task.Delay(5000);
            Console.WriteLine("Done");
        });
        Console.WriteLine("All done");
    }



c#  

c#