Iniciación a la programación en ADO .NET Fecha: 14/Sep/2004 (14/Septiembre/2004) |
INTRODUCCIÓN
La programación en ADO .NET, la tecnología de .NET para el acceso a datos, ha despertado gran interés y muchas personas se están iniciando en ella. A pesar de tratarse de una tecnología que facilita enormemente la programación en relación con las bases de datos, hay algunas dificultades que pueden retrasar el aprendizaje del programador principiante, entre ellas, aprender el uso adecuado del lenguaje SQL para realizar las tareas deseadas en los datos dentro de la aplicación ASP .NET.
Pensando en esos programadores que empiezan el estudio de ADO .NET, propongo este ejercicio, realizado en lenguaje Visual Basic .NET, basado en estas premisas:FUNDAMENTOS DEL EJERCICIO
- se trata de una aplicación de consola, para ganar en rapidez de desarrollo y de ejecución y también para evitar las dificultades relacionadas con el manejo de los formularios Windows y con los controles específicos de que dispone ADO .NET. Se centra en los conceptos básicos de ADO .NET y SQL y, una vez aprendidos, no resultará difícil trasladarlos a una aplicación de interfaz gráfica.
- todas las operaciones realizadas sobre los datos se hacen mediante instrucciones SQL, tanto las que muestran los datos al usuario como las que los modifican.
- se ha elegido una base de datos de Microsoft Access, en lugar de SQL Server, como origen de los datos, por 3 motivos:
Sin embargo, dada la importancia que, a nivel empresarial, tiene SQL Server, se proporciona el código de la aplicación para los 2 proveedores, uno para conectar con una base de datos de Access y otro para conectar con SQL Server en modo local.
- Access es muy asequible por estar instalado en un porcentaje elevado de los ordenadores personales y su uso en ejercicios como el propuesto es más sencillo que el de SQL Server.
- es muy probable que la inmensa mayoría de los programadores noveles en ADO .NET sepan usar Access y, sin embargo, apenas hayan tenido contacto con SQL Server.
- Access no requiere tener un servidor de bases de datos en marcha en la máquina local, como sí es necesario en SQL Server.
- el código ha de estar comentado ampliamente para que sea fácilmente comprensible en su totalidad por el programador principiante.
- el programa va informando de las acciones al usuario, al que se requiere la pulsación de la tecla INTRO para continuar; se busca ante todo que la aplicación sea demostrativa.
- manejo de excepciones para que el usuario siempre reciba información útil en caso de error (no poder acceder a la base de datos, borrar una tabla que no existe, crear una tabla que ya existe, etc...).
- se puede utilizar Visual Studio 2003 para su desarrollo pero no es imprescindible aunque sí muy cómodo, en realidad basta con tener estos 2 elementos en nuestro sistema:
- un editor de texto para escribir los archivos con el código Visual Basic .NET (por ej: EditPlus, ver aquí).
- SDK (Kit de Desarrollo de Software) de .NET Framework: está disponible para Windows NT con SP6a, Windows 2000, Windows XP y Windows .NET 2003. La instalación de Visual Studio .NET conlleva automáticamente la del SDK de .NET Framework.
Los pasos para desarrollar el ejercicio son:
DESARROLLO DEL EJERCICIO
- Antes de ejecutarlo por primera vez, hay que crear una nueva base de datos de Microsoft Access, llamada pruebasDB.mdb, que contenga 3 tablas:
- Prueba1: es la tabla que es modificada desde el programa.
- Prueba2: contiene unos registros que el programa copia a la tabla Prueba1.
- Prueba3: es la tabla de reserva con todos los registros originales para evitar tener que volver a escribirlos.
Las 3 tablas han de tener la misma estructura:
- Nombre: campo de texto, max. 40 caracteres.
- Teléfono: campo de texto, max. 9 caracteres.
Un tercer campo Id, de tipo autonumérico, será creado mediante una sentencia SQL desde la aplicación ADO .NET.
La tabla Prueba1 puede estar vacía, pero la tabla Prueba2 ha de tener registros. La base de datos pruebasDB.mdb ha de estar situada en la carpeta \bin del proyecto (ahí es donde la buscará la aplicación por defecto).
Para mayor comodidad, se puede descargar la base de datos de Microsoft Access, ya creada con esa estructura, desde aquí (o también para SQL Server aquí).
- Es necesario importar los espacios de nombres System, System.Data y System.Data.OleDb pues en ellos están las clases requeridas para este tipo de conexiones:
Imports System Imports System.Data Imports System.Data.OleDb- Se utilizan 3 objetos del espacio de nombres System.Data.OleDb:
- OleDbConection (para conectar con la base de datos)
- OleDbCommand (comando para realizar las acciones sobre los datos)
- OleDbDataReader (lector de los datos, de sólo lectura, que solamente puede leer hacia adelante pero es rápido y sencillo de usar). Las clases en .NET pueden ser, entre otros tipos, abstractas ("abstract") o selladas ("sealed", precintadas):
- una clase abstracta requiere necesariamente una clase derivada para proporcionar una implementación.
- una clase sellada no admite una clase derivada.
OledbDataReader es una clase precintada o sellada -"sealed"-, no se puede heredar de ella:Private con As OleDbConnection = New OleDbConnection Private com As OleDbCommand = New OleDbCommand Private col As OleDbDataReader ' OleDbDataReader no se construye con New, a diferencia de OleDbConnection y OleDbCommand- Se usa una cadena de conexión muy sencilla, típica del acceso a bases de datos de Access:
con.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=pruebasDB.mdb;" con.Open()- El objeto OleDbCommand se asigna a la conexión creada con la instrucción com.Connection = con. Los objetos de comando tienen distintos métodos de ejecución:
- para visualizar los datos se emplea el método ExecuteReader() que ejecuta una sentencia SQL y devuelve un objeto OleDbDataReader con los datos; se captura su valor de retorno.
- para modificar los datos se emplea el método ExecuteNonQuery() que ejecuta una sentencia SQL que no devuelve datos (DELETE, UPDATE, INSERT...); no se captura su valor de retorno.
- para devolver un valor único (ej: una única columna o un resultado como una suma, un valor máximo, etc.) después de ejecutar una sentencia SELECT se emplea el método ExecuteScalar().
Al comenzar el ejercicio se borra la tabla Prueba para que no haya error al crearla:
com.CommandText = "DROP TABLE Prueba;" com.ExecuteNonQuery()Se crea la tabla Prueba1 con 3 campos (Id, Nombre, Teléfono). El campo Id se declara como:
- Tipo: entero (INTEGER).
- IDENTITY: la propia base de datos va asignando valores incrementalmente por lo que no se declara explícitamente en los comandos SQL. Es equivalente al campo Autonumérico de Microsoft Access.
- PRIMARY KEY: es la clave primaria de la tabla.com.CommandText = "CREATE TABLE Prueba1 " & _ "(Id INTEGER IDENTITY PRIMARY KEY, Nombre VARCHAR(40), Teléfono CHAR(9));" com.ExecuteNonQuery()Se insertan registros nuevos mediante sucesivas sentencias SQL de inserción (se proporcionan valores para los campos Nombre y Teléfono pero no para el campo Id, que es creado automáticamente por la base de datos):
com.CommandText = "INSERT INTO Prueba1 (Nombre, Teléfono) VALUES ('Fifth Third BanCorp', '920126032');" com.ExecuteNonQuery() com.CommandText = "INSERT INTO Prueba1 (Nombre, Teléfono) VALUES ('Ohio Casualty Corp.', '900125458');" com.ExecuteNonQuery() com.CommandText = "INSERT INTO Prueba1 (Nombre, Teléfono) VALUES ('Paine Webber Group Inc.', '999521477');" com.ExecuteNonQuery() ' siguen comandos...Se ejecutan varios comandos SQL (de selección, de eliminación, de inserción...) y se van mostrando los datos con las modificaciones efectuadas por las sentencias SQL. Por ejemplo, para mostrar los datos en pantalla sin ordenar (es decir, siguiendo el orden interno de la base de datos que es el de la adición de los registros)
' Seleccionar todos los registros de la tabla com.CommandText = "SELECT * FROM Prueba1" com.ExecuteNonQuery()Para mostrar los datos ordenados por el campo Nombre:
' Seleccionar todos los registros de la tabla ordenados por Nombre com.CommandText = "SELECT * FROM Prueba1 ORDER BY Nombre" com.ExecuteNonQuery()Para borrar un registro con el Nombre especificado:
' Borrar un registro, el que tiene por nombre Eastman Kodak Co. com.CommandText = "DELETE FROM Prueba1 WHERE Nombre='Eastman Kodak Co.';" com.ExecuteNonQuery()Para borrar un registro con el ID especificado:
' Borrar un registro, el que tiene Id=6 com.CommandText = "DELETE FROM Prueba1 WHERE Id=6;" com.ExecuteNonQuery()Para insertar nuevos registros (sin proporcionar valor para el campo Id):' Comando de inserción (inserta registros nuevos en la tabla) com.CommandText = "INSERT INTO Prueba1 (Nombre, Teléfono) VALUES ('Parker Hannifin Corp.', '923652547');" com.ExecuteNonQuery()Para insertar un registro nuevo solicitando Nombre y Teléfono al usuario mediante el método Console.ReadLine(), que lee la siguiente línea de caracteres de la secuencia de entrada (la pantalla en este caso), es decir, lee la cadena que teclea el usuario:
' Variable para contener lo que teclea el usuario Dim n1 As String Console.WriteLine("--- Escribe un nombre y pulsa INTRO") n1 = Console.ReadLine() ' Restricción sencilla para evitar que el nombre introducido supere ' los 40 caracteres de tamaño máximo del campo correspondiente de la tabla While n1.Length > 40 Console.WriteLine("ERROR: El nombre no puede tener más de 40 caracteres.") End While com.CommandText = "INSERT INTO Prueba1 (Nombre) VALUES ('" & n1 & "');" com.ExecuteNonQuery()Para insertar nuevos registros pero copiándolos desde otra tabla existente llamada Prueba2 (ambas tablas, origen y destino de la copia, han de tener idéntica estructura):
' Comando de inserción (inserta 16 registros nuevos en la tabla copiándolos desde la tabla Prueba2) com.CommandText = "INSERT INTO Prueba1 SELECT Nombre, Teléfono FROM Prueba2;" com.ExecuteNonQuery()Mostrar en pantalla, tras cada operación que modifique los datos, los nombres de columna y las filas con los registros:
- Los nombres de columna se pueden obtener mediante el método GetName() del DataReader, que devuelve el nombre de la columna con el índice pasado como parámetro:
' Mostrar los nombres de las columnas de la tabla Console.Write(col.GetName(0).ToUpper() & Space(4) & _ col.GetName(1).ToUpper() & Space(34) & _ col.GetName(2).ToUpper()) ' ' Otra manera de mostrar los nombres de las columnas de la tabla: ' el DataReader tiene una propiedad FieldCount que devuelve el nº ' de campos o columnas de la tabla. 'For i = 0 To (col.FieldCount - 1) 'Console.Write(col.GetName(i).ToUpper() & Space((i + 2) * 7.7)) 'Next- El objeto de comando tiene un método ExecuteReader() que devuelve un DataReader, como ya se ha comentado. Algunas características importantes del DataReader son:
- no puede trabajar en modo desconectado
- es un objeto de sólo lectura
- su finalidad es la de acceder lo más rápido posible a los datos
- sólo permite movimiento hacia adelante a través de los registros
- se usan bucles para recorrer el DataReader hasta el final
Así, para recorrer el DataReader hacia adelante e ir mostrando los datos (col es el DataReader):col = com.ExecuteReader() ' recorrer el DataReader hacia adelante While (col.Read) ' Se muestran 3 formas de obtener datos de un OleDbDataReader: ' - recuperación por índice, devuelve datos de la columna ' con el índice declarado (variables ide y nom), se usan ' 2 propiedades diferentes: Item(i) y GetValue(i) ' - recuperación por nombre, devuelve datos de la columna ' con el nombre declarado (variable tel) ide = CInt(col.Item(0)) nom = CStr(col.GetValue(1)) tel = CStr(col("Teléfono")) ' Mostrar los datos en pantalla Console.WriteLine(ide.ToString & " / " & nom & " / " & tel) End While ' ' Otra manera de mostrar los datos en pantalla 'While (col.Read) ' Console.Write(col(0).ToString() & " / ") ' Console.Write(col(1).ToString() & " / ") ' Console.WriteLine(col(2).ToString()) 'End While ' el DataReader debe ser cerrado explícitamente al terminar de leer col.Close()Se cierra explícitamente la conexión OleDb (si no se hace así, la información de bloqueo de registros de la base de datos de Access permanece en forma de archivo con extensión *.ldb junto al archivo pruebasDB.mdb y puede provocar errores en accesos futuros a la base de datos): ' Cerrar la conexión y los objetos de comando com.Dispose() con.Close() Console.WriteLine("Conexión OLEDB cerrada.")La aplicación se ha desarrollado en 2 módulos:
- Mod_sql.vb: contiene solamente al método principal Main() de entrada al programa, el método Main() llama a otro método Call manejoSQL() más complejo que es desarrollado en el otro módulo, el método manejoSQL() es el que realiza el grueso del trabajo.
- Mod_sql2.vb: contiene 2 métodos, manejoSQL() que conecta con la base de datos y realiza las operaciones con los comandos SQL, y mostrarDatos() que muestra los resultados por pantalla.
Para comprender cómo interactúan ambos módulos entre sí, es conveniente recordar qué son los espacios de nombres (NAMESPACE): es la manera que tiene .NET de organizar componentes cuyo código ha de ser compartido. Las clases que forman parte de .NET se agrupan, para mejor funcionalidad, en unidades de orden superior (Espacios de nombres - Namespaces). Declarando un mismo Namespace para ambos módulos de esta aplicación, es posible compartir objetos entre ellos.
El proceso de mostrar los datos en forma de columnas y filas formateadas se ha encapsulado en el procedimiento llamado mostrarDatos() para que el código sea más legible, ya que ese procedimiento es llamado en varias ocasiones a lo largo de la aplicación y, de esta manera, se acorta mucho el código y es más fácil de leer.