2009-11-27 17 views
5

Nella mia applicazione ho bisogno di visualizzare un elenco di record restituiti da diverse stored procedure. Ogni procedura di archiviazione restituisce diversi tipi di record (ovvero il numero di colonne e il tipo di colonna sono diversi).Funzione C# per restituire oggetti/entità generici

Il mio pensiero originale era quello di creare una classe per ogni tipo di record e creare una funzione che eseguisse la stored procedure corrispondente e restituisse l'elenco < MyCustomClass>. Qualcosa di simile a questo:

public class MyCustomClass1 
    { 
     public int Col1 { get; set; } //In reality the columns are NOT called Col1 and Col1 but have proper names 
     public int Col2 { get; set; } 
    } 

    public static List<MyCustomClass1> GetDataForReport1(int Param1) 
    { 

     List<MyCustomClass1> output = new List<MyCustomClass1>(); 

     using (SqlConnection cn = new SqlConnection("MyConnectionString")) 
     using (SqlCommand cmd = new SqlCommand("MyProcNameForReport1", cn)) 
     { 
      cmd.CommandType = CommandType.StoredProcedure; 
      cmd.Parameters.Add("@Param1", SqlDbType.Int).Value = Param1; 

      SqlDataReader rdr=cmd.ExecuteReader(); 

      int Col1_Ordinal = rdr.GetOrdinal("Col1"); 
      int Col2_Ordinal = rdr.GetOrdinal("Col2"); 

      while (rdr.Read()) 
      { 
         output.Add(new MyCustomClass1 
         { 
          Col1 = rdr.GetSqlInt32(Col1_Ordinal).Value, 
          Col2 = rdr.GetSqlInt32(Col2_Ordinal).Value 
         }); 
      } 
      rdr.Close(); 
      } 

     return output; 

    } 

Questo funziona bene, ma come non ho bisogno di manipolare i record nel mio codice cliente (ho solo bisogno di legarli ad un controllo grafico nel mio livello di applicazione) in realtà non ha senso farlo in questo modo, in quanto finirei con un sacco di classi personalizzate che non utilizzerei in realtà. Ho trovato questo, che fa il trucco:

public static DataTable GetDataForReport1(int Param1) 
    { 

     DataTable output = new DataTable(); 

     using (SqlConnection cn = new SqlConnection("MyConnectionString")) 
     using (SqlCommand cmd = new SqlCommand("MyProcNameForReport1", cn)) 
     { 
      cmd.CommandType = CommandType.StoredProcedure; 
      cmd.Parameters.Add("@Param1", SqlDbType.Int).Value = Param1; 

      output.Load(cmd.ExecuteReader()); 
     } 

     return output; 

    } 

Ciò restituisce un DataTable cui posso associare a qualsiasi controllo che uso nel mio livello di applicazione. Mi chiedo se l'utilizzo di un DataTable sia davvero necessario.

Non potrei restituire un elenco di oggetti creata usando classi anonime:

public static List<object> GetDataForReport1(int Param1) 
    { 

     List<object> output = new List<object>(); 

     using (SqlConnection cn = new SqlConnection("MyConnectionString")) 
     using (SqlCommand cmd = new SqlCommand("MyProcNameForReport1", cn)) 
     { 
      cmd.CommandType = CommandType.StoredProcedure; 
      cmd.Parameters.Add("@Param1", SqlDbType.Int).Value = Param1; 

      SqlDataReader rdr=cmd.ExecuteReader(); 

      int Col1_Ordinal = rdr.GetOrdinal("Col1"); 
      int Col2_Ordinal = rdr.GetOrdinal("Col2"); 

      while (rdr.Read()) 
      { 
         output.Add(new 
         { 
          Col1 = rdr.GetSqlInt32(Col1_Ordinal).Value, 
          Col2 = rdr.GetSqlInt32(Col2_Ordinal).Value 
         }); 
      } 
      rdr.Close(); 
      } 

     return output; 

    } 

altre idee? Fondamentalmente voglio solo che la funzione restituisca "qualcosa" che posso associare a un controllo grafico e preferisco non dover creare classi personalizzate in quanto non verrebbero effettivamente utilizzate. Quale sarebbe l'approccio migliore?

+1

btw, la migliore procedura per SqlDataReader è il seguente stile di utilizzo: using (var rdr = cmd.ExecuteReader() {while (rdr.Read() {..}} - quindi verrà chiuso (disposto) automaticamente – abatishchev

+0

Ben individuato ... – Anthony

risposta

4

La buona notizia è: non è la prima volta che si presenta il problema entities vs datasets. È un dibattito molto più antico della mia esperienza di programmazione. Se non vuoi scrivere DTO o entità personalizzate allora le tue opzioni sono DataTable/DataSet o potresti reinventare nuovamente questa ruota. Non saresti il ​​primo e non sarai l'ultimo. L'argomento per DTO/Entities è che non si hanno i costi generali delle prestazioni che si ottengono con DataSet. I DataSet devono memorizzare molte informazioni aggiuntive sui tipi di dati delle varie colonne, ecc ...

Una cosa, si potrebbe essere felici di sentire che se si va il percorso dell'entità personalizzata e si è contenti per il proprio oggetto i nomi delle proprietà per abbinare i nomi delle colonne restituiti dai tuoi sproc, puoi saltare tutte le funzioni di mappatura GetDataForReport() dove stai usando GetOrdinal per mappare le colonne alle proprietà. Fortunatamente, alcune scimmie intelligenti hanno correttamente analizzato il problema here.

EDIT: ero alla ricerca di un problema del tutto diverso oggi (insiemi di dati a DataGrid Silverlight vincolante) e mi sono imbattuto this article da Vladimir Bodurov, che mostra come trasformare un IEnumerable di IDictionary a un oggetto IEnumerable di oggetti creati dinamicamente (utilizzando IL). Mi è venuto in mente che è possibile modificare facilmente il metodo di estensione per accettare un datareader anziché l'IEnumerable di IDictionary per risolvere il problema delle raccolte dinamiche. È molto bello. Penso che otterrebbe esattamente ciò che cercavi, dal momento che non hai più bisogno né del set di dati né delle entità personalizzate.In effetti finisci con una collezione di entità personalizzate ma perdi il sovraccarico di scrivere le classi attuali.

Se siete pigri, ecco un metodo che trasforma un DataReader nella collezione dizionario di Vladimir (è meno efficiente che in realtà la conversione il suo metodo di estensione):

public static IEnumerable<IDictionary> ToEnumerableDictionary(this IDataReader dataReader) 
{ 
    var list = new List<Dictionary<string, object>>(); 
    Dictionary<int, string> keys = null; 
    while (dataReader.Read()) 
    { 
     if(keys == null) 
     { 
      keys = new Dictionary<int, string>(); 
      for (var i = 0; i < dataReader.FieldCount; i++) 
       keys.Add(i, dataReader.GetName(i)); 
     } 
     var dictionary = keys.ToDictionary(ordinalKey => ordinalKey.Value, ordinalKey => dataReader[ordinalKey.Key]); 
     list.Add(dictionary); 
    } 
    return list.ToArray(); 
} 
+0

Vedo cosa intendi, ma la mia domanda, penso, è un punto intermedio. Non è solo "dovrei restituire un elenco di entità personalizzate o un set di dati" .Sentevo anche che potevo restituire un elenco di oggetti generici (Lista < object >). In questo modo non sarà un elenco di entità personalizzate ma non sarà un set di dati. – Anthony

+0

Se stai usando .net 4 potresti essere in grado di usare un elenco . Le tue dinamiche sono oggetti Expando attuali (http://bit.ly/TDzbN). – grenade

+0

Grazie per il link all'articolo. Sembra che sia esattamente ciò di cui ho bisogno e fornirebbe davvero una soluzione che non sia "entità o set di dati". Sfortunatamente non sto usando .net 4, ma terrò questa idea. – Anthony

1

Se si sta per XmlSerializzare e volere che sia efficiente, non possono essere anonimi. Hai intenzione di rispedire le modifiche? Le classi anonime non saranno molto utili per questo.

Se non si ha intenzione di fare qualcosa con i dati diversi dalla griglia, un DataTable potrebbe essere una buona risposta per il proprio contesto.

In generale, se è in atto un qualsiasi tipo di accesso aziendale interessante, è necessario utilizzare gli oggetti di trasferimento dati ecc. Anziché limitarsi alle griglie.

+0

Nessun dato non verrà modificato. Se utilizzo un oggetto Data Tansfer, non è lo stesso che usare una classe personalizzata? Dovrò creare un DTO per ogni tipo di record – Anthony

0

Se si utilizzano le classi anonime, si sta ancora definendo ogni genere. Devi solo usare Type Inference per ridurre la quantità di digitazione, e il tipo di classe non ingombrerà nessuno spazio dei nomi.

Lo svantaggio di usarli qui è che si desidera restituirli al di fuori del metodo. Come hai notato, l'unico modo per farlo è di lanciarli sugli oggetti, il che rende inaccessibili tutti i loro dati senza riflettere! Non eseguirai mai alcuna elaborazione o calcolo con questi dati? Visualizza una delle colonne come immagine invece di un valore o nasconderla in base a qualche condizione? Anche se avrai solo una singola istruzione condizionale, farlo con la versione anonimo della classe ti farà desiderare di non averlo fatto.