Cómo pasar datos entre diferentes orígenes de datos
Por Enrique Martínez Montejo
Última revisión: 11/04/2010
 

Aunque cada motor de base de datos puede disponer de mecanismos para el traspaso de la información desde un origen de datos a otro, puede ser que en determinadas circunstancias lo más conveniente sea utilizar un objeto DataTable para transferir datos entre diferentes orígenes de datos, bien sea desde una base de datos Microsoft SQL Server a otra de Microsoft Access, o a un simple archivo de texto delimitado, o viceversa.

El objeto DataSet, al igual que sus objetos relacionados, forman parte de lo que se conoce como la arquitectura desconectada de ADO .NET, lo que significa que los datos existentes en un objeto DataTable son independientes de cualquier origen de datos, y se necesita un objeto DataAdapter adecuado con el origen de datos que se esté utilizando, tanto para recuperar la información como para posteriormente actualizarla. Esto quiere decir que podemos configurar un objeto OleDbDataAdapter para rellenar un objeto DataTable con los datos de un archivo de texto delimitado, un archivo de Excel o una base de datos Microsoft Access, y posteriormente configurar otro objeto SqlDataAdapter para actualizar una tabla de una base de datos de SQL Server con los datos existentes en el objeto DataTable rellenado, por poner un ejemplo de traspaso de datos entre un origen de datos OleDb y una base de datos Microsoft SQL Server.

Lo único que se requiere es que los tipos de datos de los campos o columnas comunes a ambos orígenes de datos sean idénticos, porque como bien comprenderá, va a ser complicado añadir datos alfanuméricos en columnas declaradas como numéricas o de fecha y hora. También sería conveniente que las filas de datos que desea exportar al origen de datos de destino fuesen únicas, para que no se produzcan errores por clave principal duplicada, aunque en el ejemplo que mostraré posteriormente explicaré la manera de solucionar estos problemas.

Como estará deseando entrar en detalle, vamos a imaginar que tiene el siguiente archivo de texto delimitado por comas, donde la primera fila no contiene el nombre de las tres columnas existentes, la primera numérica, la segunda alfanumérica y la tercera de fecha y hora:

1012,JUAN GARCIA PALACIOS,23/01/1958
2234,CARMEN HUERTAS BUENDIA,14/02/1962
2298,FELIPE CASTRO BERMUDEZ,12/03/1955
4394,EUSEBIO LENDINEZ MARTOS,13/12/1978
5283,NATIVIDAD MARTINEZ GONZALEZ,11/02/1975
5733,ROBERTO MENDIETA ALVARADO,29/02/1968
7078,CONCEPCION RODRIGUEZ NUÑEZ,27/11/1966
8001,PEDRO GARCIA GARCIA,11/08/1945
8098,DAMIAN GOMEZ HERRADOR,12/09/1948
9910,RAFAEL DAMAS OLMEDO,14/08/1978

Nuestra intención va a ser añadir estos registros a los campos IdSocio, Nombre y FechaNto existente en una hipotética tabla de Socios de una base de Microsoft SQL Server.

Lo primero que vamos a hacer es escribir una función que nos devuelva un objeto DataTable con los datos del archivo de texto delimitado. Para ello, bien nos podría servir el siguiente procedimiento:

Imports System.Data.SqlClient
Imports
System.Data.OleDb

Private Function
GetDataTable() As DataTable

    Using cnn As New OleDbConnection()

        Try
            ' Definimos la cadena de conexión OleDb,
            ' indicando nuestra intención de conectarnos
            ' con un archivo de texto delimitado donde
            ' la primera fila no contiene los nombres
            ' de los campos.

            '
            cnn.ConnectionString = _
                "Provider=Microsoft.Jet.OLEDB.4.0;" & _
                "Data Source=C:\Bases\Texto;"
& _
                "Extended Properties='TEXT;HDR=No;'"


            ' Construimos la consulta SQL de selección.
            '

            Dim sql As String = "SELECT * FROM Archivo#csv"

            ' Creamos el adaptador de datos.
            '

            Dim da As OleDbDataAdapter = New OleDbDataAdapter(sql, cnn)

            ' Mapeamos la tabla para que los nombres de los campos se
            ' correspondan con los existentes en la tabla de SQL Server.
            '

            With da.TableMappings.Add("Archivo", "SociosMapping")
                .ColumnMappings.Add("F1", "IdSocio")
                .ColumnMappings.Add("F2", "Nombre")
                .ColumnMappings.Add("F3", "FechaNto")
            End With

            ' Construimos el objeto DataTable, utilizando el nombre
            ' de la tabla mapeada.
            '

            Dim dt As DataTable = New DataTable("SociosMapping")

            ' Rellenamos el objeto DataTable.
            '

            da.Fill(dt)

            ' Devolvemos el objeto DataTable.
            '

            Return dt

        Catch ex As Exception
            ' Devolvemos la excepción al procedimiento llamador.
            '

            Throw

        End Try

    End Using

End Function

Lo mismo se estará preguntando el motivo de mapear el objeto DataTable. Como la primera línea del archivo de texto no contiene el nombre de las columnas, al obtener el objeto DataTable los campos se llamarán F1, F2 y F3. Es por éste motivo por el cual mapeamos el objeto DataTable para que los nombres de los campos sean los que figuran en la tabla de SQL Server donde deseamos añadir los registros. Por supuesto, a la hora de crear el objeto DataTable tendrá que especificar el nombre del objeto DataTableMapping creado, en el ejemplo SociosMapping, porque si elige otro nombre cualquiera, entonces las columnas del objeto DataTable tendrán por nombre F1, F2 y F3, cuestión que no deseamos que así sea.

Ya tenemos creado el método para recuperar los registros del archivo de texto delimitado. Recuerde que puede modificar el procedimiento para recuperar un objeto DataTable de cualquier otro origen de datos distinto, como una tabla de una base de Oracle, de MySQL, de un archivo de Excel, o de cualquier otro origen para el que exista un proveedor .net adecuado.

Ahora vamos a escribir el procedimiento para insertar en la tabla de la base de SQL Server, los datos existentes en el objeto DataTable recuperado. Para ello, ejecutaremos el siguiente código fuente:

Private Function UpdateData(ByVal dt As DataTable) As Integer

    ' Si el objeto DataTable no es válido, o no contiene
    ' registros, abandonamos el procedimiento.
    '

    If ((dt Is Nothing) OrElse _
       (dt.Rows.Count = 0)) Then Return 0

    ' Establecemos la conexión con la base de SQL Server.
    '

    Using cnn As New SqlConnection()

        Try
            ' Construimos la cadena de conexión con la instancia
            ' local de SQL Server, utilizando la seguridad
            ' integrada de Windows NT.
            '

            cnn.ConnectionString = _
                "Data Source=(local);" & _
                "Initial Catalog=Prueba;"
& _
                "Integrated Security=SSPI"


            ' Construimos la consulta T-SQL de selección, especificando
            ' expresamente las columnas que vamos a rellenar de datos.
            '

            Dim sql As String = _
                "SELECT IdSocio, Nombre, FechaNto FROM Socios"

            ' Creamos el adaptador de datos.
            '

            Dim da As SqlDataAdapter = New SqlDataAdapter(sql, cnn)

            ' Instalamos el controlador para el evento RowUpdated.
            '

            AddHandler da.RowUpdated, AddressOf AdapterOnRowUpdated

            ' Creamos un objeto SqlCommandBuilder.
            '

            Dim cb As SqlCommandBuilder = New SqlCommandBuilder(da)

            ' Configuramos únicamente el comando de inserción del
            ' adaptador de datos.
            '

            da.InsertCommand = cb.GetInsertCommand()

            ' Para que se pueda actualizar los datos, el valor de la propiedad RowState
            ' de los objetos DataRow tiene que ser distinta a Unchanged, por lo que de
            ' ser así, la pasaremos a Added.
            '

            For Each row As DataRow In dt.Rows
                If (row.RowState = DataRowState.Unchanged) Then
                    row.SetAdded()
                End If
            Next

            ' Actualizamos la tabla.
            '

            Dim n As Integer = da.Update(dt)

            ' Devolvemos el número de registros afectados.
            '

            Return n

        Catch ex As Exception
            ' Devolvemos la excepción al procedimiento llamador.
            '

            Throw

        End Try

    End Using

End Function

Habrá observado que he instalado el controlador para el evento RowUpdated del objeto SqlDataAdapter. Ello significa que tendrá que añadir en su clase el siguiente procedimiento de evento:

Private Sub AdapterOnRowUpdated( _
    ByVal sender As Object, _
    ByVal e As System.Data.SqlClient.SqlRowUpdatedEventArgs)

    If (e.Status = UpdateStatus.ErrorsOccurred) Then
        ' Si se ha producido un error, intentamos
        ' continuar con la inserción de registros.
        '

        e.Status = UpdateStatus.Continue

        If (m_rows Is Nothing) Then
            Array.Resize(m_rows, 1)
        Else
            Array.Resize(m_rows, m_rows.Length + 1)
        End If

        ' Copiamos en el array el registro que no
        ' se ha podido insertar.
        '

        e.CopyToRows(m_rows, m_rows.Length - 1)

    End If

End Sub

El evento RowUpdated se desencadenará por cualquier intento de actualización que se haga en la llamada al método Update del objeto SqlDataAdapter, por tanto, si deseamos añadir 1.000 registros, el evento se desencadenará 1.000 veces, una por cada registro, de ahí que tiene que ser cuidadoso con el código que se ejecuta en dicho evento a fin de que no se demore la actualización del origen de datos.

Si el registro se ha insertado satisfactoriamente, la propiedad Status del parámetro SqlRowUpdatedEventArgs devolverá el valor 0 (UpdateStatus.Continue), por lo que sólo ejecutemos el código cuando el valor de la propiedad sea 1 (UpdateStatus.ErrorsOcurred), síntoma de que se ha producido una excepción, con lo cual insertaremos en un array de objetos DataRow el registro que no se ha podido insertar, mayormente por si posteriormente desea analizar los registros que han producido una excepción. Pero porque algunos registros no se hayan podido añadir, a lo mejor no desea deshechar aquellos que sí se pueden insertar, es por ello que asignamos el valor UpdateStatus.Continue a la propiedad Status, para continuar con la operación de inserción.

¡Bueno! Pues ya estamos en condiciones de traspasar los datos desde el archivo de texto delimitado por comas a la tabla de SQL Server. Para ello, simplemente ejecutaremos el siguiente código: 

Private m_rows() As DataRow

Try
    ' Obtenemos el objeto DataTable.
    '

    Dim dt As DataTable = GetDataTable()

    ' Establecemos a Nothing el valor del array de objetos DataRow.
    '

    m_rows = Nothing

    ' Procedemos a actualizar la base de SQL Server.
    '

    Dim n As Integer = UpdateData(dt)

    MessageBox.Show("Número de registros afectados: " & CStr(n))

Catch ex As Exception
    MessageBox.Show(ex.Message)

End Try

Recuerde que en el array existente a nivel de la clase (m_rows) dispondrá de aquellos registros (objetos DataRow) que no se han actualizado.

Espero que el artículo le haya sido de utilidad. 

 

Otros enlaces de interés:

Indice de Ejemplos de ADO .NET


Enrique Martínez Montejo - 2010

NOTA: La información contenida en este artículo, así como el código fuente incluido en el mismo, se proporciona COMO ESTÁ, sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo explicado, recomendado o sugerido en el presente artículo.

NOTE: The information contained in this article and source code included therein, is provided AS IS without warranty of any kind, and confers no rights. You assume any risk to implement, use or run it explained, recommended or suggested in this article.