sexta-feira, 17 de abril de 2015

Guardar Imagens numa base dados do Access

Uma discussão que nunca terá fim diz respeito ao melhor modo de guardar imagens num servidor, dentro da base de dados ou somente em pastas utilizando o sistema de ficheiros?

Em minha opinião, como tudo na vida, depende da situação. Mas adiante, neste post vou mostrar como se podem guardar imagens dentro de uma base de dados do Access utilizando C#.

Começamos por criar um projeto novo no VS 2013.





De seguida vamos criar uma classe para gerir a base de dados.


Nesta classe precisamos de adicionar uma referência para uma DLL que permite criar o ficheiro da base de dados do Access.





No gestor de referências pesquisas na secção COM.

De volta à classe adicionamos um namespace: using ADOX;


 Agora adicionamos três propriedades à nossa classe: o caminho para a base de dados, uma string de ligação e um objeto de ligação à base de dados.

    class BaseDados
    {
        string caminhoBD;
        string strLigacao;
        OleDbConnection ligacaoBD;
    }

Para o objeto de ligação precisamos de outro namespace: using System.Data.OleDb;

 No construtor da classe vamos definir a string de ligação, o caminho para o ficheiro da base de dados e executar as funções que vão criar a base de dados e a tabela que vai conter as imagens.

        //construtor
        public BaseDados()
        {
            caminhoBD = Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData) + @"\BD_Access";
            if (Directory.Exists(caminhoBD) == false)
                Directory.CreateDirectory(caminhoBD);

            string nomeBD = @"\myAccessFile.accdb";
            caminhoBD += nomeBD;
            Console.WriteLine(caminhoBD);
            strLigacao = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + caminhoBD + ";";
            strLigacao += "Jet OLEDB:Database Password='12345';";  
            createDB();
            openDB();
            createTables();
        }
Na string de ligação foi definida a palavra passe de acesso à base de dados.

O próximo passo é criar essas funções:

        private void createDB()
        {
            if(File.Exists(caminhoBD)==false)
            {
                Catalog cat = new Catalog();
                cat.Create(strLigacao);
            }
        }

        private void openDB()
        {
            try
            {
                ligacaoBD = new OleDbConnection(strLigacao);
                ligacaoBD.Open();
            }
            catch (Exception erro)
            {
                Console.WriteLine(erro.Message);
            }
        }

        private void createTables()
        {
            string strSQL = "CREATE TABLE Images(";
            strSQL += "_id COUNTER,";
            strSQL += "_name VARCHAR(200),";
            strSQL += "_image OLEOBJECT,";
            strSQL += "PRIMARY KEY(_id));";
            OleDbCommand comando = new OleDbCommand(strSQL, ligacaoBD);
            try
            {
                comando.ExecuteNonQuery();
            }
            catch (Exception erro)
            {
                Console.WriteLine(erro.Message);
            }
            comando.Dispose();
            comando = null;
        }
Deve-se ter algum cuidado com o nome dos campos pois o Access não aceita determinadas palavras por serem reservadas.

Para fechar a base de dados vamos criar um destrutor para a classe
        //destrutor
        ~BaseDados()
        {
            closeDB();
        }

        private void closeDB()
        {
            try
            {
                ligacaoBD.Close();
                ligacaoBD.Dispose();
                ligacaoBD = null;
            }
            catch (Exception erro)
            {
                Console.WriteLine(erro.Message);
            }
        }

Agora necessitamos duas funções. Uma para ler um ficheiro e devolver o seu conteúdo num vetor de bytes, que posteriormente será inserido no campo do tipo OLEOBJECT, e outra função que recebe um vetor de bytes e cria um ficheiro com o seu conteúdo.
Assim vamos criar uma Helper classe para conter essas duas funções do tipo static para que não seja necessário criar um objeto antes de as utilizar.

    class Helper
    {
        static public byte[] ImagemParaVetor(string imagem)
        {
            FileStream fs = new FileStream(imagem, FileMode.Open, FileAccess.Read);
            byte[] dados = new byte[fs.Length];
            fs.Read(dados, 0, (int)fs.Length);
            fs.Close();
            fs.Dispose();
            return dados;
        }

        static public void VetorParaImagem(byte[] imagem, string nome)
        {
            FileStream fs = new FileStream(nome, FileMode.Create, FileAccess.Write);
            fs.Write(imagem, 0, imagem.GetUpperBound(0));
            fs.Close();
            fs.Dispose();
        }
    }

Para inserir uma imagem na base de dados primeiro temos de a escolher, assim construimos o seguinte formulário.


O primeiro botão vai permitir escolher a imagem.

         private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog ficheiro = new OpenFileDialog();

            ficheiro.Filter = "Imagens|*.JPG;*.PNG";
            if (ficheiro.ShowDialog() == DialogResult.Cancel) return;
            string nome = ficheiro.FileName;

            pictureBox1.Image = Image.FromFile(nome);
            lbFile.Text = nome;
        }

Para o segundo botão necessitamos de criar uma função na classe que recebe a imagem a inserir e executa o SQL necessário.

         private void button2_Click(object sender, EventArgs e)
        {
            byte[] image = Helper.ImagemParaVetor(lbFile.Text);
            bd.insertImage(textBox1.Text, image);
        }

Neste segundo botão fazemos uso da função definida anteriormente, passando-lhe o nome do ficheiro e recebendo o vetor de bytes com a imagem.

A função na classe fica assim:

public bool insertImage(string name, byte[] foto)
        {
            string strSQL = "INSERT INTO Images (_name,_image) VALUES (?,?);";
            OleDbCommand comando = null;
            try
            {
                comando = new OleDbCommand(strSQL, ligacaoBD);
                ////////////////////////////////preencher os parâmetros
                comando.Parameters.AddWithValue("?", name);
                comando.Parameters.AddWithValue("?", foto);
                ////////////////////////////////executar o comando
                comando.ExecuteNonQuery();
            }
            catch (Exception erro)
            {
                Console.WriteLine(erro.Message);
                comando.Dispose();
                comando = null;
                return false;
            }
            comando.Dispose();
            comando = null;
            return true;
        }

Agora vamos criar um segundo formulário para listar todas as imagens existentes na base de dados.

O formulário vai conter um painel para fazer o scroll vertical pelas imagens que são adicionadas como pictureboxes ao painel.

 public partial class Form2 : Form
    {
        Form1 f = Application.OpenForms[0] as Form1;
        BaseDados bd;
        Panel panel1 = new Panel();

        public Form2()
        {

            InitializeComponent();
            bd = f.bd;
            panel1.Dock = DockStyle.Fill;
            panel1.AutoScroll = true;
            this.Controls.Add(panel1);
        }

        private void Form2_Load(object sender, EventArgs e)
        {
            DataTable dados = bd.listImages();
            int count=0;
            foreach(DataRow row in dados.Rows)
            {
                PictureBox pb = new PictureBox();
                pb.Name = row[0].ToString();
                pb.Parent = this.panel1;
                pb.Size = new Size(320, 240);
                pb.SizeMode = PictureBoxSizeMode.StretchImage;
                pb.Location = new Point(0, count * 240);

                //criar um ficheiro com a imagem
                byte[] image=(byte[])row[2];
                Helper.VetorParaImagem(image, "temp"+count+".jpg");
                pb.Image = Image.FromFile("temp"+count+".jpg");

                count++;
            }
        }

        private void Form2_FormClosing(object sender, FormClosingEventArgs e)
        {
            foreach(Control ctrl in this.Controls)
            {
                ctrl.Dispose();
            }
            GC.Collect();
        }
    }


Primeiro é criada uma referência para o formulário principal que já contém um objeto do tipo BaseDados, que vamos utilizar aqui para aceder à base de dados.

Na base de dados adicionamos uma função para devolver todos os registos.

        public DataTable listImages()
        {
            DataTable dados = new DataTable();
            string strSQL = "SELECT * FROM Images;";
            OleDbDataReader registos;
            OleDbCommand comando = new OleDbCommand(strSQL, ligacaoBD);

            registos = comando.ExecuteReader();

            dados.Load(registos);

            return dados;
        }

Quando o formulário é fechado forçamos a libertação de todos os controlos para evitar erros no acesso aos ficheiros que são criados com as imagens.

O resultado final é o seguinte.

O projeto.

1 comentário: