Imagens no banco de dados 3 – Windows Forms

Não é somente em aplicações Web que se podem ter arquivos salvos em banco de dados, também é possível desenvolver aplicações com Windows Forms para salvar e recuperar imagens ou arquivos do banco de dados.

Para um breve exemplo, vamos criar uma aplicação Windows Forms no Visual Studio, com um formulário com um botão ‘Escolher’ para o “upload” da imagem/arquivo e um botão ‘Mostrar’ para exibir a imagem. Para a caixa de seleção do arquivo, precisaremos de um Open File Dialog e para exibição, um Picture Box:

Desta vez, estaremos utilizando Entity Framework para salvar a imagem/arquivo no banco de dados (ainda com a mesma modelagem do artigo anterior).

Ajustamos os eventos do formulário:

using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using Microsoft.Win32;

namespace DemoApp.WinForms
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public int ArquivoId { get; set; }

        /// <summary>
        /// Clique do botão Escolher abre OpenFileDialog
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnEscolher_Click(object sender, EventArgs e)
        {
            ofdEscolher.ShowDialog();
        }

        /// <summary>
        /// Retorno do OpenFileDialog
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ofdEscolher_FileOk(object sender, CancelEventArgs e)
        {
            var file = new FileInfo(ofdEscolher.FileName);

            var obj = new Arquivos
                          {
                              Nome = file.Name,
                              Extensao = file.Extension,
                              Tipo = GetMimeType(file.Extension)
                          };

            using (var stream = ofdEscolher.OpenFile())
            using (var reader = new BinaryReader(stream))
                obj.Bytes = reader.ReadBytes((int)stream.Length);

            using (var context = new DemoAppEntities())
            {
                context.AddObject("Arquivos", obj);
                context.SaveChanges();
            }

            // Regra somente para efeito de demonstração,
            // pois restrições devem ser criadas no Open File Dialog
            if (obj.Extensao == ".jpg")
                ArquivoId = obj.ArquivoId;
        }

        /// <summary>
        /// Recuperar Content Type do registro do Windows,
        /// por compatibilidade às aplicações Web
        /// Original.: http://www.codeproject.com/KB/dotnet/ContentType.aspx
        /// </summary>
        /// <param name="ext"></param>
        /// <returns></returns>
        public string GetMimeType(string ext)
        {
            var classesRoot = Registry.ClassesRoot;

            var typeKey = classesRoot.OpenSubKey(@"MIME\Database\Content Type");

            if (typeKey == null) return string.Empty;

            return (
                       from regExt in typeKey.GetSubKeyNames()
                       let path = @"MIME\Database\Content Type\" + regExt
                       let curKey = classesRoot.OpenSubKey(path)
                       where curKey != null
                       let value = curKey.GetValue("Extension")
                       where value != null && value.ToString().ToLower() == ext
                       select regExt
                   ).FirstOrDefault() ?? string.Empty;
        }

        /// <summary>
        /// Recuperando imagem do banco de dados e exibindo em um PictureBox
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnMostrar_Click(object sender, EventArgs e)
        {
            if (ArquivoId == 0) return;

            Arquivos obj;

            using (var context = new DemoAppEntities())
                obj = context.Arquivos.FirstOrDefault(a => a.ArquivoId == ArquivoId);

            if (obj == null) return;

            var stream = new MemoryStream(obj.Bytes);

            picImagem.Image = Image.FromStream(stream);
        }
    }
}

Agora na execução, clicamos no botão “Escolher”:

Escolhemos o arquivo para importação, no caso uma imagem JPG.

Por fim, com um clique sobre o botão Mostrar, teremos a imagem no Picture Box:

Em um cenário real, criem restrições dos tipos de arquivos no processo de importação/download da imagem/arquivo. No caso de aplicações Windows Forms, estas restrições serão no Open File Dialog.

Imagens no banco de dados 2 – ASP.NET MVC

Quem aderiu ao ASP.NET MVC também pode salvar imagens ou arquivos no banco de dados? Mas é claro e para provar isso, vamos ao exemplo.

Após criar a tabela no banco de dados (conforme o artigo anterior), o primeiro passo será definir a tecnologia de acesso aos dados. No caso utilizaremos LINQ to SQL:

Para a interface, a nossa Index.cshml necessitará somente do seguinte código:

@using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    <div>
        <input id="file" name="file" type="file" />
    </div>
    <div>
        <button type="submit">
            Enviar</button>
    </div>
}

No caso, estou utilizando Razor, mas é fácil adequar para ASPX.

E na Controller, teremos os métodos Index para salvar o arquivo no banco de dados e o método Viewer para recuperar o arquivo do banco de dados:

using System.IO;
using System.Linq;
using System.Web.Mvc;

namespace DemoApp.AspMvc.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View();
        }

        //
        // POST: /Home/

        [HttpPost]
        public ActionResult Index(FormCollection form)
        {
            if (Request.Files.Count != 1) return View();

            var post = Request.Files[0];

            if (post == null) return View();

            var obj = new Models.Arquivo();

            var file = new FileInfo(post.FileName);
            obj.Nome = file.Name;
            obj.Extensao = file.Extension;
            obj.Tipo = post.ContentType;

            using (var reader = new BinaryReader(post.InputStream))
                obj.Bytes = reader.ReadBytes(post.ContentLength);

            using (var context = new Models.DemoAppDataContext())
            {
                context.Arquivos.InsertOnSubmit(obj);
                context.SubmitChanges();
            }

            return RedirectToAction("Viewer", new { id = obj.ArquivoId });
        }

        //
        // GET: /Home/Viewer/id

        public ActionResult Viewer(int id)
        {
            ActionResult result;

            using (var context = new Models.DemoAppDataContext())
            {
                var obj = context.Arquivos.FirstOrDefault(a => a.ArquivoId == id);

                result = obj == null
                    ? (ActionResult)View("Error")
                    : new FileContentResult(obj.Bytes.ToArray(), obj.Tipo);
            }

            return result;
        }
    }
}

Por fim, a página de upload:

E a página de visualização do arquivo:

E ai Paulo, já acabou esta série de artigos? Não, ainda teremos outros exemplos, aguardem!

Imagens no banco de dados 1 – ASP.NET Web Forms

Atendendo ao pedido do Luiz H. S. Pereira, vamos ver um pouco como trabalhar com aplicações e imagens/arquivos salvos em banco de dados.

Neste primeiro artigo, veremos como fazer isso com ASP.NET Web Forms.

O primeiro passo é a tabela onde serão armazenados os arquivos:

USE DemoApp
GO
CREATE TABLE Arquivos (
	ArquivoId	INT IDENTITY	PRIMARY KEY,
	Nome		VARCHAR(250)	NOT NULL,
	Bytes		VARBINARY(MAX)	NOT NULL,
	Extensao	CHAR(5)			NOT NULL,
	Tipo		VARCHAR(250)	NOT NULL
)

O segundo passo é criar uma página com o componente FileUpload (fupArquivo) e um Button (btnEnvio) para o envio:

Trataremos o evento do Button de envio, salvando os dados do arquivo no banco de dados:

using System;
using System.Data.SqlClient;
using System.IO;
using System.Web.UI;

namespace DemoApp.WebForms
{
    public partial class Default : Page
    {
        protected void btnEnviar_Click(object sender, EventArgs e)
        {
            string url;

            const string connString =
                @"Data Source=localhost\SQLEXPRESS;Initial Catalog=DemoApp;Integrated Security=True";

            using (var conn = new SqlConnection(connString))
            {
                conn.Open();

                using (var cmd = conn.CreateCommand())
                {
                    cmd.CommandText =
                        "INSERT INTO [Arquivos] VALUES (@Nome, @Bytes, @Extensao, @Tipo)"
                        + " SELECT SCOPE_IDENTITY()";

                    var file = new FileInfo(fupArquivo.FileName);
                    cmd.Parameters.AddWithValue("@Nome", file.Name);
                    cmd.Parameters.AddWithValue("@Bytes", fupArquivo.FileBytes);
                    cmd.Parameters.AddWithValue("@Extensao", file.Extension);
                    cmd.Parameters.AddWithValue("@Tipo", fupArquivo.PostedFile.ContentType);

                    var codigo = cmd.ExecuteScalar();

                    url = string.Format("ImageHandler.ashx?id={0}", codigo);
                }
            }

            Response.Redirect(url);
        }
    }
}

Para exibição do arquivo salvo no banco de dados, criaremos um Generic Handler:

Com Handler criado:

Configuraremos o Handler para recuperar o arquivo do banco de dados:

using System.Data.SqlClient;
using System.Web;

namespace DemoApp.WebForms
{
    /// <summary>
    /// Summary description for ImageHandler
    /// </summary>
    public class ImageHandler : IHttpHandler
    {
        public bool IsReusable { get { return false; } }

        public void ProcessRequest(HttpContext context)
        {
            var codigo = context.Request.QueryString["id"];

            if (codigo == null) return;

            const string connString =
                @"Data Source=localhost\SQLEXPRESS;Initial Catalog=DemoApp;Integrated Security=True";

            using (var conn = new SqlConnection(connString))
            {
                conn.Open();

                using (var cmd = conn.CreateCommand())
                {
                    cmd.CommandText =
                        "SELECT Tipo, Bytes FROM [Arquivos] WHERE ArquivoId = @ArquivoId";

                    cmd.Parameters.AddWithValue("@ArquivoId", codigo);

                    var dr = cmd.ExecuteReader();

                    if (dr.Read())
                    {
                        context.Response.ContentType = dr.GetString(0);

                        context.Response.BinaryWrite((byte[])dr[1]);
                    }
                }
            }
        }
    }
}

Por fim, importamos o arquivo:

Se tudo der certo, o arquivo será exibido:

ADO.NET: DataReader e Transações

Neste artigo trabalharei dois outros objetos muito importantes no ADO.Net, o DataReader e o Transaction, responsáveis pela leitura de dados no banco e pela ACID (Atomicidade, Consistência, Isolamento, Durabilidade) na manipulação de dados repectivamente.

Quando se espera o resultado de uma consulta SQL com mais de uma célula, pode ser utilizado objetos DataReader como forma de executar uma consulta no banco de dados retornando gradualmente o resultado, mantendo a conexão com o banco de dados até que a conexão seja encerrada.

DataReader: Recomendado em situações onde há a necessidade de ler o resultado de uma consulta. Abaixo dois exemplos típicos de como realizar consultas com o DataReader:

//Consulta de várias linhas.
Dictionary<int, string> dic = new Dictionary<int, string>();
string queryString = "SELECT id, nome FROM pessoa";
using (SqlConnection connection = new SqlConnection(connectionString))
{
  connection.Open();
  SqlCommand command = new SqlCommand(queryString, connection);
  using (SqlDataReader reader = command.ExecuteReader())
  {
    while (reader.Read())
    {
      dic.Add((int)reader[0], (string)reader[1]);
    }
  }
}
return dic;
//Consulta de uma única linha
string result = string.Empty;
string queryString = "SELECT id, nome FROM pessoa";
using (SqlConnection connection = new SqlConnection(connectionString))
{
  connection.Open();
  SqlCommand command = new SqlCommand(queryString, connection);
  using (SqlDataReader reader = command.ExecuteReader())
  {
    if (reader.Read())
    {
      result = String.Format("{0} - {1}", reader[0], reader[1]));
    }
  }
}
return result;

Visto a dependência da conexão com o banco de dados, o DataReader pode ser adaptado para fechar a conexão automaticamente ao finalizar a leitura, utilizando “CommandBehavior.CloseConnection”.

//Consulta de uma única linha
string queryString = "SELECT id, nome FROM pessoa";
SqlConnection connection = new SqlConnection(connectionString);
connection.Open();
SqlCommand command = new SqlCommand(queryString, connection);
return command.ExecuteReader(CommandBehavior.CloseConnection);

O DataReader pode retornar mais de uma consulta, que pode ser acessada facilmente com o método NextResult:

//Consulta de várias linhas
Dictionary<int, string> dic = new Dictionary<int, string>();
string queryString = "SELECT id, nome FROM pessoa1;\n"
  + "SELECT id, nome FROM pessoa2;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
  connection.Open();
  SqlCommand command = new SqlCommand(queryString, connection);
  using (SqlDataReader reader = command.ExecuteReader())
  {
    while (reader.Read())
    {
      dic.Add((int)reader[0], (string)reader[1]);}
      reader.NextResult();
      while (reader.Read())
      {
        dic.Add((int)reader[0], (string)reader[1]);
      }
    }
  }
}
return dic;

ou

//Consulta de várias linhas
Dictionary<int, string> dic = new Dictionary<int, string>();
string queryString = "SELECT id, nome FROM pessoa1;\n"
  + "SELECT id, nome FROM pessoa2;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
  connection.Open();
  SqlCommand command = new SqlCommand(queryString, connection);
  using (SqlDataReader reader = command.ExecuteReader())
  {
    do
    {
      while (reader.Read())
      {
        dic.Add((int)reader[0], (string)reader[1]);
      }
    } while(reader.NextResult());
  }
}
return dic;

Transaction: Para situações onde é necessário inserir ou alterar muitos registros no banco de dados de uma só vez e desfazer estas inserções ou alterações caso ocorra algum erro, é recomendado o uso de transação (Transaction).

Conforme o exemplo abaixo, é aberta uma conexão e criada uma transação pelo método “BeginTransaction”. Dentro da transação são executados dois métodos de inserção, caso algum deles dê erro será executado o método “RollBack” que desfará as inserções no banco de dados. Caso não haja erro, será necessário executar o método “Commit” para que as inserções tenham efeito.

using (SqlConnection connection = new SqlConnection(connectionString))
{
  connection.Open();
  using (SqlTransaction transaction = connection.BeginTransaction())
  {
    SqlCommand command = connection.CreateCommand();
    command.Connection = connection;
    command.Transaction = transaction;
    try
    {
      command.CommandText = "INSERT INTO tabela VALUES ('valor')";
      command.ExecuteNonQuery();
      command.CommandText = "INSERT INTO tabela VALUES ('valor')";
      command.ExecuteNonQuery();
      transaction.Commit();
    }
    catch
    {
      transaction.Rollback();
    }
  }
}

Transações não possuem “segredo”, são simples de serem utilizadas, mas em compensação, a execução dos comandos SQL podem demorar um pouco mais, afim de garantir atomicidade, consistência, isolamento, durabilidade do que esta sendo executado. Mas não se preocupe com situações simples, como a execução de um único comando de delete, insert ou update, pois o SQL Server possui transações implícitas nestes comandos, para evitar situações como alterar, deletar ou inserir “pela metade”.

ADO.NET: Introdução

Com o advento Framework.Net, se possibilitou novos recursos de acessos a dados, de forma a facilitar do desenvolvimento até migração entre plataformas de SGDB.

O ADO.Net, principal recurso de acesso a banco de dados na plataforma .Net, se popularizou por sua arquitetura orientada a objeto e o suporte as principais plataforma de SGDB do mercado, como Oracle e SQL Server, além de suporte a OleDb e ODBC, que possibilitam a integração com Excel, DB2, Interbase, PostGree, Firebird e MySQL, sem a necessidade de utilizar qualquer componente de terceiros.

Também existem outros recursos desenvolvidos que estendem ou reimplementam a arquitetura de acessos a SGDB de forma especializada, como no caso da Oracle, do Firebird e do MySQL, que desenvolvem suas próprias extensões do ADO.Net, afim de atender necessidade específicas de suas plataformas. Em relação ao SQL Server, a Framework.Net possui outros recursos exclusivos para prover integração com CLR.

Existem duas formas básicas de trabalhar com o ADO.Net, por meio de classes conectadas e desconectadas.

As classes conectadas permitem o acesso ao SGBD, por meio do .Net Data Provider, que constitui basicamente das seguintes entidades:

Connection: Responsável pela conexão ao SGDB, esta classe varia SGDB para SGDB (ex. SQLConnection, OracleConnection, OleDbConnection e OdbcConnection), possuindo métodos e propriedades que facilitam criar a conexão com cada um dos SGDB.

Por questões de arquitetura, para exigir que a conexão se feche depois de realizada a operação é recomendado o uso do bloco “Using”, exemplo:

// Criando o objeto de conexão ao banco de dados
using (SqlConnection conn = new SqlConnection("string de conexão"))
{
  // Chamando o método responsável pela abertura da conexão.
  conn.Open();
}

Recomendável não utilizar nenhum retorno de métodos dentro de blocos “Using”:

// Criando o objeto de conexão ao banco de dados
using (SqlConnection conn = new SqlConnection("string de conexão"))
{
  // Chamando o método responsável pela abertura da conexão.
  conn.Open();
  // Nunca faça isso:
  return true;

}

String de conexão: É responsável por identificar o endereço do SGDB, formas de autenticação e configurações de acesso.

Em aplicações Web, normalmente esta string é armazenada no arquivo web.config ou aplicações Windows e Windows Mobile no arquivo app.config, por questões de reaproveitamento e para facilitar alterações. Exemplo:

<!--web.config-->
<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="nome" connectionString="Data Source=in;Initial Catalog=db;User=us;Password=sn;"/>
  </connectionStrings>
</configuration>

Método responsável por retornar a string de conexão do web.config, as vezes se faz necessário adicionar a referência a System.Configuration ao projeto.

ConfigurationManager.ConnectionStrings["nome"].ToString();
// Criando o objeto de conexão ao banco de dados
string connectionString = ConfigurationManager.ConnectionStrings["nome"].ToString()
using (SqlConnection conn = new SqlConnection(connectionString))
{
  // Chamando o método responsável pela abertura da conexão.
  conn.Open();
}

Também existem formas de gerar a connection string em tempo de execução.
(at. http://msdn.microsoft.com/en-us/library/ms254947(VS.80).aspx )

Alguns exemplos de string de conexão para SQL Server e outros bancos de dados:
https://sqlfromhell.wordpress.com/2009/08/04/connection-strings/
http://msdn.microsoft.com/en-us/library/ms254500.aspx

Command: Depois de estabelecida a conexão com o SGDB, esta classe é o responsável por executar comandos e consultas no banco de dados. Seus objetos podem ser instanciado da forma tradicional por meio de um construtor ou chamando o método CreateCommand de objetos de conexão.Exemplo:

using (SqlConnection conn= new SqlConnection(""))
{
  using (SqlCommand cmd = new SqlCommand())
  {
    cmd.Connection = conn;
  }
}

Ou

using (SqlConnection conn= new SqlConnection(""))
{
  using (SqlCommand cmd = conn.CreateCommand())
  {
  }
}

Estes comandos podem ser comandos em SQL, nomes de StoredProcedures ou nomes de tabelas, para define qual o tipo comando é possível definir por meio do enumerador CommandType. Ex.:

// Para comandos SQL
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT * FROM tabela";
// Para stored procedure
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "SpTabelaSel";
// Para tabela
cmd.CommandType = CommandType.TableDirect;
cmd.CommandText = "tabela";

Parâmetros: É a forma ideal de passar valores para comandos SQL e Stored Procedures. São eficazes para combater possíveis ataques de “SQL-Injection”. Exemplos:

//Forma de errada de passar valores para os comandos
cmd.CommandText = "INSERT INTO tabela (valor1) VALUES " + valor + ")";
//Utilizando parâmetros para inserir dados
cmd.CommandText = "INSERT INTO tabela (valor1) VALUES (@nome_parametro)";
cmd.Parameters.Add(new SqlParameter("@nome_parametro", valor);
//Utilizando parâmetros para excluir
cmd.CommandText = "DELETE FROM tabela WHERE valor1 = @nome_parametro";
cmd.Parameters.Add(new SqlParameter("@nome_parametro", valor);
//Utilizando parâmetros para alterar
cmd.CommandText = "UPDATE tabela SET valor1 = @nome_parametro1 WHERE valor2 = @nome_parametro2";
cmd.Parameters.Add(new SqlParameter("@nome_parametro1", valor1);
cmd.Parameters.Add(new SqlParameter("@nome_parametro2", valor2);
//Utilizando parâmetros para consultas
cmd.CommandText = "SELECT * FROM tabela WHERE valor1 = @nome_parametro";
cmd.Parameters.Add(new SqlParameter("@nome_parametro", valor);
//Utilizando parâmetros para StoreProcedures
cmd.CommandText = " SpTabelaSel";
cmd.Parameters.Add(new SqlParameter("@nome_parametro", valor);

A forma ideal de criar parâmetros é definir o tipo, tamanho e valor de forma separada.

//Forma ideal de criar um parâmetro
SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "@nome_parametro";
parameter.SqlDbType = SqlDbType.VarChar;
parameter.Size = 90;
parameter.Value = valor;

ou

SqlParameter parameter = new SqlParameter("@nome_parametro", SqlDbType.VarChar, 90);
parameter.Value = valor;

Métodos de execução: Para comandos que não são esperados retornos é recomendado o uso do método ExecuteNonQuery, cujo o retorno é um inteiro especificando o número de linhas afetadas. Exemplo:

cmd.CommandText = "INSERT INTO tabela (coluna) VALUES ('valor')";
cmd.ExecuteNonQuery();

Para comandos que se espera uma única célula, exemplo a execução das funções SQL, COUNT, SUM, MAX, MIN ou qualquer outra consulta que ternar somente um valor, é recomendado o uso do método ExecuteScalar. Exemplo:

cmd.CommandText = "SELECT COUNT(*) FROM tabela";
int count = (int)cmd.ExecuteScalar();

Materiais do Student 2 Business (2008-2009)

Pessoal, no meu disco virtual no 4Shared coloquei os materiais que foram disponibilizados aos alunos e instrutores do Student 2 Business na época que estive dando as aulas aqui no Centro de Inovação Microsoft de Curitiba-PR.
Para quem tiver interesse, é um ótimo material para começar a aprender programação orientada a objetos com C# e introdução a acesso a banco de dados, SQL e as novidades do .NET Framework 3.0 e 3.5.
Link para meu disco virtual:

Pasta: S2B – FY 2009 \ Material
[2011-03-29] Problema com a limitação do 4shared fez com que meus arquivos compartilhados fossem enviados para o limbo (…) desculpem o transtorno…