Cómo leer archivos planos con ADO.NET

sábado 11 de noviembre de 2006

(Antes que me la rieguen, sí, ya sé que son "archivos de texto sencillo" pero este blog está en pocho, ¿no? Digo, no hay archivos de computadora planos, o cúbicos o esféricos, que yo sepa, pero así les dicen muchos desarrolladores)

Aunque hoy en día ya todo mundo debería de estar utilizando (según yo) XML para el intercambio de información, sigue siendo muy comun que para mandar información de un sistema a otro se haga a través de archivos "planos" de texto.

Si alguna vez has lideado con sistemas legacy o en plataformas chiples (mainframe, or SAP anyone?) estoy seguro que sabes de lo que hablo. Otro caso común es cuando requieres que tu usuario genere algún archivo para cargar esos datos en tu aplicación. Harta cantidad de gerentoides y chalanes no saben ni (bleep) de sistemas o de computadoras, pero eso sí, son masters sensai del Excel, así que es fácil decirles que le den un "Save As... CSV" a un archivo para subir sus datos.

Ahora, ¿cómo le harías para leer esos archivos con .NET? Si eres entusiasta probablemente luego luego te las ingeniarías para usar un FileStream y parsear el contenido línea por línea, dar 3 maromas... qué se yo. Sin embargo, existe un truco sencillo que puede ahorrarte broncas: utilizar el OLEDB Provider de ADO.NET para hacerlo. Esta técnica funciona bastante bien para leer archivos CSV o archivos de texto con columnas en posiciones fijas (si son secuenciales, ya valiste cake).

Leyendo un archivo CSV

Por ejemplo, imagina que eres achichincle del Jason (el de las películas de terror) y te manda a hacer sus compras, de lo contrario terminarás con tu cabeza y cuerpo en diferentes sectores de la ciudad. El vato hace su "chopin list" en Excel y lo guarda en el siguiente archivo llamado jason.csv:

Producto,Cantidad,Precio
Sierra eléctrica,1,250
Máscara de hockey,1,15.50
Machete,5,2.70
Detergente para ropa (con quita-manchas),1,10
Delantal,2,7.25
Afilador,3,5

Entonces, como buen dotnetero, podrías leerlo con una rutina como esta:

// asumiendo que tenemos
// using System.Data.OleDb;
// using System.Data;
 
// en este connection string:
//     HDR=Yes       : indica que el primer registro contiene los encabezados 
//                     (nombres) de las columnas, no datos.
//     FMT=Delimited : indica que el los campos están delimitados por un caracter
//                     (coma por default).
string connectionString =
    @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\DirectorioDeArchivosCSV;" +
    "Extended Properties='text;HDR=Yes;FMT=Delimited'";
 
DataTable dt = new DataTable("miTabla");
using (OleDbConnection conn = new OleDbConnection(connectionString))
using (OleDbDataAdapter da = new OleDbDataAdapter("SELECT * FROM jason.csv", conn))
{
    da.Fill(dt);
}
 
// hacer algo con los datos en el DataTable

El código es bastante sencillo, es el patrón estándar para usar un DataAdapter. Lo único de especial que tiene es que utiliza el OLEDB Data Provider, y que en el connection string le especificamos el directorio donde se encuentra el archivo, así como el formato que tiene. Nota que emites un SELECT de SQL común y corriente, por lo cual podrías agregar una cláusula WHERE si así lo quisieras.

En fin, para comprobar que en realidad funcionara el código, puse un breakpoint e invoqué el DataSet Visualizer desde Visual Studio. El resultado:

Pero, ¿qué tan inteligente es el OLEDB Provider? ¿Adivinó correctamente el tipo de mis datos?

Leyendo un archivo de texto con posiciones fijas

Ahora, asume que el méndigo Jason te la puso más difícil y en lugar de darte un archivo CSV, te da un archivo de texto sencillo como este (jason.txt):

El código sería muy similar al anterior:

// asumiendo que tenemos
// using System.Data.OleDb;
// using System.Data;
 
// en este connection string:
//     HDR=Yes   : indica que el primer registro contiene los encabezados 
//                 (nombres) de las columnas, no datos.
//     FMT=Fixed : indica que el los campos están en posiciones fijas y el tamaño
//                 de cada campo se especifican con un archivo SCHEMA.INI
//                 en el mismo directorio donde está el archivo a leer.
string connectionString =
    @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\DirectorioDeArchivosTXT;" +
    "Extended Properties='text;HDR=Yes;FMT=Fixed'";
 
DataTable dt = new DataTable("miTabla");
using (OleDbConnection conn = new OleDbConnection(connectionString))
using (OleDbDataAdapter da = new OleDbDataAdapter("SELECT * FROM jason.txt", conn))
{
    da.Fill(dt);
}
 
// hacer algo con los datos en el DataTable


De hecho, lo único que cambió fue el parámetro FMT en el connectionString, el nombre del archivo y el directorio donde localizarlo. Sin embargo, para que esto funcione con archivos de posiciones fijas es necesario un paso adicional: especificar el tamaño (y tipo) de las columnas en el archivo.

Esto se hace mediante un archivo schema.ini que debe estar en el mismo directorio que el archivo que vas a leer. Consulta esta página para saber todas las opciones disponibles. En nuestro caso un archivo como el siguiente sería suficiente:

[jason.txt]
Format=FixedLength
Col1=Producto Char Width 40
Col2=Cantidad Long Width 10
Col3=Precio Double Width 10


Factorizando código

Si generalizamos el código un poco, podemos extraer una función sencilla que pueda ser reutilizada en varios de nuestros programas. Esa rutina podría ser como esta, en donde le pasas como parámetros el tipo y archivo a leer y te regresa un DataTable poblado ya con los datos:

// asumiendo que tenemos 
// using System.Data.OleDb;
// using System.Data;
// using System.IO;
 
public enum TipoDeArchivoPlano { Delimited, Fixed }
 
public static DataTable LeerArchivoPlano(
    FileInfo archivo, bool tieneEncabezado, TipoDeArchivoPlano tipoDeArchivo )
{
    if (!archivo.Exists)
        throw new FileNotFoundException(
            "No se encontró el archivo especificado");
 
    string conEncabezado = tieneEncabezado ? "YES" : "NO";
 
    string connectionString = String.Format(
        @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};" +
        "Extended Properties='text;HDR={1};FMT={2}'",
        archivo.DirectoryName, conEncabezado, tipoDeArchivo.ToString());
 
    DataTable dt = new DataTable("miTabla");
    using (OleDbConnection conn = new OleDbConnection(connectionString))
    using (OleDbDataAdapter da =
        new OleDbDataAdapter("SELECT * FROM " + archivo.Name, conn))
    {
        da.Fill(dt);
    }
 
    return dt;
}


De manera que pueda ser llamado así:

DataTable dt = LeerArchivoPlano(
    new FileInfo(@"C:\DirectorioDeArchivosCSV\jason.csv"),
    true, TipoDeArchivoPlano.Delimited);


En fin, la idea es esa.

Enjoy.


Actualización, 13 diciembre 2007:

Gracias a los comentarios de fredy, publiqué otro artículo con el código en Visual Basic 2005, para aquellos que les interese.

categorías: , , ,

48 comentarios:

  1. Anónimo dijo:

    Me parace que el articulo de Cómo leer un archivo plano es excelente, muy útil y sencillo. Gracias

  2. Abe dijo:

    Excelente tip :)

  3. Anónimo dijo:

    Los ejemplos estan excelentes...
    Ahora unicamente estoy batallando con el directorio que contiene el archivo csv, por que en mi compu es un path y cuando copio la aplicación al servidor es otro directorio, es que lo estoy usando en un webservice y no encuentro como obtener el app.path como lo hacia en vb6

  4. KaMiKaZe aka Carlos dijo:

    Si quieres obtener el path donde se ejecuta tu aplicación, en una aplicación de Windows Forms, es bien sencillo, solo tienes que consultar la propiedad Application.ExecutablePath

    Sin embargo, para una aplicación ASP.NET es un poco más "misterioso". Tienes que usar la clase Assembly. Algo así como:
    Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location)

  5. Anónimo dijo:

    Buenas me parecio excelente el programa ahora tengo un problema tengo 2 archivos planos y los deseo comparar con un where estos 2 archivos tienen distinta ubicacion el problema que se me presenta es con la cadena de conexion y no se que hacer

  6. KaMiKaZe aka Carlos dijo:

    Pues sin saber muchos de los detalles quizá esto pueda ser útil. Estoy asumiendo que ese WHERE en realidad es un JOIN (donde quieres comparar valores de un archivo contra otros). La técnica que describí era para leer un archivo a la vez. No conozco una forma en que puedas especificar en el connection string dos archivos a leer.

    Lo que se me ocurre es que leas cada archivo en un DataTable separado. De esa forma tendrías, algo así como:

    dt1 = LeerArchivoPlano(@"Archivo1.csv"...);
    dt2 = LeerArvhivoPlano(@"Archivo2.csv"...);

    Ya teniendo los datos en memoria, podrías agregar los DataTable a un DataSet y establecer una relación entre las tablas, o filtrar los datos con un DataView o usando la propiedad RowFilter, etc. Es decir ya podrías manipular tus datos por código y hacer lo que tengas que hacer.

  7. Anónimo dijo:

    Excelente Tip!!

    Yo tengo un archivo delimitado por ";". Como hago para leerlo utilizando la misma tecnicca?

  8. KaMiKaZe aka Carlos dijo:

    Para un archivo delimitado por ";" creo que puedes hacerlo utilizando un archivo schema.ini (de manera similar a como se hizo con el de posiciones fijas).

    El contenido del archivo schema.ini sería algo como esto:

    [miarchivodelimitadoporpuntoycoma.txt]
    ColNameHeader=True
    Format=Delimited(;)
    Col1=Producto Char Width 40
    Col2=Cantidad Long
    Col3=Precio Double


    No sé si poniendo FMT=Delimited(;) en el string de conexión también funciona.

  9. Hugo dijo:

    Hola, estoy haciedno una aplicacion para leer un archivo plano y subirlo a una BDD, mi problema es que cuando leo la ruta del archivo, lee la ruta del servidor, com ohago para que lea la ruta pero del cliente que utiliza la aplicacion? de antemano muchas gracias, mi codigo es este

    string fileDirectory = Path.GetDirectoryName(txtFile.PostedFile.FileName);
    string fileName = txtFile.PostedFile.FileName; // Path.GetFileName(txtFile.PostedFile.FileName);
    string strCommand = @"SELECT * FROM " + Path.GetFileName(txtFile.PostedFile.FileName);
    string properties = @"""text;FMT=Delimited""";
    string strCon = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + fileDirectory + ";Extended Properties=" + properties;

    conexionXls = new OleDbConnection(strCon);
    DataTable dt = new DataTable("Autos");

    try
    {
    conexionXls.Open();
    OleDbDataAdapter adapter = new OleDbDataAdapter(strCommand, conexionXls);

    adapter.Fill(dt);

    // se llena el datagrid solo para ver el resultado
    //Datagrid2.DataSource = dt;
    //Datagrid2.DataMember = "Autos";
    //Datagrid2.DataBind();

    conexionXls.Close();

    }
    catch(Exception err)
    {
    Response.Write("Error : " + err.ToString());
    dt = null;
    }

    return dt;

  10. Hugo dijo:

    quise utilizar como lo indicas mas arriba con Path.GetDirectoryName( Assembly.GetExecutingAssembly().Location)

    y el resultado es algo como esto

    @"c:\windows\microsoft.net\framework\v1.1.4322\temporary asp.net files\pruebaajax\e12f2105\10aa5fe7\assembly\dl2\678e2b31\8f5fe85f_63cac701"

    sabes que pueda estar pasando?

    gracias

  11. KaMiKaZe aka Carlos dijo:

    Hugo:

    Parece que lo que intentas hacer es leer un archivo que el usuario va a subir a tu sitio ¿Te estoy entendiendo bien?

    Si es ese el caso entonce puedes hacerlo mediante un control FileUpload. Tú determinas la ruta a donde se guarda ese archivo en el servidor (de donde podrás leerlo).

    Por ejemplo, puedes tener lo siguiente en tu página aspx:

    <asp:FileUpload id="miFileUpload" runat="server" />
    <asp:Button ID="botonSubir" runat="server" Text="Subir Archivo" OnClick="SubirArchivo" />

    Y luego agregar el siguiente manejador para el evento en el code-behind:

    protected void SubirArchivo(object sender, EventArgs e)
    {
    if (miFileUpload.HasFile)
    {
    miFileUpload.SaveAs(@"C:\larutaenmiserver\" + miFileUpload.FileName);
    // código para leer el contenido archivo va aquí...
    }
    }

    Asegúrate solamente que el usuario ASPNET tenga permisos de escritura sobre el directorio a donde quieres subir los archivos.

    Espero te sirva.

  12. Hugo dijo:

    que tal kamikaze:

    muchas gracias por tu ayuda, la verdad mi intencion era leer un archivo desde la ruta del cliente, pero lo que hice con el codigo que me expusiste fué, subir el archivo a mi servidor, y desde ahi leerlo, ya una ves que lo leí y lo almacene en mi base de datos, elimino el archivo para que no me genere espacio en el servidor

    muchas gracias por tu ayuda

  13. Anónimo dijo:

    Excelente! Me gustaria saber cómo utilizar este mismo programa con archivos de texto o csv grandes de aprox. 200MB?

  14. KaMiKaZe aka Carlos dijo:

    No conozco de ninguna restricción en cuanto a tamaño, más que la memoria de tu máquina. A final de cuentas lo estás metiendo en un DataTable vulgar, común y corriente.

    ¿Has tenido algún problema en específico?

  15. Anónimo dijo:

    Como logro leer un archivo .csv sin usar OleDB?

  16. Anónimo dijo:

    Excelente interpretacìón, hasta un novato podría implementarlo.
    Un saludo

  17. Anónimo dijo:

    Disculpa tengo un problema con respecto a que el oledb no me adivino correctamente los datos tengo una columna donde tengo mas numeros que alfanumericos y me puso solamente los numericos y se esta comiendo los de texto no sabes que puedo hacer para moverle al tipo de dato que recibo o a que le muevo para que me reciba los de texto

  18. KaMiKaZe aka Carlos dijo:

    Creo que la forma en que funciona el adaptador OleDB, para determinar los tipos de dato, es que intenta inferirlos de acuerdo al primer registro de datos. Así que si para una columna tienes renglones con números y luego renglones con letras creo que toma el primero para determinar el tipo de dato (en este caso números). Todos los renglones que no cumplen con los el tipo de dato son ignorados (en este caso los renglones con letras).

    Sin embargo, siempre puedes hacer "override" de los tipos de dato que infiere y especificar los correctos utilizando un archivo schema.ini, como lo muestra la sección de "Leyendo un archivo de texto con posiciones fijas".

    Espero te sirva.

  19. KaMiKaZe aka Carlos dijo:

    Respecto a cómo leer un archivo plano sin usar esta técnica... yo lo he hecho con un FileStream común y corriente, pero luego tienes que leer línea por línea y "parsearla" (dividir los valores de acuerdo a las comas), poner los valores de cada renglón en una colección o en un data table, etcétera).

    Esto parece no tener mucho chiste dado que podrías usar String.Split() para hacerlo, pero luego te metes en broncas de "¿qué pasa si los valores de una de mis columnas son strings y contienen una coma como parte del valor?" y cosas divertidas como esas.

    El adaptador OleDB ya trae lógica para lidear con esas situaciones decentemente.

    En fin, igual y en un artículo futuro me aviento cómo hacer esto. Si les interesa, háganmelo saber.

  20. Anónimo dijo:

    Buenas,

    Creo que mi problema va a ser de veriones (Frwk 2.0 VS 2005) por que utilizo la forma delimitada y el problema que tengo es que solo lee una columna. Tengo

  21. Noe Fernando dijo:

    Hola amigos yo estoy iniciando en Visiual B6, tengo un problema yo quiero leer un archivo plano, pero este esta delimitado por comas, (por eso no hay problema, a lavez los campos estan entre comillas dobles asi: "BYCO1","NOE","07/10/2007" como hacer para que los saque de la cadena y los coloque es variables diferentes

  22. Anónimo dijo:

    Me parece muy interesante este articulo.

    Quisiera saber como puedo escribir la info que poseo en un gridview y llevarlo a un archivo plano.

    mil gracias.

  23. Anónimo dijo:

    El articulo esta super.... pero ahora tengo una siguiente duda yo debo procesar muchos archivos con la misma estructura pero con nombre diferente, como le puedo hacer??

  24. KaMiKaZe aka Carlos dijo:

    Noe--

    Supongo que estás intentando leer la info en Visual Basic ".NET". La neta ya ni me acuerdo cómo se hacía en VB6.

    Una vez que tus datos ya están en el DataTable, ese objeto te va a dar acceso a una colección de rows, y cada row tendrá una columna por cada columna leída del archivo. De ahí puedes extraer los datos y ponerlos en variables distintas, si así lo quisieras. Es mucho rollo para explicar en un comentario, así que te recomiendo otro de los artículos de este sitio: ADO.NET para novatos


    Anónimo (1)--
    ¿Estás hablando de un GridView de Windows o de ASP.NET? Pregunto porque se "bindean" a fuentes distintas en WinForms y en WebForms. Por ejemplo, en WebForms, típicamente le das un SqlDataSource (usando la propiedad DataSourceID) o un DataSet/DataTable (usando las propiedades DataSource y DataMember). En el caso del SqlDataSource es un poco más difícil "exportar" los datos, ya que solo tienes acceso los comandos configurados. Lo que puedes hacer es pasar el SelectCommand a un DataReader, p.ej. y de ahí hacer una rutina que lea los datos y escriba al archivo .CSV. En el caso de un DataSet/DataTable, pues ya tienes los datos ahí, solo necesitas iterar por la colección de rows de la tabla que te interese y escribir el archivo como te guste.

    Anónimo (2)--
    Si tus archivos no necesitan un schema.ini, entonces lo único "difícil" es obtener la lista de archivos, iterar por ella y pasarle cada archivo al método LeerArchivoPlano(). Esto es bien sencillo, y se puede lograr con las propiedades de la clase Directory o DirectoryInfo de System.IO. Roberto Galindo tiene un buen post sobre esto en su blog. Ahora que si tus archivos sí requiren de un schema.ini, entonces la cosa se complica un poquitito más; lo que yo he hecho en el pasado es copiar cada archivo y renombrarlo para que tenga el nombre del archivo especificado en el schema.ini, leerlo, luego borrar el archivo, copiar el siguiente y ponerle nombre, leer ese, borrarlo, etcétera. Todo esto no es tan complicado, las clases File y FileInfo (también de System.IO) te ayudan con todo eso.

  25. piyey dijo:

    Hola,

    Exelente trabajo, estaba buscando justamente esto, pero me surge un gran problema.

    Al igual que un anónimo anterior, me da error en la carga de datos. Si lo hago con csv solamente me despliega una columna incluyendo el encabezado como datos.

    P.e.:

    NOMBRE,SALARIO,IR
    Mirna Reyes,
    Gloria Solorzano,
    etc,etc,etc

    Si lo hago con txt me aparecen las 3 columnas, supongo que porque el archivo ini está bien, pero me aparecen las celdas vacías.

    Revisé los archivos con notepad y aparentemente están bien estructurados y todo eso.

    Gracias de antemano

  26. KaMiKaZe aka Carlos dijo:

    piyey--

    Pudiera ser que se comporte así si no especificas los valores para las tres columnas (al menos en el primer renglón de datos).

    ¿Has intentado con algo así?

    NOMBRE,SALARIO,IR
    Mirna Reyes,10,loquesea
    Gloria Solorzano,0,otrovalor

  27. KaMiKaZe aka Carlos dijo:

    O (acabo de pensar en esto), si los valores para salario e IR deben ser vacíos, solo necesitarías las puras comas:

    NOMBRE,SALARIO,IR
    Mirna Reyes,,
    Gloria Solorzano,,

    Espero te sirva.

  28. Gonzalo dijo:

    Para leer en secuencia varios archivos con la misma extensión (VB.NET 2003) tienes que:
    1. Crear un manejador de la info del directorio y ubicar el directorio:
    Dim infoDir As New DirectoryInfo(directorio)
    2. Crear un manejador para los archivos en ese manejadro de directorio
    Dim infoFiles As FileInfo() = infoDir.GetFiles()

    3. Crear un manejador para la info de cada archivo
    Dim filesInfo As FileInfo

    4. Procesar cada uno de los archivos recuperando nombres, extensiones, tamaños, o lo que quieras

    For Each j In infoFiles
    sSql = "SELECT * FROM " & j.Name
    If j.Length > 0 Then
    CsvTable = New DataTable
    CsvAdapter = New OleDbDataAdapter(sSql, CxCsv)
    CsvAdapter.Fill(CsvTable)
    CsvDataset.Tables.Add(CsvTable)
    End If
    Next

  29. piyey dijo:

    Hola kamikaze, y gracias por la respuesta. Pero el problema es que el archivo csv si contiene los datos, de hecho probé con tu mismo ejemplo, y lo que yo puse por acá es lo que se muestra en el grid... ahora me aparece todos los datos pero en una sola columna... esto es:

    P.e.:

    Fila1,Fila2,Fila3
    valor1,valor2,valor3
    ovalor1,ovalor2,ovalor3
    ...

    Todo eso en una sola columna...

    Creo que voy a tener que hacerlo con alguna rutina que lea el archivo excel o algún copy en el portapapeles porque ya no se que hacer y en mi trabajo me estan ·$%&/( jejeje

    Saludos y gracias

  30. Anónimo dijo:

    hola como esta me a surgido un problema similar en mi caso es como pasar un archivo con otro tipo que no es txt sino de tipo LIS ademas quisiera saber si hay una manera de pasar automaticamente con un click los datos a mi BD y tambien Como exportar mis tablas de la BD a formato de texto plano con esta regla
    si el campo nombre es de 10 y solo e colocado el nombre salvador el resto de campos se tendria que llenar con espacios en blanco en el archivo de texto plano
    ejemplo
    campo longitud
    nom 8
    ape 10

    si nom=juan y ape=garcia al momento de exportar en el texto tedria que ser asi
    juanbbbbgarciabbbb
    donde b es espacio en blanco, necesito si ayuda por favor

  31. KaMiKaZe aka Carlos dijo:

    Me temo que no estoy familiarizado con los archivos .LIS, así que no creo poderte ayudar con eso. Pero si son basado en texto y son delimitados por algún caracter o por posiciones como describe este artículo, no rebes tener problemas.

    Ahora, eso de subir tus datos a tu base de datos no es automático, pero no requirere mucho esfuerzo. La rutina que se mostró en este artículo pone tus datos en un DataTable, el cual podrías utilizar con un DataSet y un DataAdapter para ejecutar las instrucciones necesarias de inserción en tu base de datos. Échale un ojo a este otro artículo para más detalles: ADO.NET para novatos.

    Ahora, para escribir los datos de tu BD a un archivo, típicamente sería a la inversa, es decir, llenarías un DataTable o DataSet a partir de tu base de datos y de ahí es sencillo hacer un ciclo que los vaya leyendo registro por registro y escribiendo los valores a un archivo. Típicamente utilizarías un FileStream o StreamWriter para hacerlo. Roberto Galindo Venzor tiene una descripción breve en su blog .

    Para agregar los valores en blanco cuando estás escribiendo los valores al archivo, puedes utilizar el método PadRight() de la clase string. P.ej. suponiendo que tienes una variable miVar de tipo string, podrías hacer myVar.PadRight(10," ") que indica "regresa el valor de mi variable con un ancho máximo de 10 caracteres y rellénalo con espacios hacia la derecha".

    Espero te ayude.

  32. fredy dijo:

    Hola, he probado el código este para llenar el DataTable y me lanza el siguiente error:

    "El motor de base de datos Microsoft Jet no pudo encontrar el objeto 'test3_07.11.06_14.21.00.csv'. Asegúrese de que el objeto existe, y que ha escrito el nombre y la ruta de acceso al objeto correctamente."

    Lo hago desde Visual Basic en ASP.NET 2, he comprobado la ruta y es correcta. Te pongo el código que uso:
    Public Shared Function LeerArchivoPlano(ByVal archivo As FileInfo, _
    ByVal tieneEncabezado As Boolean, _
    ByVal tipoDeArchivo As TipoDeArchivoPlano) As DataTable

    If (Not archivo.Exists) Then
    Throw New FileNotFoundException("No se encontró el archivo especificado")
    End If

    Dim conEncabezado As String = IIf(tieneEncabezado, "YES", "NO")


    Dim connectionString As String = String.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};" + "Extended Properties='text;HDR={1};FMT={2}'", archivo.DirectoryName, conEncabezado, tipoDeArchivo.ToString())

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

    Using conn As OleDbConnection = New OleDbConnection(connectionString)
    'Using da As OleDbDataAdapter = New OleDbDataAdapter("SELECT * FROM " + Path.GetFileNameWithoutExtension(archivo.FullName), conn)
    Using da As OleDbDataAdapter = New OleDbDataAdapter("SELECT * FROM " + archivo.Name, conn)
    da.Fill(dt)
    End Using
    End Using

    Return dt
    End Function

  33. KaMiKaZe aka Carlos dijo:

    Qué tal Fredy,

    Tu código se ve bien (muy buena "traducción", por cierto).

    ¿Ya verificaste que el usuario ASPNET tenga permisos de seguridad sobre el directorio donde están los archivos que quieres leer? Normalmente si el directorio es distinto a donde corre la aplicación ASP.NET, por seguridad, no tendrá permisos ni de leer los archivos. Chécalo y me dices si no para ver qué más pudiera ser.

  34. fredy dijo:

    Hola Carlos, las pruebas las he hecho en local. La carpeta donde estan los .csv estan dentro (en una subcarpeta) de la aplicación. No tendría que tener problemas de permiso ya que es en local y hago otro proceso (en el mismo directorio) que descomprime un archivo y copia los archivos en otra carpeta.
    No se si puede afectar que aunque trabaje en local, tenga la conexion de SqlServer apuntando al servidor remoto, no tiene porque afectar no?
    Gracias.

  35. KaMiKaZe aka Carlos dijo:

    Me hiciste volver a hacer el ejemplo en Visual Basic, canijo. ;-)

    Pues creo que ya establecimos que no es bronca de código. Si no le estuvieras pasando bien el archivo a la rutina te estaría arrojando el error de "no se encontró el archivo especificado", no el error acerca del motor de datos jet (creo yo).

    Así que es tiempo de revisar la configuración de tu máquina. Igual y te falta algún driver o algo así.

    Lo que hay que hacer es ir a Control Panel > Administrative Tools > Data Sources (ODBC), pestaña de drivers (aquí hay una imagen de cómo aparece en mi máquina).

    Yo tengo listado varios drivers de texto:
    Driver da Microsoft para arquivos texto (*.txt, *.csv) v4.x...
    Microsoft Access Text Driver (*.txt, *.csv) v12.x....
    y Microsoft Text Driver (*.txt, *.csv) v4.

    Estoy casi seguro que el de Access es inconsecuente. Y el que está en portugués no sé ni que rollo, pero igual y te sirve para comparar con tu máquina.

    Espero te sirva.

  36. KaMiKaZe aka Carlos dijo:

    Oh, y no, no creo que tu conexión a SQL Server tenga nada que ver tampoco.

  37. fredy dijo:

    Hola, resulta al final que el problema era el nombre del archivo, ya que contenia puntos en el nombre, una vez sin puntos ya no da error.
    Ahora el problema es que me devuelve una sola columna y con los primeros datos solo (los de tipo int), a partir del primer campo de tipo string ya no los devuelve.
    Estoy usando asp .net 2.
    Que láaaaaaaaaaaastima!! con lo bonito que parecía llegar a ser.
    Sabes como arreglar esto?
    Gracias.

  38. KaMiKaZe aka Carlos dijo:

    Órale, esa no me la sabía (lo de los puntos en el nombre del archivo). Normalmente, por seguridad (y muchas veces para facilitar las cosas) renombro los archivos que recibo de los usuarios, así que nunca había tenido ese problema.

    Las broncas de que no te traiga correctamente los datos (especialmente cuando contienen muchas columnas) las he visto porque las columnas de texto contienen comas como parte de los datos. Lo mismo se hacen pelotas el Excel o Access (ambos usan el mismo driver de OleDb que el ejemplo del artículo, detrás de las cámaras). En este caso lo que debes asegurarte es que las columnas de texto estén delimitado por comillas.

    Agregar un schema.ini también ayuda mucho a asegurar que se lea el tipo de dato correcto por cada columna.

    Espero te ayude

  39. Anónimo dijo:

    Estuve trabajando el ejercicio tal cual el unico problema es que los datos del archivo plano o csv quedan impresos solo en una columna.
    Si alguien sabe el porque le agradesco su retroalimentacion.

  40. Diego Bernal dijo:

    El ejercicio esta bien solo que me muestra todos los datos del archivo plano en una sola columna, asi el archivo tenga varias columnas, si alguien sabe por que .....

  41. Carlos Rubalcava dijo:

    Para los que están teniendo el problema de que sus datos aparecen en una sola columna:

    Parece que Diego ya le atinó con algunas cosas para resolverlo. Yo le hice el comentario que si tienes campos tipo fecha a veces son latosos (porque en "español" y en "inglés" se leen distinto, p.ej. 05/06/2008 es 6 de mayo en EE.UU. pero es 5 de junio en países hispanos). En fin, les copio aquí su mensaje:

    "Carlos Cordial Saludo. Escribo para decir que ya pude llenar el dataset completamente con columnas y todo, gracias a tus consejos me puse a indagar profundamente en archivos txt, csv, y schemas.ini. En síntesis mi problema era con el idioma de mi sistema operativo , las comillas me sirvieron en muchos campos pero implicaba hacer un trabajo extra pero el ( ; ) sirvió perfectamente .Me tope con un excelente articulo el cual me llevo por otros caminos en cuanto al trabajar con archivos de este tipo y me encontré con muchas características tales como se ven en este articulo:

    http://www.mvp-access.es/softjaen/articulos/texto/jet_text_isam.htm#Index21

    De pronto hace una simbiosis perfecta con tu articulo y puede ayudar a mucha gente que tiene mi problema de pronto si quieres hacer un síntesis y complementar tu gran articulo que me sirvió bastante para realizar mi aplicación.

    De Nuevo Gracias por la atención y la ayuda .

    Diego Fernando Bernal B.
    Desde Colombia."


    PD. También pudiera serles útil la página de referencia sobre schema.ini en MSDN (parece que cambió desde que escribí el artículo)

    Espero les ayude

  42. crisangel dijo:

    cel ejemplo es muy bueno pero como le hago para llamarlo en un aplicacion de escritorio, llenar un grid con un boton alguin sabe porfavor gracias

  43. Carlos Rubalcava dijo:

    Como la rutina regresa un DataTable, debe ser sencillo. Creo que solo necesitas ponerlo como la propiedad DataSource de tu grid.

  44. Anónimo dijo:

    Y si deseas levantar filas filtradas? como harias?
    Ejemplo.. "SELECT * FROM " & archivo.name & " WHERE condicion 1"

    Se podria realizar algo de ese estilo?
    Gracias.

  45. Carlos Rubalcava dijo:

    Debes de poder hacerlo. Como lo menciona el artículo, es una sentencia de SQL común y corriente. No estoy seguro de GROUP BY's y cosas así pero si es un simple filtro WHERE columna=valor debes poder hacerlo. Quizá necesites utilizar un archivo schema.ini para que sepas de antemano el nombre de las columnas, pero aparte de eso no veo ningún impedimento.

  46. Anónimo dijo:

    Saludos, quiero colaborar con una solución que me funcionó para la separación de los datos y evitar que todos se carguen en la misma columna, es algo muy simple pero sólo de esta forma logré solucionarlo, el truco está en declarar las columnas antes que el delimitador, siguiendo el ejemplo posteado sería así por ejemplo



    [jason.txt]

    Col1=Producto Char Width 40
    Col2=Cantidad Long Width 10
    Col3=Precio Double Width 10
    Format=Delimited(;)

    Con esto tna sencillo logré separar los datos, si a alguno ha probado las soluciones anteriores y no le ha funcionado como a mi, podría probar este pequeño cambio a ver si mejora la suerte.

  47. Anónimo dijo:

    Tu articulo me parecio excelente. Fue una gran ayudaaa
    Felicitaciones desde Lima, Peru

  48. Anónimo dijo:

    Agradecera que me pudieran ayudar con lo siguiente:
    Tengo que hacer una aplicacion que me lea un archivo de texto delimitado(",") y subir esos datos a un datagrid; pero como son bastantes datos dentro del archivo de texto y estan por parrafos con informacion repetida quisiera seleccionar de cada parrafo algunos datos para que el datagrid los visualice en la primera fila y que del siguiente parrafo yo pueda seleccionar ls mismos datos y visualizarlos en la 2da fila y asi sucesivamente hasta llegar al final del archivo.

    Gracias