2012-07-27 7 views
10

Tengo una clase como esta:¿Cómo puedo poblar una clase de los resultados de una consulta SQL en C#?

public class Product 
{ 
    public int ProductId { get; private set; } 
    public int SupplierId { get; private set; } 

    public string Name { get; private set; } 
    public decimal Price { get; private set; } 
    public int Stock { get; private set; } 
    public int PendingStock { get; private set; } 
} 

que puedo pedir los detalles de mi base de datos de la siguiente manera:

SELECT product_id, supplier_id, name, price, total_stock, pending_stock 
FROM products 
WHERE product_id = ? 

yo no quiero tener que ejecutar manualmente a través de una o DataSetDataTable para establecer los valores.

Estoy seguro de que hay una manera de completar la clase utilizando algún tipo de mecanismo de enlace/asignación, pero lo único que pude encontrar fue para enlazar a los componentes de winforms o usar XAML.

¿Existe algún tipo de atributo que pueda aplicar a mis propiedades/clase para que la clase se rellene automáticamente desde una fila de consulta?

Respuesta

10

He decidido proponer otra respuesta, que en realidad la extensión de la respuesta proporcionada por Alex (así que todos los créditos a él), pero introduce atributos en aras de nombre-columna-2-propiedad-nombre de correlación.

En primer lugar atributo personalizado para sostener que se necesita nombre de la columna:

[AttributeUsage(AttributeTargets.Property, Inherited = true)] 
[Serializable] 
public class MappingAttribute : Attribute 
{ 
    public string ColumnName = null; 
} 

El atributo debe ser aplicado a aquellas propiedades de la clase, que van a ser poblada desde la fila de datos:

public class Product 
{ 
    [Mapping(ColumnName = "product_id")] 
    public int ProductId { get; private set; } 

    [Mapping(ColumnName = "supplier_id")] 
    public int SupplierId { get; private set; } 

    [Mapping(ColumnName = "name")] 
    public string Name { get; private set; } 
    [Mapping(ColumnName = "price")] 
    public decimal Price { get; private set; } 
    [Mapping(ColumnName = "total_stock")] 
    public int Stock { get; private set; } 
    [Mapping(ColumnName = "pending_stock")] 
    public int PendingStock { get; private set; } 
} 

Y el resto va como Alex propuso, excepto que el atributo se usa para recuperar el nombre de columna:

T MapToClass<T>(SqlDataReader reader) where T : class 
{ 
     T returnedObject = Activator.CreateInstance<T>(); 
     PropertyInfo[] modelProperties = returnedObject.GetType().GetProperties(); 
     for (int i = 0; i < modelProperties.Length; i++) 
     { 
      MappingAttribute[] attributes = modelProperties[i].GetCustomAttributes<MappingAttribute>(true).ToArray(); 

      if (attributes.Length > 0 && attributes[0].ColumnName != null) 
       modelProperties[i].SetValue(returnedObject, Convert.ChangeType(reader[attributes[0].ColumnName], modelProperties[i].PropertyType), null); 
     } 
     return returnedObject; 
} 
+0

Terminé escribiendo casi exactamente esto. ¡Gracias por la ayuda! :) – Polynomial

+0

Solo recuerde que la reflexión no es gratuita, por lo que puede ser necesario un poco de almacenamiento en caché al mapear más de unas líneas;) – madd0

+0

Estoy totalmente de acuerdo con madd0 (dije lo mismo en mi otra respuesta). Si desea usar el código en exceso, deberá proporcionar algo de almacenamiento en caché. –

0

A continuación, usted debe utilizar Entity Framework o LINQ to SQL y si usted no desea utilizar eso, entonces será necesario asignar/llenarlo yr auto

más información sobre el marco de la entidad http://msdn.microsoft.com/en-us/data/ef.aspx

más información sobre LINQ a SQL http://msdn.microsoft.com/en-us/library/bb386976.aspx

+0

¿Podría proporcionar un ejemplo rápido para el marco de entidad? He revisado la documentación, pero es muy vaga sobre cómo la usas realmente. – Polynomial

+0

El problema es que la clase tiene que crearse desde la entidad, no puede usar sus propias clases para eso, y de mi conocimiento no hay forma automática de convertir a menos que sea un mapeo manual. – JSantos

2

me gustaría utilizar LINQ to SQL y hacerlo de la siguiente manera:

public class Product 
{ 
    public int ProductId { get; private set; } 
    public int SupplierId { get; private set; } 
    public string Name { get; private set; } 
    public decimal Price { get; private set; } 
    public int Stock { get; private set; } 
    public int PendingStock { get; private set; } 

    public Product(int id) 
    { 
     using(var db = new MainContext()) 
     { 
      var q = (from c in product where c.ProductID = id select c).SingleOrDefault(); 
      if(q!=null) 
       LoadByRec(q);   
     } 
    } 
    public Product(product rec) 
    { 
     LoadByRec(q); 
    } 
    public void LoadByRec(product rec) 
    { 
     ProductId = rec.product_id; 
     SupplierID = rec.supplier_id; 
     Name = rec.name; 
     Price = rec.price; 
     Stock = rec.total_stock; 
     PendingStock = rec.pending_stock; 
    } 
} 
+0

¿Podría agregar alguna explicación a esto? No veo 'db' en ningún lugar del' using', y no hay referencia a la consulta.¿No hay forma de que 'LoadByRec' funcione como un conjunto de atributos en lugar de asignarlos manualmente en un método? – Polynomial

+0

@Poly tiene que insertar un contexto de datos: http://msdn.microsoft.com/en-us/library/bb384470.aspx que en este caso nombraría 'MainContext'. Definitivamente jugar con él, es realmente muy potente y ahorra mucho tiempo –

12

Debe asignar las propiedades usted mismo o usar un ORM (asignador relacional de objetos).

Microsoft proporciona Entity Framework, pero Dapper requiere menos sobrecarga y podría ser una opción viable en función de sus necesidades.

En su caso, el código Dapper sería algo como:

var query = @"SELECT product_id, supplier_id, name, price, total_stock, pending_stock 
FROM products 
WHERE product_id = @id"; 

var product = connection.Query<Product>(query, new { id = 23 }); 

En aras de la exhaustividad, es importante señalar que estoy hablando de Dapper aquí porque los resultados pregunta se refiere a la correlación de SQL a objetos. EF y Linq to SQL también lo harán, pero también harán cosas adicionales, como traducir consultas Linq a sentencias SQL, lo que también podría ser útil.

+2

+1 para Dapper aquí –

1

No existe tal funcionalidad por defecto en .NET Framework sin formato. Podría usar Entity Framework, pero si no es una buena solución para usted, entonces una alternativa sería el mecanismo de reflexión.

  1. crear alguna clase de atributo personalizado que puede contener un nombre de columna para cada propiedad pública de su clase.

  2. Después de recuperar el registro de la base de datos instanciar un objeto de la clase Product y enumerar las propiedades. Para cada propiedad que tenga su atributo personalizado, use SetValue de PropertyInfo para cambiar el valor de acuerdo con el nombre de columna definido en el atributo personalizado.

Tome en consideración lo siguiente:

  • la solución es bastante y la sobrecarga de tareas simples; que sólo tiene sentido si tiene muchas mesas y muchas clases como Product - y desea escribir un código para inicializar automáticamente todos ellos
  • reflexión es una sobrecarga en sí - por lo que sería necesaria alguna almacenamiento en caché en el largo plazo
+0

Esto suena interesante. Voy a echarle un vistazo. Además, ¿cómo podría * no * votar una respuesta de un chico con * L * como su avatar? :) – Polynomial

+0

Gracias. :] Por favor, mire la respuesta de Alex aquí, ya que ha proporcionado el código para lo que propuse aquí, por lo que su respuesta es mucho mejor * excepto * que recomiendo usar atributos personalizados en los campos de 'Clase de producto '. –

+0

Sí, estoy investigando ahora. Saludos :) – Polynomial

4

Si no desea aprovechar un marco ORM (Entity Framework etc.) puede hacerlo a mano:

T MapToClass<T>(SqlDataReader reader) where T : class 
{ 
     T returnedObject = Activator.CreateInstance<T>(); 
     List<PropertyInfo> modelProperties = returnedObject.GetType().GetProperties().OrderBy(p => p.MetadataToken).ToList(); 
     for (int i = 0; i < modelProperties.Count; i++) 
      modelProperties[i].SetValue(returnedObject, Convert.ChangeType(reader.GetValue(i), modelProperties[i].PropertyType), null); 
     return returnedObject; 
} 

que lo utilice como esto:

Product P = new Product(); // as per your example 
using(SqlDataReader reader = ...) 
{ 
while(reader.Read()) { P = MapToClass<Product(reader); /* then you use P */ } 
} 

Lo único que hay que tener en cuenta es el orden de los campos en la consulta (DEBE coincidir con el orden de las propiedades tal como están definidas en su clase).

Todo lo que necesitas hacer es construir la clase, escribir una consulta, y luego se encargará del "mapeo".

ADVERTENCIA utilizo este método mucho y nunca tuvieron ningún problema, pero no funciona correctamente para las clases parciales. Si vienes a modelos parciales, es mucho mejor utilizar un marco ORM de todos modos.

+0

Alex, esto está mal. GetProperties() no garantiza que los objetos PropertyInfo devueltos estén en orden de declaración. –

+0

Uso este método * mucho * y siempre aparecen de la manera correcta ... De todos modos, hacer cumplir el pedido es bastante fácil. Aprovecharé la oportunidad para actualizar el código. – Alex

+0

Lo hago también;] pero la documentación es clara en esto: http://msdn.microsoft.com/en-us/library/aky14axb ¿Cómo se va a hacer cumplir la orden si no tiene asignador de nombre-columna-2- ¿nombre de la propiedad? –

-1
select 'public ' + case DATA_TYPE 
when 'varchar' then 'string' 
when 'nvarchar' then 'string' 
when 'DateTime' then 'DateTime' 
when 'bigint' then 'long' 
else DATA_TYPE end +' '+ COLUMN_NAME + ' {get; set;}' 
from INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME ='YOUR TABLE NAME' ORDER BY ORDINAL_POSITION 
+0

Por favor, edite con más información. Se desalientan las respuestas de solo código y "prueba esto", ya que no contienen contenido que se pueda buscar y no explican por qué alguien debe "probar esto". – abarisone

+0

Esto no responde la pregunta en absoluto. – Polynomial

Cuestiones relacionadas