2012-12-31 6 views
6

Ich habe einige Klassen, die Datenbanktabellen darstellen, um die Zeilen jeder Tabelle auf einem DataGridView zu laden, habe ich eine List<> Funktion, die innerhalb einer Schleife alle Zeilen aus dieser Tabelle ruft.Wie dynamisch Typ der Liste <> Funktion angeben?

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

Und für andere Tabellen, einige andere Funktionen: list_rows_table2, list_rows_table3 ... Meine Frage ist: Wie erstelle ich eine einzige List<> Funktion, wo ich dynamisch den Typ der List<> zurück angeben können, oder wie man konvertieren, zum Beispiel ein List<object> zu List<myClass> vor der Rückkehr.

+3

Dies ist im Grunde, was ein ORM tut. Warum nicht ein ORM verwenden? Entity Framework funktioniert ziemlich gut, wir verwenden es in einer großen LOB-Anwendung mit mehr als 400 Kunden, die eine SAAS-Anwendung (mit jeweils mehr als 3 Computern) ausführen, und die Serverseite wird auf unseren Servern gehostet. –

+2

Werfen Sie einen Blick auf [ValueInjecter] (http://valueinjecter.codeplex.com/) und speziell auf [dieses Beispiel] (http://goo.gl/mD5OG), dies ordnet einen Datenleser einer Liste von Domänenobjekten zu so wie du es machen willst. Regardas und ein gutes neues Jahr! – Hugo

Antwort

1

Oliviers Umsetzung ist gut. Es verwendet Generics und Interfaces, die jeder Entity eine eigene Implementierung von FillFromDataReader() geben.

Sie können es weiter bringen. Durch Verwendung der Konvention kann der gesamte Datenhydrierungscode zentralisiert und abstrahiert werden.

Ich gehe davon aus, dass Ihre Klasseneigenschaftsnamen und Ihre Spaltennamen identisch sind. Wenn dies nicht der Fall ist, kann der folgende Code so erweitert werden, dass den Eigenschaftsnamen Alias-Attribute hinzugefügt werden. Manchmal wird eine Eigenschaft aus anderen Werten im Objekt berechnet, diese Eigenschaft kann nicht hydriert werden. Ein Ignore-Attribut kann in der folgenden Klasse erstellt und implementiert werden.

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

Es gibt ein paar Vorbehalte im obigen Code. DBNulls und NULL-Typen sind Sonderfälle und erfordern benutzerdefinierten Code, um mit ihnen umzugehen. Normalerweise konvertiere ich DBNull in null. Ich bin nie auf einen Fall gestoßen, in dem ich den Unterschied zwischen den beiden unterscheiden musste. Für Nullalbe-Typen, erkennen Sie einfach den Nullable-Typ und behandeln den Code entsprechend.

Ein ORM würde viel von dem Kopfschmerz beim Umgang mit Datenzugriff entfernen. Der Nachteil ist, dass Sie oft mit den DTOs und dem Datenbankschema verbunden sind. Natürlich kann dieses Problem durch die Verwendung von Abstraktionen eingedämmt werden.Viele Unternehmen verwenden immer noch streng gespeicherte Prozeduren, die meisten ORMs fallen herunter, wenn sie gezwungen werden, gespeicherte Prozeduren zu verwenden. Sie sind nicht entworfen, um mit gespeicherten Prozeduren zu arbeiten.

Ich schrieb ein Datenzugriffs-Framework namens "Hypersonic." Es ist auf GitHub, es wurde speziell entwickelt, um mit gespeicherten Prozeduren zu arbeiten. Der obige Code ist eine leichte Implementierung des it.

+1

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

+1

Probleme mit dem Code in dieser Antwort: 1. SQLConnection wird nicht ordnungsgemäß in einem "using" oder "try/finally" angeordnet. 2. Warum verwendet es "reader.GetValue (reader.GetOrdinal (column))" anstelle von reader [column]? 3. SetValue verwendet Reflektion, um den Wert festzulegen. Wenn Sie eine große Anzahl von Elementen haben, wird dies sehr langsam sein. 4. GetColumnNames sollte nur typeof (T) anstelle von new T() verwenden. GetType(). –

7

Sie könnten eine Schnittstelle, die alle Ihre Datenklassen

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

dann Ihre Methode ändern wie diese

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

Also im Grunde jede Klasse ihre eigenen Felder für die Befüllung wäre verantwortlich implementieren müssen.

Die Einschränkung where T : IData, new() für den generischen Typparameter ist entscheidend. Er teilt der Methode mit, dass T die Schnittstelle IData implementieren muss. Dies ist notwendig, um die Methode FillFromReader ohne Casting aufrufen zu können. Die Datenklassen müssen einen Standard-Konstruktor haben (dies wird durch new() angegeben ist. Dieses Sie ein mit new T() instanziiert ermöglicht.


ich den Code umgeben die Verbindung über den Befehl und die Datenleser mit using Aussagen. Die using Anweisung schließt und gibt die Ressourcen automatisch am Ende des Blocks und stellt sicher, dass dies der Fall ist, auch wenn eine Ausnahme ausgelöst werden soll oder der Anweisungsblock sollte vorzeitig mit einer Rückkehr-Anweisung zum Beispiel gelassen werden.

Siehe using Statement (C# Reference)

Verwandte Themen