el Guille, la Web del Visual Basic, C#, .NET y más...

Transacciones simples con ADO.NET usando un DataAdapter

 
Publicado el 10/Jul/2008
Actualizado el 10/Jul/2008
Autor: Guillermo 'guille' Som

En este artículo veremos cómo realizar transacciones si el acceso a los datos lo realizamos por medio de un DataAdapter y la información a controlar se maneja con objetos DataTable (o DataSet).



 

Introducción:

Ayer vimos cómo realizar transacciones de forma simple mediante un objeto Connection, pero hoy te voy a explicar las cosas que debes tener en cuenta si te decides a usar transacciones y el acceso a los datos lo haces por medio de un objeto del tipo DataAdapter.

En realidad no tiene mucho sentido hacer este tipo de comprobaciones de que todo va bien cuando usamos un adaptador, ya que la actualización (o modificación de los datos) se hace siempre en modo desconectado, por tanto siempre podemos comprobar que la información que hemos alterado está bien antes de hacer que se actualice la base de datos, pero... como siempre puede ocurrir cualquier imprevisto a la hora de que el adaptador ejecute los comandos correspondientes, pues no estará de más que tengamos esta forma de comprobar que esa actualización de datos se ha hecho de forma correcta, y lo mejor para comprobar que todo se hace bien es usando una transacción.

Cosas a tener en cuenta

Cuando utilizamos un objeto del tipo DataAdapter (por ejemplo SqlDataAdapter) éste se encarga de crear la conexión a la base de datos, abrir dicha conexión y después de actualizar los datos también se encarga de cerrar la conexión. Y lo importante es que NUNCA necesitamos un objeto Connection específico (e independiente) para hacer todo ese proceso de conexión, ya que el adaptador se encarga de todo ese tema por nosotros.

Pero para que todo esto de las transacciones con un DataAdapter funcione, lo mejor es crear nuestro objeto Connection para poder obtener la transacción por medio del método BeginTransaction (a lo mejor hay otra forma más fácil de hacerlo, pero esta es la que yo conozco y es la que te explico).

El segundo punto que debemos tener en cuenta es que si los comandos de actualización, inserción y eliminación los automatizamos con un objeto del tipo CommandBuilder tendremos que hacer un pequeño truco para que todo funcione como debe funcionar, ya que a esos comandos debemos asignarle el objeto Transaction que hemos obtenido por medio del método BeginTransaction de la conexión.

Si los comandos de actualización los creamos de forma manual, tendremos que hacer lo que te expliqué en el artículo anterior para asignar el objeto Transaction que cada comando debe usar. Aunque esto no es demasiado útil, al menos si queremos tener la "automatización" que nos da el modo desconectado... pero bueno, que sepas que se puede hacer.

En cualquier caso, solo debemos asignar el objeto de la transacción a los comandos INSERT, UPDATE y DELETE, pero no al de selección. Y si algunos de esos tres comandos no vamos a usarlo, por ejemplo porque no permitamos eliminar datos, no será necesario crear el comando correspondiente y por tanto no hay que asignarle nada... pero ante la duda... lo mejor es asignarlo a todos, al menos si usamos un objeto SqlCommandBuilder, para que no tengamos un error por falta de "previsión".

Un ejemplo para usar transacciones con un DataAdapter

Lo mejor es que veamos qué tenemos que hacer para que todo esto que te he comentado funcione correctamente.

En el siguiente ejemplo sólo veremos la parte en la que se utiliza un objeto CommandBuilder.

En estos ejemplos voy a usar una base de datos de SQL Server 2005 Express, que es la misma que ya usé en el ejemplo del artículo anterior.

Nota:
Por ahora solo te muestro el código de Visual Basic, en otra ocasión actualizaré el código para incluir el de C#.

Aquí solamente veremos la parte de la actualización, es decir, lo que debemos ejecutar para actualizar los datos una vez modificados, por tanto este código que te muestro es cuando ya has modificado los datos... eso no te lo muestro porque en realidad no cambia con respecto a lo que ya te he explicado en otras ocasiones, desde el índice de la sección de ADO.NET tienes varios ejemplos de cómo acceder a los datos usando un adaptador.

Nota:
Hay que tener en cuenta que este código solo es para mostrar los pasos a dar, ya que antes se han tenido que leer los datos, asignarlos al DataSet o DataTable, y modificarlos de alguna forma que aquí no se muestra (esto lo aclaro para que después no andes preguntando o diciendo que esto no cambia nada en la base de datos o que este ejemplo no funciona, y te puedo asegurar que sí funciona, que yo lo he probado insertando y modificando datos...)

Paso 1. Creamos el adaptador (y la conexión y el objeto Connection)

El comando SELECT es necesario para que el CommandBuilder sepa cómo debe crear los comandos.

' La cadena de conexión
Dim csb As New SqlConnectionStringBuilder
With csb
    ' El servidor al que nos conectamos
    .DataSource = "(local)\SQLEXPRESS"
    ' La base de datos que vamos a usar
    .InitialCatalog = "prueba"
    ' Usamos la seguridad integrada
    .IntegratedSecurity = True
End With

' Creamos la conexión
' la ponemos dentro de Using para asegurarnos de que se cierre si hay errores
Using con As New SqlConnection(csb.ConnectionString)

    Dim dt As New DataTable

    ' Creamos el adaptador usando el objeto Connection
    Dim da As New SqlDataAdapter("SELECT * FROM Table1", con)
    da.MissingSchemaAction = MissingSchemaAction.AddWithKey

 

Paso 2. Creamos los comandos y los asignamos al adaptador, si no lo hacemos de esta forma, al intentar acceder a los comandos desde el objeto del DataAdapter... obtendremos un error indicando que no están creados (son nulos).

' Creamos los comandos con el CommandBuilder
Dim cb As New SqlCommandBuilder(da)

' pero para asignarle el objeto Transaction debemos
' obtenerlos por medio de los métodos Get...Command
' ya que si intentamos acceder directamente a esos comandos
' del adaptador nos indicará que no están asignados (son nulos)
da.InsertCommand = cb.GetInsertCommand()
da.UpdateCommand = cb.GetUpdateCommand()
da.DeleteCommand = cb.GetDeleteCommand()

 

Paso 3. Abrimos la conexión y creamos el objeto Transaction llamando al método BeginTransaction, y asignamos ese objeto a cada uno de los comandos del adaptador, salvo al comando de selección.

' Abrimos la conexión
con.Open()

' Creamos el objeto Transaction
Dim tran As SqlTransaction = con.BeginTransaction

' Asignamos el objeto Transaction a los comandos
da.InsertCommand.Transaction = tran
da.UpdateCommand.Transaction = tran
da.DeleteCommand.Transaction = tran

 

Paso 4. Dentro de un bloque Try/Catch (para detectar los errores) es cuando actualizamos los datos llamando al método Update del adaptador. Es importante que la conexión siga abierta, ya que el objeto de la variable tran depende de esa conexión, pero el adaptador no depende directamente de la conexión abierta en la variable con, ya que el adaptador gestiona por su cuenta las conexiones y si ve que está cerrada, la abrirá, pero esa apertura es independiente del objeto que hemos usado para indicarle cuál es la conexión que debe usar... ¡un lío! pero es así...

Hay que tener en cuenta que la "aceptación" de los cambios del DataTable (o DataSet) se hace de forma independiente de la llamada a los métodos Commit (o Rollback) del objeto Transaction. En el caso de que todo vaya bien, el propio método Update del adaptador se encarga de decirle a la tabla que acepte los cambios, por tanto no es necesario llamar de forma explícita al método AcceptChanges, ya que esa es la forma "predeterminada" de actuar (lee el comentario del código si quieres saber algo más de esto... y, por supuesto, después te lees la documentación para informarte mejor, que con un par de líneas de comentarios no lo vas a aprender todo, jejeje).

Cuando se cancela la actualización (en el bloque Catch del ejemplo), se hace una llamada al método Rollback del objeto Transaction, y también podrías llamar al método RejectChanges de la tabla, pero esto supondría perder TODAS las modificaciones que hayas hecho en los datos que están en memoria. Y debido a que esos datos están en memoria, puedes volver a actualizarlos posteriormente si así lo consideras necesario. Nuevamente te invito a que leas los comentarios del código para que veas qué debes hacer.

Try
    ' Para probar con un error
    If chkProducirError.Checked Then
        Throw New Exception("Candemore")
    End If

    ' Actualizamos los datos de la tabla
    da.Update(dt)

    ' Si llega aquí es que todo fue bien,
    ' por tanto, llamamos al método Commit
    tran.Commit()

    ' Esto no es necesario si dejamos el valor predeterminado
    ' de la propiedad AcceptChangesDuringUpdate del adaptador
    dt.AcceptChanges()

    txtInfo.Text = "Se han actualizado los datos"


Catch ex As Exception
    ' Si hay error, desahacemos lo que se haya hecho
    tran.Rollback()

    ' Desechar los cambios del DataTable
    ' si así lo estimamos oportuno,
    ' pero esto obligará a volver a añadir, modificar o eliminar
    ' nuevamente los datos.
    ' Por tanto, solo debemos hacerlo si no intentaremos nuevamente
    ' la actualización de los datos.
    ' Yo preguntaría al usuario antes de hacerlo...
    'dt.RejectChanges()


    txtInfo.Text = "ERROR: " & vbCrLf & ex.Message
End Try

 

Paso 5. Finalmente cerramos la conexión, aunque no es necesario hacerlo de forma explícita ya que al incluir el objeto Connection dentro de un bloque Using, se cerrará automáticamente.

    ' Cerramos la conexión,
    ' aunque no es necesario ya que al finalizar
    ' el using se cerrará
    con.Close()
End Using

 

Y esto es todo... ya te digo que esto de las transacciones tienen más sentido si no usas el adaptador (que es la forma que te mostré ayer), ya que en modo desconectado (con un adaptador y una tabla o un DataSet) pues... en fin... que lo mismo no es tan necesario... en fin... al menos sabes cual es la forma en que tendrás que hacerlo... y si decides hacerlo así, pues... es tu decisión... ¡como siempre!

Nos vemos.
Guillermo

 


Espacios de nombres usados en el código de este artículo:

System.Data.SqlClient
System.Data 



 


La fecha/hora en el servidor es: 22/12/2024 17:35:34

La fecha actual GMT (UTC) es: 

©Guillermo 'guille' Som, 1996-2024