2012-12-31 16 views
6

Ho alcune classi che rappresentano tabelle di database, per caricare le righe di ogni tabella su un DataGridView, ho una funzione List<> che all'interno di un ciclo ottiene tutte le righe da quella tabella.Come specificare dinamicamente il tipo di funzione Lista <>?

public List<class_Table1> list_rows_table1() 
{ 
    // class_Table1 contains each column of table as public property 
    List<class_Table1> myList = new List<class_Table1>(); 

    // sp_List_Rows: stored procedure that lists data 
    // from Table1 with some conditions or filters 
    Connection cnx = new Connection; 
    Command cmd = new Command(sp_List_Rows, cnx); 

    cnx.Open; 
    IDataReader dr = cmd.ExecuteReader(); 

    while (dr.Read()) 
    { 
     class_Table1 ct = new class_Table1(); 

     ct.ID = Convert.ToInt32(dr[ID_table1]); 
     ct.Name = dr[name_table1].ToString(); 
     //... all others wanted columns follow here 

     myList.Add(ct); 
    } 
    dr.Close(); 
    cnx.Close(); 

    // myList contains all wanted rows; from a Form fills a dataGridView 
    return myList(); 
} 

E per altre tabelle, alcune altre funzioni: list_rows_table2, list_rows_table3 ... La mia domanda è: come si crea un solo List<> funzione, dove posso specificare dinamicamente il tipo di List<> restituito, o come convertire, ad esempio, un List<object> a List<myClass> prima di tornare.

+3

Questo è fondamentalmente ciò che fa un ORM. Perché non usare un ORM? Entity Framework funziona piuttosto bene, lo stiamo utilizzando in una grande applicazione LOB con oltre 400 clienti che eseguono un'applicazione SAAS (con almeno 3 computer di cui ciascuno) e il lato server è ospitato nei nostri server. –

+2

Dai uno sguardo a [ValueInjecter] (http://valueinjecter.codeplex.com/) e specialmente a [questo esempio] (http://goo.gl/mD5OG), questo mappa un lettore di dati in un elenco di oggetti di dominio nel modo in cui vuoi farlo. Regardas e felice anno nuovo! – Hugo

risposta

1

L'implementazione di Olivier è buona. Usa generici e interfacce dando a ciascuna entità la propria implementazione di FillFromDataReader().

Si può prendere più lontano. Utilizzando la convenzione, tutti i dati relativi al codice di idratazione possono essere centralizzati e astratti.

Ho intenzione di assumere che i nomi delle proprietà di classe e i nomi delle colonne siano gli stessi. Se non lo sono, è possibile estendere il seguente codice per aggiungere gli attributi alias ai nomi di proprietà. A volte una proprietà viene calcolata da altri valori nell'oggetto, questa proprietà non può essere idratata. Un attributo Ignore può essere creato e implementato nella classe sottostante.

public class DataAccess 
{ 
    /// <summary> 
    /// Hydrates the collection of the type passes in. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="sql">The SQL.</param> 
    /// <param name="connection">The connection.</param> 
    /// <returns>List{``0}.</returns> 
    public List<T> List<T>(string sql, string connection) where T: new() 
    { 
     List<T> items = new List<T>(); 

     using (SqlCommand command = new SqlCommand(sql, new SqlConnection(connection))) 
     { 
      string[] columns = GetColumnsNames<T>(); 
      var reader = command.ExecuteReader(CommandBehavior.CloseConnection); 

      while (reader.Read()) 
      { 
       T item = new T(); 

       foreach (var column in columns) 
       { 
        object val = reader.GetValue(reader.GetOrdinal(column)); 
        SetValue(item, val, column); 
       } 

       items.Add(item); 
      } 

      command.Connection.Close(); 

     } 

     return items; 
    } 

    /// <summary> 
    /// Sets the value. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <param name="item">The item.</param> 
    /// <param name="value">The value.</param> 
    /// <param name="column">The column.</param> 
    private void SetValue<T>(T item, object value, string column) 
    { 
     var property = item.GetType().GetProperty(column); 
     property.SetValue(item, value, null); 
    } 

    /// <summary> 
    /// Gets the columns names. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <returns>System.String[][].</returns> 
    private string[] GetColumnsNames<T>() where T : new() 
    { 
     T item = new T(); 

     return (from i in item.GetType().GetProperties() 
       select i.Name).ToArray(); 
    } 
} 

Ci sono un paio di avvertimenti nel codice sopra. I tipi DBNull e Nullable sono casi speciali e richiedono un codice personalizzato per gestirli. Di solito converto DBNull a null. Non mi sono mai imbattuto in un caso in cui dovevo distinguere il diverso tra i due. Per i tipi Nullalbe, è sufficiente rilevare il tipo Nullable e gestire il codice di conseguenza.

Un ORM rimuoverebbe gran parte del problema relativo alla gestione dell'accesso ai dati. Lo svantaggio è che molte volte si è accoppiati al DTO e allo schema del database. Ovviamente questo problema può essere contenuto usando le astrazioni.Molte aziende usano ancora procedure rigorosamente archiviate, la maggior parte delle ORM cadono quando sono costrette a consumare stored procedure. Non sono progettati per funzionare con le stored procedure.

Ho scritto un quadro di accesso ai dati denominato "Hypersonic". È su GitHub, è specificamente progettato per funzionare con stored procedure. Il codice sopra è una leggera implementazione di esso.

+1

[Hydration] (http://stackoverflow.com/a/4929478/880990) –

+1

Problemi con il codice in questa risposta: 1. SQLConnection non viene smaltito correttamente in un "using" o "try/finally". 2. Perché sta utilizzando "reader.GetValue (reader.GetOrdinal (column))" invece di reader [column]? 3. SetValue utilizza la riflessione per impostare il valore. Se hai un numero elevato di oggetti, questo sarà molto lento. 4. GetColumnNames dovrebbe usare solo typeof (T) invece di new T(). GetType(). –

7

si potrebbe avere un'interfaccia che tutte le classi di dati devono implementare

public interface IData 
{ 
    void FillFromReader(IDataReader dr); 
} 

poi cambiare metodo come questo

public List<T> GetList<T>(string sqlText) 
    where T : IData, new() 
{ 
    List<T> myList = new List<T>(); 

    using (Connection cnx = new Connection(connString)) 
    using (Command cmd = new Command(sqlText, cnx)) { 
     cnx.Open(); 
     using (IDataReader dr = cmd.ExecuteReader()) { 
      while (dr.Read()) 
      { 
       T item = new T(); 
       item.FillFromReader(dr); 
       myList.Add(item); 
      } 
     } 
    } 
    return myList(); 
} 

Quindi, in pratica ogni classe avrebbe il compito di riempire i propri campi.

Il vincolo where T : IData, new() per il parametro di tipo generico è fondamentale. Indica al metodo che T deve implementare l'interfaccia IData. Questo è necessario per poter chiamare il metodo FillFromReader senza eseguire il casting. Le classi di dati devono avere un costruttore di default (questo è specificato da new(). Ciò consente di un'istanza uno con new T().


ho circondato il codice utilizzando la connessione, il comando e il lettore di dati con using dichiarazioni. Il using dichiarazione si chiude e rilascia automaticamente le risorse alla fine del blocco e assicura che questo accade, anche se un'eccezione dovrebbe essere gettato o il blocco di istruzioni dovrebbe essere lasciato prematuramente con un ritorno-dichiarazione per esempio.

Vedi using Statement (C# Reference)