2012-12-31 14 views
6

Mam kilka klas reprezentujących tabele bazy danych, aby załadować wiersze z każdej tabeli na DataGridView, Mam funkcję List<>, że wewnątrz pętli pobiera wszystkie wiersze z tej tabeli.Jak dynamicznie określać typ funkcji List <>?

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

A dla innych tabel, niektóre inne funkcje: list_rows_table2, list_rows_table3 ... Moje pytanie brzmi: Jak mogę utworzyć tylko List<> funkcję, gdzie mogę dynamicznie określić typ List<> wrócił, czyli jak przekonwertuj, na przykład na List<object> na List<myClass> przed powrotem.

+3

To jest zasadniczo to, co robi ORM. Dlaczego nie skorzystać z ORM? Entity Framework działa całkiem nieźle, używamy go w dużej aplikacji LOB z ponad 400 klientami korzystającymi z aplikacji SAAS (z średnio ponad 3 komputerami), a serwer jest hostowany na naszych serwerach. –

+2

Spójrz A [ValueInjecter] (http://valueinjecter.codeplex.com/) i specjalnie [danego przykład] (http://goo.gl/mD5OG) Mapuje czytnika danych do Lista obiektów domeny w taki sposób, w jaki chcesz to zrobić. Regardas i szczęśliwego nowego roku! – Hugo

Odpowiedz

1

Implementacja Oliviera jest dobra. Używa generycznych i interfejsów, dając każdemu podmiotowi własną implementację FillFromDataReader().

Możesz go zabrać dalej. Używając konwencji, cały kod nawodnienia danych może zostać scentralizowany i usunięty.

Zamierzam założyć, że nazwy klas i nazwy kolumn są takie same. Jeśli tak nie jest, można rozszerzyć poniższy kod, aby dodać atrybuty aliasów do nazw właściwości. Czasami własność jest obliczana na podstawie innych wartości w obiekcie, ta właściwość nie może być uwodniona. Atrybut Ignoruj ​​można utworzyć i zaimplementować w poniższej klasie.

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

W powyższym kodzie znajduje się kilka zastrzeżeń. Typy DBNulls i Nullable są przypadkami specjalnymi i będą wymagać niestandardowego kodu, aby sobie z nimi poradzić. Zwykle konwertuję DBNull na null. Nigdy nie spotkałem się z przypadkiem, w którym potrzebowałem rozróżnić różne między nimi. W przypadku typów Nullalbe wystarczy wykryć typ Nullable i odpowiednio obsłużyć kod.

ORM może usunąć wiele problemów związanych z dostępem do danych. Minusem jest to, że wiele razy jesteś połączony ze schematem DTO i schematem bazy danych. Oczywiście problem ten można ograniczyć za pomocą abstrakcji.Wiele firm nadal stosuje ściśle przechowywane procedury, większość ORM spada, gdy są zmuszeni do korzystania z procedur przechowywanych. Po prostu nie są zaprojektowane do pracy z procedurami przechowywanymi.

pisałem ramy dostępu do danych o nazwie „Hypersonic”. Jest na GitHub, jest specjalnie zaprojektowany do pracy z procedurami przechowywanymi. Powyższy kod jest lekką implementacją tego kodu.

+1

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

+1

problemy z kodem, w tym odpowiedzi: 1. SQLConnection nie są umieszczone właściwie w „używając” lub „próby/w końcu”. 2. Dlaczego używa "reader.GetValue (reader.GetOrdinal (column))" zamiast czytnika [kolumna]? 3. SetValue używa odbicia, aby ustawić wartość. Jeśli masz dużą liczbę przedmiotów, będzie to bardzo powolne. 4. Funkcja GetColumnNames powinna po prostu używać typeof (T) zamiast nowej T(). GetType(). –

7

Można mieć interfejs, który wszystkie swoje zajęcia danych musi implementować

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

Następnie zmień metodę jak to

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

Więc w zasadzie każda klasa będzie odpowiedzialny za wypełnianie własnych pól. Niezbędne jest ograniczenie where T : IData, new() dla ogólnego parametru typu. Mówi ona o tym, że T musi implementować interfejs IData. Jest to konieczne, aby móc wywoływać metodę bez odlewania. Klasy danych muszą mieć domyślnego konstruktora (ta jest określona przez new(). To pozwala na instancję jednego z new T().


I otoczony kodu przy użyciu połączenia, polecenia i czytnik danych z using wypowiedzi. using zestawienie zamyka i zwalnia zasoby automatycznie na końcu bloku i zapewnia, że ​​to się dzieje, nawet jeśli wyjątek powinien być rzucony lub blok oświadczenie powinno być pozostawione przedwcześnie z powrotnym-rachunku np.

Zobacz using Statement (C# Reference)

Powiązane problemy