martes, 15 de enero de 2019

Redondear a decimales columnas de DataGridView (obtener tipo de dato de la columna y darle formato) en VB.NET


En el trabajo me tope con un escenario curioso. Estaba cargando tablas en DataGridView con múltiples campos. Pero cuando se hace la carga de los datos, aquellos que son de tipo decimal, se muestran con varios decimales. Lo que me pidieron, fue que los datos se mostrarán a solo dos decimales. En el caso de datos que se ponen en textbox o labels es sencillo de hacer.

Y para poder formatear correctamente nuestro Grid, basta con ejecutar la siguiente instrucción:
columna.DefaultCellStyle.Format = "N2"

Con esto le decimos que aplique un formato de dos decimales a los datos contenidos en dicha columna. Pero para hacerlo, primero debemos identificar qué columnas de nuestro Grid tienen datos en decimal. Así que el código completo que necesitaremos será el siguiente:

        For Each columna As DataGridViewColumn In dgv.Columns
            If columna.ValueType.ToString() = "System.Decimal" Then
                columna.DefaultCellStyle.Format = "N2"
            End If
        Next

Primero que nada recorremos el Grid, el arreglo de columnas que contiene.  Luego con

  If columna.ValueType.ToString() = "System.Decimal" Then

Obtenemos el valor de los datos contenidos en cada columna. Si el tipo de dato es decimal, entonces procedemos a darle el formato a dos decimales.

lunes, 14 de enero de 2019

Diferencias entre ADODB.Recordset y DataSet

Ambos objetos nos permiten manipular datos y extraerlos de la base de datos. Pero tienen algunas diferencias significativas. En particular el objeto DataSet es mas recomendable para su uso en las versiones .NET

1.-Colección de Tablas. El objeto ADODB.Recordset, permite navegar a través de una sola tabla de información. Por supuesto esta tabla puede haberse formado con la unión de múltiples tablas y puede regresar datos de columnas de múltiples tablas. En cambio el DataSet es capaz de manejar instancias de múltiples tablas. Se compone de una colección de tablas. Si las tablas tienen una relación pueden ser manipuladas en una relación de Padre-Hijo. Tiene la habilidad de soportar múltiples tablas con llaves, restricciones y relaciones de interconexión entre las mismas. Por eso DataSet puede ser considerado como una pequeña base de datos relacional en memoria.

2.-Navegación. La navegación en el ADODB.Recorset está basada en el modo cursor. Por esa razón necesita estar conectado a la base de datos. En cambio el objeto DataSet está localizado enteramente en la memoria, por eso está disponible fuera de línea .

3.-Modelo de conectividad. El ADODB.Recorset  fue diseñado originalmente sin la habilidad de operar en un ambiente desconectado. En cambio el DataSet está especialmente diseñado para funcionar desconectado de la base de datos, ya que los datos residen en memoria. Sigue un modelo enteramente fuera de línea, por lo que da más versatilidad, rendimiento y funciona aunque se pierda la conexión.

4.-Transformación y serialización. En COM podemos pasar datos de un componente COM a otro en cualquier momento,esto implica copiar y procesar datos en un tipo complejo que sea igual al que envía el componente y al que recibe el otro componente. Esta es una operación costosa. En cambio el objeto DataSet es serializable en formato XML. Para enviar datos en lugar de las costosas transformaciones a tipos complejos, se envía el XML serializado. Esto hace que se aprovechen mucho mejor los recursos.

En resumen el tipo Dataset permite manipular los datos sin conexión a la Base de Datos, además puede contener múltiples tablas de datos , mientras el recordset solo una. Y tiene el plus de ser serializable, por lo que es fácil transportarlo entre componentes de código. Por ello en .NET es preferible usar un DataSet a un ADODB.Recorset.








miércoles, 9 de enero de 2019

Diferencia entre CHAR y VARCHAR SQL SERVER


Cuando guardamos cadenas de texto, solemos utilizar los campos de tipo char o varchar. ¿Cual es la diferencia?

La principal diferencia es que los campos varchar tienen una longitud variable, mientras que los char tienen una longitud fija. Es decir que si declaramos dos campos:

Nombre1 char(20)
Nombre2 varchar(20)

En el primer caso, por ser char, el campo siempre tendrá longitud de 20. Es decir si introducimos un nombre de 5 caracteres, SQLSERVER rellenará el resto del campo con espacios en blanco. Y al momento de recuperarlo claro los eliminará. Mientras que con los campos varchar, si introduzco un nombre de 5 caracteres, solo ocupará el espacio que utilize. Es decir, la longitud de mi campo en realidad es variable.

Para el campo de tipo varchar, lo que le indicó es la longitud máxima, es decir que puedo introducir cadenas de o a n caracteres. Por consiguiente en general ocupan menos espacio en la BD que el equivalente char, ya que char siempre rellena el campo. Aunque varchar usa un par de bytes para guardar la longitud del campo.

Sin embargo la ventaja del char es que al ser cadenas fijas, el procesamiento de las mismas es más rápido, no tenemos que leer su longitud , o que en varchar crea una pequeña penalización de rendimiento. 

Es de especial importancia, que en los stored procedures donde armemos querys, nunca pasemos un parámetro a utilizar como char porque vendrá con espacios de relleno. Por ejemplo si tengo el siguiente parámetro en un stored:

@stringToSearch as char(255)=''",

Y lo uso así:

Descripcion LIKE @stringToSearch OR @stringToSearch='%%' 

Nunca obtendré el resultado deseado. Debido a que los char se rellenan con espacios en blanco, mi cadena de búsqueda no funciona . Así que cuando uses parámetros para hacer búsquedas en stored procedures, siempre utiliza el tipo de dato varchar.


viernes, 28 de diciembre de 2018

DataGridView, generado dinámicamente o generado en modo de diseño

El control DataGridView, es un control muy versátil. Quizá muchos habrán observado que al asignarle un DataSource, el DataGrid en automático se construye, tomando los nombres de las columnas de acuerdo a la Fuente de Datos.
Pongamos un ejemplo:

DataGridViewProducto.DataSource = ProductoList

Con esa simple instrucción, mi Grid tomará los datos de ProductoList, que tengo definido como una lista de objetos, y mostrará en las columnas las propiedades de cada objeto.En este caso lo tengo definido así:

  Public Class ProductoDisplayObject

        Private _productoId As Integer
        Private _descripcion As String
        Private _clasificacion As String
        Private _unidadMedida As String

Por lo tanto el Grid tendría las columnas productoId, descripcion, clasificacion y unidadMedida. Ahora bien , también puedo definir las columnas en el Designer de la forma, y darles formato. Pero si quiero que estas se enlacen automáticamente al campo correspondiente del DataSource debo hacer lo siguiente:

 Dim productoId As New DataGridViewTextBoxColumn
  With productoId
            .DataPropertyName = "productoId "
            .Name = "productoId "
            .HeaderText = "Id Prodcuto"
End With
With MiDataGridView.Columns
            .Clear()
            .Add(productoId )

 End With


Aquí lo que hago es declarar mi columna del Grid, luego le asigno las propiedades Name y DataPropertyName. La propiedad DataPropertyName es la más importante, pues me sirve de llave para enlazar el DataSource con la columna del Grid. Siempre que encuentre un campo en el DataSource con el mismo nombre que asigne en esta propiedad, copiará el valor en la columna adecuada del Grid.

Luego agrego la columna que defini a mi Grid con el método .Add

Si no hago este mapeo, y defino ciertas columnas en mi grid, y además le asigno un Datasource, el Grid no sabrá cómo enlazar las columnas y me duplicara las mismas. Es decir, dejará las que tenía en modo de diseño y además agregará los campos que tiene el DataSource. Por el contrario si hago el mapeo completo, los valores del DataSource se pondrán en la columna correspondiente. 

Si tengo campos que no mapee con la propiedad DataPropertyName, esos si se agregarán al Grid en tiempo de ejecución.

Pero a veces este comportamiento es indeseable, deseo definir bien mi Grid desde el diseño y no deseo que le agregue columnas nuevas en tiempo de ejecución. En ese caso en el mismo Designer de la forma debo ejecutar la siguiente instrucción:

Me.MiDataGridView.AutoGenerateColumns = False

viernes, 21 de diciembre de 2018

Problema con BindingList en DataGridView, mapeo a Dataset (rellenado de DataSet manual) en VB.NET

Alguno de nosotros quizá ha tenido que trabajar con el tipo de dato BindingList que contiene un grupo de objetos. Esta lista puede asignarse a un objeto DataGridView como DataSource y sin problemas nos mostrará en el Grid todos los registros contenidos en la misma.

El problema viene si el usuario quiere poder ordenar los datos por las columnas de Grid. Para poder hacer esto, el DataSource del Grid debe ser un DataTable. Además el tipo BindingList, no soporta ordenamiento de datos. 

Una solución para hacerlo, es mapear el BindingList a un DataSet, y usar ese Dataset como source del Grid.

Private Function mapeaDataSet(ByVal lista As BindingList(Of DestinatariosDisplayObject)) As DataSet
        Dim ds As New DataSet
        Dim dt As New DataTable
        Dim Nombre, Puesto, Dependencia, Telefono, Estado, Municipio As String

        dt.Columns.Add("DestinatarioId", Type.GetType("System.Int32"))
        dt.Columns.Add("EstadoId", Type.GetType("System.Int32"))
        dt.Columns.Add("MunicipioId", Type.GetType("System.Int32"))
        dt.Columns.Add("Nombre", Type.GetType("System.String"))
        dt.Columns.Add("Puesto", Type.GetType("System.String"))
        dt.Columns.Add("Dependencia", Type.GetType("System.String"))
        dt.Columns.Add("Telefono", Type.GetType("System.String"))
        dt.Columns.Add("Estado", Type.GetType("System.String"))
        dt.Columns.Add("Municipio", Type.GetType("System.String"))
        dt.Columns.Add("Periodo", Type.GetType("System.Int32"))
        dt.Columns.Add("TipoRevision", Type.GetType("System.Int32"))

        For Each Destinatario As DestinatariosDisplayObject In lista
            If Not (Destinatario.Nombre Is DBNull.Value) Then
                Nombre = Destinatario.Nombre
            Else
                Nombre = ""
            End If
            If Not (Destinatario.Puesto Is DBNull.Value) Then
                Puesto = Destinatario.Puesto
            Else
                Puesto = ""
            End If
            If Not (Destinatario.Dependencia Is DBNull.Value) Then
                Dependencia = Destinatario.Dependencia
            Else
                Dependencia = ""
            End If
            If Not (Destinatario.Telefono Is DBNull.Value) Then
                Telefono = Destinatario.Telefono
            Else
                Telefono = ""
            End If
            If Not (Destinatario.Estado Is DBNull.Value) Then
                Estado = Destinatario.Estado
            Else
                Estado = ""
            End If
            If Not (Destinatario.Municipio Is DBNull.Value) Then
                Municipio = Destinatario.Municipio
            Else
                Municipio = ""
            End If
   dt.Rows.Add(Destinatario.DestinatarioId, Destinatario.EstadoId, Destinatario.MunicipioId, Nombre, Puesto, Dependencia, Telefono, Estado, Municipio, Destinatario.periodo, Destinatario.TipoRevision)
        Next

        ds.Tables.Add(dt)
        Return ds
    End Function

Esta función hace el trabajo del mapeo, simplemente creamos un DataTable, definimos las columnas con Columns.Add y agregamos las necesarias, en este caso se le da nombre y tipo de columna.

Luego con el Rows.Add se le agrega una fila a la vez, metiendo los datos que traigo en mi objeto contenido en el BindingList.

Para asignarlo simplemente hacemos esto:
 dataDestinatario = mapeaDataSet(listaCoincidencias)
Me.DestinatariosDataGridView.DataSource = dataDestinatario.Tables(0)

Como podemos ver dataDestinatario es un DataSet, y de este tomamos solo la primera DataTable que asignamos como DataSource.

Ahora bien también podemos hacer el proceso inverso, mapeando los datos del Grid en un objeto:

destinatarioDisplayObject = mapeaDataGridRowToDisplayObject(DestinatariosDataGridView.CurrentRow.DataBoundItem)

La función que hace el mapeo es la siguiente:


Private Function mapeaDataGridRowToDisplayObject(ByVal filaGrid As System.Data.DataRowView) As DestinatariosDisplayObject 
        Dim Destinatario As New DestinatariosDisplayObject

        Destinatario.DestinatarioId = CType(filaGrid.Item(0).ToString(), Integer)
        Destinatario.EstadoId = CType(filaGrid.Item(1).ToString(), Integer)
        Destinatario.MunicipioId = CType(filaGrid.Item(2).ToString(), Integer)
        Destinatario.Nombre = filaGrid.Item(3).ToString()
        Destinatario.Puesto = filaGrid.Item(4).ToString()
        Destinatario.Dependencia = filaGrid.Item(5).ToString()
        Destinatario.Telefono = filaGrid.Item(6).ToString()
        Destinatario.Estado = filaGrid.Item(7).ToString()
        Destinatario.Municipio = filaGrid.Item(8).ToString()
        Destinatario.periodo = CType(filaGrid.Item(9).ToString(), Integer)
        Destinatario.TipoRevision = CType(filaGrid.Item(10).ToString(), Integer)
        Return Destinatario
End Function

Por último agregamos la línea que permite que el usuario haga la ordenación de datos:

 DestinatariosDataGridView.AllowUserToOrderColumns = True

Esta línea no funcionaria si el DataSource es el BindingList.

Por último quizá te preguntes, qué objetos pueden ser Fuente de Datos para un Grid. Bueno los objetos de cualquier tipo que implemente una de las siguientes interfaces:

  • La interfaz IList, incluyendo los arreglos unidemensionales.
  • La interfaz IListSource, como los DataTable y los DataSet.
  • La interfaz  IBindingList, como la clase  BindingList<T>.
  • La interfaz  IBindingListView , como la clase BindingSource.


viernes, 30 de noviembre de 2018

Parámetros opcionales en VB.NET

El uso de parámetros opcionales es una facilidad que nos ofrece VB.NET que viene heredada de Visual Basic. Indica que al momento de invocar una función o método podemos definir parámetros opcionales, los cuales pueden ser o no mandados. Esta funcionalidad nos permite escribir código más compacto, ya que nos evita el usar la sobrecarga de métodos. Y es tan practica que ha sido portada desde VB.NET a C# en la versión 4.

Pongamos un ejemplo:

Sub MiRutina(ByVal number As Integer, Optional ByVal nombre As String = "Desconocido")
        MsgBox("El nombre es es: " & nombre)
 End Sub

Esta rutina admite dos parámetros, pero el segundo es opcional. Si yo no ingresó un nombre, este tomará el valor "Desconocido".

Como vemos al usar parámetros opcionales, es necesario definir un valor por default. En este sentido puede ser una ventaja/desventaja. Si no deseamos usar valores por default, es mejor usar la sobrecarga de métodos. Si por el contrario si deseamos usar este valor quizá sea mejor manejar parámetros opcionales.

Hay otras dos reglas que debemos tener en cuenta al usar parámetros opcionales:
  • El valor predeterminado de un parámetro opcional debe ser una expresión constante.
  • Todos los parámetros que vayan a continuación de un parámetro opcional en la definición del procedimiento también deben ser opcionales.

Las ventajas de usar parámetros opcionales son la siguientes:
  • Permiten usar menos código. En lugar de escribir varias veces el método usando sobrecarga, a veces podemos definirlo solo una vez usando parámetros opcionales.
  • El código generado ocupa menos espacio y se ejecuta más rápido. En este enlace se analiza más a detalle esta y otras ventajas.
Sin embargo el uso de parámetros opcionales tiene una gran desventaja. Cuando programamos la llamada a un método que usa parámetros opcionales, la firma de este método se compila en nuestro programa que hace la llamada, quemando los valores por default. Es decir que si cambiamos el método al que hacemos referencia, obtendremos inconsistencias, por que los parámetros opcionales se compilan en el método que hace la llamada.

Es decir que aunque yo cambie mi método, todas las llamadas que se hacen al mismo y que fueron compiladas con anterioridad, me traerán como resultado la salida que se producía antes del cambio, por que ahí los valores default no cambiaron. Si quiero que se actualize mi lógica de programación debo recompilar todas las llamadas que hacen referencia al método. Cuando trabajamos en un solo proyecto o solución esto no significa ningún problema , pues en realidad compilamos todo el código junto. Pero cuando llamo a mi método desde otro ensamblado, aquí si van a producirse inconsistencias. Tendría que recompilar todos los ensamblados que hacen la llamada a mi método.

Así que siempre debemos tener en cuenta esta limitante al usar parámetros opcionales.



miércoles, 28 de noviembre de 2018

¿Por que usar Llaves Foráneas?

Una llave foránea es un campo que nos sirve de apuntador para relacionar dos tablas. Por regla general suele contener la llave primaria de la tabla externa, para que mediante ella podamos establecer la relación entre tablas.

Por ejemplo supongamos que tenemos una tabla de alumnos con los siguientes campos:DomicilioId, HorarioId, GrupoId , etc. Estos campos son llaves que apuntan a las tablas Domicilio, Horario, Grupo, etc. Y asu vez en la tabla Domiclio podríamos tener los campos: EstadoId, MunicipioId, PoblacionId, ColoniaId, etc, que apuntan a catálogos que contienen los elementos que componen el domicilio.

Es importante mencionar que cada cosa en nuestra base de datos debe ser una entidad, es importante tener catálogos para seleccionar datos. Podríamos dejar que estos campos fueran de tipo varchar e introducir texto en ellos. Pero esta es una mala práctica, pues a menos que se introduzca el texto exactamente igual, podríamos estar duplicando información debido a las diversas formas de escritura. Como ejemplo podríamos entrar una colonia de las siguientes formas:
col. Vicente Guerrero
colonia Vicente Guerrero
col vicente guerrero
COL VICENTE GUERRERO
VICENTE GUERRERO
Vicente Guerrero
vicente guerrero

A pesar de las distintas formas de llamarla, la colonia es la misma. Así que no tenemos nuestra información normalizada, y se presta a errores. En cambio si tenemos un catalogo de colonias, el usuario selecciona exactamente la colonia que es, es imposible duplicar información a menos que el llenado del Catalogo se haga mal. Otra ventaja de hacer una normalización de información de este tipo, es que si cambiamos algún dato de la colonia, solo se cambia una tabla, en lugar de cambiar las múltiples tablas en donde se le puede hacer referencia.

Claro podríamos hacer la relación en nuestras tablas sin especificarle al motor de Base de Datos que usamos una llave foránea. Simplemente con usar un campo  numérico e insertar en él el Id de la tabla a que hará referencia. Pero si hacemos esto, estamos dejando que la aplicación se encargue de la integridad de los datos. Ya que nuestra Base de Datos no sabe que existe la relación entre las tablas, no nos ayudará a preservar la integridad de los datos.

El ejemplo más clásico es el borrado de registros en una tabla a la que apuntan varias tablas mediante llaves foráneas. Si intentamos hacer esto y definimos las llaves foráneas, la base de datos nos impedirá borrar el registro pues llevaría a tener inconsistencias de datos. Así que solo nos permitirá eliminar el registro cuando ninguna tabla apunte a él mediante una llave foránea. En cambio si no le indicamos a la base de datos que usamos la llave foránea, nos permitirá borrarlo sin problemas aunque nosotros lo usemos para establecer relaciones.


Otro punto a tener en cuenta, es que el campo que usemos como llave foránea, debe llamarse igual en ambas tablas. Como generalmente una llave foránea apunta a una llave primaria, es lógico pensar que usaremos un identificador único de campo en toda la Base de Datos. Pero si los campos se llaman diferentes, aunque la Base de Datos puede manejar la relación , a nosotros puede hacersenos muy difícil entender las relaciones entre las tablas por simple inspección. De modo que desarrollar nueva lógica de programación puede ser increíblemente complicado, ya que no sabemos definir las relaciones entre tablas con facilidad.






Redondear a decimales columnas de DataGridView (obtener tipo de dato de la columna y darle formato) en VB.NET

En el trabajo me tope con un escenario curioso. Estaba cargando tablas en DataGridView con múltiples campos. Pero cuando se hace la carga...