Blog alskare

.Net y lo que surja

Exportar datos a formato dbf

Posted by alskare en 22/07/2009

Hoy me encuentro con un problema. Una aplicación que tengo corriendo bajo Sql Server 2005 tiene que exportar datos a un formato .DBF con la finalidad de poder traspasar los datos a un programa de contabilidad. Esta es una de aquellas cosas que , por lo menos para mí, son desagradables de crear, puesto que, comúnmente están mal valoradas. En la interfaz del cliente tan sólo se ve un formulario en el que se solicita un fichero o carpeta de salida y un botón que suele decir Exportar. El resto de horas de trabajo, sencillamente, no se aprecian.

Como siempre, empezando con una busca genérica, poco se ve del tema, así que mucho me temo que va a ser uno de aquellos días de prueba-error hasta que consiga obtener un apaño que hago algo similar a lo que se quiere inicialmente.

Por algún sitio habrá que empezar, así que inicio escribiendo la firma de los métodos estáticos que quiero crear:

 

using System;
using System.Data.Common;


namespace jnSoftware.Traspasos
{
    public static class Databases
    {

        /// <summary>
        /// Exporta una expresión Sql a formato Dbf (dBase III)
        /// </summary>
        /// <param name="cadenaConexion">Cadena de conexión con el servidor SQL</param>
        /// <param name="sql">Secuencia Select SQL para exportar</param>
        /// <param name="ficheroSalida">Nombre completo del fichero que se creará</param>
        /// <returns>true si la exportación es correcta</returns>
        public static bool Sql2Dbf(string cadenaConexion, string sql, string ficheroSalida)
        {
            DbProviderFactory factoria = DbProviderFactories.GetFactory("System.Data.SqlClient");
            return Databases.Db2Dbf(factoria, cadenaConexion, sql, ficheroSalida);
        }


        /// <summary>
        /// Exporta una expresión Sql a formato Dbf (dBase III)
        /// </summary>
        /// <param name="factoria">Factoría de System.Data.Common en el que se encuentra el formato de origen</param>
        /// <param name="cadenaConexion">Cadena de conexión con la base de datos de origen</param>
        /// <param name="sql">Secuencia Select SQL para exportar</param>
        /// <param name="ficheroSalida">Nombre completo del fichero que se creará</param>
        /// <returns>true si la exportación es correcta</returns>
        public static bool Db2Dbf(DbProviderFactory factoria, string cadenaConexion, string sql, string ficheroSalida)
        {
            // TODO 
            return false;
        }

    }
}

 

Es genial esto de Internet: una búsqueda un poco más detallada, me da una gran idea, gracias a uno de los “grandes de las news”, el Maestro Enrique Martinez ( Exportar de SQL a DBF ), así que intento hacer una adaptación que me permita tener un módulo más o menos útil.

Bueno, debo reconocer que no he sido capaz de hacer un volcado directo de una secuencia desde SQL a DBF. Me daba todo tipo de errores, sobre todo, por las cadenas de conexión, es decir, he podido hacer conexiones tanto a SQL como a DBF, pero no me he sido capaz de montar una buena consulta tal como comentaba Enrique en el foro. Por tanto, opto por otra opción.

Vueltas por aquí y por allí me hacen pensar en si se podría crear una tabla con la instrucción CREATE TABLE de Sql y… Bingo!. Me crea el fichero correctamente e incluso hasta me deja insertar datos. Al fin y al cabo, como se trata de crear un fichero “temporal” cuyo objetivo será el de traspasar datos, tampoco me voy a poner puntilloso con el tema de las restricciones de tablas, así que, después de crear las pruebas que incluyo a continuación, ya empiezo a pensar en una lectura de la estructura de la tabla de SQL Server que sirva también para la creación de la tabla de salida. ¡Veremos qué hacemos!. De momento, dejo las pruebas de creación de la base:

private static void CrearTabla()
{
    string ruta = @"C:\";
    string strConnDbase = @"Provider = Microsoft.Jet.OLEDB.4.0" +
                           ";Data Source = " + ruta +
                           ";Extended Properties = dBASE IV" +
                           ";User ID=Admin;Password=;";

    // Se borra el fichero en caso de que exista
    File.Delete(ruta + "\\salida.DBF");

    using (OleDbConnection cn = new OleDbConnection(strConnDbase))
    {
        // Pruebo a crear una tabla directamente
        string sql = "CREATE TABLE salida ( Campo1 Varchar(10), Campo2 Varchar(5) )";
        using (OleDbCommand cmd = new OleDbCommand(sql, cn))
        {
            cn.Open();
            cmd.ExecuteNonQuery();
            cn.Close();
        }

        // Realizo un par de pruebas de inserción de datos en la tabla.
        string sqlCommand = "Insert into salida(Campo1,Campo2) VALUES(@campo1,campo2);";
        OleDbCommand cmdInsertar = new OleDbCommand(sqlCommand, cn);
        cmdInsertar.Parameters.Add("@campo1", OleDbType.VarChar, 10);
        cmdInsertar.Parameters.Add("@campo2", OleDbType.VarChar,5);

        cn.Open();
        for (int i = 1; i <= 10; i++)
        {
            cmdInsertar.Parameters["@campo1"].Value = "h" + i;
            cmdInsertar.Parameters["@campo2"].Value = "i" + i;
            cmdInsertar.ExecuteNonQuery();
        }
        cn.Close();

    }
}

 

Resultado final.

Bueno, al final ha salido una versión que, de momento, hace lo que quería. Para la creación he seguido la el esquema que me había propuesto:

  1. Método estático que permite acceder desde otros proyectos. De hecho, esta parte casi la tenía creada con anterioridad
  2. Clase DatabaseToDbf, que se encarga de realizar todos los pasos:
    1. Lectura de la estructura en la base de datos de origen. Por comodidad he creado una clase auxiliar Campo, que permite trabajar de manera más cómoda con los campos de la “tabla” de origen.
    2. Creación de la base de dBase. Aunque inicialmente tenía la intención de hacer este método también con el namespace System.Data.Common, he desistido por comodidad. Si un día de estos tuviese tiempo, igual lo intento. De esta manera, con algún arreglo, podría ser un método que, con suerte, permite hacer un traslado de datos con un abanico más amplio de posibilidades.
    3. Recorrido por los datos de origen, realizando una inserción en la tabla dbf de destino.

 

Clase: Database

using System.Data.Common;

/*
 * Databases.cs
 * Servicios de traspaso de información entre bases de datos
 * 
 * alskare, Jul/09
 */

namespace jnSoftware.Traspasos
{
    public static class Databases
    {

        /// <summary>
        /// Exporta una expresión Sql a formato Dbf (dBase III)
        /// </summary>
        /// <param name="cadenaConexion">Cadena de conexión con el servidor SQL</param>
        /// <param name="sql">Secuencia Select SQL para exportar</param>
        /// <param name="ficheroSalida">Nombre completo del fichero que se creará</param>
        /// <returns>true si la exportación es correcta</returns>
        public static bool Sql2Dbf(string cadenaConexion, string sql, string ficheroSalida)
        {
            DbProviderFactory factoria = DbProviderFactories.GetFactory("System.Data.SqlClient");
            return Databases.Db2Dbf(factoria, cadenaConexion, sql, ficheroSalida);
        }


        /// <summary>
        /// Exporta una expresión Sql a formato Dbf (dBase III)
        /// </summary>
        /// <param name="factoria">Factoría de System.Data.Common en el que se encuentra el formato de origen</param>
        /// <param name="cadenaConexion">Cadena de conexión con la base de datos de origen</param>
        /// <param name="sql">Secuencia Select SQL para exportar</param>
        /// <param name="ficheroSalida">Nombre completo del fichero que se creará</param>
        /// <returns>true si la exportación es correcta</returns>
        public static bool Db2Dbf(DbProviderFactory factoria, string cadenaConexion, string sql, string ficheroSalida)
        {
            bool retval;
            DatabaseToDbf export = new DatabaseToDbf(factoria, cadenaConexion, sql, ficheroSalida);
            retval = export.Exporta();
            return retval;
        }


    }
}

Clase: DatabaseToDbf 

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.OleDb;
using System.IO;
using System.Text.RegularExpressions;

/*
 * DatabaseToDbf.cs
 * Servicios que permiten el traspaso de información de una base de datos a un fichero .dbf
 * 
 * alskare, Jul/09
 */


namespace jnSoftware.Traspasos
{

    /// <summary>
    /// Servicios que nos permiten realizar un traspaso de una base de datos 
    /// a un formato dbf
    /// </summary>
    internal class DatabaseToDbf
    {

        private DbProviderFactory factoria;
        private string cadenaConexion;
        private string sql;
        private string pathSalida;
        private string ficheroSalida;
        private string nombreTabla;

        private List<string> campos = new List<string>();
        private List<string> parametros = new List<string>();
        private List<string> nombreCampos = new List<string>();

        private string strConnDbf;



        /// <summary>
        /// Constructor de la clase
        /// </summary>
        /// <param name="factoria">Factoría del origen de los datos</param>
        /// <param name="cadenaConexion">Cadena de conexión a la base de datos de origen</param>
        /// <param name="sql">Instrucción Select SQL para exportar a dbf</param>
        /// <param name="ficheroSalida">Nombre completo del fichero de salida</param>
        public DatabaseToDbf(DbProviderFactory factoria, string cadenaConexion, string sql, string ficheroSalida)
        {
            this.factoria = factoria;
            this.cadenaConexion = cadenaConexion;
            this.sql = sql;

            // En base al fichero, se desglosa la información para usarla más tarde.
            FileInfo f = new FileInfo(ficheroSalida);
            pathSalida = f.DirectoryName;
            this.ficheroSalida = f.FullName;
            nombreTabla = new Regex(f.Extension + "$").Replace(f.Name, "");

            // Creación de la cadena de conexión al fichero .dbf
            strConnDbf = @"Provider = Microsoft.Jet.OLEDB.4.0" +
                                   ";Data Source = " + pathSalida +
                                   ";Extended Properties = dBASE IV" +
                                   ";User ID=Admin;Password=;";
        }





        /// <summary>
        /// Realiza la exportación de la base de datos de origen a un fichero con formato .dbf.
        /// </summary>
        /// <returns>True si el traspaso ha sido correcto</returns>
        public bool Exporta()
        {
            bool retVal = false;
            try
            {
                LeeEstructura();
                CreaTabla();
                InsertaDatos();

                // Se ha llegado hasta aquí, se supone que está todo bien
                retVal = true;
            }
            catch
            {
                retVal = false;
            }
            return retVal;
        }




        /// <summary>
        /// Lectura de la estructura de la tabla de origen
        /// </summary>
        private void LeeEstructura()
        {
            using (DbConnection cn = factoria.CreateConnection())
            {
                cn.ConnectionString = cadenaConexion;
                using (DbCommand cmd = cn.CreateCommand())
                {
                    cmd.Connection = cn;
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = sql;

                    DbDataAdapter da = factoria.CreateDataAdapter();
                    da.SelectCommand = cmd;

                    DataTable dt = new DataTable();
                    da.FillSchema(dt, SchemaType.Mapped);

                    foreach (DataColumn columna in dt.Columns)
                    {
                        Campo c = new Campo(columna);
                        // Se crea la lista de parámetros
                        campos.Add(c.Tabla());
                        parametros.Add(c.Parametro());
                        nombreCampos.Add(c.Nombre());

                        c = null;
                    }
                }
            }
        }





        /// <summary>
        /// Creación del fichero DBF.
        /// </summary>
        private void CreaTabla()
        {
            // Se borra el fichero en el caso de que exista
            File.Delete(ficheroSalida);

            using (OleDbConnection cn = new OleDbConnection(strConnDbf))
            {
                using (OleDbCommand cmd = new OleDbCommand())
                {
                    cmd.Connection = cn;
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = GetSql_Create();

                    cn.Open();
                    cmd.ExecuteNonQuery();
                    cn.Close();
                }
            }
        }





        /// <summary>
        /// Realiza el traspaso de datos de la base de datos de origen al fichero dbf.
        /// </summary>
        private void InsertaDatos()
        {
            // Preparación del comando de inserción
            OleDbConnection cnDestino = new OleDbConnection(strConnDbf);

            OleDbCommand cmdInserta = new OleDbCommand();
            cmdInserta.CommandType = CommandType.Text;
            cmdInserta.CommandText = GetSql_Insert();
            cmdInserta.Connection = cnDestino;

            // Parámetros 
            for (int i = 0; i < parametros.Count; i++)
            {
                // Añado los parámetros con un valor nulo. Luego se cambiará. 
                cmdInserta.Parameters.Add(new OleDbParameter(parametros[i], DBNull.Value));
            }

            // Se hace un recorrido por los datos de origen
            using (DbConnection cnOrigen = factoria.CreateConnection())
            {
                cnOrigen.ConnectionString = cadenaConexion;
                using (DbCommand cmd = cnOrigen.CreateCommand())
                {
                    cmd.Connection = cnOrigen;
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = sql;

                    cnOrigen.Open();
                    DbDataReader dr = cmd.ExecuteReader();
                    cnDestino.Open();

                    while (dr.Read())
                    {
                        for (int i = 0; i < nombreCampos.Count; i++)
                        {
                            // Se establece el valor del parámetro
                            cmdInserta.Parameters[i].Value = dr[i];
                        }

                        // Ejecución de la inserción en dbf
                        cmdInserta.ExecuteNonQuery();
                    }
                    dr.Close();
                    cnDestino.Close();
                    cnOrigen.Close();
                }
            }
        }





        /// <summary>
        /// Devuelve la instrucción SQL que permitirá la inserción de los datos
        /// </summary>
        private string GetSql_Insert()
        {
            return "INSERT INTO " + nombreTabla +
                    string.Concat("(", string.Join(",", nombreCampos.ToArray()), ")")
                    + " VALUES " + string.Concat("(", string.Join(",", parametros.ToArray()), ")")
                    ;
        }



        /// <summary>
        /// Obtiene la instrucción SQL que permitirá la creación de la tabla
        /// </summary>
        private string GetSql_Create()
        {
            return "CREATE TABLE " + nombreTabla +
                string.Concat("(", string.Join(", ", campos.ToArray()), ")");

        }


    }
}

 

Clase auxiliar: Campo

using System;
using System.Data;

namespace jnSoftware.Traspasos
{



    /// <summary>
    /// Clase que representa un campo de la tabla de origen.
    /// </summary>
    /// <remarks>
    /// Como la estructura de la tabla de destino tendrá la misma estructura que la sentencia
    /// de entrada, es importante obtener todos los datos posibles de dicha estructura.
    /// </remarks>
    internal class Campo
    {

        /// <summary>
        /// Tipos de datos que puede tener la tabla de origen. 
        /// </summary>
        /// <remarks>Como que la lectura de la base de datos de origen se hace mediante clases genéricas
        /// de acceso a datos, pueden surgir tipos no conocidos aquí</remarks>
        private enum TiposCampoEnum { Varchar, Int, Double, Bit, DateTime }

        private string nombre;
        private TiposCampoEnum tipo; 
        private int tamano;


        /// <summary>
        /// Constructor. 
        /// </summary>
        public Campo(System.Data.DataColumn columna)
        {
            
            nombre = columna.ColumnName ;
            tipo = TipoCampo(columna) ;
            tamano = columna.MaxLength;
            // Se omiten intencionadamente los campos Memo
            if (tamano > 254)
                tamano = 254;
        }



        /// <summary>
        /// Adaptación del tipo de campo de origen a un formato simple dbf
        /// </summary>
        /// <remarks>Es posible que si se usan otras bases de datos haya que modificar este
        /// procedimiento</remarks>
        private TiposCampoEnum TipoCampo(DataColumn columna)
        {
            TiposCampoEnum s = TiposCampoEnum.Varchar;

            switch (columna.DataType.FullName)
            {
                case  "System.String" :
                    s = TiposCampoEnum.Varchar;
                    break;
            
                case "System.Int":
                case "System.Int16":
                case "System.Int32":
                case "System.Int64":
                    s = TiposCampoEnum.Int;
                    break;

                case "System.Double":
                case "System.Decimal":
                    s = TiposCampoEnum.Double;
                    break;


                case "System.Boolean":
                    s = TiposCampoEnum.Bit;
                    break;

                case "System.DateTime":
                    s = TiposCampoEnum.DateTime;
                    break;

                default :
                    throw new ArgumentOutOfRangeException("El tipo " + columna.DataType.FullName + 
                        " no está contemplado en el procedimiento Campo.TipoCampo");
            }
            return s;
        }



        /// <summary>
        /// Obtiene el campo en formato de parámetro
        /// </summary>
        /// <returns></returns>
        public string Parametro()
        {
            return  "@" + nombre; 
        }


        /// <summary>
        /// Obtiene el nombre del campo
        /// </summary>
        /// <returns></returns>
        public string Nombre()
        {
            return nombre;
        }


        /// <summary>
        /// Obtiene la estructura necesaria que se empleará en la creación de la tabla.
        /// </summary>
        /// <returns></returns>
        public string Tabla()
        {
            return "[" + nombre + "]" + " " + tipo + (tipo != TiposCampoEnum.Varchar ? "" : "(" + tamano + ")");
        }

    }
}

 

En las búsquedas que anduve haciendo sólo vi aplicaciones similares de pago, así que, con unos pocos cambios, es probable que tenga su utilidad.

3 comentarios to “Exportar datos a formato dbf”

  1. alskare said

    Mira, te paso un ejemplo de cómo traspasar una base de datos de Access a formato .DBF. Como verás, sólo se trata de copiar las clases del post y ejecutar, desde un ejecutable las siguientes instrucciones:


    class Program
    {
    public static void Main()
    {
    string strConnOrigen = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\samples\dbTest.mdb";
    string fileOut = @"c:\samples\salida.dbf";
    System.Data.Common.DbProviderFactory factoria = System.Data.Common.DbProviderFactories.GetFactory("System.Data.OleDb");

    Databases.Db2Dbf(factoria, strConnOrigen, "select * from Empresas", fileOut);
    }
    }

  2. Ramiro Flores said

    Soy nuevo en esto de C#, como se usa esta class, puedes poner un ejemplo de como usarlo?

    gracias de antemano

  3. kevedin said

    Macho, me has salvado la vida.
    Funciona de puta madre.

    Muchas gracias!!

Sorry, the comment form is closed at this time.

 
A %d blogueros les gusta esto: