Quantcast
Channel: André Alves de Lima
Viewing all articles
Browse latest Browse all 210

Salvando imagens no banco de dados utilizando C#

$
0
0

Uma dúvida recorrente entre programadores que estão iniciando no mundo do C# diz respeito a salvar imagens no banco de dados. Essa não é uma tarefa complicada, mas, devido à grande diversidade de maneiras de acessar o banco de dados no .NET, o desenvolvedor acaba ficando um pouco perdido. Após um dos inscritos na minha newsletter ter feito justamente essa mesma pergunta, resolvi que era hora de abordar esse assunto. Vamos conferir neste artigo algumas das maneiras de salvar imagens no banco de dados. Espero que alguma delas acabe te ajudando.

Como mencionado no parágrafo anterior, um dos motivos para que essa seja uma dúvida recorrente, é que existem diversas maneiras de resolver essa tarefa. Porém, em todas elas, um ponto que não se altera é o banco de dados. Portanto, o primeiro passo deste artigo mostrará a criação da tabela onde as imagens serão armazenadas.

Criando o banco de dados

Em um cenário “normal” de desenvolvimento, antes de começar a codificar, é bom parar para pensar na estrutura do banco de dados. Existem algumas tecnologias que permitem que você se “esqueça” do banco de dados e só se preocupe com a estrutura de classes da sua aplicação (como, por exemplo, o Entity Framework Code First). Na minha visão (pelo menos nas experiências que eu tive até hoje), na maioria das vezes, planejaremos o banco de dados antes de começarmos a programar. Portanto, não será abordada nenhuma tecnologia do tipo “Code First” neste artigo.

Como a ideia deste artigo é mostrar, da maneira mais simples possível, como salvar imagens no banco de dados, vamos criar uma tabela bem simples para armazenar algumas fotos. Vamos utilizar um banco de dados SQL Server Express (que você pode baixar gratuitamente – mais informações aqui) e criar essa tabela (que dei o nome de “Foto“) com somente três colunas: FotoID, Descricao e Foto.

Veja o script SQL para a criação dessa tabela:

CREATE TABLE [Foto] (
	[FotoID] [int] IDENTITY(1,1) NOT NULL,
	[Descricao] [varchar](200) NOT NULL,
	[Foto] [image] NOT NULL,
	CONSTRAINT [PK_Foto] PRIMARY KEY CLUSTERED ([FotoID] ASC)
)

Uma vez criada a tabela, vamos partir para a criação do exemplo.

Criando a aplicação de exemplo

Vamos utilizar um projeto do tipo “Windows Forms” simplesmente pelo motivo de essa ainda ser a tecnologia mais utilizada hoje em dia para desenvolvimento de aplicativos desktop com a plataforma .NET. Porém, as maneiras de salvar imagens no banco de dados que serão demonstradas neste artigo são independentes do Windows Forms, ou seja, você poderia tirar proveito dessas dicas mesmo se a sua aplicação estiver utilizando WPF.

A ideia básica é estender o artigo sobre fotos com a webcam no C# que foi publicado em Novembro de 2014. Portanto, após ter criado o projeto Windows Forms, ajuste o Form1 de forma que ele fique parecido com a imagem abaixo:

Seguindo o tutorial sobre webcam em C#, vamos implementar o suporte à webcam da seguinte maneira (confira maiores detalhes sobre as referências necessárias no artigo sobre webcam):

    public partial class FormFotoDB : Form
    {
        AForge.Video.DirectShow.VideoCaptureDevice videoSource;

        public FormFotoDB()
        {
            InitializeComponent();

            AForge.Video.DirectShow.FilterInfoCollection videosources = new AForge.Video.DirectShow.FilterInfoCollection(AForge.Video.DirectShow.FilterCategory.VideoInputDevice);

            if (videosources != null)
            {
                videoSource = new AForge.Video.DirectShow.VideoCaptureDevice(videosources[0].MonikerString);
                videoSource.NewFrame += (s, e) => webCamPictureBox.Image = (Bitmap)e.Frame.Clone();
                videoSource.Start();
            }
        }

        protected override void OnClosed(EventArgs e)
        {
            base.OnClosed(e);

            if (videoSource != null && videoSource.IsRunning)
            {
                videoSource.SignalToStop();
                videoSource = null;
            }
        }
    }

Além disso, vamos adicionar a string de conexão no arquivo de configurações da aplicação. Para isso, abra o arquivo “Settings.settings” e adicione a string de conexão como demonstrado nas imagens abaixo.

Feito isso, estamos prontos para adicionar o suporte à gravação das imagens no banco de dados. Iremos conferir nesse artigo três maneiras de implementar essa funcionalidade: utilizando ADO.NET puro (com DbConnection e DbCommand), utilizando um DataSet tipado (com DataAdapter embutido) e, finalmente, utilizando o Entity Framework.

Salvando a imagem com ADO.NET puro

Quando falamos de ADO.NET “puro”, estamos falando da utilização manual das classes básicas do ADO.NET (DbConnection, DbCommand, etc) juntamente com comandos SQL a fim de recuperarmos dados do banco de dados ou executarmos sentenças de INSERT ou DELETE diretamente no banco de dados.

Essa maneira de persistir dados é ao mesmo tempo a mais simples e a mais complicada. Ela é a mais simples pois não exige que tenhamos que adicionar nenhuma referência ao nosso projeto, nem exige que criemos outras classes adicionais para lidar com a persistência de dados (apesar de ser uma boa prática separar a camada de acesso a dados).

O primeiro ajuste que faremos no nosso projeto diz respeito ao carregamento da lista de fotos no controle “fotosDBListBox“. Para isso, vamos adicionar um método ao nosso Form que fará uma query buscando todos os registros da tabela “Foto” e armazenará todos esses registros retornados em uma DataTable. Após feito o carregamento, utilizaremos essa DataTable como DataSource da ListBox. Confira a implementação no método a seguir.

        private void CarregarThumbnailsAdoNet()
        {
            using (var connection = new System.Data.SqlClient.SqlConnection(Properties.Settings.Default.ExemploDBConnectionString))
            {
                try
                {
                    connection.Open();

                    if (connection.State == ConnectionState.Open)
                    {
                        using (var command = connection.CreateCommand())
                        {
                            command.CommandText = "SELECT * FROM Foto";
                            var dataReader = command.ExecuteReader();
                            DataTable dataTable = new DataTable();
                            dataTable.Load(dataReader);
                            fotosDBListBox.DataSource = dataTable;
                            fotosDBListBox.DisplayMember = "FotoID";
                            fotosDBListBox.ValueMember = "FotoID";
                        }
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Erro ao abrir conexão com o banco de dados.\n{0}", ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

Como podemos notar no código acima, para retornar todos os registros da tabela “Foto“, precisamos primeiramente criar e abrir uma conexão com o banco de dados. Utilizamos as classes do namespace System.Data.SqlClient pois o banco de dados utilizado nesse exemplo é o SQL Server. Caso você trabalhe com outro banco de dados, você precisará utilizar outro provider (por exemplo, “OleDb” para bancos Microsoft Access). Porém, a implementação será muito parecida, pois as classes dos providers específicos sempre herdam das classes base do .NET Framework.

O construtor de SqlConnection recebe como parâmetro a string de conexão (que criamos na seção anterior). Após abrir a conexão utilizando o método Open, precisamos conferir se a conexão foi realmente aberta. Para isso, utilizamos a propriedade State.

Uma vez criada e aberta a conexão, basta chamarmos o método CreateCommand para criar uma nova instância de SqlCommand, configurar a propriedade CommandText com a query SQL a ser executada e chamar o método ExecuteReader (pois nesse caso estamos lendo dados do banco). Com o DataReader em mãos, podemos criar a DataTable e chamar o método Load passando o DataReader. Finalmente, uma vez que a DataTable está preenchida, podemos utilizá-la como DataSource da ListBox, configurando as propriedades DataSource, DisplayMember e ValueMember.

Adicione uma chamada ao método “CarregarThumbnailsAdoNet” no final do construtor do Form e execute a aplicação de exemplo para conferir o resultado. Até o momento, como não temos nenhuma foto armazenada na tabela “Foto“, a ListBox aparecerá vazia.

Agora vamos ver como ficaria o código para salvar a imagem sendo exibida pela webcam no banco de dados utilizando o ADO.NET puro. Para isso, vamos criar dois novos métodos, chamados “SalvarFotoAdoNet” e “ConverterFotoParaByteArray“:

        private void SalvarFotoAdoNet()
        {
            using (var connection = new System.Data.SqlClient.SqlConnection(Properties.Settings.Default.ExemploDBConnectionString))
            {
                try
                {
                    connection.Open();

                    if (connection.State == ConnectionState.Open)
                    {
                        using (var command = connection.CreateCommand())
                        {
                            command.CommandText = "INSERT INTO Foto (Descricao, Foto) VALUES (@Descricao, @Foto)";
                            command.Parameters.AddWithValue("Descricao", DateTime.Now.ToString());
                            command.Parameters.AddWithValue("Foto", ConverterFotoParaByteArray());
                            command.ExecuteNonQuery();
                        }
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Erro ao abrir conexão com o banco de dados.\n{0}", ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private byte[] ConverterFotoParaByteArray()
        {
            using (var stream = new System.IO.MemoryStream())
            {
                webCamPictureBox.Image.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
                stream.Seek(0, System.IO.SeekOrigin.Begin);
                byte[] bArray = new byte[stream.Length];
                stream.Read(bArray, 0, System.Convert.ToInt32(stream.Length));
                return bArray;
            }
        }

Note que uma boa parte do código de “SalvarFotoAdoNet” é muito parecido com o que vimos anteriormente. A diferença é que, após criar o SqlCommand, nós temos que configurar a propriedade CommandText para uma sentença de INSERT. Como essa sentença utiliza parâmetros (para maiores informações sobre parâmetros no ADO.NET, confira este artigo) – @Descricao e @Foto – precisamos adicioná-los ao SqlCommand. Fazemos isso com o método AddWithValue, sendo que o primeiro argumento é o nome do parâmetro e o segundo argumento é o valor.

A grande sacada nesse ponto está no método “ConverterFotoParaByteArray“. Ao criar uma coluna do tipo “image” no SQL Server, precisamos passar o seu valor para o ADO.NET como um array de bytes. Isso pode ser facilmente realizado utilizando um MemoryStream, como pode ser observado no método “ConverterFotoParaByteArray“.

Finalmente, adicione um handler para o evento “Click” do botão “Click!” e, dentro desse handler, chame os métodos “SalvarFotoAdoNet” e “CarregarThumbnailsAdoNet“:

        private void clickButton_Click(object sender, EventArgs e)
        {
            SalvarFotoAdoNet();
            CarregarThumbnailsAdoNet();
        }

Execute a aplicação, salve algumas fotos no banco de dados utilizando o botão “Click” e veja que a ListBox é carregada com os IDs das fotos (que é um campo auto incremento). Porém, como fazer para que, ao selecionar um item na ListBox, o thumbnail seja exibido na PictureBox? É só implementar um handler para o evento SelectedIndexChanged da ListBox, conforme podemos conferir a seguir:

        private void fotosDBListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            var dataRowView = fotosDBListBox.SelectedItem as DataRowView;
            if (dataRowView != null)
            {
                using (var stream = new System.IO.MemoryStream((byte[])dataRowView.Row["Foto"]))
                {
                    thumbnailPictureBox.Image = Bitmap.FromStream(stream);
                }
            }
        }

Nesse handler, nós recuperamos o item selecionado na ListBox, convertemos para DataRowView e pegamos o valor da coluna “Foto“. O conteúdo dessa coluna é, como vimos anteriormente, um array de bytes. Para convertê-lo em imagem, basta criarmos um MemoryStream passando o array de bytes no construtor e, em seguida, utilizar esse MemoryStream no método FromStream da classe Bitmap.

Execute o aplicativo novamente e veja que, ao clicar em um item da ListBox, o thumbnail da imagem é exibido com sucesso. Mas, ainda está faltando o código para excluir a imagem. Como ficaria esse código? Confira logo abaixo no método “ExcluirFotoAdoNet“:

        private void ExcluirFotoAdoNet()
        {
            var dataRowView = fotosDBListBox.SelectedItem as DataRowView;
            if (dataRowView != null)
            {
                using (var connection = new System.Data.SqlClient.SqlConnection(Properties.Settings.Default.ExemploDBConnectionString))
                {
                    try
                    {
                        connection.Open();

                        if (connection.State == ConnectionState.Open)
                        {
                            using (var command = connection.CreateCommand())
                            {
                                command.CommandText = "DELETE FROM Foto WHERE (FotoID = @FotoID)";
                                command.Parameters.AddWithValue("FotoID", dataRowView.Row["FotoID"]);
                                command.ExecuteNonQuery();
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("Erro ao abrir conexão com o banco de dados.\n{0}", ex.Message, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
        }

Novamente, boa parte do código é parecida com os códigos anteriores. A parte de criação e abertura da conexão, bem como a parte de criação do comando, é idêntica aos exemplos anteriores. Inclusive, uma boa prática seria extrair essa parte do código em um método para que não tenhamos que ficar repetindo o mesmo código várias vezes. Porém, vou pular esse passo, uma vez que esse não é o foco do artigo.

Finalmente, adicione um handler para o evento “Click” do botão “Excluir” conforme o código a seguir:

        private void excluirButton_Click(object sender, EventArgs e)
        {
            ExcluirFotoAdoNet();
            CarregarThumbnailsAdoNet();
        }

Agora sim, o exemplo utilizando ADO.NET puro está completo. Execute a aplicação e teste todas as etapas: criação das imagens, exibição das imagens no thumbnail e exclusão das imagens.

Salvando a imagem com DataSet tipado

Uma forma um pouco mais simples de salvar a imagem seria utilizando DataSets tipados. Com eles, o Visual Studio cria automaticamente DataAdapters que tornam a nossa vida muito mais fácil. O primeiro passo é criarmos um DataSet tipado contendo a tabela “Foto“. Para isso, adicione ao projeto um novo item do tipo “DataSet” (no exemplo abaixo utilizamos o nome de FotoDataSet):

Feito isso, abra o Server Explorer (menu View => Server Explorer) e adicione a conexão com o banco de dados:

E então arraste a tabela “Foto” para dentro do DataSet tipado:

Após isso, podemos utilizar esse DataSet tipado para salvarmos a imagem no banco. Vamos ver como ficam os métodos “CarregarThumbnailsDataSetTipado“, “SalvarFotoDataSetTipado” e “ExcluirFotoDataSetTipado“:

        private void CarregarThumbnailsDataSetTipado()
        {
            using (FotoDataSetTableAdapters.FotoTableAdapter dataAdapter = new FotoDataSetTableAdapters.FotoTableAdapter())
            {
                FotoDataSet.FotoDataTable dataTable = new FotoDataSet.FotoDataTable();
                dataAdapter.Fill(dataTable);
                fotosDBListBox.DataSource = dataTable;
                fotosDBListBox.DisplayMember = "FotoID";
                fotosDBListBox.ValueMember = "FotoID";
            }
        }

        private void SalvarFotoDataSetTipado()
        {
            using (FotoDataSetTableAdapters.FotoTableAdapter dataAdapter = new FotoDataSetTableAdapters.FotoTableAdapter())
            {
                dataAdapter.Insert(DateTime.Now.ToString(), ConverterFotoParaByteArray());
            }
        }

        private void ExcluirFotoDataSetTipado()
        {
            var dataRowView = fotosDBListBox.SelectedItem as DataRowView;
            if (dataRowView != null)
            {
                using (FotoDataSetTableAdapters.FotoTableAdapter dataAdapter = new FotoDataSetTableAdapters.FotoTableAdapter())
                {
                    dataAdapter.Delete((int)dataRowView.Row["FotoID"], dataRowView.Row["Descricao"].ToString());
                }
            }
        }

Veja que o código utilizando DataSet tipado é muito mais simples que o código utilizando ADO.NET puro. Isso se deve principalmente ao fato da criação do DataAdapter pelo DataSet tipado. Com o DataAdapter fica muito mais fácil adicionar, editar e excluir registros do banco de dados. Basta utilizarmos os métodos específicos para essas ações (Insert, Delete e Fill, como vimos no código acima), sem termos que escrever nenhuma linha de SQL.

É importante ressaltar que a criação de DataAdapters pode também ser feita sem a necessidade de criarmos DataSets tipados. Porém, é muito mais fácil utilizarmos DataSets tipados, uma vez que, nesse caso, os DataAdapters para cada tabela são gerados automaticamente pelo Visual Studio.

Para testar o exemplo, substitua todas as chamadas de “CarregarThumbnailsAdoNet“, “SalvarFotoAdoNet” e “ExcluirFotoAdoNet” por “CarregarThumbnailsDataSetTipado“, “SalvarFotoDataSetTipado” e “ExcluirFotoDataSetTipado” respectivamente.

Salvando a imagem com o Entity Framework

Nessa última seção, vamos verificar como fazer as mesmas ações utilizando o Entity Framework. O primeiro passo que devemos executar é adicionar o contexto para o nosso banco de dados. Para isso, adicione uma pasta no projeto chamada “EntityFramework” e, dentro dessa pasta, adicione um novo item do tipo “ADO.NET Entity Data Model“:

Escolha a opção “EF Designer from database“, escolha a connection string criada anteriormente, marque a tabela “Foto” na lista de objetos do banco de dados e clique em “Finish“:

Durante esse processo, algumas mensagens de segurança serão exibidas. Simplesmente clique “OK” e continue com o processo para gerar o modelo de dados.

Com o contexto em mãos, basta o instanciarmos para termos acesso aos dados da tabela “Foto“. Veja como fica o código para carregar as thumbnails, salvar novas fotos e excluir fotos existentes utilizando o Entity Framework:

        private void CarregarThumbnailsEntityFramework()
        {
            using (EntityFramework.DemoDBEntities context = new EntityFramework.DemoDBEntities())
            {
                fotosDBListBox.DataSource = context.Foto.ToList();
                fotosDBListBox.DisplayMember = "FotoID";
                fotosDBListBox.ValueMember = "FotoID";
            }
        }

        private void SalvarFotoEntityFramework()
        {
            using (EntityFramework.DemoDBEntities context = new EntityFramework.DemoDBEntities())
            {
                context.Foto.Add(new EntityFramework.Foto()
                {
                    Descricao = DateTime.Now.ToString(),
                    Foto1 = ConverterFotoParaByteArray()
                });
                context.SaveChanges();
            }
        }

        private void ExcluirFotoEntityFramework()
        {
            var foto = fotosDBListBox.SelectedItem as EntityFramework.Foto;
            if (foto != null)
            {
                using (EntityFramework.DemoDBEntities context = new EntityFramework.DemoDBEntities())
                {
                    var fotoNoContext = context.Foto.Find(foto.FotoID);
                    context.Foto.Remove(fotoNoContext);
                    context.SaveChanges();
                }
            }
        }

        private void fotosDBListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            var foto = fotosDBListBox.SelectedItem as EntityFramework.Foto;
            if (foto != null)
            {
                using (var stream = new System.IO.MemoryStream(foto.Foto1))
                {
                    thumbnailPictureBox.Image = Bitmap.FromStream(stream);
                }
            }
        }

Observe que é muito simples trabalhar com o Entity Framework. Só temos que criar uma instância do DbContext e utilizar a entidade “Foto” para acessar as fotos cadastradas no banco de dados. Para adicionar uma foto nova, basta criar uma nova instância da classe Foto, adicioná-la à coleção e chamar o método SaveChanges. E para deletar uma foto, basta encontrar o registro na entidade Foto, removê-lo da coleção e chamar o método SaveChanges.

Uma observação importante é que o handler para mostrar o item selecionado na ListBox (“fotosDBListBox_SelectedIndexChanged“) é um pouco diferente de quando estamos utilizando ADO.NET puro ou DataSets tipados. Isso ocorre porque o SelectedItem não será um DataRowView nesse caso, mas sim, uma instância da classe Foto.

Outra observação importante é que o Entity Framework utilizou “Foto1” como nome da coluna “Foto” da nossa tabela. Isso se deve ao fato de que a coluna e a tabela tem nomes iguais, e o Entity Framework não consegue lidar com isso de forma amigável. Nesse caso, o melhor seria ter dado outro nome para a coluna “Foto” (como, por exemplo, “ConteudoFoto“).

Qual dessas opções eu devo utilizar?

Agora que você conhece essas três opções para salvar as imagens no banco de dados, surge a dúvida: qual delas eu devo utilizar? A resposta é aquela clássica: depende. Se você estiver procurando por performance, provavelmente a melhor opção é utilizar ADO.NET puro ou DataSets tipados. Se você estiver procurando por praticidade (e performance não for importante no seu cenário), a melhor opção provavelmente será o Entity Framework.

Não vou abordar esse comparativo, pois o André Baltieri já gravou um vídeo excelente sobre esse tópico. Confira no canal dele no Youtube:

Screencast André Baltieri: ADO puro ou Frameworks ORM?

E com isso eu finalizo o artigo de hoje. Para ficar por dentro das novidades do meu site, assine a minha newsletter clicando aqui ou preenchendo o formulário abaixo. É lá que eu compartilho as notícias dos posts publicados além de enviar informações que eu só divido por e-mail! Por exemplo, o tópico desse post foi baseado em uma sugestão de um inscrito. Não perca tempo e assine agora mesmo!

Até a próxima!

André Lima

Icon by Heart Internet used under Creative Commons
https://www.iconfinder.com/icons/63467/database_storage_icon

Newsletter do André Lima

* indicates required



Powered by MailChimp

The post Salvando imagens no banco de dados utilizando C# appeared first on André Alves de Lima.


Viewing all articles
Browse latest Browse all 210