1 ADO.NET La programación de BD en .NET utiliza unas cuantas clases en System.Data y sus espacios de nombres hijos, lo que en conjunto se conoce como ADO.NET. Estas clases y sus métodos permiten recuperar datos a partir de Microsoft SQL Server o una de las fuentes de datos OLE DB más genérica, procesarlos y actualizar las tablas de la base de datos original. Un proveedor de datos funciona como un puente entre una aplicación y la fuente de datos. Los proveedores de datos permiten que una aplicación lea y escriba los datos almacenados en una fuente de datos. En la actualidad ADO.NET soporta tres proveedores: • OLE DB .NET. Permite acceder a una fuente de datos para la que exista un proveedor OLE DB, aunque necesita un conmutador para administrar código no administrado. Es compatible con los siguientes proveedores OLE DB: - SQLOLEDB (para SQL Server) - MSDAORA (para Oracle) - Microsoft.Jet.OLEDB.4.0 (para Access) • SQL Server .NET. Permite acceder a una fuente de datos SQL Server 7.0 o versiones posteriores. • ODBC .NET. Funciona como un puente a una fuente ODBC. Actualmente sólo soporta controladores ODBC de: Access, SQL Server y Oracle El modelo de objetos ADO.NET Los objetos que componen la arquitectura se han dividido en dos grupos: - Los objetos que están incluidos en el Proveedor de datos .NET - Y los que pertenecen a la arquitectura desconectada de ADO.NET Proveedor de datos .Net Connection DataAdapter (Datos sin conexión) DataSet Command Parameter DataReader Connection. Su función es establecer una conexión con la fuente de datos. Dispone, entre otros, de: Propiedades: ConnectinString. Cadena de caracteres con el nombre de la base de datos. Métodos: Open. Permite abrir la conexión Close. Permite cerrar la conexión. 2 Command. Permite realizar una consulta a la base de datos, enviarle un comando (orden SQL) o invocar alguno de los procedimientos almacenados. Para ello, entre otros, dispone de distintos métodos Execute: ExecuteNonQuery. Ejecuta la consulta de acción y devuelve el número de filas afectadas. (Ej: INSET, UPDATE o DELETE SQL) ExecuteReader. Ejecuta la consulta de selección, y devuelve el objeto DataReader que permite acceder al resulset (Conjunto de filas y columnas). (Ej: SELECT ...) ExecuteEscalar. Ejecuta la consulta de selección y devuelve un valor escalar. Propiedades que permiten crear el comando: CommandText. Cadena con la sentencia SQL de la consulta. Connection. Objeto Connection asociado al comando. DataReader. Es el objeto que devuelve el método ExecuteReader. Representa un resulset de avance, de sólo lectura. Un resulset es el conjunto de filas y columnas obtenidas por la ejecución de un comando de selección. DataReader dispone del método Read que permite obtener una nueva fila de resultados, la fila actual. Una vez obtenida la fila se pueden realizar consultas a cada campo utilizando la propiedad Item, el método GetValue, o los métodos Getxxxx con declaración de tipos (GetString, GetInt32, ...) Propiedades: Item(i). Sólo lectura. Permite acceder a cualquier campo, por su nombre o por el índice de su columna (comienza por 0). FieldCount. Obtiene el Nº de campos. Métodos: Read. Pasa a la fila siguiente y devuelve True si hay más filas y False si ha llegado al final del resulset. Es decir, obtiene una nueva fila de resultados. Close. Cierrar el objeto. GetName(i). Recupera el valor del campo de índice i, de la fila actual. GetValue(i). Obtiene el valor de un campo (en su formato nativo). DataSet. Es el objeto que contiene una copia local de los datos leídos de una o varias fuentes de datos. Su objetivo es guardar y procesar los datos. Se puede considerar como una base de datos relacional disminuida, en memoria. Está completamente desconectado de la fuente de datos, tanto física como lógicamente. Por ejemplo, se puede rellenar un DataSet con uno o varios resulset procedentes de sentencias SQL sobre distintas bases de datos, incluso de distintos proveedores; se puede rellenar un DataSet con una base de datos, o incluso se puede rellenar con una tabla de datos que se haya creado por código. Una vez cargados los datos en el objeto DataSet, se puede: - Crear relaciones entre los diferentes resulset. - Navegar por los resulset La independencia de la fuente de datos específica se consigue gracias al objeto DataAdapter. Este es el objeto que carga los datos en el DataSet y es capaz de actualizar una fuente de datos con los datos que la aplicación ha agregado, eliminado o modificado. DataAdapter. Funciona como un puente entre los objetos Connection y DataSet. Puede recuperar y actualizar datos de un origen a través de sus métodos Fill y Update. Fill. Copia los datos del origen de datos al DataSet. Update. Mueve los datos del DataSet a la base de datos. Actualiza la BD, añade, elimina o actualiza sus filas. 3 Espacios de nombres Los diferentes proveedores utilizan diferentes espacios de nombres y también utilizan diferentes nombres para los objetos. Para utilizar en las aplicaciones estos espacios de nombres se deben importar. Por lo tanto se utilizará alguna de las siguientes sentencias dependiendo del proveedor de datos que se desee utilizar: Imports SystemData. Contiene los objetos DataSet y sus derivados: DataTable, DataRow, DataRelation Imports System.Data.SqlClient. Contiene objetos del proveedor SQL Server .Net: SqlConnection, SqlCommand, SqlDataReader y SqlDataAdapter Imports.System.OleDb. Contiene objetos del proveedor OLE DB .NET: OleDbConecction, OleDbCommand, OleDbDataReader y OleDbDataAdapter Nosotros utilizaremos OLE DB. Se describen los objetos con sus miembros para este proveedor. El Objeto Connection Tanto si se trabaja en modo conectado como sin conexión, lo primero que se debe hacer con una fuente de datos es abrir una conexión en ella. Esto implica que se crea un objeto Connection que conecta con la base de datos especificada. Relación incompleta de miembros soportados por OLE DB. Propiedades: ConnectinString. Cadena utilizada para conectar con la fuente de datos. ConectionTimeout. Número de segundos después del cual una conexión fallida se interrumpe. Es de sólo lectura, ya que este valor se configura en la propiedad ConnectionString. El valor predeterminado es de 15 segundos. DataBase. Devuelve el nombre de la base de datos especificada en la propiedad ConnectionString. Sólo lectura. DataSource. Devuelve el nombre del atributo Data Source, especificado en ConnectionString. Sólo lectura. Provider. Devuelve el valor del atributo Provider (proveedor) especificado en ConnectionString. Sólo lectura. ServerVersion. Devuelve la versión del servidor conectado en formato xx.yy.zzzz o una cadena vacía si no puede obtener esta información State. Devuelve es estado actual de la conexión. Es un campo de bit codificado. Puede ser un valor de la enumeración ConnectionState (Closed, Open, ...) Métodos: Open. Abre la conexión. Close. Cierrra la conexión y libera todas las fuentes de datos asociadas. BeginTransation. Comienza una transacción de base de datos utilizando el nivel de aislamientos especificado en el argumento opcional. ChangeDataBase. Cambia el nombre de la base de datos para la conexión actual CreateCommand. Crea un objeto Command asociado a la conexión actual. Sucesos: StateChange. Se activa cuando la propiedad State cambia. 4 Propiedad ConnectionString . Cadena de caracteres que define el tipo de la BD a la que está conectado (Proveedor), su ubicación, su nombre y otros atributos separados por ; Atributos: Provider. Especifica el nombre del proveedor OLE DB utilizado: - SQLOLEDB (para SQL Server) - MSDAORA (para Oracle) - Microsoft.Jet.OLEDB.4.0 (para Access) Data Source. Especifica dónde está la base de datos. Puede ser: - La ruta y nombre de una base de datos Access. - El nombre de la máquina en la que está la base de datos Oracle o SQL Server. User ID. Nombre del usuario de una cuenta válida para la BD. Password. Contraseña de esa cuenta. Procedimientos de apertura de una conexión a una base de datos. La apertura de la conexión es imprescindible tanto en modo desconectado como conectado 1. Crear una instancia del objeto Connection. 2. Establecer la cadena de conexión. 3. Abrir la conexión. 1. Crear una instancia del objeto Connection. Este objeto puede devolver eventos o no. Dim Cn As New OleDbConnection -o- Dim WithEvents Cn As New OleDbConnection 2. Establecer la cadena de conexión. El atributo Data Source de la cadena de conexión contiene el nombre de la BD con su ruta de acceso. Si la BD se encuentra en el mismo directorio que el archivo .exe del proyecto, no es necesario escribir la ruta. En este caso Biblio.mdb se encuentra en el subdirectorio bin\Debug del proyecto Dim CadenaConexion As String CadenaConexion = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Biblio.mdb;" Cn.ConnectionString = CadenaConexion Los pasos 1 y 2 se pueden agrupar: Dim CadenaConexion As String CadenaConexion = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Biblio.mdb;" Dim Cn = New OleDbConnection(CadenaConexion) 3. Abrir la conexión. Cn.Open() Al abrir la conexión conviene comprobar su estado y controlar los errores. Dim cadena As String Try Cn.Open() cadena = Cn.State.ToString Me.TxtConexion.Text = cadena Catch ex As Exception MessageBox.Show(ex.Message) End Try Procedimientos de Cierre de una conexión. If Cn.State = ConnectionState.Open Then Cn.Close() El método Close no da error si la conexión estaba cerrada. Cn.Close() 5 El Objeto Command Una vez abierta una conexión se puede elegir entre trabajar en modo conectado o desconectado. En modo conectado, lo normal es crear un objeto Command que contenga una consulta a una base de datos y a continuación ejecutar el método Executexxxx que corresponda con el tipo de consulta: De selección para obtener datos de la BD De acción para actualizar datos en la BD Propiedades: Connection. Obtiene o establece el objeto Connection de este comando. CommandText. String. Obtiene o establece la instrucción SQL, nombre de la tabla o procedimiento almacenado, que se va a ejecutar en el origen de datos. CommandType. Obtiene o establece un valor que indica el tipo de consulta. Es decir, cómo se interpreta la propiedad CommandText. Sus valores pueden ser: Text. Sentencia SQL (por defecto) StoredProcedure. Procedimiento almacenado TableDirec. Tabla CommandTimeout. Integer. Obtiene o establece el tiempo de espera hasta que se interrumpa el intento de ejecutar un comando y se genere un error. Parameters. Colección de parámetros del comando. Transaction. Obtiene o establece el objeto Transaction correspondiente a la transacción en la que se ejecuta este comando. UpdateRowSource. Obtiene o establece la manera en que se aplican los resultados del comando a DataRow cuando lo utiliza el método Update del DataAdapter. (Sólo en desconectado) Métodos: Cancel. Cancela la ejecución del comando. ResetCommandTimeout. Restablece el valor predeterminado de la propiedad CommandTimeout (30 segundos). Para lectura de datos: ExecuteNonQuery. Ejecuta la consulta de acción y devuelve el número de filas afectadas. ExecuteReader. Ejecuta una consulta de selección y devuelve un objeto DataReader que permite acceder al resulset (Conjunto de filas y columnas obtenidos por la consulta). Este método puede incluir un valor opcional CommandBehavior. CommandBehavior es una enumeración que proporciona una descripción de los resultados de la consulta y de sus efectos en la base de datos. Se puede utilizar una combinación bit a bit de sus valores. Por ejemplo si se devuelve una única fila o si se debe cerrar la conexión cuando finalice el método, etc. ExecuteScalar. Ejecuta la consulta y devuelve un valor escalar. Es decir, devuelve la primera columna de la primera fila del conjunto de resultados devuelto por la consulta. Funciona con cualquier consulta SQL y devuelve el primer campo de la primera fila. Para consultas parametrizadas: CreateParameter. Crea un objeto Parameter conectado a este comando parametrizado. 6 Procedimientos de creación y ejecución de comandos El procedimiento general es el siguiente: 1º. Abrir la conexión 2º. Crear y ejecutar el comando 3º. Cerrar la conexión Crear el comando: 1. Crear una instancia del objeto Command Dim Cmd As New OleDbCommand 2. Establecer, al menos, las propiedades CommandText y Connection Por defecto se va a interpretar que CommandText contiene una sentencia SQL, si no es así también hay que establecer CommandType con el valor correspondiente. Dim Sql As String Sql = "Select ..." ó "Insert ..." ó "Update... " ó "Delete ..." With Cmd .CommandText = Sql .Connection = Cn End With Los pasos 1 y 2 se pueden agrupar: Dim Sql As String Sql = "Select ..." ó "Insert ..." ó "Update... " ó "Delete ..." Dim Cmd As New OleDbCommand(Sql, Cn) Ejecutar el comando: Para ejecutar el comando se utilizará el método adecuado en función de la consulta establecida. Es recomendable controlar los errores. Las consultas de acción: Insert, Delete, Update, devuelven el número de registros afectados: Try Dim NregAfectados As Integer = Cmd.ExecuteNonQuery 'mostrar el resultado Catch ex As Exception MessageBox.Show(ex.Message) Finally 'Cierra siempre la conexión 'Cn.Close() End Try Las consultas de selección que devuelven un valor escalar: "Select Count(*) from ...", ... Dim Num As Integer = CInt(Cmd.ExecuteScalar) Las consultas de selección que devuelven un objeto DataReader: "Select * from ..." 1. Declarar el objeto DataReader Dim Dr As OleDbDataReader 2. Asignar al objeto DataReader el resultado de la ejecución del comando. Es conveniente comprobar si la conexión está abierta Try If Cn.State = ConnectionState.Open Then Dr = Cmd.ExecuteReader End If Catch ex As Exception MessageBox.Show(ex.Message) End Try 7 Objeto DataReader. Obtiene un resulset resultado de la ejecución de un comando de selección. Un objeto DataReader permite la navegación hacia adelante (Foward Only) y en modo sólo lectura (Read Only), de los registros devueltos por una consulta (resulset). Los DataReader permanecen conectados durante todo el tiempo que realizan el recorrido por los registros que contienen, ya que efectúan la lectura de la fuente de Datos (BD) fila a fila (registro a registro). El uso de este objeto puede incrementar el rendimiento de una aplicación y reducir la carga del sistema ya que solo se almacena en el buffer del Cliente una sola fila (registro) cada vez. No es posible ejecutar ningún comando en una conexión mientras un obj DataReader esté activo en esa conexión. Por lo tanto, se debe cerrar un DataReader, cuando ya no se tienen que procesar más filas, para liberar recursos y dejar disponible la conexión para otros comandos. Propiedades: IsClosed. True si DataReader está cerrado. Item(i). Sólo lectura. Devuelve el valor de un campo de la fila actual. Permite acceder a cualquier campo, por su nombre o por el índice de su columna (comienza por 0). Es el miembro predeterminado, por lo que se puede omitir. dr.Item(IdiceColumna) dr(IdiceColumna) dr.Item(“NombreCampo”) FieldCount. Obtiene el Nº de campos de la fila actual. HasRows. Bolean. Obtiene un valor que indica si el DataReader contiene alguna fila. RecordsAffected. Devuelve el número de filas insertadas, eliminadas o actualizadas por la instrucción SQL Métodos: Read. Desplaza el cursor actual al siguiente registro del resulset permitiendo obtener los valores del mismo. Pasa a la fila siguiente y devuelve True si hay más filas y False si ha llegado al final del resulset. La posición del objeto DataReader en el momento inicial es antes del primer registro, por lo tanto para recorrer un objeto DataReader debemos comenzar con una llamada al método Read(), y así situarnos en el primer registro. Close. Cierrar el objeto, libera todos los recursos asignados a él y hace que la conexión esté disponible para otros comandos. NextResult. Pasa al siguiente resulset y devuelve True si hay otro resulset. Desplaza el puntero actual al siguiente conjunto de registros cuando la sentencia SQL devuelve más de un conjunto de registros (Más de una sentencia Select separada por punto y coma). Como los que pueden devolver un procedimiento almacenado o un procesamiento por lotes. GetName(i). Recupera el nombre del campo de índice i. GetOrdinal(NombreCampo). Devuelve el índice de la columna correspondiente al nombre de campo pasado como argumento. 8 Métodos de la fila actual leída con Read IsDBNull(i). Devuelve True si el campo de índice i contiene un valor DBNul. If Dr.IsDBNull(i) = True Then ... GetValue(i). Obtiene el valor de un campo (en su formato nativo). Da error si el campo es nulo 'recupera el valor de un campo If Dr.IsDBNull(0) = False Then Me.TextBox1.Text = Dr.GetValue(0).ToString End If GetValues(Matriz). Devuelve los valores de todos los campos de la fila actual en una matriz de objetos. Este procedimiento es más lento que el anterior.? Dim cadena As String Dim TbCampos(Dr.FieldCount - 1) As Object 'obtiene todos los campos Dr.GetValues(TbCampos) 'itera todos los campos TbCampos(i) cadena = "" For i = 0 To Dr.FieldCount - 1 If Not IsDBNull(TbCampos(i)) Then cadena = cadena & TbCampos(i) & "-" End If Next Me.TextBox1.Text = cadena Getxxxx(i). Recupera el valor del campo, con declaración de tipo. (GetString, GetInt32, ...) Los Datos obtenidos por un DataReader están en el formato binario propio del proveedor de datos. Estos valores se deben convertir a los tipos adecuados, para ello DataReader dispone de un conjunto de métodos que permiten obtener los valores de los campos en el tipo de datos deseado. GetBoolean(i), GetInt32(i), GetString(i), GetChar(i), etc. GetFieldsType(i). Devuelve el objeto System.Type del tipo de campo. 9 Procedimientos de lectura del resulset El procedimiento general es el siguiente: 1º. Abrir la conexión 2º. Crear y ejecutar el comando 3º. Leer los datos, mostrarlos o procesarlos ... 4º. Cerrar la conexión Leer los datos y mostrar el nombre de los campos y sus valores: El número y nombre de los campos se puede obtener después de ejecutar el comando. El valor de los campos se obtiene al ejecutar el método Read del DataReader 1. Leer la siguiente fila. Cerrar el lector DataReader cuando no haya más filas 'lee la siguiente fila If Dr.Read = False Then MessageBox.Show("no hay más registros") Dr.Close() Else 'mostrar los resultados End if 2. Mostrar los resultados 'mostrar el nombre de los campos Label1.Text = Dr.GetName(1) Label2.Text = Dr.GetName(2) Label2.Text = Dr.GetName(3) ... 'mostrar el valor de los campos Txt1.Text = Dr.Item(1).ToString Txt2.Text = Dr.Item(2) Txt3.Text = Dr(3) ... If Dr.IsDBNull(4) = False Then Txt4.Text = Dr.GetValue(4).ToString 'da error si el campo es nulo End If Los pasos 1 y 2 se pueden agrupar: Dim TbLabel() As Label = {Me.Label2, Me.Label3, Me.Label4,...} Dim TbCaja() As TextBox = {Me.TextBox1, Me.TextBox2, Me.TextBox3, ...} If Dr.Read = False Then MessageBox.Show("no hay más registros") Dr.Close() Else 'mostrar resultados For i = 0 To Dr.FieldCount - 1 'mostrar nombres de campos TbLabel(i).Text = Dr.GetName(i) 'mostrar valor de los campos TbCaja(i).Text = Dr.Item(i).ToString Next End if 10 Para leer todos los registros: 'recupera el nombre de los campos y los muestra For i = 0 To Dr.FieldCount - 1 Me.TxtNombreCampos.AppendText(Dr.GetName(i) & " Next ") 'recupera el valor de los campos del resulset, fila a fila 'y los muestra en una lista fija Dim cadena As String Do While Dr.Read = True cadena = "" For i = 0 To Dr.FieldCount - 1 cadena = cadena & Dr.Item(i) & "-" Next Me.LstCampos.Items.Add(cadena) Loop 11 Consultas parametrizadas. En muchas ocasiones es necesario utilizar consultas SQL con valores de campos variables (o parámetros). Es decir, un campo podrá tomar, en tiempo de ejecución, distintos valores; como, por ejemplo, el valor asignado por el usuario. Ejemplos. De la BD Biblio.mdb: - Obtener el título de los libros escritos por un autor seleccionado por el usuario. - Obtener los datos de los libros publicados por la editorial seleccionada por el usuario. - Obtener el título de los libros publicados, en el año seleccionado por el usuario, por la editorial seleccionada también por el usuario. En estos casos el comando que se ha de crear ha de tener parámetros, y utilizar, en la consulta SQL, unos signos u otros como marcadores de posición. La sintaxis depende del proveedor de datos. Los comandos parametrizados son útiles cuando se tiene que realizar el mismo tipo de consulta más de una vez, con diferentes valores en los parámetros. Procedimiento de utilización de comandos parametrizados: 1. Crear el comando con los marcadores de posición 2. Configurar la colección de parámetros 3. Ejecutar el comando. Crear el comando con los marcadores de posición Dependiendo del proveedor de datos la sentencia SQL utilizada tendrá unos marcadores de posición u otros. Para el proveedor SQL Server .NET, se utiliza @ como marcador de posición. Sql = "Select * From Titles Where PubId=@ParName" Para el proveedor OLE DB .NET, se utiliza ? como marcador Sql = "Select Title From Titles Where PubID=? And [Year Published]=?" Aunque los procedimientos son similares, no son iguales. Aquí se va a describir los utilizados para el proveedor de datos OLE DB 'Crea el comando con signos de interrogación como marcadores de posición Dim Sql As String Sql = "Select Title From Titles Where PubID=? And [Year Published]=?" Dim Cmd As OleDbCommand Cmd = New OleDbCommand(Sql, Cn) 12 Configurar la colección de parámetros Se deben crear tantos objetos Parameter como parámetros tenga la consulta SQL y luego agregarlos a la colección Parameters del objeto Command, en el mismo orden en que aparecen los parámetros en el comando SQL. Existen 3 modos de crear un objeto Parameter: a) Utilizando el constructor del objeto Parameter 'define el parámetro Dim Par As New OleDbParameter("ParIdEditorial", OleDbType.Integer) Par.Value = 1 'asigna valor inicial. No es necesario Cmd.Parameters.Add(Par) 'añade el parámetro a la colección de parámetros 'define el 2ºparámetro Dim Par2 As New OleDbParameter("ParAñoPubli", OleDbType.SmallInt) Cmd.Parameters.Add(Par2) 'añade el parámetro a la colección de parámetros b) Utilizando el método CreateParameter del objeto Command No es obligatorio definir ni el nombre del parámetro ni el tipo. 'configura el 1er parámetro Dim Par As OleDbParameter 'declara el parámetro Par = Cmd.CreateParameter 'crea el parámetro Par.Value = 1 ' no es necesario Cmd.Parameters.Add(Par) añade el parámetro a la colección 'configura el 2º parámetro Dim Par As OleDbParameter = Cmd.CreateParameter Cmd.Parameters.Add(Par) c) Invocando al nuevo método AddWithValue de la colección Parameters Introduce nombre y valor en el método AddWithValue de la colección Parameters 'invoca al método AddWithValue Cmd.Parameters.AddWithValue("ParIdEditorial", 1) Cmd.Parameters.AddWithValue("ParAñoPubli", 1992) Ejecutar el comando - Asignar valor a los parámetros - Ejecutar el comando 'Asigna valor a los paarámetros Dim Valor1 As Integer = Me.TextBox1.Text Cmd.Parameters(0).Value = Valor1 Dim Valor2 As Short = Me.TextBox2.Text Cmd.Parameters(0).Value = Valor2 'Ejecuta el comando Dim Dr As OleDbDataReader Dr = Cmd.ExecuteReader 'obtiene el resultado