2015-04-25 5 views
6

Sto utilizzando Ado per recuperare un singolo record tramite id. Osservare:Ottenere un singolo record da SQL Server nel modo corretto

public async Task<Image> GetImage(int id) 
{ 
    var image = new Image(); 

    using (SqlConnection conn = new SqlConnection(ConnectionString)) 
    { 
     conn.Open(); 

     string sql = @" SELECT * FROM Images where id = @id"; 

     using (SqlCommand comm = new SqlCommand(sql, conn)) 
     { 
      comm.Parameters.AddWithValue("@id", id); 

      var reader = await comm.ExecuteReaderAsync(); 

      int ordId = reader.GetOrdinal("id"); 
      int ordName = reader.GetOrdinal("name"); 
      int ordPath = reader.GetOrdinal("path"); 

      while (reader.Read()) 
      { 
       image.Id = reader.GetInt32(ordId); 
       image.Name = reader.GetString(ordName); 
       image.Path = reader.GetString(ordPath); 
      } 

      return image; 
     } 
    } 
} 

Come si vede Sto usando mentre per scorrere tra i record. Dal momento che è significativo che ci sia più di un record per iterare, credo che questo possa essere il modo sbagliato per ottenere un singolo record. Considerando che ADO ha ExecuteScalar per un campo di una riga, è possibile che abbiano un modo specifico per campi multipli di una riga. Esiste un modo specifico per ottenere un singolo record in ADO?

+0

Ovviamente funzionerà così com'è, ma per chiarire a chi leggerà il codice sorgente è possibile sostituire 'while (reader.Read())' con 'if (reader.Read())' e (eventualmente aggiungi 'TOP 1' al tuo' SELECT', nel caso in cui SQL Server abiliti l'ottimizzazione della query a causa di ciò). Date anche un'occhiata a LINQ '.Single()' implementazione (per SQL o EF) per qualche ispirazione. –

+0

Il metodo ExecuteScalar è solo un metodo di convenienza che restituisce la prima colonna della prima riga del primo gruppo di risultati. Quindi, se puoi fare la stessa cosa per più colonne eseguendo Leggi una volta, verifica un risultato vero e chiudi immediatamente il lettore. –

risposta

9

Vorrei andare con il vostro approccio attuale, tranne che eliminerei il ciclo while. Se si desidera assicurarsi che venga restituito un solo record, eseguire un ulteriore Read per assicurarsi che restituisca false. Questo è simile alla semantica dell'operatore LINQ Single.

if (!reader.Read())   
    throw new InvalidOperationException("No records were returned."); 

image.Id = reader.GetInt32(ordId); 
image.Name = reader.GetString(ordName); 
image.Path = reader.GetString(ordPath); 

if (reader.Read()) 
    throw new InvalidOperationException("Multiple records were returned."); 

Supponendo che la colonna id nel database è una chiave primaria (unico), non c'è bisogno di specificare una clausola TOP nella query SQL; l'ottimizzatore di query di SQL Server dedurrebbe che solo al massimo un record viene restituito a causa della clausola WHERE. Tuttavia, se non si dispone di una chiave primaria o di un indice/vincolo univoci sulla colonna id, è necessario immettere una clausola TOP (2) per limitare il numero di righe restituite. Evita di usare TOP (1) perché non sarai in grado di rilevare (e generare un errore per) corrispondenze extra.

string sql = @"SELECT TOP (2) * FROM Images WHERE id = @id" 
+0

'TOP' senza un' ORDER BY' è discutibile - quale "top 2" stai ottenendo? A meno che tu ** definisca esplicitamente ** un 'ORDER BY', l'ordinamento (e quindi le" prime due righe ") sia arbitrario .... –

+2

@marc_s: Non ti importa quali due stai ricevendo, a lungo come sei in grado di confermare che stai, in effetti, ottenendo due. (Inoltrare comunque un'eccezione e scartare il loro contenuto.) Entity Framework utilizza la stessa nozione quando si converte 'Single()' in SQL. – Douglas

2

È possibile utilizzare Top(1) in questo caso nella query per ottenere solo record singolo del database:

SELECT Top(1) * FROM Images 
where id = @id 
order by id desc -- will get the latest record 
+2

'TOP' senza un' ORDER BY' è discutibile - quale "top 1" stai ottenendo? A meno che tu ** definisca esplicitamente ** un 'ORDER BY', l'ordinamento (e quindi la" prima riga ") è arbitrario .... –

+2

@marc_s Hai persuaso il povero ragazzo ad aggiungere un ordine ridondante per clausola alla sua domanda! – cja

+0

@cja. è ** NON ** ridondante .... –

4

Cosa se hai appena letto una volta:

using (SqlConnection conn = new SqlConnection(ConnectionString)) 
{ 
    conn.Open(); 

    string sql = @" SELECT id, name, path FROM Images where id = @id"; 

    using (SqlCommand comm = new SqlCommand(sql, conn)) 
    { 
     comm.Parameters.AddWithValue("@id", id);   

     using (var reader = await comm.ExecuteReaderAsync()) 
     { 
      if (!reader.Read()) 
       throw new Exception("Something is very wrong"); 

      int ordId = reader.GetOrdinal("id"); 
      int ordName = reader.GetOrdinal("name"); 
      int ordPath = reader.GetOrdinal("path"); 

      image.Id = reader.GetInt32(ordId); 
      image.Name = reader.GetString(ordName); 
      image.Path = reader.GetString(ordPath); 

      return image; 
     } 
    } 
} 

PS: Ho anche modificato l'istruzione select per selezionare solo i campi richiesti e il lettore spostato nell'utilizzo dell'istruzione.