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

Ejemplo de acceso a datos a una base de SQL Server

Publicado: 19/Abr/2003
Actualizado: 21/May/2003

En este ejemplo veremos cómo acceder a una base de datos de SQL Server, usando los objetos del espacio de nombres System.Data.SqlClient.
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:
También puedes ver estos mismos conceptos, pero con una base de datos de Access.
Si notas parecido entre esta página y la de Access, no te extrañe, ya que son hermanitas, pero cada una de ellas mostrando los datos de tipos de bases distintas.


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.SqlClient, 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.SqlClient 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 SQL Server con ADO.NET            (25/May/02)
' Módulo con las declaraciones de objetos
'
' ©Guillermo 'guille' Som, 2002-2003
'------------------------------------------------------------------------------

Imports System.Data.SqlClient

Module ADONETUtil
    Friend dbConnection As Data.SqlClient.SqlConnection
    Friend dbCommand As Data.SqlClient.SqlCommand
    Friend dbDataReader As Data.SqlClient.SqlDataReader
    '
    Friend dbDataTable As Data.DataTable
    Friend dbDataSet As Data.DataSet
    Friend dbDataAdapter As Data.SqlClient.SqlDataAdapter
    '
    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 de SQL Server

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
    '
    If CadenaConexion = "" Then
        '
        ' Nota: Deberías cambiar el nombre del equipo, etc.     (16/Abr/03)
        '
        CadenaConexion = "data source=GUILLEACER\NETSDK;" & _
                         "initial catalog=" & ArchivoDatos & ";" & _
                         "integrated security=SSPI;persist security info=True;" & _
                         "workstation id=GUILLEACER;packet size=4096"
    End If
    '
    Try
        dbConnection = New Data.SqlClient.SqlConnection(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 Data.SqlClient.SqlDataAdapter(CadenaSelect, dbConnection)
    '
    Dim commandBuilder As New Data.SqlClient.SqlCommandBuilder(dbDataAdapter)
    '
    dbDataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
    Try
        dbDataAdapter.Fill(dbDataSet, NombreTabla)
    Catch ex As Exception
        MessageBox.Show(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 de SQL Server, 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 deberíamos tenerla asignada antes de llamar a este procedimiento, de no ser así, se usará la que hemos indicado en el código, aunque deberías cambiarla para adaptarla a tu configuración.
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 de SQL Server

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(Optional ByVal nombreBase As String = "") As String()
    '
    Dim nomTablas() As String
    Dim dataTable As New Data.DataTable()
    Dim i As Integer
    '
    If dbConnection Is Nothing Then
        dbConnection = New Data.SqlClient.SqlConnection(CadenaConexion)
    End If
    If dbConnection.State <> ConnectionState.Open Then
        dbConnection.Open()
    End If
    '
    Dim schemaDA As New Data.SqlClient.SqlDataAdapter( _
                                    "SELECT * FROM INFORMATION_SCHEMA.TABLES " & _
                                    "WHERE TABLE_TYPE = 'BASE TABLE' " & _
                                    "ORDER BY TABLE_TYPE", _
                                    dbConnection)
    '
    schemaDA.Fill(dataTable)
    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.
Para conseguir los nombres de las tablas, buscamos las tablas del tipo "BASE 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(CadenaConexion, CadenaSelect)
    '
    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.

 

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 Object, ByVal e As EventArgs) _
		Handles btnAbrirBase.Click
    '
    Dim nomTablas() As String
    Dim i As Integer
    '
    nomTablas = NombresTablas(txtNombreBase.Text)
    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 Object, ByVal e As 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(CadenaConexion, CadenaSelect)
    '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 ADONET_SQL.ADONETUtil

ADONET_SQL 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 = "pubs"
    Me.txtSelect.Text = "SELECT * FROM authors"
    '
    ArchivoDatos = txtNombreBase.Text
    CadenaConexion = "data source=GUILLEACER\NETSDK;" & _
                     "initial catalog=" & ArchivoDatos & ";" & _
                     "integrated security=SSPI;persist security info=True;" & _
                     "workstation id=GUILLEACER;packet size=4096"
    '
    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 ADONET_SQL.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 = "pubs"
    End If
    If CommandString = "" Then
        CommandString = "SELECT * FROM authors"
    End If
    '
    lblInfo.Text = _
		"Base: " & NombreBase(DataSource) & ", Select: " & CommandString
    '
    If Not dbConnection Is Nothing Then
        dbConnection.Close()
    End If
    Conectar(DataSource, CommandString)
    '
    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 Object, _
                           ByVal e As 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 Object, _
                                ByVal e As 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.

 

Como habrás podido comprobar, si lo comparas con el ejemplo de Access, el código es prácticamente igual, sólo cambia la "procedencia" de algunos objetos, (espacios de nombres), así como el nombre de los mismos, pero en general el código no tiene apenas cambios.

En otra ocasión, espero que no se haga tanto de rogar como esta con respecto a la de Access, veremos un código aún más genérico para acceder tanto a bases de datos de Access como de SQL Server; si veo que tardo mucho en "depurar" ese código, lo publicaré como esté y ya habrá tiempo de actualizarlo o mejorarlo.

Nos vemos.
Guillermo


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


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