2009-11-27 19 views
5

En mi aplicación, necesito mostrar una lista de registros devueltos por diferentes procedimientos almacenados. Cada procedimiento de almacenamiento devuelve diferentes tipos de registros (es decir, el número de columnas y el tipo de columna son diferentes).Función C# para devolver objetos/entidades genéricos

Mi idea original era crear una clase para cada tipo de registros y crear una función que ejecutaría el procedimiento almacenado correspondiente y devolver la lista < MyCustomClass>. Algo como esto:

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; 

    } 

Esto funciona bien, pero como yo no necesito manipular esos registros en mi código de cliente (que sólo tiene que unirse a un control gráfico en mi capa de aplicación) que en realidad no tiene sentido hacerlo de esta manera, ya que terminaría con muchas clases personalizadas que realmente no usaría. He encontrado esto lo que hace el truco:

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; 

    } 

Esto devuelve un DataTable que puedo obligar a que sea el control que utilizo en mi capa de aplicación. Me pregunto si realmente se necesita el uso de una DataTable.

No podría devolver una lista de objetos creados desde clases anónimas:

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; 

    } 

alguna otra idea? Básicamente, solo quiero que la función devuelva 'algo' que puedo vincular a un control gráfico y prefiero no tener que crear clases personalizadas, ya que realmente no se usarían. ¿Cuál sería el mejor enfoque?

+1

Por cierto, la mejor práctica para SqlDataReader es el siguiente estilo de uso: using (var rdr = cmd.ExecuteReader() {while (rdr.Read() {..}} - por lo que se cerrará (eliminará) automáticamente – abatishchev

+0

Bien manchado. – Anthony

Respuesta

4

La buena noticia es que esta no es la primera vez que aparece el problema entities vs datasets. Es un debate mucho más antiguo que mi propia experiencia de programación. Si no desea escribir DTO o entidades personalizadas, sus opciones son DataTables/DataSets o puede reinventar esta rueda nuevamente. No serías el primero y no serías el último. El argumento para las Entidades/DTO es que no tienes los gastos generales de rendimiento que obtienes con los DataSets. Los DataSets tienen que almacenar mucha información adicional sobre los tipos de datos de las distintas columnas, etc.

Una cosa que te alegrará es que si vas a la ruta de entidad personalizada y estás contento por tu objeto los nombres de propiedad para que coincidan con los nombres de columna devueltos por sus sprocs, puede omitir la escritura de todas las funciones de asignación GetDataForReport() donde está utilizando GetOrdinal para asignar columnas a las propiedades. Afortunadamente, algunos monos inteligentes han sospechado correctamente el problema here.

EDIT: yo estaba investigando un problema totalmente diferente hoy en día (conjuntos de datos a DataGrids silverlight vinculante) y se encontró con this article por Vladimir Bodurov, que muestra cómo transformar un IEnumerable de IDictionary a un IEnumerable de objetos creados de forma dinámica (usando IL). Se me ocurrió que podía modificar fácilmente el método de extensión para aceptar un lector de datos en lugar de IEnumerable de IDictionary para resolver su problema de colecciones dinámicas. Es genial. Creo que lograría exactamente lo que buscabas en cuanto a que ya no necesitas el conjunto de datos o las entidades personalizadas.En efecto, terminas con una colección de entidades personalizada pero pierdes la sobrecarga de escribir las clases reales.

Si usted es perezoso, aquí hay un método que convierte un datareader en Colección de diccionarios de Vladimir (que es menos eficiente que en realidad la conversión de su método de extensión):

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

Veo lo que quiere decir, pero mi pregunta, creo, está en algún punto intermedio. No es solo 'debería devolver una lista de entidades personalizadas o un conjunto de datos'. También mencioné que podría devolver una lista de objetos genéricos (List < object >). De esta forma no será una lista de entidades personalizadas, pero tampoco será un conjunto de datos. – Anthony

+0

Si usa .net 4, es posible que pueda usar una lista . Wher Las dinámicas son objetos Expando reales (http://bit.ly/TDzbN). – grenade

+0

Gracias por el enlace al artículo. Parece que es exactamente lo que necesito y realmente proporcionaría una solución que no es 'entidad o conjunto de datos'. No estoy usando .net 4, lamentablemente, pero voy a mantener esta opinión. – Anthony

1

Si va a XmlSerializarlos y quiere que sean eficientes, no pueden ser anónimos. ¿Vas a devolver los cambios? Clases anónimas no serán de mucha utilidad para eso.

Si realmente no va a hacer nada con los datos que no sean la cuadrícula, una DataTable puede ser una buena respuesta para su contexto.

En general, si hay algún tipo de inicio de sesión comercial interesante, debe utilizar Objetos de transferencia de datos, etc. en lugar de simplemente transportar alrededor de las cuadrículas.

+0

No se van a cambiar los datos. Si uso un Data Tansfer Object, ¿no es lo mismo que usar una clase personalizada? Tendré que crear un DTO para cada tipo de registro – Anthony

0

Si utiliza clases anónimas, que todavía están definiendo cada uno tipo. Solo tiene que usar Type Inference para reducir la cantidad de tipeo y el tipo de clase no saturará ningún espacio de nombres.

La desventaja de usarlos aquí es que desea devolverlos fuera del método. Como ha notado, la única forma de hacerlo es enviarlos a objetos, lo que hace que todos sus datos sean inaccesibles sin reflexión. ¿Nunca va a hacer ningún procesamiento o cálculo con esta información? ¿Muestra una de las columnas como una imagen en lugar de un valor, u ocultarla en función de alguna condición? Incluso si solo va a tener una sola declaración condicional, hacerlo con la versión de clase anónima hará que desearía no haberlo hecho.