Lectura de datos de un archivo CSV con tipos de datos mixtos

Esto funciona siempre que cada 'elemento' sea numérico O cada elemento sea alfanumérico. Cuando tengo 'elementos' que son numéricos Y alfanuméricos, no completa los valores correctamente.

Aquí está el código:

public void updateInventory()
{
    try
    {
        if (File.Exists(inventoryUpdateDirectory + "inventoryUpdate.csv"))
        {
            csvReader csv = new csvReader();
            DataTable inventory = csv.read(inventoryUpdateDirectory + "inventoryUpdate.csv");
            //int test = inventory.Rows.Count;
            string sql = "";

            foreach (DataRow inventoryItem in inventory.Rows)
            {
                try
                {
                    sql = " Update Inventory set OnHand = " + inventoryItem[1] + " WHERE Sku = '" + inventoryItem[0].ToString().Trim() + "'";
                    //executeSQL(sql);
                }
                catch { }
            }

            File.Delete(inventoryUpdateDirectory + "inventoryUpdate.csv");
        }
        else
        {
            writeToFile("fileDoesntExist", inventoryUpdateDirectory + "error.txt");
        }
    }
    catch { }
}

Aquí está el archivo que lee:

ALB001,0
ALB002,66
10001,0
10016,348

Así funciona:

ALB001,0
ALB002,66

Así funciona:

10001,0
10016,348

Esto no funcionará:

ALB001,0
ALB002,66
10001,0
10016,348

Rellena el inventoryItem matriz como un vacío {}

+       inventoryItem[0]    {}  object {System.DBNull}

que debe tener el valor de ALB001

La primera 'columna' en el CSV siempre debe tratarse como una cadena, ya que puede contener números de letras, la segunda 'columna' siempre serán números.

¿Alguien que pueda ayudarme a resolver esto?

Creo que solo necesito editar la consulta sql para convertirlos como una cadena, pero no estoy seguro.

EDICIÓN DEL LECTOR DE CSV:

namespace CSV
{
    public class csvReader
    {
        public DataTable read(string strFileName)
        {
            OleDbConnection conn = new OleDbConnection("Provider=Microsoft.Jet.OleDb.4.0; Data Source = " + System.IO.Path.GetDirectoryName(strFileName) + "; Extended Properties = \"Text;HDR=NO;FMT=Delimited\"");
            conn.Open();
            string strQuery = "SELECT * FROM [" + System.IO.Path.GetFileName(strFileName) + "]";
            OleDbDataAdapter adapter = new OleDbDataAdapter(strQuery, conn);
            DataSet ds = new DataSet("CSV File");
            adapter.Fill(ds);
            return ds.Tables[0];
        }

        public DataTable read(string strFileName, bool firstRowHeaders)
        {
            string hdr = "NO";
            if (firstRowHeaders) { hdr = "YES"; }

            OleDbConnection conn = new OleDbConnection("Provider=Microsoft.Jet.OleDb.4.0; Data Source = " + System.IO.Path.GetDirectoryName(strFileName) + "; Extended Properties = \"Text;HDR=" + hdr + ";FMT=Delimited\"");
            conn.Open();
            string strQuery = "SELECT * FROM [" + System.IO.Path.GetFileName(strFileName) + "]";
            OleDbDataAdapter adapter = new OleDbDataAdapter(strQuery, conn);
            DataSet ds = new DataSet("CSV File");
            adapter.Fill(ds);
            return ds.Tables[0];
        }
    }
}

preguntado el 03 de mayo de 12 a las 22:05

No use SQL en línea, use parámetros y defina sus tipos de datos. -

Esto fue hecho por otro desarrollador que ya no está aquí, reescribir esto no es una opción en este momento. -

¿Qué es un lector csv? Es casi seguro que el problema comienza ahí.

Está fallando porque el primer valor "OnHand" va a estar implícito en el primer valor que ve como cadena, pero cambia esto unas líneas más tarde a un int. Lo mejor es envolver el valor con '' para decirle a SQL que siempre obtiene una cadena:

Ese es un archivo de inyección de SQL oculto. Utilice siempre parámetros si no tiene control total sobre la entrada. Y los bloques catch vacíos son mucho peores que no intentar/atrapar. -

4 Respuestas

El problema obviamente está en la clase CsvReader. Dado que no adjuntó su código fuente, es difícil saber por qué no está llenando esa tabla de datos con el contenido y lo mejor que puedo hacer es adivinar.

Intentaré ayudarte sugiriéndote que uses este lector csv de codeproject: http://www.codeproject.com/Articles/86973/C-CSV-Reader-and-Writer

no necesitará trabajar con tablas de datos, ya que le permite iterar sobre las filas del archivo usando un ciclo while simple. Su código se verá así:

using (CsvReader reader = new CsvReader(FilePath, Encoding.Default))
{
   while (reader.ReadNextRecord())
   {
          sql = " Update Inventory set OnHand = " + reader.Fields[1] + " WHERE Sku = '" + reader.Fields[0] + "'";
   }
} 

contestado el 03 de mayo de 12 a las 22:05

Bueno, según el código que publicaste, no tienes mucho control sobre cómo se genera el CSV. Strongyl sugiero reemplazar el CsvReader por el que sugerí en mi respuesta. Es confiable, muy rápido y, lo que es más importante, le brinda control total sobre cómo leer el archivo csv. Le sugiero enfáticamente que, como lo hicieron otros, no use consultas SQL explícitas sino que use parámetros. Su aplicación está muy expuesta a las inyecciones de sql: koby mizrahi

Estoy completamente de acuerdo contigo, si pudiera volver a escribir esto lo haría (todas las cosas que uso tienen instrucciones de uso, parámetros, etc.). Sólo tengo que escuchar a los chicos de arriba. Veré el ejemplo que publicaste y veré si puedo hacer un trabajo rápido. :) - James Wilson

Descargué y arrojé esto en el proyecto existente y usé su ejemplo anterior y funciona de maravilla. ¡Gracias! - James Wilson

Sé que esta es una pregunta anterior y he intentado muchas cosas para resolver un problema similar a este, así que pensé en ofrecer mi solución. Aunque estoy usando C#, puede heredar bibliotecas VB y la biblioteca TextFieldParser maneja esto muy bien. Para mis datos, no sé cuántas columnas se importarán, aunque cada fila tendrá la misma cantidad de columnas. Algunas columnas contienen comas pero no están rodeadas por comillas "". Primero creé un DataTable que convirtió todas las columnas en una cadena donde validaré los tipos más adelante. También elimino la fila de encabezado que tiene el archivo que uso para crear el DataTable primero. Espero que esto pueda ayudar a alguien.

Ejemplo de datos de lo que estaba trabajando:

Cabecera1, Cabecera2, Cabecera3
A, 123, A
B, 123, A
C,A,Algunos datos, luego algunos datos más

Aquí está la solución que finalmente se me ocurrió que funciona muy bien.

using Microsoft.VisualBasic.FileIO;

        private static DataTable GetDataTableFromCsv(string path)
        {
            var dataTable = new DataTable("ImportData");
            var rows = File.ReadAllLines(path);
            var columns = rows[0].Split(',');
            foreach (var column in columns)
            {
                dataTable.Columns.Add(new DataColumn(column.Trim(), typeof(string)));
            }
            using (var parser = new TextFieldParser(path))
            {
                parser.Delimiters = new[] { "," };
                while (true)
                {
                    var parts = parser.ReadFields();
                    dataTable.Rows.Add(parts);
                    if (parser.EndOfData) break;
                }
            }
            dataTable.Rows[0].Delete();
            return dataTable;
        }

respondido 14 mar '14, 14:03

Me ayudó. Gracias - Manjunath K Mayya

En el siguiente ejemplo, agregué comillas simples alrededor del valor OnHand, para decirle a SQL que siempre debe esperar una cadena. Aún es mejor que cambie su instrucción SQL para usar "@Parameters" y asigne los valores en línea.

sql = " Update Inventory set OnHand = '" + inventoryItem[1] + "' WHERE Sku = '" + inventoryItem[0].ToString().Trim() + "'";

contestado el 03 de mayo de 12 a las 22:05

Esto no lo solucionó. El OnHand siempre será un número de todos modos. Es el Sku (que tiene marcas individuales) que puede ser un número o alfanumérico. Esperaba que esto pudiera solucionarlo, pero el mismo problema que antes. =( - James Wilson

Puedes leer CSV con LINQ

var data = (from line in File.ReadAllLines(fileName).AsParallel()
            select line.Split(',')).ToList();

Luego haga el casting adecuado, actualizando.

contestado el 03 de mayo de 12 a las 22:05

¿Esto me obligaría a pasar mucho tiempo reescribiendo el código existente para trabajar con este nuevo objeto? Solo estoy buscando la solución más simple para hacer que el código base existente funcione. Todavía soy un novato en asp.net, lo que me da escalofríos al reescribir estas cosas existentes. - James Wilson

Dividir usando comas no cumple con las especificaciones csv. Por ejemplo, si el valor de la primera columna contiene una coma, producirá resultados con 3 columnas en lugar de 2. Sin embargo, se puede traducir fácilmente al código de queja csv llamando a un método que "divide de forma segura" la línea en lugar de la Línea. Llamada dividida - koby mizrahi

Como puedo ver, le tomaría un par de minutos ya que tiene una lista para completar su tabla de datos. También sería mucho más limpio. - Matija Grcic

@KobyMizrahy no veo ningún problema aquí para leer datos CSV bien estructurados. También puede agregar Skip (1) para omitir los encabezados de las columnas. ReadAllLines devuelve una matriz de cadenas. Cada cadena contiene una sola línea del archivo. ReadAllText devuelve una sola cadena que contiene todas las líneas del archivo y la cadena resultante no contiene el retorno de carro ni el salto de línea de terminación. - Matija Grcic

@plurby: todo lo que quería enfatizar es que dividir los datos csv usando comas no cumple con las especificaciones de CSV. Por ejemplo, si desea guardar el valor "Soy una cadena que contiene, coma", se guardará con comillas dobles. El lector csv debe manejar explícitamente tal escenario; de lo contrario, la cadena anterior se dividirá en 2 columnas diferentes, lo que no se desea. - koby mizrahi

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.