2013-08-27 4 views
57

Mam ten kod starszego typu:Błąd „Nie ma już otwartą DataReader skojarzony z tym poleceniem, które muszą być zamknięte pierwszy”, gdy za pomocą 2 różnych poleceń

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


stworzyłem dwa oddziela polecenia do skorygowania problem, ale nadal trwa: "Istnieje już otwarty obiekt DataReader skojarzony z tym rozkazem, który musi najpierw zostać zamknięty".

Dodatkowe informacje: ten sam kod działa w innej aplikacji.

+21

Masz podatność na atak SQL injection. – SLaks

+0

możliwy duplikat [Istnieje już otwarty obiekt DataReader powiązany z tym poleceniem, który musi zostać najpierw zamknięty] (http://stackoverflow.com/questions/6062192/there-is-already-an-open-datareader-associated-with- this-command-which-must-be-c) –

+3

To nie tylko polecenie, tylko połączenie. Jeśli używasz tego samego połączenia dla obu poleceń, pojawi się błąd. –

Odpowiedz

7

Sugeruję, aby utworzyć dodatkowe połączenie dla drugiego polecenia, rozwiązałoby to. Spróbuj połączyć oba zapytania w jednym zapytaniu. Utwórz podzapytanie dla liczby.

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

Dlaczego należy przesuwać tę samą wartość raz za razem?

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

Wystarczająco.

+5

tworzenie drugiego połączenia z tą samą bazą danych może rozwiązać problem, ale nie ułatwia odczytu i utrzymania kodu –

+0

+1. W ten sposób mógłby być niezależny od dostawcy serwera, choć do tego musi używać DbConnection zamiast SqlConnection, a klasy Db isnetad dowolnej klasy Sql *. Ale najlepsze jest to, że przepisuje swoje zapytania, tak jak ty (i każda odpowiedź) mówią. –

+0

Mam nadzieję, że to może być pomocne [Odpowiedź] (http://stackoverflow.com/a/6064422/4471013) –

9

Założę problem jest przedstawiony na tej linii

SqlDataReader dr3 = com2.ExecuteReader(); 

Sugeruję, aby wykonać pierwszy czytnik i zrobić dr.Close(); i powtórzyć, historicos, z innej pętli, wykonując 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; 
+1

Myślę, że nie może go zamknąć, ponieważ wtedy nie może go odczytać. –

+0

@CsabaTo dwie pętle jedna do odczytu danych do dados_historico i zamknij czytnik, a następnie kolejną pętlę, aby wykonać drugi czytnik. –

+0

+1 Wiem. I zdaje się, że gromadzi dane w jakiejś tablicy, aby mógł je ponownie wykorzystać. Twoje jest prawdziwym rozwiązaniem, chyba że jest jeszcze lepsze jedno zapytanie. –

18
  1. Optymalnym rozwiązaniem byłoby, aby spróbować przekształcić swoje rozwiązanie do formularza, w którym nie trzeba mieć dwa czytniki otwartych naraz. Idealnie byłoby to pojedyncze zapytanie. Nie mam teraz czasu, aby to zrobić.
  2. Jeśli twój problem jest tak szczególny, że naprawdę potrzebujesz więcej otwartych czytników jednocześnie, a twoje wymagania nie mogą być starsze niż SQL Server 2005, to magiczne słowo to MARS (wiele aktywnych zestawów wyników). http://msdn.microsoft.com/en-us/library/ms345109%28v=SQL.90%29.aspx. Rozwiązanie powiązane z tematem Boba Vale pokazuje, jak go włączyć: określ MultipleActiveResultSets=true w ciągu połączenia. Po prostu mówię to jako interesującą możliwość, ale powinieneś raczej zmienić swoje rozwiązanie.

    • w celu uniknięcia wspomnianej możliwości wstrzyknięcia SQL, należy ustawić parametry do samej instrukcji SQLC zamiast osadzać je w łańcuchu zapytania. Ciąg zapytania powinien zawierać tylko odniesienia do parametrów przekazywanych do SqlCommand.
+0

Csaba Toth czy ustawienie MARS działa na innej bazie danych, która nie jest serwerem MS SQL SERVER? –

+0

Jest to funkcja serwera MS SQL. 2005 i więcej. Inne RDBMS mogą również mieć podobną funkcję, ale specyficzną dla konkretnego typu serwera. –

+1

Mogą być również wady: http://stackoverflow.com/questions/374444/disadvantages-of-mars-multiple-active-resultsets –

3

Spróbuj połączyć zapytanie, to będzie działać znacznie szybciej niż wykonanie dodatkowej kwerendy w każdym wierszu. Ik nie podoba mi się ciąg [], którego używasz, utworzyłbym klasę do przechowywania informacji.

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

Niewygodne, ale maybee daje pewien pomysł.

10

Możesz mieć taki problem, gdy jesteś two different commands na tym samym połączeniu - szczególnie wywołując drugie polecenie w loop. To wywołuje drugie polecenie dla każdego rekordu zwróconego od pierwszego polecenia.Jeśli pierwsza komenda zwróci około 10 000 rekordów, ten problem będzie bardziej prawdopodobny.

Kiedyś unikałem takiego scenariusza, robiąc to jako pojedyncze polecenie. Pierwsze polecenie zwraca wszystkie wymagane dane i wczytuje je do DataTable.

Uwaga: MARS może być rozwiązaniem - ale może być ryzykowne i wielu ludziom się to nie podoba.

referencyjny

  1. What does "A severe error occurred on the current command. The results, if any, should be discarded." SQL Azure error mean?
  2. Linq-To-Sql and MARS woes - A severe error occurred on the current command. The results, if any, should be discarded
  3. Complex GROUP BY on DataTable
+0

key: jeśli wywołanie foreach() na IQueryable (zrobiłeś .Where(), ale nie podążyłeś za pomocą .ToList()), a następnie spróbujesz użyć kontekstu w tej pętli, może to spowodować problem. – mmcrae

189

Wystarczy dodać następujące w ciągu połączenia:

MultipleActiveResultSets=True; 
+3

http://stackoverflow.com/questions/374444/disadvantages-of-mars-multiple-active-resultsets – Amicable

+6

Powinna być zaakceptowaną odpowiedzią. –

1

Dodaj MultipleActiveResultSets=true do części dostawcy ciągu połączenia. Zobacz przykład poniżej:

<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" /> 
Powiązane problemy