Recentemente ministrei um treinamento para uma empresa com um pedido não muito usual, que foi a inspiração deste artigo. O pedido do Alexandre de Almeida foi que eu não poderia usar o Entity Framework, apenas o ADO.NET, visto que a empresa já trabalha desta forma e há um legado exatamente assim. O outro motivo é que existem muitas aplicações que usam o ADO.NET e não podemos nos esquecer deste legado.
Os pré-requisitos para este artigo são o Visual Studio .NET 2013, ADO.NET e conhecimento da linguagem C#.
Estrutura gráfica da solução
Veja na Figura 1 a representação gráfica da solução, sendo que existe uma classe chamada Produto, uma interface chamada IGerenciaCRUD, que é implementada na classe DALProduto que controla todo o CRUD. O Controller ProdutoController usa a DALProduto para controlar toda a estrutura de acesso aos dados, que será chamada pelas Actions deste Controller.

Projeto ASP.NET MVC 5
Neste artigo, criei um projeto no Visual Studio .NET 2013 com o template de ASP.NET MVC 5. Como o template já cria toda a estrutura de MVC, a primeira coisa a fazer é criar a classe Produto na pasta Models, conforme mostrado a seguir. É uma classe com quatro propriedades simples, sendo a chave o ProdutoID.
namespace Mvc_AdoNet.Models { public class Produto { public int ProdutoID { get; set; } public string Nome { get; set; } public decimal Preco { get; set; } public int Estoque { get; set; } } }
Dica: Por que a chave chama-se ProdutoID e não ID ou qualquer outro nome? Porque se um dia você resolver utilizar o Entity Framework 6 ou superior, não é preciso usar o Data Annotations ou Fluent API para informar qual propriedade é a chave primária – o EF já reconhece a chave se você usar o ID ou o nome da classe seguido do ID (ProdutoID, ClienteID, PedidoID etc).
Agora pense comigo: a classe já existe, como posso criar um Controller usando o template do MVC embutido no VS 2013 de forma que o mesmo use o ADO.NET? Para resolver esta questão, usei uma estratégia que funciona 100% e é super rápida: usei o Code First juntamente com o Migrations; e eles, por sua vez, usam o Entity Framework. Desta forma, foram criados o DbContext, o Controller ProdutoController e o Configuration.cs na pasta Migrations. Ou seja, o projeto está pronto e você já pode executá-lo e o banco de dados será criado em tempo de execução. Não vou mostrar o passo a passo porque já escrevi artigos mostrando isto, o foco aqui é produtividade, afinal, se o projeto todo já está criado com o Entity Framework, basta alterar as Actions dos Controllers para usar a classe de acesso a dados com o ADO.NET (que irei detalhar neste artigo). Enfim, espero que tenha acompanhado o raciocínio. Agora, se preferir escrever na mão todas as linhas, fique à vontade.
A título de curiosidade, quando você invocar o Migrations com o comando Enable-Migrations [nome do contexto], veja que no Configuration.cs é gerado o método Seed e eu já criei uma lista com alguns nomes de produtos (veja a listagem a seguir) a serem inseridos apenas na primeira vez que o Migration for executado com o Add-Migration [labelInicial] e o Update-Database (este, sim, cria o banco fisicamente).
protected override void Seed(Mvc_AdoNet.Models.BancoContexto context) { // This method will be called after migrating to the latest version. // You can use the DbSet<T>.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. E.g. // // context.People.AddOrUpdate( // p => p.FullName, // new Person { FullName = "Andrew Peters" }, // new Person { FullName = "Brice Lambson" }, // new Person { FullName = "Rowan Miller" } // ); // new List<Produto> { new Produto { Nome="Produto A", Estoque=10, Preco=10 }, new Produto { Nome="Produto B", Estoque=1, Preco=140 }, new Produto { Nome="Produto C", Estoque=6, Preco=110 }, new Produto { Nome="Produto D", Estoque=150, Preco=110 }, new Produto { Nome="Produto E", Estoque=120, Preco=210 }, new Produto { Nome="Produto F", Estoque=40, Preco=450 }, new Produto { Nome="Produto G", Estoque=10, Preco=50 } }.ForEach(p => context.Produtos.Add(p)); }
Outra curiosidade a ser destacada é que quando criei o Controller ProdutoController, informei o nome do contexto a ser criado (novo), chamado BancoContexto, o qual herda de DbContext, faz a referência à chave BancoContexto criada no Web.Config e que aponta para o banco de dados. E também já referencia o DbSet para a entidade de Produto, conforme listagem a seguir.
public class BancoContexto : DbContext { public BancoContexto() : base("name=BancoContexto") { } public DbSet<Produto> Produtos { get; set; } }
Interface da classe
Quando pensamos em acesso a dados com o ADO.NET, logo vem à cabeça o uso de SqlConnection, DataReader, DataTable, ExecuteNonQuery, bons comandos do ADO.NET. Mas que tal criarmos uma implementação baseada em uma interface, com métodos bem definidos, a fim de tornar um padrão para toda e qualquer implementação das entidades no banco?
Sem dúvida, isto tem muita relevância, padrões e regras bem definidos é a melhor forma de desenvolver, principalmente na manutenção de códigos, equipes diferenciadas e descentralizadas. Sendo assim, crie uma pasta chamada DAL (Data Access Layer) e dentro desta, adicione uma interface chamada IGerenciaCRUD.
Veja a estrutura da IGerenciaCRUD, que já estou pensando em generalizar a interface, permitindo usá-la para qualquer funcionalidade de CRUD. Note que declaro o parâmetro chamado <TEntity> e digo que este é uma classe (where TEntity : class). O conteúdo da interface são os métodos de CRUD, que recebem ou a instrução SQL, o ID ou o objeto (TEntity) em si. Salve a classe e compile o projeto.
using System.Collections.Generic; namespace Mvc_AdoNet.DAL { interface IGerenciaCRUD<TEntity> where TEntity : class { List<TEntity> Get(string sql); TEntity GetById(int id); void Cadastra(TEntity obj); void Atualiza(TEntity obj); void Exclui(int id); } }
Pergunta: Renatão, o certo não é criar um projeto de DAL e referenciá-lo neste projeto? Sim, você pode e deve criar um projeto de DAL para isolar o acesso a dados, mas como estou ilustrando com apenas uma entidade, farei no mesmo.
Classe de acesso a dados com ADO.NET
Uma vez que a interface esteja implementada, que será o padrão de código com os devidos métodos, chegou a hora de criarmos a classe de acesso a dados. Sendo assim, dentro da pasta DAL adicione uma classe chamada DALProduto. Adicione os usings necessários para referenciar a classe Produto em Models e o System.Collections.Generic. Assim que a classe é criada, o primeiro passo a fazer é implementar a interface IGerenciaCRUD, o que necessita referenciar a classe Produto. Mas o fato de escrever: IGerenciaCRUD<Produto> não adianta nada, então, o próprio Visual Studio já mostra o aviso de que é preciso implementar. Deixe o cursor no texto IGerenciaCRUD<Produto> e dê um CTRL + . (ponto). Isto irá implementar a estrutura desta interface. Pode babar com este recurso, caso não o conheça, pois evita erros de implementação.
Sem seguida, é preciso fazer uma referência ao banco de dados; na verdade precisamos saber qual é a string de conexão que está declarada no Web.Config, na chave BancoContexto, conforme a seguir.
<connectionStrings> <add name="BancoContexto" connectionString="Data Source=(localdb)\v11.0; Initial Catalog=BancoContexto-20140814211547; Integrated Security=True; MultipleActiveResultSets=True; AttachDbFilename=|DataDirectory|BancoContexto-20140814211547.mdf" providerName="System.Data.SqlClient" /> </connectionStrings>
No código C# veja como fazer para ler tal chave:
string conexao = WebConfigurationManager.ConnectionStrings["BancoContexto"].ConnectionString;
Em seguida, você verá o código completo da classe já implementada usando o ADO.NET, onde detalho cada método.
O método Get deverá retornar uma lista de produtos, por isto o uso do List<Produto>. Ele recebe uma instrução SQL, abre a conexão com o SqlConnection, executa o comando com o SqlCommand e quando fizer o looping do DataReader com o ExecuteReader, adiciona cada produto na lista chamada dados. Esta lista de dados é que retorna para quem a chamou.
using Mvc_AdoNet.Models; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Web.Configuration; namespace Mvc_AdoNet.DAL { public class DALProduto : IGerenciaCRUD<Produto> { // string de conexao string conexao = WebConfigurationManager.ConnectionStrings["BancoContexto"].ConnectionString; public List<Produto> Get(string sql) { string sql = "Select * FROM Produtoes ORDER BY Nome"; using (var conn = new SqlConnection(conexao)) { var cmd = new SqlCommand(sql, conn); List<Produto> dados = new List<Produto>(); Produto p = null; try { conn.Open(); using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { while (reader.Read()) { p = new Produto(); p.ProdutoID = (int)reader["ProdutoID"]; p.Nome = reader["Nome"].ToString(); p.Preco = (decimal)reader["Preco"]; p.Estoque = (int)reader["Estoque"]; dados.Add(p); } } } finally { conn.Close(); } return dados; } }
Neste método GetById será preciso aplicar um filtro que é o próprio ID declarado como parâmetro do método. Note que o retorno do método é um objeto do tipo Produto. É usado o SqlConnection e é declarada a instrução T-SQL com o cuidado de declarar o parâmetro com o cmd.Parameters no SqlCommand para evitar injeção de SQL por hackers. Em seguida, é usado o ExecuteReader para verificar se há algum registro correspondente; e caso haja, é montado o objeto Produto com as devidas propriedades para retornar no método.
public Produto GetById(int id = 0) { using (var conn = new SqlConnection(conexao)) { string sql = "Select * FROM Produtoes WHERE ProdutoID=@cod"; SqlCommand cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@cod", id); Produto p = null; try { conn.Open(); using (var reader = cmd.ExecuteReader(CommandBehavior.CloseConnection)) { if (reader.HasRows) { if (reader.Read()) { p = new Produto(); p.ProdutoID = (int)reader["ProdutoID"]; p.Nome = reader["Nome"].ToString(); p.Preco = (decimal)reader["Preco"]; p.Estoque = (int)reader["Estoque"]; } } } } finally { conn.Close(); } return p; } }
Veja o método Cadastra que recebe o objeto Produto montado, usa o SqlConnection e o SqlCommand com todos os parâmetros declarados, e na execução do ExecuteNonQuery é inserido o produto no banco de dados. Cabe ressaltar a escrita da instrução Insert do T-SQL com os parâmetros. Muitos desenvolvedores usam a concatenação de strings para montar o T-SQL, sem ter o cuidado de declarar os parâmetros. Isto é uma porta aberta para injeção de códigos maliciosos, portanto, atente a isto.
public void Cadastra(Produto obj) { using (var conn = new SqlConnection(conexao)) { string sql = "INSERT INTO Produtoes (Nome, Preco, Estoque) VALUES (@nome, @preco, @estoque)"; SqlCommand cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@nome", obj.Nome); cmd.Parameters.AddWithValue("@preco", obj.Preco); cmd.Parameters.AddWithValue("@estoque", obj.Estoque); try { conn.Open(); cmd.ExecuteNonQuery(); } finally { conn.Close(); } } }
Já o método Atualiza recebe o objeto Produto montado, usa o SqlConnection, monta a instrução Update do T-SQL e no SqlCommand são declarados todos os parâmetros. Ao final, o ExecuteNonQuery efetiva a operação.
public void Atualiza(Produto obj) { using (var conn = new SqlConnection(conexao)) { string sql = "UPDATE Produtoes SET Nome=@nome, Preco=@preco, Estoque=@estoque Where ProdutoID=@cod"; SqlCommand cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@cod", obj.ProdutoID); cmd.Parameters.AddWithValue("@nome", obj.Nome); cmd.Parameters.AddWithValue("@preco", obj.Preco); cmd.Parameters.AddWithValue("@estoque", obj.Estoque); try { conn.Open(); cmd.ExecuteNonQuery(); } finally { conn.Close(); } } }
E para finalizar, temos o método Exclui, que recebe apenas o ID do produto, monta a instrução Delete do T-SQL e finaliza com a execução do ExecuteNonQuery.
public void Exclui(int id = 0) { using (var conn = new SqlConnection(conexao)) { string sql = "DELETE Produtoes Where ProdutoID=@cod"; SqlCommand cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@cod", id); try { conn.Open(); cmd.ExecuteNonQuery(); } finally { conn.Close(); } } } } }
A dica para a passagem de parâmetros é que, às vezes, é preciso passar uma coleção de parâmetros, pois muitas vezes montam-se os critérios da clausula Where do T-SQL dinamicamente. Neste caso, indico que seja um List<> de dicionário, tipo uma HashTable chave e valor. Assim, na hora de montar o T-SQL você faz um looping nesta coleção List.
Controller com o ADO.NET ao invés do Entity Framework
No início do artigo, eu disse que criei o Controller baseado no Entity Framework, assim todas as Actions são criadas automaticamente. Agora que já temos a classe DALProduto criada, é preciso alterar algumas coisas no Controller ProdutoController, a fim de adaptar o código para o uso do ADO.NET.
Veja a seguir a listagem completa do Controller ProdutoController.cs com as referências dos usings necessários, os códigos comentados do Entity Framework e os novos códigos que fazem referência à classe DALProduto. Cada Action está bem descrita e documentada, a fim de melhorar o entendimento e as chamadas dos métodos da classe de acesso a dados.
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Linq; using System.Web; using System.Web.Mvc; using Mvc_AdoNet.Models; using System.Data.SqlClient; using Mvc_AdoNet.DAL; namespace Mvc_AdoNet.Controllers { public class ProdutoController : Controller { //private BancoContexto db = new BancoContexto(); DALProduto DAL = new DALProduto(); // // GET: /Produto/ public ActionResult Index() { // usando EF //return View(db.Produtos.ToList()); // usando ADO.NET com DAL return View(DAL.Get().ToList()) } // // GET: /Produto/Details/5 public ActionResult Details(int id = 0) { // usando EF //Produto produto = db.Produtos.Find(id); //usando ADO.NET com DAL Produto produto = DAL.GetById(id); if (produto == null) { return HttpNotFound(); } return View(produto); } // // GET: /Produto/Create public ActionResult Create() { return View(); } // // POST: /Produto/Create [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(Produto produto) { if (ModelState.IsValid) { // usando EF //db.Produtos.Add(produto); //db.SaveChanges(); // usando ADO.NET com DAL DAL.Cadastra(produto); return RedirectToAction("Index"); } return View(produto); } // // GET: /Produto/Edit/5 public ActionResult Edit(int id = 0) { // usando EF //Produto produto = db.Produtos.Find(id); //usando ADO.NET com DAL Produto produto = DAL.GetById(id); if (produto == null) { return HttpNotFound(); } return View(produto); } // // POST: /Produto/Edit/5 [HttpPost] [ValidateAntiForgeryToken] public ActionResult Edit(Produto produto) { if (ModelState.IsValid) { // usando EF //db.Entry(produto).State = EntityState.Modified; //db.SaveChanges(); //usando ADO.NET com DAL DAL.Atualiza(produto); return RedirectToAction("Index"); } return View(produto); } // // GET: /Produto/Delete/5 public ActionResult Delete(int id = 0) { // usando EF //Produto produto = db.Produtos.Find(id); //usando ADO.NET com DAL Produto produto = DAL.GetById(id); if (produto == null) { return HttpNotFound(); } return View(produto); } // // POST: /Produto/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] public ActionResult DeleteConfirmed(int id) { // usando EF //Produto produto = db.Produtos.Find(id); //db.Produtos.Remove(produto); //db.SaveChanges(); //usando ADO.NET com DAL DAL.Exclui(id); return RedirectToAction("Index"); } //protected override void Dispose(bool disposing) //{ // db.Dispose(); // base.Dispose(disposing); //} } }
Execução do projeto
Compile todo o projeto e execute-o. Veja que os produtos aparecem no cadastro, afinal quando usei o Migrations para criar o banco, o método Seed já cadastrou uma lista de produtos, conforme a figura 2. Agora basta testar toda a manutenção CRUD dos produtos.

Sugiro que você coloque breakpoints em cada Action do Controller e execute passo a passo F11 para acompanhar que o DALProduto é chamado e executado conforme a ação.
Conclusão
Nem sempre o que há de mais novo se aplica à todos os clientes e aplicações. É preciso pensar no legado, na integração e produtividade. Por isto que neste artigo abordei uma criação inicial com o que há de mais recente e produtivo no MVC com Entity Framework, e depois implementamos a interface e classe de acesso a dados em ADO.NET.
Agradeço a oportunidade de poder compartilhar o conhecimento deste artigo. Qualquer dúvida e preparação de times de desenvolvimento, por favor, me contate.