Índice de la sección dedicada a .NET (en el Guille) ADO.NET

Ejemplo de acceso a datos con una base de Access

Publicado: 13/Jun/2002
Actualizado: 19/Abr/2003

En este ejemplo veremos cómo acceder a una base de datos de Access, usando los objetos del espacio de nombres OLEDB.
Los conceptos que se tratarán serán los siguientes:
- Crear una conexión a la base de datos.
- Mostrar las tablas de la base de datos indicada.
- Mostrar las columnas (o campos) de una tabla.
- Crear un DataAdapter para acceder a los datos devueltos por una consulta.
- Crear un DataSet basado en los datos asociados al DataAdapter.
- Crear nuevos registros.
- Modificar registros.
- Eliminar registros.
- Mostrar el contenido de la tabla en un control ListView.

 

Nota del 20/Jul/2002:
He recibido varios mensajes diciendo que les daba error el ejemplo aquí puesto, he probado y efectivamente, me da error... le he puesto una captura (en el método Conecta) y me muestra un error de que la cadena SQL no es válida, pero si continuo, funciona bien.
Aunque, he de aclarar que ese error se produce en el código del ejemplo adjunto, si se deja el texto que por defecto tiene la caja de textos en la que se indica la cadena SQL, si se deja en blanco, no se produce error.
He modificado el código aquí mostrado, el cual puedes ver pulsando este link.

Nota del 06/Ene/2006:
Si este ejemplo te parece muy complicado, puedes ver otro más simple.


Los objetos a usar en el proyecto.

Los objetos que vamos a usar en este ejemplo, en su gran mayoría residen en el espacio de nombres System.Data.OleDb, aunque también se usarán objetos genéricos (DataSet, DataRow, DataColumn) que residen en System.Data.
Para que nos sea más fácil declarar esos objetos, importaremos el espacio de nombres
System.Data.OleDb y dado que usaremos procedimientos genéricos, estos los incluiremos en un módulo, al que llamaremos: ADONETUtil, veamos el código con las declaraciones de los objetos que usaremos en el proyecto:


'------------------------------------------------------------------------------
' Prueba de acceso a base de tipo Access con ADO.NET                (25/May/02)
' Módulo con las declaraciones de objetos
'
' ©Guillermo 'guille' Som, 2002
'------------------------------------------------------------------------------

Imports System.Data.OleDb

Module ADONETUtil
    Friend dbConnection As OleDbConnection
    '
    Friend dbDataTable As Data.DataTable
    Friend dbDataSet As Data.DataSet
    Friend dbDataAdapter As OleDbDataAdapter
    '
    Friend CadenaConexion As String
    Friend CadenaSelect As String
    '
    Friend ArchivoDatos As String
    Friend NombreTabla As String = "Tabla1"
    '

La variable CadenaConexion será la cadena con la que conectaremos a la base de datos.
La variable
CadenaSelect será el código SQL que usaremos para acceder a la tabla de esa base de datos.
La variable
ArchivoDatos será el nombre completo de la base de datos (Path incluido).
La variable
NombreTabla será el nombre que usaremos para identificar a los datos que cargaremos en el objeto DataAdapter, ese nombre no tiene nada que ver con el nombre de la tabla a la que vamos a acceder, es sólo un nombre que usaremos con los distintos objetos de ADO.NET.

 

Conectar a una base de datos del tipo Access

Para conectar a la base de datos y crear los objetos que cargarán la tabla (definida en la consulta SQL contenida en la variable CadenaSelect), vamos a crear un procedimiento en el mismo módulo ADONETUtil:


    Friend Sub Conectar(Optional ByVal nombreBaseDatos As String = "", _
                        Optional ByVal commandString As String = "")

        If nombreBaseDatos = "" Then
            nombreBaseDatos = ArchivoDatos
        End If
        ArchivoDatos = nombreBaseDatos
        If ArchivoDatos = "" Then
            Exit Sub
        End If
        '
        If CadenaSelect = "" Then
            CadenaSelect = "SELECT * FROM Table1"
        End If
        If commandString = "" Then
            commandString = CadenaSelect
        End If
        CadenaSelect = commandString
        '
        CadenaConexion = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" & ArchivoDatos
        '
        Try
            dbConnection = New OleDbConnection(CadenaConexion)
        Catch e As Exception
            MessageBox.Show("Error al crear la conexión:" & vbCrLf & e.Message)
            Exit Sub
        End Try
        '
        dbConnection.Open()
        '
        dbDataSet = New Data.DataSet()
        '
        dbDataAdapter = New OleDbDataAdapter(CadenaSelect, dbConnection)
        '
        Dim commandBuilder As New OleDbCommandBuilder(dbDataAdapter)
        '
        dbDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
        'dbDataAdapter.Fill(dbDataSet, NombreTabla)
        ' Cambio para comprobar que funciona,                     (20/Jul/02)
        ' En la versión Architect me da error, pero continua funcionando.
	' Aunque el error lo da si se pasa una cadena SQL que no es correcta,
	' por ejemplo, cuando se inicia la aplicación y se deja el texto por defecto.
        Try
            dbDataAdapter.Fill(dbDataSet, NombreTabla)
        Catch ex As Exception
            MessageBox.Show("Error en Fill:" & vbCrLf & ex.Message)
        End Try
        '
    End Sub

A este procedimiento se le pueden indicar dos parámetros:
El primero indicará el nombre de la base de datos (path incluido), mientras que el segundo será la instrucción SQL que nos permitirá acceder a la tabla. Cuando veamos el código del formulario, tendremos ocasión de ver cómo se llama a este procedimiento.

Lo que aquí tenemos que destacar es lo siguiente:
La cadena de conexión que tenemos que usar para conectar con la base de datos es igual que la usada en las versiones anteriores de ADO, en esto no ha cambiado nada; se le indica el tipo de proveedor y el nombre de la base de datos.
Creamos un nuevo objeto del tipo DataSet, que será el que nos permita acceder a los datos.
Creamos un objeto del tipo DataAdapter, este será el que realmente nos permita acceder a los datos físicos de la base de datos, primero para rellenar el DataSet y posteriormente para actualizar los cambios realizados en la base de datos.
Es importante saber que los datos contenidos en el objeto DataSet están en memoria y se acceden y manipulan de forma independiente, sin ninguna relación directa con la base de datos original. Para que los cambios realizados en memoria se puedan reflejar de forma permanente en la base de datos, tenemos que usar el objeto DataAdapter.
Una vez creado el DataAdapter, al que se le indica la cadena de selección y la cadena de conexión o el objeto del tipo Connection. En este caso, le he indicado el objeto Connection, pero si no queremos crear una conexión permanente, también podemos usar la misma cadena de conexión usada para crear el objeto Connection en lugar de ese objeto.
Para poder realizar cambios en la base de datos, hay que indicarle al DataAdapter los comandos SQL que habría que usar para añadir, modificar y eliminar, pero esos comandos se pueden crear de forma automática creando un nuevo objeto del tipo CommandBuilder, al que se le pasará como parámetro el adaptador que usará dichos comandos, en realidad el objeto CommandBuilder no se usa nada más que para que de esta forma se asignen dichos comandos de actualización de datos.
Si la tabla a la que queremos acceder tiene clave principal (cosa que es común o debería serlo en todas las tablas), le indicamos mediante la asignación a la propiedad MissingSchemaAction el valor de añadir con clave. Si no hacemos esta asignación y la tabla tiene un campo que se incrementa automáticamente, no podríamos crear varios registros (o filas) en memoria y después actualizar esas nuevas filas de forma permanente, ya que nos daría un error de que hay duplicidad en el campo (o columna) autoincremental.
Por último, llenamos el objeto DataSet con los datos del DataAdapter, el segundo parámetro es el nombre que le vamos a dar a la tabla virtual (la que se crea en memoria), si no le damos un nombre a esa tabla virtual del DataSet, se asignará de forma predeterminada con el nombre Table. Es conveniente asignar un nombre, ya que un objeto DataSet permite tener en memoria distintos datos, de la misma o de otras tablas o consultas. Ese nombre será el que usaremos para acceder a esos datos en memoria, ya que todos los accesos al contenido del DataSet, como he comentado antes, se realizan de forma desconectada, es decir sin relación ninguna con la base de datos física.

 

Los nombres de las tablas de una base de datos

Vamos a crear una función que devuelva una matriz (o array) del tipo String, con los nombres de las tablas de la base de datos.


    Friend Function NombresTablas() As String()
        Dim nomTablas() As String
        Dim dataTable As Data.DataTable
        Dim dbNull As System.DBNull
        Dim restrictions() As Object = {dbNull, dbNull, dbNull, "TABLE"}
        Dim i As Integer
        '
        If dbConnection Is Nothing Then
            dbConnection = New Data.OleDb.OleDbConnection(CadenaConexion)
        End If
        If dbConnection.State <> ConnectionState.Open Then
            dbConnection.Open()
        End If
        '
        dataTable = dbConnection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, restrictions)
        i = dataTable.Rows.Count - 1
        If i > -1 Then
            ReDim nomTablas(i)
            For i = 0 To dataTable.Rows.Count - 1
                nomTablas(i) = dataTable.Rows(i).Item("TABLE_NAME").ToString()
            Next
        End If
        ' 
        Return nomTablas
    End Function

Antes de llamar a esta función tendremos que tener asignada la cadena de conexión o, mejor aún, creada la conexión a la base de datos. Eso es lo que se hace en las primeras líneas, si el objeto del tipo Connection no está creado, se crea un nuevo objeto, usando la cadena de conexión. En caso de que esté creado el objeto, se comprueba si está abierta dicha conexión, de no ser así, se abre.
A continuación asignamos un objeto del tipo DataTable con el "esquema" de las tablas contenidas en la base de datos a la que apunta el objeto Connection. El "truco" para conseguir los nombres de las tablas, está en el array restrictions, particularmente en el cuarto elemento: "TABLE".
Recorremos el contenido del objeto DataTable y accedemos a cada una de las filas cuyo elemento contenga la cadena "TABLE_NAME", el cual nos dará el nombre de cada una de las tablas. Ese nombre lo asignamos a cada uno de los elementos del array que estamos usando de forma interna, el cual será el que la función devuelva.

 

Los nombres de los campos (o columnas) de una tabla

Para saber los nombres de los campos o columnas de una tabla, usaremos el contenido del objeto DataSet que hace referencia a la tabla que hemos cargado mediante el DataAdapter, aunque también podría servirnos para acceder a cualquier tabla virtual contenida en el DataSet.
También vamos a crear una función que devuelva una matriz del tipo String:


    Friend Function NombresColumnas() As String()
        Dim columna As Data.DataColumn
        Dim i, j As Integer
        Dim nomCol() As String
        '
        j = dbDataSet.Tables(NombreTabla).Columns.Count - 1
        ReDim nomCol(j)
        For i = 0 To j
            columna = dbDataSet.Tables(NombreTabla).Columns(i)
            nomCol(i) = columna.ColumnName
        Next
        Return nomCol
    End Function

Creo que el código es bastante auto-explicativo y no necesita más aclaración.

 

Asignar la cabecera de un control ListView con los nombres de las columnas o campos de una tabla

Para terminar con el código del módulo ADONETUtil, vamos a ver unos métodos que usaremos para asignar las columnas (o cabecera) de un ListView con los nombres de las columnas o campos de la tabla que vamos a utilizar.
El método se llama AsignarCabeceraLista y tendrá dos implementaciones, una indicando sólo el nombre del ListView y la otra en la que además se indicará un control ComboBox en el cual se asignarán también esos nombres de las columnas de la tabla.


    Friend Sub AsignarCabeceraLista(ByVal ListView1 As ListView)
        Dim columna As Data.DataColumn
        Dim i, j As Integer
        '
        With ListView1
            .View = View.Details
            .FullRowSelect = True
            .GridLines = True
            .LabelEdit = False
            .HideSelection = False
            .Columns.Clear()
        End With
        '
        Dim lasColumnas() As String
        lasColumnas = NombresColumnas()
        '
        If Not lasColumnas Is Nothing Then
            For i = 0 To lasColumnas.Length - 1
                ListView1.Columns.Add(lasColumnas(i), 100, HorizontalAlignment.Left)
            Next
        End If
        '
    End Sub

    Friend Sub AsignarCabeceraLista(ByVal ListView1 As ListView, _
                                    ByVal cboCampos As ComboBox)
        Dim i As Integer
        '
        AsignarCabeceraLista(ListView1)
        cboCampos.Items.Clear()
        For i = 0 To ListView1.Columns.Count - 1
            cboCampos.Items.Add(ListView1.Columns(i).Text)
        Next
        '
        If cboCampos.Items.Count > 0 Then
            cboCampos.SelectedIndex = 0
        End If
    End Sub

Creo que tampoco necesita explicación, ya que lo único que se hace es llamar a la función NombresColumnas y el contenido de ese array es el que se asigna a la cabecera del ListView que se ha indicado en el parámetro.
En cuanto a la segunda implementación, se asigna al control ComboBox pasado como parámetro esos mismos nombres.
Cuando veamos el código de los formularios, sabremos cuando usar una u otra versión de este método.

 

Llenar un ListView con el contenido de una tabla

El siguiente método del módulo ADONETUtil rellenará un control ListView con los datos de la tabla cargada en el objeto DataSet.


    Friend Sub LLenarLista(ByVal unListView As ListView)
        Dim i As Integer
        Dim lwItem As ListViewItem
        Dim fila As Data.DataRow
        '
        unListView.Items.Clear()
        '
        For Each fila In dbDataSet.Tables(NombreTabla).Rows
            For i = 0 To unListView.Columns.Count - 1
                If i = 0 Then
                    lwItem = unListView.Items.Add(fila(i).ToString)
                    lwItem.Tag = fila
                Else
                    lwItem.SubItems.Add(fila(i).ToString)
                End If
            Next
        Next
    End Sub

El código es también bastante simple, sólo quiero aclarar un detalle: La asignación del objeto fila al TAG del elemento del ListView.
Realmente no es necesario, pero yo lo utilizo esa fila para acceder a cada uno de los registros de la tabla, ya que al modificar los datos, sólo los reflejaremos en el contenido del ListView y de esa forma sabremos a que fila estamos accediendo. Cuando actualicemos los datos, usaremos el objeto que hemos guardado en la propiedad Tag del objeto ListViewItem, y ese mismo objeto será el que usaremos para eliminar una fila del DataSet.

 

Para terminar con el código del módulo, una función que usaremos para mostrar información de la base de datos a la que estamos accediendo. En este caso es una función que recibe como parámetro un path completo y devuelve sólo el nombre de la base de datos:


    Friend Function NombreBase(ByVal path As String) As String
        ' Devolver sólo el nombre de la base de datos
        Dim i As Integer
        '
        i = path.LastIndexOf("\")
        If i > -1 Then
            Return path.Substring(i + 1)
        End If
    End Function

Aquí se podrían haber usado funciones "clásicas" de Visual Basic, como InStr o Mid, pero he preferido usar sus equivalentes de VB.NET, entre otras cosas porque forman parte del objeto String. En el caso de LastIndexOf, busca la última ocurrencia de la cadena indicada en el parámetro, si dicha cadena no forma parte del objeto, (en este caso la variable path), se devuelve un valor -1 y en caso de que si esté, se devuelve la posición, pero hay que tener en cuenta que la primera posición está representada por el valor cero.

 

El formulario principal

Este es el aspecto del formulario principal (Form1) en tiempo de diseño:


El formulario principal en tiempo de diseño

Este formulario permitirá que se arrastre una base de datos y ese nombre se asignará a la caja de textos del nombre de la base de datos. Los controles tienen asignados los valores de la propiedad Anchor para que se ajusten al tamaño que el usuario quiera darle al formulario, esos detalles podremos verlo en el código completo, ya que aquí sólo mostraré la parte que realmente interesa, es decir lo que está relacionado con el acceso a la base de datos.

Empecemos por el código del botón "Mostrar tablas" y el evento producido cuando se selecciona una tabla del ComboBox:


    Private Sub btnAbrirBase_Click(ByVal sender As System.Object, _
                                   ByVal e As System.EventArgs) _
                                   Handles btnAbrirBase.Click
        '
        Conectar(txtNombreBase.Text, txtSelect.Text)
        '
        '
        Dim nomTablas() As String
        Dim i As Integer
        '
        nomTablas = NombresTablas()
        cboTablas.Items.Clear()
        If Not nomTablas Is Nothing Then
            For i = 0 To nomTablas.Length - 1
                cboTablas.Items.Add(nomTablas(i))
            Next
        End If
        If cboTablas.Items.Count > 0 Then
            cboTablas.SelectedIndex = 0
        End If
    End Sub

    Private Sub cboTablas_SelectedIndexChanged(ByVal sender As System.Object, _
                                   ByVal e As System.EventArgs) _
                                   Handles cboTablas.SelectedIndexChanged
        txtSelect.Text = "SELECT * FROM " & cboTablas.Text
        '
        '' Si se quieren mostrar individualmente los nombres de los campos
        'Dim lasColumnas() As String
        'Dim s As String, i As Integer
        ''
        'CadenaSelect = txtSelect.Text
        'lasColumnas = NombresColumnas()
        'For i = 0 To lasColumnas.Length - 1
        '    s &= lasColumnas(i) & ", "
        'Next
        '' Quitar la última coma
        'i = s.LastIndexOf(", ")
        's = s.Substring(0, i)
        's = "SELECT " & s & " FROM " & cboTablas.Text
        'txtSelect.Text = s
    End Sub

El primer procedimiento intercepta la pulsación en el botón y asigna los nombres de las tablas en el combo.
El segundo, simplemente crea la cadena SQL que se usará para acceder a dicha tabla.
El código que está comentado sirve para mostrar los nombres de los campos o columnas de forma individual.

Cuando pulsamos en el botón "Mostrar", se muestra el contenido de la tabla indicada, información que se obtiene del DataSet que contiene los datos en memoria. Aunque, el código mostrado, realmente refresca esa información, esto lo he hecho así para que al volver de modificar los datos, se pueda comprobar que los datos realmente se han guardado en la base de datos.


    Private Sub btnMostrar_Click(ByVal sender As System.Object, _
                                 ByVal e As System.EventArgs) _
                                 Handles btnMostrar.Click
        '
        With ListView1
            .View = View.Details
            .FullRowSelect = True
            .GridLines = True
            .LabelEdit = False
            .HideSelection = False
            .Columns.Clear()
        End With
        '
        ' Volver a reconectar para actualizar los datos desde la base
        If Not dbConnection Is Nothing Then
            dbConnection.Close()
        End If
        Conectar(txtNombreBase.Text, txtSelect.Text)
        '
        AsignarCabeceraLista(ListView1)
        LLenarLista(ListView1)
    End Sub

Como podemos comprobar, aquí se llaman a los métodos del módulo ADONETUtil, por tanto al principio del código del formulario debemos hacer la importación del espacio de nombres de ese módulo para no tener que especificarlo cada vez que queramos acceder a cualquiera de los procedimientos o variables en el declarado:

' Importamos el módulo con las declaraciones de los objetos a usar
Imports ADONET1.ADONETUtil

ADONET1 es el nombre del "espacio de nombres" (Namespace) de este proyecto.

Para acabar con el código de este formulario, veamos los eventos que se producen al cargarse el formulario (Load) y al cerrarse (Closing), además del evento producido al pulsar en el botón "Mostrar", el cual mostrará el formulario en el que se editan los datos de la tabla indicada.


    Private Sub Form1_Load(ByVal sender As Object, _
                           ByVal e As System.EventArgs) _
                           Handles MyBase.Load
        Me.txtNombreBase.Text = "E:\gsCodigo\Vb6\Pruebas\Bases\db2000NET.mdb"
        '
        ArchivoDatos = txtNombreBase.Text
        CadenaConexion = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" & ArchivoDatos
        '
        With ListView1
            .View = View.Details
            .FullRowSelect = True
            .GridLines = True
            .LabelEdit = False
        End With
        '
        cboTablas.Text = ""
    End Sub

    Private Sub Form1_Closing(ByVal sender As Object, _
                              ByVal e As System.ComponentModel.CancelEventArgs) _
                              Handles MyBase.Closing
        ' Cerrar la conexión
        Try
            If dbConnection.State = ConnectionState.Open Then
                dbConnection.Close()
            End If
        Catch
        End Try
    End Sub

    Private Sub btnModificar_Click(ByVal sender As System.Object, _
                                   ByVal e As System.EventArgs) _
                                   Handles btnModificar.Click
        Dim f2 As New Form2()
        With f2
            .DataSource = txtNombreBase.Text
            .CommandString = txtSelect.Text
            .ShowDialog()
        End With
        btnMostrar_Click(btnMostrar, e)
    End Sub

En el evento Load, asignamos los valores iniciales, en el evento Closing, comprobamos si tenemos la conexión abierta y de ser así la cerramos, debido a que puede ser que se cierre el formulario sin necesidad de haber creado dicho objeto, interceptamos el error que se pudiera producir.
Por otro lado, cuando pulsamos en el botón Modificar, creamos una nueva instancia del formulario en el que modificaremos la información y asignamos los valores de la base de datos y la cadena SQL que usaremos para conectar, esto lo hago así por si se pulsa en dicho botón sin haber creado la conexión.

El formulario de edición de datos

Para terminar con este ejemplo de acceso a datos usando ADO.NET, veamos el formulario que usaremos para modificar la información de la base de datos.

El aspecto del formulario (FORM2) será el mostrado en la siguiente imagen:


El formulario de introducción de datos

En este formulario, los controles también están "anclados" para que se adapten al tamaño que el usuario quiera darle al formulario.

Empecemos por las variables o propiedades que este formulario expone al mundo externo:


' Importamos el módulo con las declaraciones de los objetos a usar
Imports ADONET1.ADONETUtil

Public Class Form2
    Inherits System.Windows.Forms.Form

    Friend DataSource As String
    Friend CommandString As String
    '
    Private lwItemActual As ListViewItem

También usamos el Imports para poder usar los procedimientos del módulo ADONETUtil y declaramos dos propiedades: DataSource y CommandString, las cuales usaremos para acceder a la base de datos.
La variable lwItemActual hará referencia al elemento del ListView que esté actualmente seleccionado.

En el evento Load del formulario, se asignarán algunos valores por defecto y se mostrará el contenido de la tabla indicada en el ListView:


    Private Sub Form2_Load(ByVal sender As System.Object, _
                           ByVal e As System.EventArgs) _
                           Handles MyBase.Load
        Me.txtCampo1.Text = ""
        '
        ' para usar este formulario como formulario inicial
        If DataSource = "" Then
            DataSource = "E:\gsCodigo\Vb6\Pruebas\Bases\db2000NET.mdb"
        End If
        If CommandString = "" Then
            CommandString = "SELECT * FROM Table1"
        End If
        '
        lblInfo.Text = "Base: " & NombreBase(DataSource) & ", Select: " & CommandString
        '
        If dbConnection Is Nothing Then
            Conectar(DataSource, CommandString)
        End If
        '
        AsignarCabeceraLista(ListView1, cboCampos)
        LLenarLista(ListView1)
        '
        Me.AcceptButton = btnAsignar
    End Sub

Cuando seleccionamos un nuevo elemento del ListView, se asigna la variable que contiene el elemento actual y se muestra la información o datos de dicha fila.


    Private Sub ListView1_SelectedIndexChanged(ByVal sender As Object, _
                                    ByVal e As System.EventArgs) _
                                    Handles ListView1.SelectedIndexChanged
        Try
            lwItemActual = ListView1.SelectedItems(0)
        Catch
        End Try
    End Sub
    Private Sub ListView1_Click(ByVal sender As Object, _
                                ByVal e As System.EventArgs) _
                                Handles ListView1.Click, cboCampos.SelectedIndexChanged
        Try
            lwItemActual = ListView1.SelectedItems(0)
            MostrarCampo()
        Catch
        End Try
    End Sub

Como podemos comprobar, el procedimiento ListView1_Click realmente intercepta dos eventos, el evento Click del ListView y el evento SelectedIndexChanged del Combo, de forma que se muestre la información del campo seleccionado en el ComboBox. De eso se encarga el procedimiento MostrarCampo:


    Private Sub MostrarCampo()
        Dim i As Integer
        '
        Try
            i = cboCampos.SelectedIndex
            If i = 0 Then
                txtCampo1.Text = lwItemActual.Text
            ElseIf i > -1 Then
                txtCampo1.Text = lwItemActual.SubItems(i).Text
            End If
        Catch
        End Try
    End Sub

Este procedimiento simplemente muestra el contenido del campo que está seleccionado en el control ComboBox.

 

Asignar los datos

Cuando pulsamos en el botón Asignar, el cual hemos asignado como botón "Aceptar" del formulario, es decir el que tomará el foco cuando pulsemos la tecla Intro, se asignarán los cambios realizados al campo (o columna) que estamos editando:


    Private Sub AsignarCampo(ByVal sender As System.Object, _
                             ByVal e As System.EventArgs) _
                             Handles btnAsignar.Click
        ' Asignar al ListView el campo modificado
        Dim i As Integer
        '
        i = cboCampos.SelectedIndex
        If i = 0 Then
            lwItemActual.Text = txtCampo1.Text
        ElseIf i > -1 Then
            lwItemActual.SubItems(i).Text = txtCampo1.Text
        End If
    End Sub

 

Eliminar una fila

Cuando pulsamos en el botón Eliminar, borramos la fila seleccionada del ListView y también esa misma fila del DataSet, y como comenté anteriormente, en la propiedad Tag del elemento del ListView tenemos una referencia a la fila de datos, por tanto usamos ese objeto para eliminar la fila de la colección Rows del objeto DataSet, ya que el método Remove de la colección Rows acepta como parámetro un objeto del tipo DataRow:


    Private Sub btnEliminar_Click(ByVal sender As System.Object, _
                                  ByVal e As System.EventArgs) _
                                  Handles btnEliminar.Click
        ' Eliminar la fila indicada
        Dim fila As Data.DataRow
        '
        fila = CType(ListView1.SelectedItems(0).Tag, Data.DataRow)
        dbDataSet.Tables(NombreTabla).Rows.Remove(fila)
        ListView1.Items.Remove(ListView1.SelectedItems(0))
    End Sub

 

Crear una nueva fila (o registro)

Para crear un nuevo registro (o fila), tendremos que asignar unos valores nulos o por defecto a una nueva fila creada en la memoria, después esa fila la añadiremos a la tabla que mantenemos en el DataSet.
Debido a que algunos campos no permiten valores nulos, tendremos que tener ese detalle en cuenta y de ser así, asignaremos un valor adecuado al tipo de datos de cada una de las columnas (o campos) del registro que hemos creado, esto lo conseguimos comprobando el tipo de datos de cada una de las columnas de la nueva fila. Hay que tener en cuenta que los tipos de datos se guardan usando el que se define en .NET Framework, no los tipos que utiliza Visual Basic, por tanto, para saber si el tipo de una columna es del tipo Integer, tendremos que usar System.Int32, de todas formas, para saber el tipo de dato, (que lo da la propiedad DataType del objeto DataColumn), he utilizado la conversión a cadena generada por ToString, por lo que dicho tipo se convierte en el formato "System.Tipo", veamos el código para aclarar todo este lío:


    Private Sub btnNuevo_Click(ByVal sender As System.Object, _
                               ByVal e As System.EventArgs) _
                               Handles btnNuevo.Click
        ' Añadir una nueva fila (o registro)
        Dim fila As Data.DataRow
        Dim i As Integer
        Dim lwItem As ListViewItem
        Dim columna As Data.DataColumn
        '
        fila = dbDataSet.Tables(NombreTabla).NewRow
        fila.BeginEdit()
        For i = 0 To dbDataSet.Tables(NombreTabla).Columns.Count - 1
            columna = dbDataSet.Tables(NombreTabla).Columns(i)
            'Debug.WriteLine(columna.DataType.ToString)
            If columna.AutoIncrement = False Then
                Select Case columna.DataType.ToString
                    Case "System.String"
                        fila(i) = columna.ColumnName
                    Case "System.Boolean"
                        fila(i) = False
                    Case "System.Byte", "System.SByte"
                        fila(i) = CByte(0)
                    Case "System.Char"
                        fila(i) = " "c
                    Case "System.DateTime", "System.TimeSpam"
                        fila(i) = Now
                    Case "System.Decimal", "System.Double", "System.Single"
                        fila(i) = 0
                    Case Else
                        'Case "System.Int32","System.UInt32"
                        '    fila(i) = 0
                        If columna.DataType.ToString.IndexOf("System.Int") > -1 Then
                            fila(i) = 0
                        ElseIf columna.DataType.ToString.IndexOf("System.UInt") > -1 Then
                            fila(i) = 0
                        End If
                End Select
            End If
        Next
        fila.EndEdit()
        ' Añadir la fila a la tabla
        dbDataSet.Tables(NombreTabla).Rows.Add(fila)
        '
        ' Mostrar la nueva fila en el ListView
        For i = 0 To ListView1.Columns.Count - 1
            If i = 0 Then
                lwItem = ListView1.Items.Add(fila(i).ToString)
                lwItem.Tag = fila
            Else
                lwItem.SubItems.Add(fila(i).ToString)
            End If
        Next
        '
    End Sub

Lo que en este evento hacemos es crear una nueva fila mediante el método NewRow, asignamos los campos (o columnas) de dicha fila y la añadimos a la colección Rows de la tabla. Hay que tener en cuenta que al crear una nueva fila con NewRow no se añade a la colección de filas (o registros), simplemente se devuelve un objeto que está preparado para que se le asignen los datos correspondientes. Antes de asignar cada una de las columnas, comprobamos si dicha columna está marcada como autoincremental, de ser así, no asignamos nada, ya que es el propio DataAdapter el que se encarga de asignar el valor de dicha columna.

En este punto quiero hacer una aclaración, debido a que los datos los estamos asignando a un objeto que mantiene la información en la memoria, si existen varias aplicaciones que acceden a la misma base de datos y cada una de ellas crea nuevas filas, el valor asignado al campo (o columna) AutoIncrement puede que no sea el que definitivamente tenga en la base de datos. Por tanto, debemos tener esto presente si el valor asignado a esa columna lo utilizamos para otros menesteres.

Una vez que hemos añadido la nueva fila a la tabla, asignamos el contenido de la misma al ListView y también asignamos a la propiedad Tag una referencia a dicha fila.

 

Guardar la información en la base de datos

Por último vamos a ver cómo pasar la información mantenida en la memoria a la base de datos, con idea de que los cambios realizados se queden guardados permanentemente.
Esto lo hacemos cuando el usuario pulsa en el botón de "Actualizar Base" y el código usado es el siguiente:


    Private Sub btnActualizarBase_Click(ByVal sender As System.Object, _
                                    ByVal e As System.EventArgs) _
                                    Handles btnActualizarBase.Click
        ' Actualizar la base de datos con los cambios realizados
        Dim fila As Data.DataRow
        Dim i, j As Integer
        Dim lwItem As ListViewItem
        Dim columna As Data.DataColumn
        '
        lblInfo.Tag = lblInfo.Text
        lblInfo.Text = "Actualizando los datos..."
        lblInfo.Refresh()
        Try
            For i = 0 To ListView1.Items.Count - 1
                lwItem = ListView1.Items(i)
                fila = CType(ListView1.Items(i).Tag, Data.DataRow)
                fila.BeginEdit()
                j = 0
                For Each columna In dbDataSet.Tables(NombreTabla).Columns
                    If j = 0 Then
                        If columna.AutoIncrement = False Then
                            fila(j) = lwItem.Text
                        End If
                    Else
                        If columna.AutoIncrement = False Then
                            fila(columna.ColumnName) = lwItem.SubItems(j).Text
                        End If
                    End If
                    ' 
                    j += 1
                Next
                fila.EndEdit()
            Next
            '
            '
            dbDataAdapter.Update(dbDataSet, NombreTabla)
            dbDataSet.AcceptChanges()
            '
            '
            lblInfo.Text = CStr(lblInfo.Tag)
        Catch errActualizar As Exception
            lblInfo.Text = errActualizar.Message
            MsgBox(errActualizar.Message)
        End Try
    End Sub

En este procedimiento actualizamos la información de cada una de las filas, para ello usamos el objeto almacenado en la propiedad Tag de cada uno de los elementos del ListView que como recordarás era en realidad una referencia a cada una de las filas de la tabla, (realmente un objeto del tipo DataRow). Recorremos cada una de las columnas y asignamos sólo los datos si no es una columna del tipo AutoIncrement.
La llamada a los métodos BeginEdit y EndEdit de cada fila es para que no se produzcan los eventos asociados a un objeto de este tipo, no son realmente necesarios para poder cambiar los contenidos de las columnas.

Una vez que hemos asignado todos los datos del ListView, llamamos al método Update del DataAdapter, este método es el que realmente hace que los datos se guarden físicamente en la base de datos.
Después llamamos al método AcceptChanges del objeto DataSet para que "sepa" que hemos aceptado la nueva información y marque la información de forma que sean iguales a los datos que tiene la base de datos real.

Y esto es todo... que no es poco... en otras ocasiones veremos cómo acceder a los datos de una base de datos mediante controles enlazados y con un DataGrid que es en realidad la forma más fácil de hacerlo.
Realmente si hubiésemos usado ese control, no tendríamos que haber escrito la mayoría del código que he mostrado, pero como se que hay gente que le gusta tener un control "casi" total sobre cómo se accede a la información... pues eso, que aquí está la forma "difícil" de hacerlo y ya habrá ocasión de ver la forma "fácil".

En otra ocasión, veremos este mismo código aplicado a una base de datos de SQL Server.

Nota 19/Abr/2003:
Esa ocasión ya ha llegado, sigue este link para el ejemplo de SQL Server.

Nos vemos.
Guillermo


Si quieres bajarte el código completo de este ejemplo, usa este link: ADONET1.zip 14.6 KB


la Luna del Guille o... el Guille que está en la Luna... tanto monta...