viernes, 27 de marzo de 2020

De CSV a DataGridView y de DatagridView a BBDD.

Leer un CSV

Se puede leer desde Visual Basic un archivo con formato separado por comas CSV, para que esto sea posible, el formato que debe cumplir un CSV debe cumplir estos requisitos:

Sea un fichero de texto plano cualquiera, eliminamos las cabeceras (suele ser la primera fila), y renombramos el fichero como .csv 

Si ahora lo abrimos (debemos tener en nuestro equipo un programa capaz de leer archivos .csv como LibreOffice por ejemplo).
Al intentar abrirlo nos muestra esta pantalla.

Abrir archivo con formato CSV



Elegimos el conjunto de caracteres unicode (UTF-8)  para que no trunque las palabras acentuadas. 

Coma y punto y coma , delimitador de cadena ?  |

En algunos caso pueden venir caracteres ocultos que producirán errores, como por ejemplo los retornos de carro LF, para quitar estos retornos de carro  LF abrimos el CSV con notepad ++ y hacemos lo siguiente:

Ctrl+F
Vamos a la pestaña "Replace" 
En "Find What" ponemos: ([^\r])\n
En "Replace with" ponemos: \1
Elegimos "Regular expression" en "Search Mode"
Y pulsamos el botón Replace All


Quitar carácteres extraños con Notepad++


Dados estos pasos previos, creamos un procedimiento de este estilo para cargar el .csv en un dataGridView de Visual Basic.

Private Sub txtLeeCSV_Click(sender As Object, e As EventArgs) Handles txtLeeCSV.Click
        Try

            OpenFileDialog1.Filter = "CSV files(*.csv)|*.csv"
            If OpenFileDialog1.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
                Dim sr As System.IO.StreamReader = New System.IO.StreamReader(OpenFileDialog1.FileName)
                sr.Close()
                ImportarCSV(grdDatosCSV, ";")
            End If
            txtCargaBBDD.Enabled = True
            txtLeeCSV.Enabled = False
        Catch ex As Exception
            MessageBox.Show(ex.Message, "ERROR")
        End Try

    End Sub

 Sub ImportarCSV(ByVal TABLA As DataGridView, ByVal DELIMITADOR As String)


        Dim fName As String = ""
      
        fName = OpenFileDialog1.FileName

        Dim TextLine As String = ""
        Dim TextAux As String = ""
        Dim SplitLine() As String
        Dim NombreColumna As String = ""
        Dim CortaEn As Integer
        Dim EsCabecera As Boolean = True
        grdDatosBBDD.Visible = False
        grdDatosCSV.Visible = True

        If System.IO.File.Exists(fName) = True Then
            Dim objReader As New System.IO.StreamReader(OpenFileDialog1.FileName, Encoding.UTF8)
            Do While objReader.Peek() <> -1
                TextLine = objReader.ReadLine()
                'Aqui detecta la primera linea para introducir los nombres de las columnas
                If EsCabecera Then
                    TextAux = TextLine
                    CortaEn = InStr(TextAux, ";")
                    Do While CortaEn <> 0
                        NombreColumna = Mid(TextAux, 1, CortaEn - 1)
                        grdDatosCSV.Columns.Add(NombreColumna, NombreColumna)
                        TextAux = Mid(TextAux, CortaEn + 1)
                        CortaEn = InStr(TextAux, ";")
                    Loop
                    'Para la última columna
                    grdDatosCSV.Columns.Add(TextAux, TextAux)
                    EsCabecera = False
                Else
                    SplitLine = Split(TextLine, ";")
                    Me.grdDatosCSV.Rows.Add(SplitLine)
                End If
            Loop
        Else
            MsgBox("Fichero no existe")
        End If
    End Sub

Cargar el DataGridView en la Base de Datos

Con el DataGridView  cargado podemos pasar los datos del grid a una base de datos Oracle fácilmente del siguiente modo:

Private Sub txtCargaBBDD_Click(sender As Object, e As EventArgs) Handles txtCargaBBDD.Click
        txtCargaBBDD.Enabled = False
        TipoAccion = Accion.Alta
        grdDatosCSV.Enabled = False
        drEdit = bs.AddNew()
        bs.MoveLast()
        grdDatosBBDD.Refresh()
        Dim i As Integer = 0
        'Usamos un ciclo For Each para recorrer nuestro DataGridView
        Dim dt As DataTable = TryCast(bs.DataSource, DataTable)
        Try
            For Each Row As DataGridViewRow In grdDatosCSV.Rows
                'Antes de insertar hay que pasar los datos de un grid a otro linea por line
                ObtenerValoresFila(Row, dt)
                i = i + 1
            Next
          
            bs.DataSource = dt
            dA.Update(bs.DataSource)
            grdDatosBBDD.Refresh()
            bolRefrescar = False
            grdDatosCSV.Refresh()
            grdDatosBBDD.Refresh()
            MsgBox((grdDatosCSV.RowCount - 1) & " Registros exportados exitosamente", MsgBoxStyle.Information, "PowerCSV")
            grdDatosBBDD.Visible = True
            grdDatosCSV.Visible = False
            txtLeeCSV.Enabled = False
            txtCargaBBDD.Enabled = True
        Catch ex As Exception
            MsgBox("No se pueden guardar los registros por: " & ex.Message, MsgBoxStyle.Critical, "PowerCSV")
            MsgBox(" algunos registros exportados exitosamente, revise el resto", MsgBoxStyle.Information, "PowerCSV")
        End Try

    End Sub


 Sub ObtenerValoresFila(ByVal fila As DataGridViewRow, dt As DataTable)
      
        Try
            'Rellenar el contenido con el valor de las celdas de la fila
            If dt IsNot Nothing Then
              
                    Dim Linea As DataRow = dt.NewRow()
                    Linea("CAMPO1") = fila.Cells(0).Value
                    Linea("CAMPO2") = fila.Cells(1).Value
                    Linea("CAMPO3") = fila.Cells(2).Value
                    Linea("CAMPO4") = fila.Cells(3).Value
                    If IsDate(fila.Cells(15).Value) Then
                        Linea("FEC_LAST_MOD") = fila.Cells(4).Value
                    Else
                        Linea("FEC_LAST_MOD") = DBNull.Value
                    End If
                    Linea("CAMPO6") = fila.Cells(5).Value
                    dt.Rows.Add(Linea)
              
            End If

        Catch ex As Exception
            If Mid(ex.Message, 1, 9) = "ORA-00001" Then
                MsgBox("Hay al menos un código duplicado, revise los datos e inténtelo de nuevo", MsgBoxStyle.Critical, "Error en la inserción masiva de datos")
            End If
            bolRefrescar = True
        End Try

    End Sub

Algunas veces se pueden cargar caracteres extraños en el DataGridView que darán problemas a la hora de insertarlos en la BBDD, hay un carácter ASCII especial que no se aprecia en muchos formatos de documentos y en CSV se muestra como una raya vertical gris que apenas se ve, para eliminarlo podemos utilizar esta función

  'esta funcion limpia el caracter ASCII Nº 63 que no se puede detectar de ninguna otra forma y da error si no se elimina
    Private Function LimpiaCadena(ByVal cadena As String) As String

        Dim sText As String
        Dim i As Integer
        Dim lenText As Integer
        Dim sASC As String
        Dim sLimpio As String
        Dim sCaracter As String
        Dim Valor As Integer

        sText = cadena
        lenText = Len(sText)
        'convierte la cadena de texto en una cadena ASCII separada por comas
        For i = 1 To lenText
            sASC = sASC & "," & CStr(Asc(Mid$(sText, i, 1)))
        Next i
        sASC = Mid(sASC, 2)
        lenText = Len(sASC)
        'Convierte la cadena ASCII en texto buscando el valor 63 y lo sustituye por un nulo
        For i = 1 To lenText
            Valor = InStr(sASC, ",")
            If Valor <= 0 Then
                sLimpio = sLimpio & Chr(sASC)
                Exit For
            End If
            sCaracter = Mid(sASC, 1, Valor - 1)
            If sCaracter = "63" Then
                sCaracter = "00"
            End If
            sLimpio = sLimpio & Chr(sCaracter)
            sASC = Mid(sASC, Valor + 1)
        Next i
        Return sLimpio

    End Function

Programa completo 

El programa completo tira de un formulario con dos DataGridView, una para BBDD y otra para el .csv, se van haciendo visibles e invisibles alternativamente para que parezca una sola grid. Además tiene un botón de importar .csv y otro de cargar en BBDD.


Imports System.Data.OleDb
Imports System.Windows.Forms
Imports System.Text
Imports System.IO


Public Class frmCargaCSV
    Dim ds As New DataSet()
    Dim bs As BindingSource = New BindingSource()
    Dim dA As New OleDb.OleDbDataAdapter
    Dim drEdit As DataRowView
    Dim bolRefrescar As Boolean
    Dim bolPrimeraVez As Boolean = True
    Const Tabla = "CSV"
    Enum Accion
        Alta = 1
        Baja = 2
        Modificacion = 3
    End Enum

    Dim TipoAccion As Accion

    Sub CargaCSV()

        Dim srSQL As StreamReader
        Dim strConsulta As String
        Dim cmdbuilder As OleDbCommandBuilder

        Try
            'Repeticion si no es la primera vez es que se ha llamado desde el mantenimiento y antes de cargarse debe borrarse la grid
            If Not bolPrimeraVez Then
                ds.Tables("CSV").Clear()
            End If
            '***********************

            strStartupPath = My.Application.Info.DirectoryPath
            srSQL = New System.IO.StreamReader(strStartupPath & "\CargarCSV.sql")
            strConsulta = srSQL.ReadToEnd
            cmdPD = New OleDb.OleDbCommand(strConsulta, cnxPD)
            dA.SelectCommand = cmdPD
            cmdbuilder = New OleDbCommandBuilder(dA)
            dA.Fill(ds, "CSV")

            If Not bolPrimeraVez Then
                Exit Sub
            End If

            bolPrimeraVez = False

            dA.UpdateCommand = cmdbuilder.GetUpdateCommand()
            dA.DeleteCommand = cmdbuilder.GetDeleteCommand()
            dA.InsertCommand = cmdbuilder.GetInsertCommand()
            bs.DataSource = ds.Tables(Tabla)
        Catch
            MsgBox("Se ha producido un error leyendo los datos: " & Err.Description)
        End Try

    End Sub

    Private Sub txtLeeCSV_Click(sender As Object, e As EventArgs) Handles txtLeeCSV.Click
        Try


            OpenFileDialog1.Filter = "CSV files(*.csv)|*.csv"
            If OpenFileDialog1.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
                Dim sr As System.IO.StreamReader = New System.IO.StreamReader(OpenFileDialog1.FileName)
                sr.Close()
                ImportarCSV(grdDatosCSV, ";")
            End If
            txtCargaBBDD.Enabled = True
            txtLeeCSV.Enabled = False
        Catch ex As Exception
            MessageBox.Show(ex.Message, "ERROR")
        End Try

    End Sub

    Sub ImportarCSV(ByVal TABLA As DataGridView, ByVal DELIMITADOR As String)


        Dim fName As String = ""
        fName = OpenFileDialog1.FileName

        Dim TextLine As String = ""
        Dim TextAux As String = ""
        Dim SplitLine() As String
        Dim NombreColumna As String = ""
        Dim CortaEn As Integer
        Dim EsCabecera As Boolean = True
        grdDatosBBDD.Visible = False
        grdDatosCSV.Visible = True

    
        If System.IO.File.Exists(fName) = True Then
            Dim objReader As New System.IO.StreamReader(OpenFileDialog1.FileName, Encoding.UTF8)
            Do While objReader.Peek() <> -1
                TextLine = objReader.ReadLine()
                'Aqui detecta la primera linea para introducir los nombres de las columnas
                If EsCabecera Then
                    TextAux = TextLine
                    CortaEn = InStr(TextAux, ";")
                    Do While CortaEn <> 0
                        NombreColumna = Mid(TextAux, 1, CortaEn - 1)
                        grdDatosCSV.Columns.Add(NombreColumna, NombreColumna)
                        TextAux = Mid(TextAux, CortaEn + 1)
                        CortaEn = InStr(TextAux, ";")
                    Loop
                    'Para la última columna
                    grdDatosCSV.Columns.Add(TextAux, TextAux)
                    EsCabecera = False
                Else
                    SplitLine = Split(TextLine, ";")
                    Me.grdDatosCSV.Rows.Add(SplitLine)
                End If
            Loop
        Else
            MsgBox("Fichero no existe")
        End If
    End Sub

    Private Sub txtCargaBBDD_Click(sender As Object, e As EventArgs) Handles txtCargaBBDD.Click
        txtCargaBBDD.Enabled = False
        TipoAccion = Accion.Alta
        grdDatosCSV.Enabled = False
        drEdit = bs.AddNew()
        bs.MoveLast()
        grdDatosBBDD.Refresh()
        Dim i As Integer = 0
        'Usamos un ciclo For Each para recorrer nuestro DataGridView
        Dim dt As DataTable = TryCast(bs.DataSource, DataTable)
        Try
            For Each Row As DataGridViewRow In grdDatosCSV.Rows
                'Me.grdDatosBBDD.Rows.Add(ObtenerValoresFila(Row))  'esta instruccion es correcta pero da error por que dice que está
                'enlazada con un bindingSourse y no se puede editar directamente, tiene que hacerse a traves de un datatable
                'SUSUTITUYO ESTA INSTRUCCION POR LO DE ABAJO
                ObtenerValoresFila(Row, dt)
                i = i + 1
            Next
          
            bs.DataSource = dt
            dA.Update(bs.DataSource)
            grdDatosBBDD.Refresh()
            bolRefrescar = False
            grdDatosCSV.Refresh()
            grdDatosBBDD.Refresh()
            MsgBox((grdDatosCSV.RowCount - 1) & " Registros exportados exitosamente", MsgBoxStyle.Information, "PowerCSV")
            grdDatosBBDD.Visible = True
            grdDatosCSV.Visible = False
            txtLeeCSV.Enabled = False
            txtCargaBBDD.Enabled = True
        Catch ex As Exception
            MsgBox("No se pueden guardar los registros por: " & ex.Message, MsgBoxStyle.Critical, "PowerCSV")
            MsgBox(" algunos registros exportados exitosamente, revise el resto", MsgBoxStyle.Information, "PowerCSV")
        End Try

    End Sub

    Sub ObtenerValoresFila(ByVal fila As DataGridViewRow, dt As DataTable)
        
        Try
            'Rellenar el contenido con el valor de las celdas de la fila
            If dt IsNot Nothing Then
               
                Dim Linea As DataRow = dt.NewRow()

                    Linea("CAMPO1") = fila.Cells(0).Value
                    Linea("CAMPO2”) = fila.Cells(1).Value
                    Linea("CAMPO3") = LimpiaCadena(fila.Cells(2).Value)
                    Linea("CAMPO4") = LimpiaCadena(fila.Cells(3).Value)
                 
                    If IsDate(fila.Cells(4).Value) Then
                        Linea("FEC_LAST_MOD") = fila.Cells(4).Value
                    Else
                        Linea("FEC_LAST_MOD") = DBNull.Value
                    End If
                    Linea("CAMPO6") = fila.Cells(16).Value
                    dt.Rows.Add(Linea)
              
            End If

        Catch ex As Exception
            If Mid(ex.Message, 1, 9) = "ORA-00001" Then
                MsgBox("Hay al menos un código duplicado, revise los datos e intentelo de nuevo", MsgBoxStyle.Critical, "Error en la inserccion masiva de datos")
            End If
            bolRefrescar = True
        End Try

    End Sub
    'esta funcion limpia el caracter ASCII Nº 63 que no se puede detectar de ninguna otra forma y da error si no se elimina
    Private Function LimpiaCadena(ByVal cadena As String) As String

        Dim sText As String
        Dim i As Integer
        Dim lenText As Integer
        Dim sASC As String
        Dim sLimpio As String
        Dim sCaracter As String
        Dim Valor As Integer

        sText = cadena
        lenText = Len(sText)
        'convierte la cadena de texto en una cadena ASCII separada por comas
        For i = 1 To lenText
            sASC = sASC & "," & CStr(Asc(Mid$(sText, i, 1)))
        Next i
        sASC = Mid(sASC, 2)
        lenText = Len(sASC)
        'Convierte la cadena ASCII en texto buscando el valor 63 y lo sustituye por un nulo
        For i = 1 To lenText
            Valor = InStr(sASC, ",")
            If Valor <= 0 Then
                sLimpio = sLimpio & Chr(sASC)
                Exit For
            End If
            sCaracter = Mid(sASC, 1, Valor - 1)
            If sCaracter = "63" Then
                sCaracter = "00"
            End If
            sLimpio = sLimpio & Chr(sCaracter)
            sASC = Mid(sASC, Valor + 1)
        Next i
        Return sLimpio

    End Function

    Private Sub frmCVS_Load(sender As Object, e As EventArgs) Handles Me.Load
        Me.grdDatosBBDD.DataSource = bs
        grdDatosBBDD.Visible = True
        grdDatosCSV.Visible = False
        CargaCSV()
    End Sub
End Class



CSV.sql

SELECT  CAMPO1,
CAMPO2,
CAMPO3,
CAMPO4,
FEC_LAST_MOD,
CAMPO6
FROM PROPIETARIO.TABLA


No hay comentarios:

Publicar un comentario