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.
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.