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