Introducción
En este articulo analizaremos como trabajar con los datos procedentes de un datatable, realizando operaciones por medio de la ayuda de linq, y luego confeccionado un datatable distinto, cuya estructura de campos difiere de la original.
Para esta operación se contara con un datatable con documento de clientes, y un campo total que informa cuantos de estos se tienen registrados.
La idea es obtener en otro datatable la cantidad total agrupando todos los clientes.
Agrupar con Linq
Para esta operación se realizaría una agrupación mediante la funcionalidad que provee linq.
private void button1_Click(object sender, EventArgs e) { IEnumerable<IGrouping<string, DataRow>> query = from item in Datos().AsEnumerable() group item by item["Documento"].ToString() into g select g; DataTable resultado = Transformar(query); dataGridView1.AutoGenerateColumns = true; dataGridView1.DataSource = resultado; }
Como se observa en la query de linq se agrupa por medio del campo “Documento”, y el resultado de esta operación se alojo en “g”, valor que es devuelto en la consulta.
Si bien podría haberse igualado toda la consulta a una variable definida como “var”, esto hubiera imposibilitado el pasaje de la información a un método de transformación, para la creación del nuevo datatable, es por eso que se crea el IEnumerable<>, que contiene el IGrouping<> también genérico.
Creación del nuevo DataTable
En esta operación en una primera implementación es algo manual, ya que toma el resultado de la consulta linq e itera para cargar el nuevo datatable, y en su camino realiza las operaciones de calculo.
private DataTable Transformar(IEnumerable<IGrouping<string, DataRow>> datos) { // // Se define la estructura del DataTable // DataTable dt = new DataTable(); dt.Columns.Add("Documento"); dt.Columns.Add("CantRegistros"); dt.Columns.Add("Total"); // // Se recorre cada valor agruparo por linq y se vuelca el resultado // en un nuevo registro del datatable // foreach (IGrouping<string, DataRow> item in datos) { DataRow row2 = dt.NewRow(); row2["Documento"] = item.Key; row2["CantRegistros"] = item.Count(); row2["Total"] = item.Sum<DataRow>(x => Convert.ToInt32(x["Total"])); dt.Rows.Add(row2); } return dt; }
Debe observarse como se trabaja con los métodos extendidos, y en ellos se aplica la funcionalidad Lambda, puntualmente al indica que campo debe sumarse.
[C#]
|
Creación del DataTable con CopyToDataTable()
Una alternativa algo mejor a la transformación de datos podrías ser por medio del uso del método de extensión CopyToDataTable()
DataTableExtensions.CopyToDataTable<(Of <(T>)>)
Hay que remarcar un punto con este método, ya que de forma estándar solo permite convertir a DataTable aquellas consulta linq que devuelvan como resultado una DataRow.
Pero en nuestro ejemplo planteado esto es diferente ya que el datatable a devolver requiere de una transformación de los registro con respecto a los datos originales.
Pero esta situación esta contemplada, ya que es posible implementar un método CopyToDataTable<> genérico, en donde a base de un tipo de dato se arme un datatable.
Para esta implementación, se hará uso de la explicación proporcionada por siguiente articulo:
Cómo implementar CopyToDataTable<T> donde el tipo genérico T no es un objeto DataRow
El código del link de msdn, se ha volcado al archivo de nombre “CopytoDataTable.cs”, se podrá observar en el código de ejemplo adjunto en el articulo.
La implementación de la funcionalidad quedo reducida al siguiente código:
private void button1_Click(object sender, EventArgs e) { var query = from item in Datos().AsEnumerable() group item by item["Documento"].ToString() into g select new DocumentoResult { Documento = g.Key, CantRegistros = g.Count(), Total = g.Sum(x => Convert.ToInt32(x["Total"])) }; DataTable resultado = query.CopyToDataTable<DocumentoResult>(); dataGridView1.AutoGenerateColumns = true; dataGridView1.DataSource = resultado; }
Esta nueva alternativa ya no necesita iterar por cada registro que dio como resultado la consulta linq, es mas se puede hacer uso del “var”, puesto que no es necesario pasar por parámetro la consulta para trabajarla.
Pero si se necesito de un pequeño cambio, en donde se ha definido una clase, de nombre “DocumentoResult”, la cual define los campos del nuevo DataTable.
public class DocumentoResult { public string Documento { get; set; } public int CantRegistros { get; set; } public int Total { get; set; } }
Siendo esta la clase usada en la definición del método genérico CopyToDataTable<>, cuando se usa en la línea:
DataTable resultado = query.CopyToDataTable<DocumentoResult>();
[C#]
|
Hola Leandro
ResponderEliminarestoy creando un sistema en VB 2008, y necesito crear un boton que guarde los registros me podrias ayudar por favor. No tengo idea de como empezar con el código o si es mas facil con BindingNavigator.
hola Jeymie
ResponderEliminarTe aconsejaria que estas pregunta la realices en el foro, yo particupo alli, o sino alguien mas podra contestarte.
Explicar este tema puede ser algo extenso, y no creo que en el mensaje del blog quede comodo hacerlo.
VB.NET Foro
saludos
Hola Leandro, al intentar cargar el datatable en un checkedlistbox que no se encuentra en el form me da un error en tiempo de ejecucion que dice lo siguiente "Al menos un objeto debe implementar IComparable.", el error se encuentra localizado en la clase CopytoDataTable, que puedo hacer??? gracias por tu tiempo.
ResponderEliminarhola Antonio José
ResponderEliminarRespondi la pregunta en el mail
saludos
Hola Leandro, no me ha llegado el correo, un saludo.
ResponderEliminarhola Antonio José
ResponderEliminarAnalice linq que estas usando.
Intenta quitando el order by del al instruccion linq, sino entendi mal es este el que causa el problema del orden, porque estas definiendo una clase anonima que no sabe como ordenar
Ese mensaje se genera en el metodo CopytoDataTable porque es alli cuando se ejecuta la instruccion linq, recuerda que puede definir el linq pero este no se ejecutara hasta la primera vez que accedas al mismo, mientras tanto no pasara nada
saludos
hola leandro estoy realizan una consulta con liq agrupando registro por nombre de cliente, me gustaria saver como agrego otro campo comun como es el rfc al datagridview
ResponderEliminarhola TONY
ResponderEliminarque seria un campo comun o un rfc en el DataGridView?
la verdad no conozco estos campos a los cuales haces referencia
saludos
el RFC es otro campo de la tabla, solo que nesecito agrupar por nombre de clientey agragr otra columna con su respectivo rfc o clave
ResponderEliminarhola TONY
ResponderEliminaralgo que no me ha quedado claro es porque mencioans un datagridview, cuando el articulo solo trabaja con dataset
bien en el caso de campos compuesto para agrupar podrias hacer
group item by new { documento = item["Documento"].ToString(), rfc = Convert.TOInt32(item["rfc"])} into g
como veras se usa el "new" para definir un tipo anonimo que cree lo compuesto en el agrupado
veras que use ademas nombre redefinidos para este grupo anonimo
ahora si haces dentro del linq
g.Key.documento
g.Key.rfc
saludos
Hola Leandro, al hacer para agrupar
ResponderEliminargroup item by new { documento = item["Documento"].ToString(), rfc = Convert.TOInt32(item["rfc"])} into g
da un error de conversion implicita en IEnumerable> ya que no devuelvo un string, como lo puedo solucionar??, un saludo y gracias.
hola ajlz
ResponderEliminarrecuerda que estas creando una entidad anonima para agrupar
deberias usar:
g.Key.documento
g.Key.rfc
ya que es una entidad compleja la que asignas en "g", porque como bien comentas no es un string, es una entidad con dos propiedades
saludos
Leandro tengo una pregunta ..de como retornar una lista de Tipo anónimo ..en una consulta de linq c#... este es el error que me sale....Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.List'
ResponderEliminarhola JosaelECR
ResponderEliminarno puedes retornar como resultado de una funcion un objeto anonimo
podrias si crear una clase concreta y asignar la info para retornarla como respuesta pero algo anonimo no se puede retornar
saludos
Hola Leandro, gracias por tu aportación. Fijate que no he podido echar andar el codigo en VS 2005. Ya agregué las referencias de System.Linq, System.XML.Ling, y DataSetExtension... que más me hace falta?.
ResponderEliminarGracias!
hola Lorenzo
ResponderEliminarlinq no existe en .net 2.0, no se como has hecho para agregar las referencias, pero dudo que compile
vas a tener que migrar la solucion a .net 3.5 (VS2008) o superior para poder usar linq
saludos
Hola ¿das clases particulares?
ResponderEliminarEste comentario ha sido eliminado por el autor.
ResponderEliminarEste comentario ha sido eliminado por el autor.
ResponderEliminarLo que estoy necesitando en realidad es parecido a lo que haces en el código anterior, estoy llenado un DataGridView con un dt, pero necesito antes de llenarlo realizar cálculos sobre la consulta que llena el data dt, como lo podría hacer? dejo el código que uso para que veas como lo estoy haciendo ahora
ResponderEliminarDim conexion As MySqlConnection = New MySqlConnection
Dim comando As MySqlCommand = New MySqlCommand
comando.Connection = conexion
Try
conexion.ConnectionString = cadenaConexion '"Server=localhost; Database=xxx; Uid=root; Password=xxx; Port=3306;"
'MessageBox.Show("conectado exitosamente")
Catch ex As Exception
MessageBox.Show(ex.Message)
End Try
comando.CommandText = "SELECT ........"
Dim dt As DataTable = New DataTable
Dim da As MySqlDataAdapter = New MySqlDataAdapter(comando)
da.Fill(dt)
dgvArticulos.DataSource = dt