Importação de CSV

Visão Geral

Neste tópico iremos explorar um cenário de importação de arquivo CSV, considerando a existência das seguintes tabelas:

archives:

 -- PostgreSQL
CREATE TABLE samples.archives
(
   id       VARCHAR (255) NOT NULL,
   name     VARCHAR(255),
   size     INT,
   blob     BYTEA,
   hash     VARCHAR(40)
);
ALTER TABLE Arquivo ADD CONSTRAINT pk_archives_id PRIMARY KEY (Id);

products:

 -- PostgreSQL
CREATE TABLE samples.products
(
    id         serial NOT NULL,
    name       text NOT NULL,
    price      numeric(12, 2) NOT NULL,
    unit       integer NOT NULL,
    CONSTRAINT pk_products_id PRIMARY KEY (id)
);

para esse exemplo também será criado uma função no banco para efetuar a gravação na tabela:

CREATE FUNCTION samples.product_add(IN name_arg text, IN price_arg text, IN unit_arg text)
    RETURNS void
    LANGUAGE 'sql'
    
AS $BODY$
insert into samples.products (name, price, unit) 
values (name_arg, price_arg::decimal, unit_arg::integer);
$BODY$;

COMMENT ON FUNCTION samples.product_add(text, numeric, integer)
    IS 'Adiciona um produto na tabela products';

E um arquivo csv no seguinte formato (Sem a linha contendo o nome das colunas) “name;price;unit”.

Iremos criar um formulário que irá usar um código c# para efetuar a leitura do csv e efetuar a gravação na tabela products. Nesse formulário conterá os seguintes componentes:

  • Variáveis:
    • guid
  • Procedimentos:
    • btnImport_Click
    • Form_Load
    • uplFile_FileUploaded
  • Campos:
    • txtSeparator
    • uplFile
    • btnImport

Lendo CSV

No procedimento btnImport_Click iremos adicionar um comando para executar um código c#. Nele iremos adicionar o seguinte código:

using LATROMI.Extensions;
using System.Collections.Generic;
using System.IO;

void Insert(List<string[]> columnsData)
{
    using (var connection = Database.CreateConnection("LATROMI"))
    {
        connection.Open();

        foreach (var data in columnsData)
        {
            connection.ExecuteNonQuery(
                "select samples.product_add(@name, @price, @unit)",
                new Dictionary<string, object>() {
                    ["@name"] = data[0],
                    ["@price"] = data[1].Replace(',', '.'),
                    ["@unit"] = data[2]
                }
            );
        }
    }
}

byte[] GetFileFromDB(string id)
{
    using (var connection = Database.CreateConnection("LATROMI"))
    {
        connection.Open();
        
        using (var reader = connection.ExecuteReader(
            string.Concat("SELECT blob FROM samples.archives WHERE id = '", id, "'")
        ))
        {
            if (reader.Read())
                return (byte[])reader["blob"];
        }
    }
    return null;
}

List<string[]> ReadCsv(Stream data, char separator = ';')
{
    List<string[]> columns = new List<string[]>();
    using (var sr = new StreamReader(data))
    {
        while (!sr.EndOfStream)
        {
            string line = sr.ReadLine() ?? string.Empty;

            if (string.IsNullOrEmpty(line))
                continue;

            string[] columnSplited = line.Split(separator);

            if (columns.Count > 0 && columns.Last().Length != columnSplited.Length)
                throw new FormatException(string.Format("The values split on line {0} are different from the other lines already read.", columns.Count + 1));

            columns.Add(columnSplited);
        }
    }
    return columns;
}

byte[] file = GetFileFromDB((string)Variables["guid"].Value);

char separator = Fields["txtSeparator"].Value.ToString()[0];
List<string[]> fileData = ReadCsv(new MemoryStream(file), separator);

Insert(fileData);

Criação do Formulário

Campos

Procedimentos

  • Form_Load:
    Procedimento vinculado no evento de Load do formulário, que irá popular a variável “guid” com o seguinte código c#:
return Guid.NewGuid();
  • uplFile_FileUploaded:
    Procedimento vinculado ao evento de FileUploaded do campo Upload, que será responsável por atualizar a variável “guid” para um novo valor usando mesmo código c# apresentado anteriormente, no populate.

  • btnImport_Click:
    Procedimento responsável, por importar o arquivo csv para a tabela products usando o código apresentado na sessão Lendo CSV:

Conclusão

Ao efetuar todos os passos acima você terá o seguinte resultado: