jueves, 6 de marzo de 2014

Cómo salvar los datos de la instancia

Vamos a conocer alguna de las clases previstas en .NET para el almacenamiento de datos de la instancia. Hay muchas opciones así que esto será un breve repaso a los mecanismos comunes de serialización y de la clase DataSet de ADO.NET.

DataSets


ADO.NET es el modelo de .NET para el acceso a datos. Los datos se almacenan en objetos DataSet que están desconectados de la base de datos. XML está integrado en el modelo, ya que los objetos DataSet se pueden serializar y deserializar fácilmente a partir de XML.

A continuación vamos a rellenar un control DataGrid con datos almacenados en un DataSet, en la parte de arriba se ha definido la estructura del conjunto de datos en tiempo de ejecución,
(conjunto de datos sin tipo)  a continuación definiremos  la estructura del conjunto de datos en tiempo de diseño.

Es necesario crear los siguientes objetos:

-DataAdapter y DataConnection son objetos que proporcionan los métodos para mover datos entre un DataSet y una base de datos o fuente de datos.

- El objeto DataSet  es un contenedor que contiene objetos DataTable e información sobre  las relaciones entre los objetos DataTable.

- DataTable contiene los datos, organizados en filas y columnas. A través de métodos se puede acceder a los datos a través de filas y columnas

Crear la interfaz de usuario


Crearemos un Nuevo proyecto windows y generamos un formulario de este estilo.

DataGrid: DtgPosiciones

Botón1: Carga Dataset

Cargar un dataset


Public Class Form1

Private m_posicionesSet As New DataSet()

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

Dim TablaPosiciones As New DataTable("Posiciones")
      m_posicionesSet.Tables.Add(TablaPosiciones)
Dim Columnas As New DataColumn("Columnas", System.Type.GetType("System.Int32"))
Dim Filas As New DataColumn("Filas",        System.Type.GetType("System.Int32"))
      TablaPosiciones.Columns.Add(Columnas)
      TablaPosiciones.Columns.Add(Filas)
      Dim col As Integer
      Dim fil As Integer
      For col = 0 To 8
            For fil = 0 To 8
                Dim newRow As DataRow = TablaPosiciones.NewRow()
                newRow("Columna") = col
                newRow("Fila") = fil
                TablaPosiciones.Rows.Add(newRow)
            Next
        Next
    End Sub
End Class

Con este código hemos creado un DataSet de la siguiente forma:
§ DataSet contiene objetos DataTable.
§ DataTable añadimos objetos “Columnas” y “Filas” de tipo DataColumn.
§ DataColumn  que posteriormente instanciaremos con un nombre y un tipo de dato, el nombre lo usaremos para referirnos a una fila o una columna concretas  que usaremos para recuperar datos.
§ DataRow contiene los datos. Conviene asegurarse de que el tipo de los datos coincide con el tipo definido en cada columna.

Creamos el evento click para el evento del botón carga sin tipo

Private Sub BtnSin_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnSin.Click

        DtgPosiciones.DataSource = m_posicionesSet
        DtgPosiciones.DataMember = "Posiciones"
End Sub

El objeto DataSet se puede usar como si fuera una Base de Datos para cargar a partir de él el DataGrid

Serialización

La serialización es el proceso mediante el cual los datos de la instancia se graban de forma secuencial en un archivo. En caso de serializar varias instancias estas también se también se guardan secuencialmente.

La deserialización es el proceso de lectura de esos datos en una instancia de la clase. Existe un estándar para la escritura y lectura de los bytes. Por ejemplo XML aunque no es el único estándar, un mapa de bits o un archivo word también son estándar.
Nosotros también podemos definir nuestro propio formato de serialización. También se puede utilizar la serialización para traspasar datos entre aplicaciones.

.NET proporciona varias clases para tareas de serialización, a continuación vamos a usar la clase  BinaryFormatter y la clase XmlSerializer. Cada clase tiene sus ventajas y limitaciones.


La interfaz de usuario de la aplicación que se va a crear se muestra en el siguiente gráfico:

Serialización

Crearemos una lista de las posiciones de las piezas sobre un tablero de ajedrez mediante la definición de cada posición de la casilla en el tablero como una fila y columna (x, y). Entonces, el usuario puede guardar la lista de posiciones en forma binaria o en forma de XML. Una vez que la lista es guardada, el usuario puede recuperar los datos en un momento posterior para restaurar la lista de posiciones. El diseño incluye tres clases: CPosicion, CPosPieza, y CColeccionPiezas. La clase CPosPieza contiene dos  instancias CPosicion, la posición actual y la siguiente, y la CColeccionPiezas  contiene cero o más casos de piezas sobre el tablero. La anidación de estas clases nos permitirá ver  cómo funciona la serialización  y ver las reglas y que se aplican con .NET.

Implementando Serialización Binaria


La Serialización binaria almacena la instancia de una clase como un flujo de Bytes, este flujo se puede guardar en un archivo de memoria o transmitirse por la red. Contiene el estado del objeto, incluyendo los campos privados y públicos de la instancia.
Se puede restaurar a través de la interfaz ISerializable, de este modo los datos no son legibles como texto paro tampoco están encriptados.

Crear las clases de datos


El modelo de datos de esta aplicación incluye las clases: CPosicion, CPosPieza y CColeccionPiezas. La interfaz de usuario contiene métodos para crear, eliminar, guardar y cargar las instancias creadas.

Crear una nueva aplicación de Windows llamada Serializacion.
Añadir una nueva clase al proyecto  llamada CPosicion. Y añadir las propiedades Columna y Fila a la clase.

Public Class CPosicion
    Implements System.Runtime.Serialization.ISerializable

    Private m_Columna As String 'Columna
    Private m_Fila As Integer 'Fila

    Public Property Columna() As String
        Get
            Return (m_Columna)
        End Get
        Set(ByVal Value As String)
            m_Columna = Value
        End Set
    End Property

    Public Property Fila() As Integer
        Get
            Return (m_Fila)
        End Get
        Set(ByVal Value As Integer)
            m_Fila = Value
        End Set
    End Property
End Class

Añadir un constructor y un método sobrecargarlo ToString.  El método ToString es usado para mostrar las instancias de CPosicion en tiempo de ejecución.

Public Sub New()
End Sub

Public Sub New(ByVal columna As String, ByVal fila As Integer)
m_Columna = Columna
      m_Fila = Fila
End Sub

Public Overrides Function ToString() As String
       Return (String.Format("({0}, {1})", Me.Columna, Me.Fila))
End Function

Añadir la clase CPosPieza al proyecto y añadirle dos propiedades para la posición actual y para la siguiente.

'para cada pieza contiene su posición actual y la siguiente (de todas las posibles la que moverá finalmente)
Public Class CPosPieza
Private m_posiciones() As CPosicion = {New CPosicion(), New CPosicion()}

    Public Property Posicion() As CPosicion()
        Get
            Return (m_posiciones)
        End Get
        Set(ByVal Value As CPosicion())
            If (Value.Length = 2) Then
                m_posiciones = Value
            End If
        End Set
    End Property
End Class

Añadir un constructor y el método sobrecargado ToString:

Public Sub New(ByVal anterior As CPosicion, ByVal siguiente As CPosicion)
m_posiciones = New CPosicion() { anterior, siguiente }
End Sub

Public Overrides Function ToString() As String
Dim cadenaposiciones As String
      Dim posicion As Integer
      For posicion = 0 To m_posiciones.Length - 1
    cadenaposiciones += m_posiciones(posicion).ToString() + " "
      Next
      Return (cadenaposiciones)
End Function

Añadir al proyecto una clase llamada CColeccionPiezas basada en la clase CollectionBase.

Public Class CColeccionPiezas
    Inherits System.Collections.CollectionBase
End Class

Añadir los métodos Agregar y Eliminar:

Public Sub Agregar(ByVal pieza As CPosPieza)
      Me.InnerList.Add(pieza)
End Sub
Public Sub Eliminar(ByVal pieza As CPosPieza)
      Me.InnerList.Remove(pieza)
End Sub


Sobrecargar el método ToString y añadirlo a la clase para mostrar las posiciones de las piezas en un control ListBox:

Public Overrides Function ToString() As String
Dim posiciones As String
Dim ActYSig As CPosPieza  'Contiene la posición anterior y la     siguiente
      For Each ActYSig In Me.InnerList
            posiciones += ActYSig.ToString() & ControlChars.CrLf
            Next()
      Return (posiciones)
End Function
Public Function ToArray() As Object()
     Dim posiciones(Me.Count - 1) As Object
     Dim ActYSig As Integer
     For ActYSig = 0 To Me.Count - 1
            posiciones(ActYSig) = InnerList(ActYSig)
     Next
     Return (posiciones)
End Function

Después añadiremos el interfaz de usuario para cargar la instancia de datos.


Crear el interfaz de usuario


Abrir el Form1 y añadir los controles listados en la siguiente tabla, poniendo las propiedades indicadas.

Control
Propiedad
Valor
CheckedListBox
Name
chkPuntosSeleccionados
Label
Text
Seleccionar dos posiciones
ListBox
Name
lstListaPosiciones
Label
Text
Posiciones
Button
Name
btnAnadirPosición

Text
Añadir
Button
Name
btnQuitarPosicion

Text
Eliminar
Button
Name
btnLimpiarTodo

Text
Limpiar todo
Button
Name
btnSalvarBinario

Text
Salvar Binario
Button
Name
btnCargarBinario

Text
Cargar Binario
Button
Name
btnSalvarXML

Text
Salvar XML
Button
Name
btnCargaXML

Text
Cargar XML

Hacer doble click sobre el formulario para añadir el siguiente código que rellena la checkedListBox con posiciones.

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim columnas As Integer
        Dim filas As Integer
        Dim descol As String
        For columnas = 1 To 8
            Select Case columnas
                Case 1
                    descol = "TD"
                Case 2
                    descol = "CD"
                Case 3
                    descol = "AD"
                Case 4
                    descol = "D"
                Case 5
                    descol = "R"
                Case 6
                    descol = "AR"
                Case 7
                    descol = "CR"
                Case 8
                    descol = "TR"
            End Select
            For filas = 1 To 8
Me.chkPuntosSeleccionados.Items.Add(New CPosicion(descol, filas))
            Next
        Next
    End Sub
Creamos los eventos para el botón Añadir al haciendo doble click sobre el botón del formulario. Agregamos un campo para la clase CColeccionPiezas y agregamos el código para el evento de añadir una nueva posición con una instancia de CColeccionPiezas. La propiedad checkedItemCollection es una propiedad del control CheckedListBox que contiene una colección de todos los elementos que se han marcado. Estos objetos se devuelven como Instancias de System.Object, por lo que debe convertir de nuevo a CPosicion una instancia de una pieza  nueva con ellos.

Private m_pospiezas As New CColeccionPiezas()

Private Sub btnAnadirPosicion_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAnadirPosicion.Click
Dim checkedPosicionesClicadas As CheckedListBox.CheckedItemCollection = Me.chkPuntosSeleccionados.CheckedItems
        If checkedPosicionesClicadas.Count = 2 Then
m_pospiezas.Agregar(New CPosPieza(CType(checkedPosicionesClicadas(0), CPosicion), CType(checkedPosicionesClicadas(1), CPosicion)))
            lstListaPosiciones.Items.Clear()
            lstListaPosiciones.Items.AddRange(m_pospiezas.ToArray())
            Dim item As Integer
            For Each item In chkPuntosSeleccionados.CheckedIndices
                chkPuntosSeleccionados.SetItemChecked(item, False)
            Next
        Else
MessageBox.Show("Debes agregar exactamente dos posiciones.")
        End If
End Sub

Creamos el controlador de eventos Click para el botón Eliminar. Añadimos el siguiente código para eliminar las dos posiciones de la  pieza seleccionada de  m_pospiezas. Como con la propiedad CheckedItemCollection de la lista  CheckedListBox, la Propiedad SelectedItem del ListBox devuelve una referencia a  System.Object. Tendremos hacer un cast  a la instancia de  CPosPieza antes de llamar al método  Eliminar de la instancia m_pospiezas  de  CColeccionPiezas.

Private Sub btnQuitarPosicion_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnQuitarPosicion.Click

If lstListaPosiciones.SelectedIndex <> -1 Then
m_pospiezas.Eliminar(CType(lstListaPosiciones.SelectedItem, CPosPieza))
            lstListaPosiciones.Items.Clear()
            lstListaPosiciones.Items.AddRange(m_pospiezas.ToArray())
      End If
End Sub

Crear el evento Click para el botón Limpiar todo. Añadir código para eliminar todas las instancias de Posiciones de  m_pospiezas.

Private Sub btnLimpiarTodo_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLimpiarTodo.Click
        m_pospiezas.Clear()
        lstListaPosiciones.Items.Clear()
End Sub

Con todas las clases definidas y sus métodos para crear y eliminar instancias, ahora se pueden definir los métodos de serialización y deserialización. Esto requerirá adiciones tanto a la interfaz de usuario y para las clases.

Definir la serialización


Para utilizar la serialización binaria, es necesario agregar el atributo Serializable para cada clase que deseemos serializar. Además, si deseamos definir los campos que se serializan y si están en serie, se puede implementar la interfaz ISerializable. El gráfico de objetos de
CColeccionPiezas contiene las clases CPosPieza y CPosicion. CPosPieza y CColeccionPiezas utilizarán la serialización predeterminada proporcionada por la adición del atributo Serializable. La clase y CPosicion definirá su propia serialización mediante la implementación del interfaz ISerializable y agregando el atributo Serializable.

En este paso se va a serializar la instancia m_pospiezas de CColeccionPiezas, que a su vez contiene instancias de CPosPieza, que a su vez contienen instancias CPosicion. Esta dependencia de clases se conoce como objeto gráfico de una clase. En tiempo de ejecución .NET es capaz revisa los objetos durante la serialización para garantizar que todas las clases están definidas correctamente y se pueden serializar.

Añadir el atributo Serializable  y el interfaz ISerializable a la declaración de la clase CPosicion


<Serializable()> Public Class CPosicion
Implements System.Runtime.Serialization.ISerializable
Añadimos código al procedimiento generado automáticamente GetObjectData como único miembro del interfaz ISerializable. El primer parámetro, info de tipo SerializationInfo, es una colección de pares de nombre / valor que pasamos al proceso de serialización. De esta manera, se puede controlar en qué forma está serializado. Aquí, le sumamos los valores de m_columna y m_fila a la colección:

Public Sub GetObjectData(ByVal info As System.Runtime.Serialization.SerializationInfo, ByVal context As System.Runtime.Serialization.StreamingContext) Implements System.Runtime.Serialization.ISerializable.GetObjectData
        info.AddValue("COLUMNA", m_Columna)
        info.AddValue("FILA", m_Fila)
End Sub

Proporcionamos un constructor que tome los parámetros SerializationInfo y StreamingContext. Este método es necesario para la serialización, pero no cumple con la definición de la interfaz, porque los interfaces no pueden definir constructores. Este constructor se llama cuando el objeto se deserializa y es necesario cuando se está implementando la interfaz ISerializable. Para que el constructor pueda leer los datos, estos deben estar igual que fueron escritos.


Public Sub New(ByVal info As System.Runtime.Serialization.SerializationInfo, ByVal context As System.Runtime.Serialization.StreamingContext)
        m_Columna = info.GetInt32("COLUMNA")
        m_Fila = info.GetInt32("FILA")
End Sub

Agregamos el atributo Serialización a las clases CPosPieza y CColeccionPiezas. Si utilizamos el  valor de serialización por defecto no son necesarios más cambios.

<Serializable()> Public Class CPosPieza
<Serializable()> Public Class CPosPiezaPublic Class CColeccionPiezas.

Serializar y deserializar los datos


Ya sólo queda crear las instancias BinaryFormatter y  FileStream para serializar los datos de la instancia m_posiciones en un archivo.

Añadimos una instrucción Imports al archivo de origen del Formulario. Esto permitirá usar el nombre no calificado de la clase BinaryFormatter.

Imports System.Runtime.Serialization.Formatters.Binary

Private m_FicheroBinario As String = Application.StartupPath + "\Posicionn_Pieza.dat"

Creamos el controlador de eventos para los botones Guardar binario y Cargar Binario y metemos código para serializar el campo m_pospiezas. Los pasos para serializar son simples: Creamos una secuencia (stream) y un objeto BinaryFormatter. El método Serialize toma como parámetros el stream serialización y el objeto que se va a serializar.

Private Sub btnSalvarBinario_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSalvarBinario.Click
Dim stream As New System.IO.FileStream(m_FicheroBinario,     System.IO.FileMode.Create)
    Dim binary As New BinaryFormatter()
    binary.Serialize(stream, m_pospiezas)
    stream.Close()
End Sub

Creamos el controlador de eventos click para el botón Cargar binario y añadimos código para deserializar el campo m_pospiezas. Después de cargar los datos, llenamos el ListBox llamado stListaPosiciones  con los datos nuevos.

Private Sub btnCargarBinario_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCargarBinario.Click
Dim stream As New System.IO.FileStream(m_FicheroBinario, System.IO.FileMode.Open)
        Dim binary As New BinaryFormatter()
  m_pospiezas = CType(binary.Deserialize(stream),            CColeccionPiezas)
        stream.Close()
        lstListaPosiciones.Items.Clear()
        lstListaPosiciones.Items.AddRange(m_pospiezas.ToArray())
End Sub

Utilizamos la clase BinaryFormatter para serializar, y ahora deserializar el campo m_posiciones. El método Deserialize toma una instancia stream y devuelve una instancia de System.Object. Se hace un cast del objeto del tipo que se serializa el stream.

 

Ejecutar y testear la aplicación


 A continuación ejecutamos y probamos la aplicación. El archivo de datos no existirá hasta que se cree por lo que  tendremos que añadir y guardar algunos datos antes de intentar cargar los datos.

Ejecutar la aplicación, el listbox de posiciones de las piezas (actual y siguiente) estará vacío.

Seleccionar dos posiciones y pulsar el botón Añadir. Tendremos una instancia CPosPieza en el listbox.
Añadir otra instancia CPosPieza.

Salvar los datos de la instancia

Pulsar el botón Salvar Binario. Se salvarán la posición actual y siguiente de dos piezas.

Pulsar el botón limpiar todo y se borrarán las posiciones guardadas en el punto anterior

Al pulsar el botón Cargar Binario. Las dos instancias guardadas aparecerán en el list box

Una porción de los datos binarios se muestran aquí en notepad donde se puede leer un poco, el stream.

Stream

 

Implementando la Serialización XML


XML es similar a HTML sólo que permite al desarrollador definir etiquetas. Aunque HTML contiene un conjunto predefinido de etiquetas, como Título y Estilo, los desarrolladores pueden crear las etiquetas que tienen que definir sus datos, como CPosicion, CPosPieza,CColeccionPiezas.

La serialización XML tiene estas características:

-Sólo se serializan los campos públicos y propiedades. Si los datos de la instancia no pueden ser vistos desde los campos públicos o propiedades, estos no se inicializarán cuando el objeto se deserializa.

- La serialización XML requiere un constructor público sin parámetros.

Hay convenciones especiales para la serialización de clases de colección, como la CColeccionPiezas.

- La salida de la serialización es texto legible, sin formato.

Serializar y des-serializar los datos


Para serializar XML, tendremos que agregar los miembros públicos necesarios para la instancia y establecer sus propiedades.

Añadiremos un  constructor sin parámetros para las clases CPosPieza y CColeccionPiezas.


Public Sub New()
End Sub

Si queremos serizalizar CColeccionPiezas, como la clase implementa el interfaz ICollection lo tenemos que hacer a través de su clase base CollectionBase.

Implementaremos:

-  Una propiedad Contar  que devuelva un entero. La clase base, CollectionBase, proporciona la propiedad Count.

- Un método Agregar que tome un parámetro del mismo tipo que el objeto devuelto por la propiedad actual del método GetEnumerator.

- Un método Item, El valor devuelto por este método debe tener el mismo tipo que el parámetro del método Agregar.
Estos miembros permitirán que el proceso de serialización pueda acceder a todos los objetos de la colección a través del método Item y deserializar el objeto mediante el método Agregar

Default Public Property Item(ByVal indice As Integer) As CPosicion
        Get
            Return CType(Me.InnerList.Item(indice), CPosicion)
        End Get
        Set(ByVal Valor As CPosicion)
            Me.InnerList.Item(indice) = Valor
        End Set
End Property

Con esto completamos los cambios necesarios para realizar en nuestras clases.

Seguidamente añadimos este código en el Form1 para que invoque la serialización de XML

Imports System.Xml.Serialization

Y definimos la cadena para el XML

Private m_FicheroXML As String = Application.StartupPath & "\Posicion_Pieza.xml"

Y el código para el botón SalvarXML

Private Sub btnSalvarXML_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSalvarXML.Click

     Dim escribir As New System.IO.StreamWriter(m_FicheroXML)
     Dim xmlSerial As New XmlSerializer(m_pospiezas.GetType())
     xmlSerial.Serialize(escribir, m_pospiezas)
     escribir.Close()

End Sub

Este código serializa m_pospiezas es similar a los método binarios  pero necesita saber el tipo de la instancia a serializar.

Y el código para el botón cargarXML

Private Sub btnCargarXML_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCargarXML.Click

    Dim lector As New System.IO.StreamReader(m_FicheroXML)
    Dim SerializaXML As New XmlSerializer(System.Type.GetType("Serializar.CColeccionPiezas"))
    m_pospiezas = CType(SerializaXML.Deserialize(lector), CColeccionPiezas)
    lector.Close()
    lstListaPosiciones.Items.Clear()
    lstListaPosiciones.Items.AddRange(m_pospiezas.ToArray())

End Sub

Ejecutamos y probamos el programa.

Cargar y salvar los datos


Si ahora pulsamos en cargar binario para cargar los datos almacenados en binario la última vez y pulsamos el botón salvar XML, limpiamos todo y pulsamos el botón cargar XML.




No hay comentarios:

Publicar un comentario