Guardar de nuevo los datos en la base de datos en aplicaciones de .NET Framework

Nota:

Los conjuntos de datos y las clases relacionadas son tecnologías heredadas de .NET Framework de principios de la década de 2000 que permiten a las aplicaciones trabajar con datos en memoria mientras están desconectadas de la base de datos. Son especialmente útiles para las aplicaciones que permiten a los usuarios modificar los datos y conservar los cambios en la base de datos. Aunque los conjuntos de datos han demostrado ser una tecnología de gran éxito, se recomienda que las nuevas aplicaciones de .NET usen Entity Framework Core. Entity Framework proporciona una manera más natural de trabajar con datos tabulares como modelos de objetos y tiene una interfaz de programación más sencilla.

El conjunto de datos es una copia en memoria de los datos. Si modifica esos datos, se recomienda guardar esos cambios en la base de datos. Esto se hace de una de estas tres maneras:

  • Llamando a uno de los métodos Update de TableAdapter

  • Llamando a uno de los métodos DBDirect de TableAdapter

  • Llamando al método UpdateAll en TableAdapterManager que Visual Studio genera automáticamente cuando el conjunto de datos contiene tablas relacionadas con otras tablas del conjunto de datos

Cuando enlaza tablas de conjuntos de datos a controles en una página de Windows Forms o XAML, la arquitectura de enlace de datos realiza todo el trabajo.

Si está familiarizado con TableAdapters, puede ir directamente a uno de estos temas:

Tema Descripción
Insertar nuevos registros en una base de datos Cómo realizar actualizaciones e inserciones mediante objetos TableAdapter o de comando
Actualizar datos mediante un TableAdapter Cómo realizar actualizaciones con TableAdapters
Actualización jerárquica Cómo realizar actualizaciones de un conjunto de datos con dos o más tablas relacionadas
Tratar las excepciones de simultaneidad Cómo controlar excepciones cuando dos usuarios intentan cambiar los mismos datos al mismo tiempo en una base de datos
Procedimiento para guardar datos mediante una transacción Cómo guardar datos en una transacción mediante el sistema. Espacio de nombres Transactions y un objeto TransactionScope
Guardar datos en una transacción Tutorial que crea una aplicación de Windows Forms para demostrar cómo guardar datos en una base de datos dentro de una transacción
Guardar datos en una base de datos (varias tablas) Cómo editar registros y guardar los cambios en varias tablas de nuevo en la base de datos
Guardar los datos de un objeto en una base de datos Cómo pasar datos de un objeto que no está en un conjunto de datos a una base de datos mediante un método DbDirect de TableAdapter
Guardar datos con los métodos DBDirect de un TableAdapter Uso de TableAdapter para enviar consultas SQL directamente a la base de datos
Guardar un conjunto de datos como XML Cómo guardar un conjunto de datos en un documento XML

Actualizaciones en dos fases

La actualización de un origen de datos es un proceso de dos pasos. El primer paso consiste en actualizar el conjunto de datos con registros nuevos, modificados o eliminados. Si la aplicación nunca devuelve esos cambios al origen de datos, habrá terminado con la actualización.

Si vuelve a enviar los cambios a la base de datos, se requiere un segundo paso. Si no usa controles enlazados a datos, debe llamar manualmente al método Update del mismo TableAdapter (o adaptador de datos) que usó para rellenar el conjunto de datos. Sin embargo, también puede usar adaptadores diferentes, por ejemplo, para mover datos de un origen de datos a otro o para actualizar varios orígenes de datos. Si no usa el enlace de datos y guarda los cambios para las tablas relacionadas, debe crear una instancia manual de una variable de la clase TableAdapterManager generada automáticamente y, a continuación, llamar a su método UpdateAll.

Diagrama conceptual de las actualizaciones del conjunto de datos

Un conjunto de datos contiene colecciones de tablas, que contienen una colección de filas. Si tiene previsto actualizar un origen de datos subyacente más adelante, debe usar los métodos de la propiedad DataTable.DataRowCollection al agregar o quitar filas. Estos métodos realizan el seguimiento de cambios necesario para actualizar el origen de datos. Si llama a la colección RemoveAt en la propiedad Rows, la eliminación no se comunicará de nuevo a la base de datos.

Combinación de conjuntos de datos

Para actualizar el contenido de un conjunto de datos, puede combinarlo con otro conjunto de datos. Esto implica copiar el contenido de un conjunto de datos de origen en el conjunto de datos que realiza la llamada (denominado conjunto de datos de destino). Cuando se fusionan mediante combinación conjuntos de datos, los registros nuevos del conjunto de datos de origen se agregan al conjunto de datos de destino. Asimismo, las columnas adicionales del conjunto de datos de origen se agregan al conjunto de datos de destino. La combinación de conjuntos de datos resulta útil cuando tiene un conjunto de datos local y obtiene un segundo conjunto de datos de otra aplicación. También es útil cuando se obtiene un segundo conjunto de datos de un componente como un servicio web XML o cuando es necesario integrar datos de varios conjuntos de datos.

Al combinar conjuntos de datos, se puede pasar un argumento booleano (preserveChanges) que indique al método Merge si debe conservar las modificaciones existentes del conjunto de datos de destino. Debido a que los conjuntos de datos mantienen varias versiones de los registros, es importante recordar que se está combinado más de una versión de los registros. En la tabla siguiente se muestra cómo se combina un registro en dos conjuntos de datos:

DataRowVersion Conjunto de datos de destino Conjunto de datos de origen
Original James Wilson James C. Wilson
Current Jim Wilson James C. Wilson

Llamar al método Merge en la tabla anterior con preserveChanges=false targetDataset.Merge(sourceDataset) da como resultado los siguientes datos:

DataRowVersion Conjunto de datos de destino Conjunto de datos de origen
Original James C. Wilson James C. Wilson
Current James C. Wilson James C. Wilson

Al llamar al método Merge con preserveChanges = true targetDataset.Merge(sourceDataset, true), se obtienen los siguientes datos:

DataRowVersion Conjunto de datos de destino Conjunto de datos de origen
Original James C. Wilson James C. Wilson
Current Jim Wilson James C. Wilson

Precaución

En el escenario de preserveChanges = true, si se llama al método RejectChanges en un registro del conjunto de datos de destino, éste volverá a tomar los datos originales del conjunto de datos de origen. Esto significa que, si intenta actualizar el origen de datos original con el conjunto de datos de destino, puede que no se pueda encontrar la fila original que se debe actualizar. Puede evitar que se produzca una infracción de simultaneidad rellenando otro conjunto de datos con los registros actualizados del origen de datos y después realizar una fusión mediante combinación para evitar que ocurra una infracción de simultaneidad. (Una infracción de simultaneidad se produce cuando otro usuario modifica un registro del origen de datos después de que se ha rellenado el conjunto de datos.)

Restricciones de actualización

Para realizar cambios en una fila de datos existente, se agregan o actualizan datos en cada columna. Si el conjunto de datos contiene restricciones (como claves externas o restricciones que no aceptan valores NULL), es posible que el registro pueda estar temporalmente en un estado de error mientras lo actualiza. Es decir, puede estar en un estado de error después de finalizar la actualización de una columna, pero antes de llegar a la siguiente.

Para impedir que se produzcan infracciones de restricciones prematuras, puede suspender temporalmente las restricciones de actualización. Esta suspensión tiene dos fines:

  • Evita que se produzca un error después de que haya terminado de actualizar una columna, pero no haya empezado a actualizar otra.

  • Evita que se provoquen determinados eventos de actualización (eventos que suelen utilizarse para la validación).

Nota

En los formularios Windows Forms, la arquitectura de enlace a datos integrada en la cuadrícula de datos suspende la comprobación de restricciones hasta que el foco sale de una fila, y no es necesario llamar explícitamente a los métodos BeginEdit, EndEdit, o CancelEdit.

Las restricciones se deshabilitan automáticamente cuando se invoca el método Merge en un conjunto de datos. Cuando la fusión mediante combinación finaliza, si el conjunto de datos tiene restricciones que no se pueden habilitar, se produce una ConstraintException. En esta situación, la propiedad EnforceConstraints se establece en false, y todas las infracciones de las restricciones se deben resolver antes de restablecer la propiedad EnforceConstraints a true.

Una vez finalizada la actualización, se puede volver a habilitar la comprobación de restricciones, con lo que también se vuelven a habilitar e iniciar los eventos de actualización.

Para obtener más información sobre cómo suspender eventos, consulte Desactivar restricciones al rellenar un conjunto de datos.

Errores de actualización de conjuntos de datos

Cuando se actualiza un registro de un conjunto de datos, existe la posibilidad de que se produzca un error. Por ejemplo, puede escribir accidentalmente datos del tipo incorrecto en una columna, o datos que son demasiado largos o datos que tienen algún otro problema de integridad. O bien, puede haber comprobaciones de validación específicas de una aplicación que generen errores personalizados durante cualquier fase de un evento de actualización. Para obtener más información, consulte Validar datos en conjuntos de datos.

Mantener información sobre los cambios

La información sobre los cambios de un conjunto de datos se mantiene de dos formas: marcando la fila que indican que han cambiado (RowState) y guardando varias copias de un registro (DataRowVersion). Con esta información, los procesos pueden determinar qué ha cambiado en el conjunto de datos y pueden enviar las actualizaciones correspondientes al origen de datos.

Propiedad RowState

La propiedad RowState de un objeto DataRow es un valor que proporciona información sobre el estado de una fila de datos determinada.

En la tabla siguiente se detallan los posibles valores de la enumeración DataRowState:

Valor de DataRowState Descripción
Added La fila se ha agregado como elemento a DataRowCollection. (Una fila con este estado no tiene una versión original correspondiente, ya que no existía cuando se llamó al último método AcceptChanges).
Deleted La fila se eliminó utilizando el método Delete de un objeto DataRow.
Detached La fila se ha creado pero no forma parte de ninguna DataRowCollection. Un objeto DataRow se encuentra en este estado inmediatamente después de ser creado y antes de que se agregue a una colección, y después de haberse quitado de una colección.
Modified Un valor de columna de la fila se ha modificado de alguna forma.
Unchanged La fila no ha cambiado desde la última vez que se llamó a AcceptChanges.

DataRowVersion (enumeración)

Los conjuntos de datos mantienen varias versiones de los registros. Los campos DataRowVersion se usan al recuperar el valor encontrado en DataRow mediante la propiedad Item[] o el método GetChildRows del objeto DataRow.

En la tabla siguiente se detallan los posibles valores de la enumeración DataRowVersion:

Valor de DataRowVersion Descripción
Current La versión actual de un registro contiene todas las modificaciones realizadas en el mismo desde la última vez que se llamó a AcceptChanges. Si la fila se ha eliminado, no existe una versión actual.
Default Es el valor predeterminado de un registro, tal como se define en el esquema del conjunto de datos o en el origen de datos.
Original La versión original de un registro es una copia del registro tal como se encontraba la última vez que se confirmaron cambios en el conjunto de datos. En términos prácticos, suele ser la versión de un registro leído de un origen de datos.
Proposed Es la versión propuesta de un registro que está disponible temporalmente, mientras está en curso una actualización, es decir, el intervalo que transcurre desde que se llama al método BeginEdit hasta que se llama al método EndEdit. Normalmente, se obtiene acceso a la versión propuesta de un registro en un controlador de un evento tal como RowChanging. Al invocar al método CancelEdit, se anulan los cambios y se elimina la versión propuesta de la fila de datos.

Las versiones original y actual son de utilidad cuando se transmite la información de actualización a un origen de datos. Normalmente, cuando envía una actualización al origen de datos, la nueva información de la base de datos está en la versión actual de un registro. Para buscar el registro que actualizar se utiliza información de la versión original.

Por ejemplo, si se modifica la clave principal de un registro, se necesita algún medio de encontrar el registro correcto en el origen de datos para actualizar los cambios. Si no existiese una versión original, lo más probable es que el registro se agregase al final del origen de datos, con lo que se obtendría no sólo un registro adicional no deseado, sino que, además, sería inexacto y obsoleto. Las dos versiones también se usan en el control de simultaneidad. Se puede comparar la versión original con un registro del origen de datos para determinar si dicho registro ha cambiado desde que se cargó en el conjunto de datos.

La versión propuesta es de utilidad cuando se necesita realizar una validación antes de confirmar los cambios en el conjunto de datos.

Incluso aunque los registros hayan cambiado, no siempre hay versiones originales o actuales de esa fila. Cuando se inserta una fila nueva en la tabla, no hay versión original, sólo una versión actual. Lo mismo sucede si se elimina una fila mediante una llamada al método Delete de la tabla: hay una versión original, pero no hay versión actual.

Para comprobar si existe una versión específica de un registro, haga una consulta en el método HasVersion de una fila de datos. Para obtener acceso a cualquiera de las versiones de un registro, pase un valor de la enumeración DataRowVersion como argumento opcional cuando solicite el valor de una columna.

Obtención de registros modificados

Es una práctica habitual no actualizar todos los registros de un conjunto de datos. Por ejemplo, un usuario puede estar trabajando con un control DataGridView de formularios Windows Forms que muestre muchos registros, pero puede actualizar sólo algunos, eliminar uno e insertar otro. Los conjuntos de datos y las tablas de datos proporcionan un método (GetChanges) para devolver sólo las filas que se hayan modificado.

Es posible crear subconjuntos de registros modificados con el método GetChanges de cualquiera de las tablas de datos (GetChanges) o del propio conjunto de datos (GetChanges). Si se llama al método de la tabla de datos, devuelve una copia de la tabla donde sólo se muestran los registros cambiados. De igual forma, si llama al método en el conjunto de datos, obtiene un nuevo conjunto de datos solo con los registros modificados.

GetChanges por sí mismo devuelve todos los registros modificados. Por contraste, pasando el DataRowState deseado como un parámetro al método GetChanges, puede especificar qué subconjunto de registros cambiados desea usar: registros recién agregados, registros marcados para su eliminación, registros desasociados o registros modificados.

Obtener subconjuntos de registros modificados es útil cuando se desea enviar registros a otro componente para su procesamiento. En lugar de enviar todo el conjunto de datos, se puede reducir la sobrecarga de la comunicación con el otro componente al obtener únicamente los registros necesarios.

Confirmación de cambios en el conjunto de datos

Cuando se realizan cambios en el conjunto de datos, se establece la propiedad RowState de las filas modificadas. Se establecen y mantienen las versiones original y actual de los registros, y se ponen a su disposición mediante la propiedad RowVersion. Los metadatos almacenados en las propiedades de estas filas modificadas son necesarios para enviar las actualizaciones correctas al origen de datos.

Si los cambios reflejan el estado actual del origen de datos, ya no es necesario mantener esta información. Normalmente, el conjunto de datos y su origen están sincronizados en dos ocasiones:

  • Inmediatamente después de haber cargado información en el conjunto de datos; por ejemplo, cuando lee datos del origen.

  • Después de enviar los cambios del conjunto de datos al origen de datos (pero no antes, porque se perdería la información sobre los cambios que es necesaria para enviar las modificaciones a la base de datos).

Para confirmar los cambios pendientes en el conjunto de datos, puede llamar al método AcceptChanges. Normalmente, se llama a AcceptChanges en las siguientes ocasiones:

  • Después de cargar el conjunto de datos. Si se carga un conjunto de datos mediante una llamada al método Fill del TableAdapter, este adaptador confirma los cambios automáticamente. Sin embargo, si carga un conjunto de datos mediante la combinación de otro conjunto de datos, los cambios se deben confirmar manualmente.

    Nota

    Puede impedir que el adaptador confirme los cambios automáticamente al llamar al método Fill estableciendo la propiedad AcceptChangesDuringFill del adaptador en false. Si se establece en false, el RowState de cada fila insertada durante la operación de rellenar se establece en Added.

  • Después de enviar los cambios del conjunto de datos a otro proceso, como un servicio Web XML.

    Precaución

    Si confirma el cambio de este modo, se borra la información sobre cambios existente. No confirme los cambios hasta que termine de realizar operaciones que requieran que la aplicación sepa qué cambios se han realizado en el conjunto de datos.

Este método realiza las funciones siguientes:

  • Escribe la versión Current de un registro en la versión Original y sobrescribe la versión original.

  • Quita cualquier fila en la que la propiedad RowState esté establecida en Deleted.

  • Establece la propiedad RowState de un registro en Unchanged.

El método AcceptChanges está disponible en tres niveles. Puede llamarlo sobre un objeto DataRow para confirmar los cambios realizados solo para esa fila. También puede llamarlo en un objeto DataTable para confirmar todas las filas de una tabla. Por último, puede llamarlo en el objeto DataSet para confirmar todos los cambios pendientes en todos los registros de todas las tablas del conjunto de datos.

En la tabla siguiente se describen los cambios que se confirman en función del objeto en el que se llame el método:

Método Resultado
System.Data.DataRow.AcceptChanges Los cambios se confirman solo en una fila específica.
System.Data.DataTable.AcceptChanges Los cambios se confirman en todas las filas de una tabla específica.
System.Data.DataSet.AcceptChanges Los cambios se confirman en todas las filas de todas las tablas del conjunto de datos.

Nota:

Si carga un conjunto de datos mediante una llamada al método Fill de TableAdapter, no tiene que aceptar explícitamente los cambios. De forma predeterminada, el método Fill llama al método AcceptChanges después de que termine de rellenar la tabla de datos.

Un método relacionado, RejectChanges, deshace el efecto de los cambios copiando la versión Original de vuelta a la versión Current de los registros. También establece RowState de cada registro en Unchanged.

Validación de datos

Para comprobar que los datos de una aplicación satisfacen los requisitos de los procesos a los que se pasan, normalmente se agrega validación. Ello puede significar tener que comprobar si es correcta la entrada de un usuario en un formulario, validar los datos enviados a la aplicación por otra aplicación, o incluso comprobar que la información calculada dentro de un componente satisface las restricciones del origen de datos y los requisitos de la aplicación.

Los datos se pueden validar de distintas maneras:

  • En la capa de empresa, agregando código a la aplicación para validar los datos. Un lugar en el que puede hacerlo es en el conjunto de datos. El conjunto de datos proporciona algunas ventajas de validación back end, como la capacidad de validar los cambios a medida que se modifican los valores de las columnas y las filas. Para obtener más información, consulte Validar datos en conjuntos de datos.

  • En la capa de presentación, agregando validación a los formularios. Para obtener más información, consulte Validación de los datos proporcionados por el usuario en formularios Windows Forms.

  • En el servidor de datos, al enviar los datos al origen de datos, por ejemplo la base de datos, y dejar que éste los acepte o los rechace. Si trabaja con una base de datos que incluye funciones sofisticadas para validar datos y proporcionar información sobre errores, puede ser un planteamiento práctico, porque los datos se pueden validar sea cual sea su procedencia. Sin embargo, este enfoque podría no adaptarse a los requisitos de validación específicos de la aplicación. Además, si los datos se validan en el origen de datos, puede que éste tenga que realizar numerosas acciones de ida y vuelta, dependiendo de cómo facilite la aplicación la resolución de los errores de validación provocados por el back-end.

    Importante

    Cuando utilice comandos de datos con una propiedad CommandType cuyo valor esté establecido en Text, compruebe minuciosamente la información enviada desde el cliente antes de pasarla a la base de datos. Usuarios con malas intenciones podrían intentar enviar (inyectar) instrucciones de SQL modificadas o adicionales con el fin de obtener acceso no autorizado o dañar la base de datos. Antes de transferir la entrada del usuario a una base de datos, compruebe siempre que la información es válida. Un procedimiento recomendado es utilizar siempre que sea posible consultas parametrizadas o procedimientos almacenados.

Transmisión de actualizaciones al origen de datos

Después de modificar un conjunto de datos, se pueden transmitir los cambios a un origen de datos. Lo más frecuente será hacerlo mediante una llamada al método Update del TableAdapter (o adaptador de datos). El método recorre cada uno de los registros de una tabla de datos, determina qué tipo de actualización se requiere (actualizar, insertar o eliminar), si se requiere alguna, y después ejecuta el comando correspondiente.

Para ilustrar cómo se realizan las actualizaciones, supongamos que su aplicación utiliza un conjunto de datos que contiene una sola tabla de datos. La aplicación obtiene dos filas de la base de datos. Después de recuperarlas, la tabla de datos en memoria sería como la siguiente:

(RowState)     CustomerID   Name             Status
(Unchanged)    c200         Robert Lyon      Good
(Unchanged)    c400         Nancy Buchanan    Pending

La aplicación cambia el estado de Nancy Buchanan a "Preferred". Como resultado de este cambio, el valor de la propiedad RowState de esa fila cambia de Unchanged a Modified. El valor de la propiedad RowState de la primera fila se mantiene como Unchanged. Ahora, la tabla de datos tiene este aspecto:

(RowState)     CustomerID   Name             Status
(Unchanged)    c200         Robert Lyon      Good
(Modified)     c400         Nancy Buchanan    Preferred

Llegados a este punto, su aplicación llama al método Update para transmitir el conjunto de datos a la base de datos. El método inspecciona cada fila de una en una. En la primera fila, el método no transmite ninguna instrucción SQL a la base de datos, porque esa fila no ha cambiado desde la primera vez que se obtuvo de la base de datos.

Sin embargo, para la segunda fila, el método Update invoca automáticamente el comando de datos correcto y lo transmite a la base de datos. La sintaxis específica de la instrucción SQL depende del dialecto de SQL que admita el almacén de datos subyacente. Con todo, cabe señalar los siguientes rasgos generales de la instrucción SQL transmitida:

  • La instrucción SQL transmitida es una instrucción UPDATE. El adaptador sabe cómo utilizar una instrucción UPDATE, porque el valor de la propiedad RowState es Modified.

  • La instrucción SQL incluye una cláusula WHERE que indica que el destino de la instrucción UPDATE es la fila donde CustomerID = 'c400'. Esta parte de la instrucción SELECT distingue la fila de destino de las demás porque CustomerID es la clave principal de la tabla de destino. La información de la cláusula WHERE se deriva de la versión original del registro (DataRowVersion.Original), en caso de que hayan cambiado valores necesarios para identificar la fila.

  • La instrucción SQL transmitida incluye la cláusula SET para establecer los nuevos valores de las columnas modificadas.

    Nota:

    Si la propiedad UpdateCommand del TableAdapter se ha establecido en el nombre de un procedimiento almacenado, el adaptador no construye una instrucción SQL. En su lugar, invoca al procedimiento almacenado pasando los parámetros correspondientes.

Paso de parámetros

Normalmente, se usan parámetros para pasar los valores de los registros que se van a actualizar en la base de datos. Cuando el método Update del TableAdapter ejecuta una instrucción UPDATE, necesita rellenar los valores de los parámetros. Dichos valores los obtiene de la colección Parameters del comando de datos correspondiente, en este caso, el objeto UpdateCommand de TableAdapter.

Si ha utilizado Visual Studio Tools para generar un adaptador de datos, el objeto UpdateCommand contendrá una colección de parámetros que se corresponden con cada marcador de posición de parámetro de la instrucción.

La propiedad System.Data.SqlClient.SqlParameter.SourceColumn de cada parámetro señala a una columna en la tabla de datos. Por ejemplo, la propiedad SourceColumn para los parámetros au_id y Original_au_id está establecida en la columna de la tabla de datos que contenga el identificador del autor. Cuando el método Update del adaptador se ejecuta, lee la columna del identificador del autor del registro que se está actualizado y rellena los valores de la instrucción.

En una instrucción UPDATE, debes especificar los valores nuevos (los que se escribirán en el registro) y los valores antiguos (para poder encontrar el registro en la base de datos). Por tanto, hay dos parámetros para cada valor: uno para la cláusula SET y otro diferente para la cláusula WHERE. Ambos parámetros leen los datos del registro que se actualiza, pero obtienen versiones diferentes del valor de la columna basándose en la propiedad SourceVersion del parámetro. El parámetro de la cláusula SET obtiene la versión actual y el parámetro de la cláusula WHERE obtiene la versión original.

Nota:

También puede establecer los valores de la colección Parameters en el código; en ese caso, sería necesario hacerlo en un controlador de eventos para el evento RowChanging del adaptador de datos.