Desenvolvimento - ASP. NET

Arquitetura de três camadas em um projeto web ASP.NET e C#

Ao desenvolver um aplicativo Web, criar a DAL deve ser uma das suas primeiras etapas, antes de você começar a criar sua camada de apresentação. Com o Visual Studio, criar uma DAL baseada em conjuntos de dados tipados é uma tarefa que você pode realizar de 10 a 15 minutos.

por Daniel Braga Cavalcante



Informação adicional postada em 01/09/2008:
O presente artigo é uma tradução do artigo original em inglês de autoria de Scott Mitchell.
http://msdn.microsoft.com/pt-br/library/aa581776(en-us).aspx.

A equipe do portal Linha de Código agradece ao Marcelo e ao Ricardo, nossos leitores pelo alerta e aproveita para informar aos autores que todo artigo traduzido DEVE OBRIGATORIAMENTE vir com link e nome do autor, e precisa ter autorização do autor original para tradução e publicação.


Introdução

Tutorial 1: Criando uma camada de acesso a dados

Ao desenvolver um aplicativo Web, criar a DAL (Data Accesss Layer) deve ser uma das suas primeiras etapas, antes de você começar a criar sua camada de apresentação. Com o Visual Studio, criar uma DAL baseada em conjuntos de dados tipados é uma tarefa que você pode realizar de 10 a 15 minutos.

Índice do Tutorial 1 (Visual C#)

Introdução
Passo 1: Criando um Web Project and Conectando-o ao Database
Passo 2: Criando a Camada de Acesso a Dados (Data Access Layer - DAL)
Passo 3: Adicionando Métodos Parametrizados a Camada de acesso a dados
Passo 4: Inserindo, Atualizando e Deletando Dados
Passo 5: Finalizando a Camada de Acesso a Dados (Data Access Layer - DAL)

Introdução

Tal como desenvolvedores Web, nossas vidas giram em torno de trabalhar com dados. Nós criamos bases de dados para armazenar os dados, codificamos para recuperá-lo e modificá-lo, e páginas web para coletar e resumir-los. Este é o primeiro tutorial em uma longa série que irá explorar técnicas para a implementação destes padrões comuns em ASP.NET 2.0. Nós iremos começar a criar uma arquitetura de software composta de um Data Access Layer (DAL) utilizando DataSets Tipados, um Business Logic Layer (BLL) que impõe regras de negócio personalizadas, e uma camada de apresentação composta de páginas ASP.NET que compartilham um mesmo layout. Uma vez que este backend esteja estabelecido, vamos avançar para elaboração de relatórios, mostrando como exibir, resumir, recolher e validar os dados de uma aplicação web.

Neste tutorial vamos começar criando o Data Access Layer (DAL), seguida pela criação da Business Logic Layer (BLL) no segundo tutorial, e no layout e navegação das páginas no terceiro tutorial. Os tutoriais após o terceiro serão baseados nos fundamentos estabelecidas nos três primeiros.

Passo 1: Criando um Web Project e Conectando-o ao Database

Antes de podermos criar a nossa Data Access Layer (DAL), primeiro temos que criar um web site e configurar nosso banco de dados. Comece criando um novo sistema de arquivos baseado em ASP.NET web site. Para isso, vá ao menu Arquivo e escolha Novo Site da Web, exibindo a caixa de diálogo Novo Web Site. Escolha o modelo ASP.NET Web Site, defina a Location para File System, escolha uma pasta para colocar o web site, e defina a linguagem para C #.


Figura 1. Criando um Novo Sistema de Arquivos Baseado em ASP.NET Web Site

Isto irá criar um novo web site com uma página ASP.NET Default.aspx e uma pasta App_Data.

Com o web site criado, o próximo passo é adicionar uma referência para o banco de dados no Visual Studio Server Explorer. Ao adicionar um banco de dados ao Server Explorer você pode adicionar tabelas, stored procedures, views, todos de dentro do Visual Studio. Você pode também ver os dados da tabela ou criar suas próprias consultas quer à mão ou graficamente através do Query Builder.

Conectando ao Database no Microsoft SQL Server 2000 ou 2005 Database Server

Você deve conectar-se a um banco de dados Northwind. Caso o seu servidor de banco de dados não tenha o banco de dados Northwind, será necessário adiciona-lo ao seu SGBD fazendo o download do script de instalação da versão SQL Server 2000 do Northwind diretamente do site da Microsoft.

Uma vez que você tenha o database instalado, vá ao Server Explorer no Visual Studio, clique com o botão direito em Data Connections, e escolha Add Connection. Se o Server Explorer não estiver visível para você, vá a View / Server Explorer, ou digite Ctrl+Alt+S. Com isso irá surgir a janela Add Connection, onde você pode especificar o servidor a se conectar, a informação de autenticação, além do nome do database. Uma vez configurado a informação de conexão do database e clicado OK, o database será adicionado ao Data Connections. Você pode expandir o nó do database para explorar suas tabelas, visões, stored procedures, e etc.


Figura 2. Adicionando uma conexão ao Northwind Database ao seu Servidor de Banco de Dados

Passo 2: Criando o Data Access Layer

Ao trabalhar com os dados uma opção é incorporar os dados específicos de uma lógica diretamente na camada de apresentação (no caso de uma aplicação web, as páginas ASP.NET compõem a camada de apresentação). Isto pode assumir a forma de escrever código ADO.NET em porções de códigos de páginas ASP.NET ou utilizando o controle SqlDataSource. Em ambos os casos, esta abordagem rigorosamente une a lógica de acesso ao dados com a camada de apresentação. A abordagem recomendada, porém, é a de separar a lógica de acesso aos dados da camada de apresentaçã. Esta camada separada é referenciada como Data Access Layer (DAL), e é geralmente implementada como um projeto separado Class Library.

Todo o código que é específico do data source subjacente - como a criação de uma conexão para o banco de dados, emissão de comandos SELECT, INSERT, UPDATE, e DELETE, e assim por diante - deve ser localizada no DAL. A camada de apresentação não deve conter quaisquer referências a estes códigos de acesso aos dados, mas sim fazer chamadas para o DAL para qualquer e todas as requisições de dados. Data Access Layers normalmente contêm métodos para acesso aos dados do banco de dados subjacente. O banco de dados Northwind, por exemplo, tem as tabelas produtos e categorias que registram os produtos à venda e as categorias a que pertencem. No nosso DAL teremos métodos como:
  • GetCategories(), retorna os dados de todas as categorias
  • GetProducts(), retorna os dados de todos os produtos
  • GetProductsByCategoryID(categoryID), retorna todos os produtos de uma categoria específica
  • GetProductByProductID(productID), retorna todas as informações sobre um produto em particular
Estes métodos, quando invocados, irão conectar ao banco de dados, efetuar a consulta apropriada e retornar os resultados. Como podemos retornar estes resultados é importante. Estes métodos poderiam simplesmente retornar um DataSet ou DataReader preenchidos por um consulta ao database, porém o ideal seria que esses resultados fossem retornados usando um objeto fortemente tipado. Um objeto fortemente tipado é aquele cujo schema é definido rigorosamente em tempo de compilação, enquanto que o contrário, um objeto fracamente tipado, é aquele cujo schema não é conhecido até o runtime.

Por exemplo, o DataReader e o DataSet (por padrão) são objetos fracamente tipados, uma vez que seu schema é definido pelas colunas retornadas pela query (consulta ao banco de dados) usadas para preenche-las. Para acessar uma coluna em particular de um DataTable fracamente tipado nós precisamos usar uma sitaxe do tipo: DataTable.Rows[index]["columnName"]. O DataTable fracamente tipado neste exemplo é exibido pelo fato de que nós precisamos acessar o nome da coluna usando uma string ou um índice ordinal. Em um DataTable fortemente tipado, por outro lado, nós teremos cada uma de suas colunas implementadas como propriedades, resultando em um código parecido com este: DataTable.Rows[index].columnName.

Para retornar obejtos fortementes tiapdos, desenvolvedores podem criar seus próprios objetos de negócios ou usar DataSets Tipados. Um objeto de negócio (business object) é implementado pelo desenvolvedor como uma clase cujas propriedades tipicamente refletem as colunas da tabela do database adjacente que representa o objeto de negócio. Um DataSet tipado é uma classe gerada para você pelo Visual Studio baseado no schema do database e cujos membros são fortementes tipados de acordo com este schema.O próprio DataSet Tipado consiste de classes que extendem as classes ADO.NET DataSet, DataTable e DataRow. Adicionalmente aos DataTables fortementes tipados, DataSets Tipados agora também incluem TableAdapters, os quais são classes com métodos para preencherem os DataTabels do DataSets e propagarem modificações dentro do DataTable de volta para o database.

Nós iremos utilizar DataSets fortementes tipados na arquitetura deste tutorial. A Figura 3 ilustra o workflow entre as diferentes camadas de uma aplicação que usar DataSets Tipados.


Figura 3. Todo o código de acesso aos dados é delegado ao DAL

Criando um DataSet Tipado e um Tabel Adapter

Para começar a criação de nossa DAL, nós iniciamos adicionando um DataSet Tipado. Para isso, clique com o botão direito no nodo do projeto no Solution Explorer e escolha Add a New Item. Selecione a opção DataSet da lista de templates e renomeia para Northwind.xsd.


Figura 4. Selecione Adicionar um Novo DataSet para Seu Projeto

Após clicar em Add, quando perguntado de adicionar o DataSet a pasta App-Code, clique em Yes. O Designer para o DataSet tipado será então mostrado, e o Wizard de Configuração do TableAdapter será iniciado, permitindo que você adicione seu primeiro TabelAdapter ao DataSet Tipado.

Um DataSet tipado serve como uma coleção de dados fortemente tipado; é composto de instâncias de DataTable fortemente tipado, cada qual composto de instâncias de DataRow fortemente tipado. Nós criaremos um DataTable fortemente tipado para cada tabela do database subjacente que nós precismano para trabalhar nessa série de tutoriais, Vamos começar criando um DatatTable para a tabela Procutos.

Tenha em mente que DataTables fortementes tipados não incluem nenhuma informação sobre como acessar os dados de suas tabelas do database adjacente. Para recuperar os dados para preencher o DataTable, nós usamos uma classe TableAdapter, que funciona como no Camada de Acessoa aos Dados (Data Access Layer). Para nosso DataTable Produtos, o TableAdapter conterá os métodos - GetProducts(), GetProductByCategoryID(categoryID), entre outros - que invocaremos da camada de apresentação. O papel do DataTable é de servir como um objeto fortemente tipado usado para transmitir dados entre as camadas.

O TableAdapter Configuration Wizard começa com a seleção de qual database trabalharemos. A lista suspensa (drop-down) exibe os databases do Server Explorer. Se você ainda não adicionou a base de dados Northwind ao Server Explorer, você deve clicar no botão New Connection para fazê-lo.


Figura 5. Escolha a base de dados Northwind na lista suspensa

Após selecionar o database e clicar em next, será perguntado se você quer salvar a string de conexão no arquivo Web.config. Salvando a string de conexão evita-se de te-lo codificado nas classes do TableAdapter, o que simplifica as coisas caso a string de conexão venha a mudar no futuro. Se você optar por salvá-la no arquivo de configuração, ela será colocada na seção <connectionStrings>, que pode ser opcionalmente encriptada para melhorar a segurança ou para ser modificada através do nova página ASP.NET 2.0 Property Page com IIS GUI Admin Tool, o qual é ideal para administradores.


Figura 6. Salve a string de conexão no Web.config

Em seguida, nós precisamos definir o schema para o primeiro DataTable fortemente tipado e fornecer o primeiro método para nosso TableAdapter, usado para preencher o DataSet fortemente tipado. Estes dois passos são realizados simultaneamentes através da criação de uma query que retorna as colunas da tabela que queremos refletido em nosso DataTable. No fim do wizard daremos um nome a essa query. Uma vez que foi realizado, este método pode ser invocado a partir de nossa camada de apresentação. O método irá executar a consulta definida e preencherá um DataTable fortemente tipado.

Para começar definindo a consulta SQL é preciso primeiro indicar a forma como queremos que o TableAdapter efetue a consulta. Podemos usar instruções SQL, criar um novo procedimento armazenado, ou usar um procedimento armazenado existente. Para estes tutoriais, usaremos instruções SQL.


Figura 7. Consulta ao dados utilzando uma instrução SQL

Neste ponto nós escreveremos a quer SQL a mão. Quando criamos o primeiro método do TableAdapter quer-se normalmente, que a query retorne aquelas colunas que precisam ser expressadas no DataTable correspondente. Podemos conseguir isso através da criação de uma consulta que retorna todas as colunas e todas as linhas a partir da tabela Produtos:


Figura 8. Digite a quey SQL dentro do textbox Alternativamente, use o Query Builder e construa a query graficamente, como mostrado na Figura 9.


Figura 9. Crie a query graficamente através do Query Editor

Após a criação da query, mas antes de anvaçar para a próxima tela, clique no botão Advanced Options. Nesta tela a única opção selecionada por padrão será "Generate Insert, Update, and Delete statements"; se você iniciar o wizard a partir de um Class Library ou um Windows Project a opção "Use optimistic concurrency" também estará selecionada. Por enquanto permaneça com a opção "Use optimistic concurrence" desmarcada. Nós iremos examinar esta opção futuramente.


Figura 10. Selecione Apenas a Opção "Generate Insert, Update, and Delete statements"

Após clicar OK em Advanced Options, clique em Next para ir para a tela final. Nesta tela seremos questionados para escolher quais métodos serão adicionados ao TableAdapter. Existem duas formas do preenchimentos dos dados:
  • Fill a DataTable - é criado um método que pega uma DataTable como parâmetro e o preenche baseado nos resultados da query. A classe ADO.NET DataAdapter, por exemplo, implementa esse padrão com seu método Fill( ).
  • Return a DataTable - cria um método que retorna um DataTable preenchido com os resultados da query.
O TableAdapter pode implementar um ou ambos desses padrões. Vamos manter as duas caixas marcadas, ainda que usaremos apenas o último padrão ao longosdesses tutoriais. Vamos também renomear o nome genérico do método GetData para GetProducts..

Se a última caixa, "GenerateDBDirectMethods", estiver marcada, os métodos Insert(), Update(), e Delete() são criados para o TableAdapter. Se essa caixa for deixada desmarcada, todos os updates precisarão serem feitos através do método único Update( ) do TableAdapter, o qual pega no DataSet, um DataTable, um simples DataRow ou um array de DataRows. (Se nas propriedades avançadas da figura 9 você desmarcou a opção "Generate Insert, Update, and Delete statements" esta opção nao terá efeito). Deixaremos esta opção marcada.


Figura 11. Altere o Nome do Método de GetData para GetProducts

Ao finalizar esse wizard nós voltaremos ao DataSet Designer, que mostra o DataTable que acabmos de criar. Podemos ver tabém a lista das colunas do DataTable Products (ProductID, ProducName, etc), bem com os métodos de ProductsTableAdapter (Fill ( ) e GetProducts ( ) ).


Figura 12. O DataTable Products e o ProductsTableAdapter foram adicionados ao DataSet

Neste ponto nós temos um simples DataTable (Northwind.Products) e uma classe DataAdapter fortemente tipada (NorthwindTableAdapters.ProductsTableAdapter) com um método GetProducts( ). Estes objetos pode ser usados para acessar um lista de todos os produtos a partir de um código como:
NorthwindTableAdapters.ProductsTableAdapter productsAdapter = new 
NorthwindTableAdapters.ProductsTableAdapter();
Northwind.ProductsDataTable products;

products = productsAdapter.GetProducts();

foreach (Northwind.ProductsRow productRow in products)
    Response.Write("Produto: " + productRow.ProductName + "<br />");
Este código não nos exige que escrevamos um bit de código de acesso a dados. Não temos que instanciar nenhuma classe ADO.NET, não temos que referenciar nenhuma string de conexão, querys SQL ou stored procedures. Em vez disso, o TableAdapter nos proporciona o código acesso aos dados de baixo nível para nós.

Cada objeto usado neste exemplo também é fortemente tipado, permitindo que o Visual Studio ofereça o IntelliSense e a checagem de tipos em tempo de compilação. E o melhor de tudo é que o todos os DataTables retornados pelo TableAdapter podem ser vinculados ao controles Web ASP.NET, tais como GridView, DetailsView, DropDownList, CheckBoxList, e muitos outros. O exemplo a seguir ilustra o bidding do DataTable retornado pelo método GetProducts( ) para um GridView em apenas três linhas de código com o evento Page_Load.
AllProducts.aspx

<%@ Page Language="C#" AutoEventWireup="true" 
CodeFile="AllProducts.aspx.cs" Inherits="AllProducts" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html  >
<head runat="server">
    <title>View All Products in a GridView</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>
            All Products</h1>
        <p>
            <asp:GridView ID="GridView1" runat="server" CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
            </p>
    
    </div>
    </form>
</body>
</html>

AllProducts.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;

public partial class AllProducts : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
        GridView1.DataSource = productsAdapter.GetProducts();
        GridView1.DataBind();
    }
}

Figura 13. A Lista dos Produtos é Mostrado em um GridView

Enquanto que com esse exmplo requer que escrevamos três linhas de código me nosso evento Page_Load de nossa página ASP.NET, futuramente nós iremos examinar como usar o objeto ObjectDataSource para recuperar os dados a partir do DAL. Com o ObjectDataSource nós não teremos que escrever nenhum código além de termos suporte a paginação e de sorting.

Passo 3: Adicionando Métodos Parametrizados ao Data Access Layer

Neste ponto nossa classe PorductsTableAdapter tem apenas um método GetProducts( ), que retorna todos os produtos de nossa base de dados. Embora trabalhar com todos os produtos é definitivamente útil, existem momentos em que queremos obter informações de um produto específico, ou ainda todos os produtos de uma determinada categoria. Para adicionar essa funcionalidade ao nosso DAL nós podemos adicionar métodos parametrizados ao TableAdapter.

Vamos adicionar o método GetProductsByCategoryID(categoryID). Este método retorna os produtos de uma determinada categoria. Para adicionar um novo método ao DAL, vá ao DataSet Designer, clique com o botão direito na seção ProductsTableAdapter e escolha Add Query.


Figura 14. Clique com o Botão Direito no TableAdapter e Escolha Add Query

Na tela que surge somos indagados sobre como queremos acessar nosso database, se usando uma declaração SQL ou uma nova ou existente stored procedure. Vamos escolher uma declaração SQL novamente. Em seguida é perguntado que tipo de query SQL nós gostaríamos de usar. Uma vez que queremos retornar todos os produtos de uma categoria específica, escolheremos a primeira opção, visto que queremos escrever uma declaração SELECT que retorna linhas.


Figura 15. Escolha SELECT Wich Return Rows

O próximo passo é difinir a query SQL a ser usada para acessar os dados. Como queremos retornar apenas aqueles produtos de uma categoria em particular, usaremos a mesma declaração SELECT de GetProducts( ), mas adicionaremos a seguinte cláusula WHERE: WHERE CategoryID = @CategoryID. O parâmetro @CategoryID indica ao wizard do TableAdapter que o método que criaremos irá requerer um parâmetro de entrada de tipo correspondente.


Figura 16. Digite uma Query para Retornar Apenas os Produtos de uma Categoria Específica Por fim podemos escolher que padrão de acesso a dados usar, bem como customizar os nomes dos métodos gerados. Para o padrão Fill, vamos mudar o nome para FillByCategoryID e para o padrão Return a DataTable (os métodos GetX), vamos digitar GetProductsByCategoryID.


Figura 17. Escolha os Nome para os Métodos do TableAdapter

Após completar este wizard, o DataSet Designer inclue os novos métodos do TableAdapter


Figura 18. Os Produtos Agora podem ser Selecionados por Categoria

Aproveite também para adicionar um método GetProductByProductID(productID) usando a mesma técnica. Estas consultas parametrizadas pode ser testadas diretamente doDataSet Designer. Clique com o botão direito no método no TableAdapter e clique em Preview Data. Em seguida, digite os valores para usar com os parâmetros e clique em Preview.


Figura 19. Os produtos da categoria Bebidas são exibidos

Com o método GetProductsByCategoryID(categoryID) em nosso DAL, podemos agora criar uma página ASP.NET que exibe apenas aqueles produtos de uma categoria específica. O exemplo abaixo mostra todos os produtos que estão na categoria Bebidas, a qual tem o valor de CategoryID igual a 1.
Beverages.aspx

<%@ Page Language="C#" AutoEventWireup="true" 
CodeFile="Beverages.aspx.cs" Inherits="Beverages" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html  >
<head runat="server">
    <title>Untitled Page</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>Beverages</h1>
        <p>
            <asp:GridView ID="GridView1" runat="server" CssClass="DataWebControlStyle">
               <HeaderStyle CssClass="HeaderStyle" />
               <AlternatingRowStyle CssClass="AlternatingRowStyle" />
            </asp:GridView>
            </p>
    </div>
    </form>
</body>
</html>

Beverages.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;

public partial class Beverages : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
        GridView1.DataSource = productsAdapter.GetProductsByCategoryID(1);
        GridView1.DataBind();
    }
}

Figura 20. Os produtos da categoria Bebidas são exibidos

Passo 4: Inserindo, Atualizando e Deletando Dados

Existem dois padrões comumentes usados para inserir, atualizar e deletar dados. O primeiro padrão, que chamaremos de padrão direto ao database, envolve a criação de métodos que, quando invocados, emite um comando INSERT, UPDATE ou DELETE para o database que opera em um único registro de banco de dados simples. Esses métodos são normalmente passados em uma série de valores escalares (Integers, strings, Booleans, DateTimes, e outros) que correspondem ao valores que serão inseridos, atualizados ou deletados. Por exemplo, com esse padrão para a tabela Products o método delete irá pegar um parâmetro integer, indicando o ProductID do registro para deletar, enquanto que o método insert irá pegar uma string para o ProductName, um decimal para UnitPrice, um integer para UnitsOnStock, e assim por diante.


Figura 21. Cada Requisição Insert, Update e Delete é Enviado para o Database Imediatamente

O outro padrão (modelo), que chamaremos de padrão de atualização em lote, é para atualizar um DataSet inteiro, DataTable, ou uma coleção de DataRows em uma única chamada ao método. Com esse modelo, o desenvolvedor deleta, insere e modifica os DataRows em um DataTable e em seguida passa estas DataRows ou DataTable em uma método update. Esse método em seguida enumera as DataRows passadas, determinina se elas foram ou não modificadas, adicionadas, ou deletadas e assegura as requisições apropriadas ao banco de dados para cada registro.


Figura 22. Todas as Mudanças são Sincronizadas com o Database Quando o Método Update é Invocado

O TableAdapter usa o modelo e atualização em lote por padrão, mas também suporta o modelo direto ao BD. Quando selecionamos a opção "Generate Insert, Update, and Delete statements" em Propriedades Avançadas quando criamos nosso TableAdapter, o ProductsTableAdapter comntém um método Update( ), que implementa o modelo de atualização em lote. Especificamente, o TableAdapter contém um método Update( ) que pode ser passado o DataSet tipado, um DataTable fortemente tipado, ou um ou mais DataRows. Se você deixou a opção "GenerateDBDirectMethods" marcado o padrão direto ao BD também será implementado através dos métodos Insert( ), Update( ) e Delete( ).

Ambos modelos de modificações de dados usam as propriedades InsertCommand, UpdateCommand e DeleteCommand do TableAdapterm para emitir seus comandos INSERT, UPDATE e DELETE para o database. Você pode inspecionar e modificar as propriedades InsertCommand, UpdateCommand e DeleteCommand clicando no TableAdapter no DataSet Designer e indo na janela de propriedades (assegure que você selecionou o TableAdapter, e que o objeto ProductsTableAdapter está selecionado na lista suspensa na janela de propriedades).


Figura 23. O TableAdapter tem as Propriedades InsertCommand, UpdateCommand, e DeleteCommand

Para examinar ou modificar qualquer umas dessas propriedades dos comandos ao database, clique na subpropriedade CommandText, que irá abrir o Query Builder.


Figura 24. Configure as Declarações INSERT, UPDATE e DELETE no Query Builder

O exemplo de código a seguir mostra como usar o modelo de atualização em lote para duplicar o preço de todos os produtos que não estão descontinuados e que tem 25 unidades ou menos em estoque:
NorthwindTableAdapters.ProductsTableAdapter productsAdapter = new 
NorthwindTableAdapters.ProductsTableAdapter(); 

// Para cada produto, duplique seu preço se ele não está descontinuado e
// tem 25 unidades ou menos em estoque
Northwind.ProductsDataTable products = productsAdapter.GetProducts();
foreach (Northwind.ProductsRow product in products)
   if (!product.Discontinued && product.UnitsInStock <= 25)
      product.UnitPrice *= 2;

// Atualiza (Update) os produtos
productsAdapter.Update(products);
O código a seguir ilustra como usar o modelo direto ao BD para programaticamente deletar um produto em particular, em seguida atualizar um, e depois adicionar um novo:
NorthwindTableAdapters.ProductsTableAdapter productsAdapter = 
   new NorthwindTableAdapters.ProductsTableAdapter(); 

// Delete o produto com ProductID 3
productsAdapter.Delete(3);

// Update Chai (ProductID igual a 1), setando o UnitsOnOrder para 15
productsAdapter.Update("Chai", 1, 1, "10 boxes x 20 bags", 18.0m, 39, 
15, 10, false, 1);

// Adicionar um novo produto
productsAdapter.Insert("New Product", 1, 1, "12 tins per carton", 
14.95m, 15, 0, 10, false);
Criando Métodos Insert, Update e Delete Customizados

Os métodos Insert(), Update(), and Delete() criados pelo método direto ao BD podem ser um pouco pesado, especialmente para tabelas com muitas colunas. Observando o código do exemplo anterior, sem a ajuda do IntelliSense não é claro qual coluna da tabela Products mapeia cada parâmetro de entrada para os métodos Update( ) e Insert( ) . Pode haver momentos em que só desejamos atualizar uma única coluna ou duas, ou queremos um método Insert( ) customizado que, talvez, retornará o valor do campo IDENTITY(auto-incremento) do novo registro inserido.

Para criar um método customizado, retorne ao DataSet Designer, clqiue com o botão direito no TableAdapter e selecione Add Query, retornado ao wizard do TableAdapter. Na segunda tela nós podemos indicar o tipo de query a criar. Vamos criar um método que adiciona um novo produto e retorna o valor ProductID do novo registro adicionado. Assim, opte por criar uma query INSERT.


Figura 25. Crie um Método para Adicionar um Novo Registro a Tabela Products

Na tela seguinte surge a caixa de texto com o comando insert. Incrementaremos essa query adicionando SELECT SCOPE_IDENTITY( ) no fim da query, o que irá fazer com que o último valor identity inserido em uma coluna IDENTITY no mesmo escopo seja retornado. Certifique-se de terminar a declaração INSERT com uma ponto-e-vírgula antes de adicionar a declaração SELECT.


Figura 26. Incremente a Query para Retornar o Valor SCOPE_IDENTITY( )

Finalmente, nomeie o novo método com o nome InsertProduct.


Figura 27. Nomeie o Novo Método como InsertProduct

Ao retornar ao DataSetDesigner você verá que o ProductsTableAdapter contém um novo método, InsertProduct. Se esse método não tiver um parâmetro para cada coluna na tabela Products, provavelmente será porque você esqueceu de terminar a declaração INSERT com um ponto-e-vírgula. Configure o método InsertProduct e assegure que tem um ponto-e-vírgula deleimitando as declarações INSERT e SELECT.

Por padrão os métodos INSERT retornam o número de linhas afetadas. Entretanto, nós queremos que o método InsertProduct retorne o valor retornado pela query, e não o número de linhas afetadas. Para isso, ajuste a propriedade ExecuteMode do método InsertProduct para Scalar.


Figura 28. Altere a Propriedade ExecuteMode para Scalar

O código a seguir demonstra o novo método InsertProduct em ação:
NorthwindTableAdapters.ProductsTableAdapter productsAdapter = 
   new NorthwindTableAdapters.ProductsTableAdapter();

// Adicionar um novo produto
int new_productID = 
  Convert.ToInt32(productsAdapter.InsertProduct("New Product", 1, 1, 
  "12 tins per carton", 14.95m, 10, 0, 10, false));

// Em seguida, excluir o produto
productsAdapter.Delete(new_productID);
Passo 5: Completando o Data Access Layer

Note que a classe ProductsTableAdapters retorna os valores CategoryID e SupplierID da tabela Products, porém não inclue a coluna CategoryName da tabela Categories ou a coluna CompanyName da tabela Suppliers, embora queremos que elas apareçam quando queremos exibir informações dos produtos. Nós podemos aumentar o método inicial do TableAdapter, GetProducts( ), para incluir ambos valores das colunas CategoryName e CompanyName, que irá atualizar o DataTable para incluir estas novas colunas também.

Isto pode apresentar um problema, uma vez que os métodos do TableAdapter para inserir, atualiza e deletar são baseados nesse método inicial. Felizmente, os métodos auto-gerados para inserir, atualizar e deletar não são afetados por subqueries na cláusula SELECT. Tendo o cuidado de adicionar nossas queries em Categories e Suppliers como subqueries, ao invés de JOINs, evitaremos de ter de reformular os métodos de modifição dos dados. Clique com o botão direito no método GetProducts( ) no ProductsTableAdapter e selecione Configure. Em seguida, ajuste a cláusula SELECT para ficar parecida com a seguinte:
SELECT     ProductID, ProductName, SupplierID, CategoryID, 
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
(SELECT CategoryName FROM Categories 
WHERE Categories.CategoryID = Products.CategoryID as CategoryName, 
(SELECT CompanyName FROM Suppliers 
WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName
FROM         Products

Figura 29. Atualize a Declaração SELECT para o Método GetProducts( )

Após atualizar o método GetProducts( ) para usar essa nova query, o DataTable irá incluir duas novas colunas: CategoryName e SupplierName.


Figura 30. O DataTable Products tem Duas Novas Colunas

Aproveite para atualizar a cláusula SELECT no método GetProductsByCategoryID(categoryID) também.

Se você atualizar o SELECT de GetProducts( ) para usar sintaxe JOIN, o DataSet Designer não poderá gerar automaticamente os métodos para inserir, atualizar e deletar dados do databese usando o modelo direto ao BD. Invés disso, você terá que cirar manualmente parecido como fizemos com o método InsertProduct mais acima nesse tutorial. Além disso, você ter que fornecer manualmente os valores para as propriedades InsertCommand,UpdateCommand e DeleteCommand se quiser utilizar o modelo de atualização em lote.

Adicionando os TableAdapters Restantes

Até agora, apenas trabalhamos com um simples TableAdapter para uma simples tabela de um database. Entretando, o database Northwind contém várias tabelas relacioandas que precicaremos para trabalhar com nossa aplicação web. Um DataSet pode conter múltipolos, DataTables realcionados. Por isso, para completar nosso DAL precisamos adicionar DataTables para as outras tabelas que utilizaremos nesse tutorial. Para adionar um novo TableAdapter a um DataSet, abra o DataSet Designer, clique com o botão direito no Designer, e seelcione Add/Table Adapter. Isso criará um novo DataTable e TableAdapter e o ecaminhará através de um wizard como já vimos nesse tutorial.

Aproveite para criar as seguintes TablesAdapters e métodos utilizando as queries seguinte. Note que as queries em ProductsTableAdapter incluem as subqueries para coletar cada categoria de produtos e os nomes dos fornecedores.

ProductsTableAdapter
GetProducts:

SELECT     ProductID, ProductName, SupplierID, CategoryID, 
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, 
Discontinued , (SELECT CategoryName FROM Categories WHERE 
Categories.CategoryID = Products.ProductID) as CategoryName, (SELECT 
CompanyName FROM Suppliers WHERE Suppliers.SupplierID = 
Products.SupplierID) as SupplierName
FROM         Products

GetProductsByCategoryID:

SELECT     ProductID, ProductName, SupplierID, CategoryID, 
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, 
Discontinued , (SELECT CategoryName FROM Categories WHERE 
Categories.CategoryID = Products.ProductID) as CategoryName, (SELECT 
CompanyName FROM Suppliers WHERE Suppliers.SupplierID = 
Products.SupplierID) as SupplierName
FROM         Products
WHERE      CategoryID = @CategoryID

GetProductsBySupplierID

SELECT     ProductID, ProductName, SupplierID, CategoryID, 
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, 
Discontinued , (SELECT CategoryName FROM Categories WHERE 
Categories.CategoryID = Products.ProductID) as CategoryName, (SELECT 
CompanyName FROM Suppliers WHERE Suppliers.SupplierID = 
Products.SupplierID) as SupplierName
FROM         Products
WHERE SupplierID = @SupplierID

GetProductByProductID

SELECT     ProductID, ProductName, SupplierID, CategoryID, 
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, 
Discontinued , (SELECT CategoryName FROM Categories WHERE 
Categories.CategoryID = Products.ProductID) as CategoryName, (SELECT 
CompanyName FROM Suppliers WHERE Suppliers.SupplierID = 
Products.SupplierID) as SupplierName
FROM         Products
WHERE ProductID = @ProductID
CategoriesTableAdapter
GetCategories

SELECT     CategoryID, CategoryName, Description
FROM         Categories

GetCategoryByCategoryID

SELECT     CategoryID, CategoryName, Description
FROM         Categories
WHERE CategoryID = @CategoryID
SuppliersTableAdapter
GetSuppliers

SELECT     SupplierID, CompanyName, Address, City, Country, Phone
FROM         Suppliers

GetSuppliersByCountry

SELECT     SupplierID, CompanyName, Address, City, Country, Phone
FROM         Suppliers
WHERE Country = @Country

GetSupplierBySupplierID

SELECT     SupplierID, CompanyName, Address, City, Country, Phone
FROM         Suppliers
WHERE SupplierID = @SupplierID
EmployeesTableAdapter
GetEmployees

SELECT     EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country
FROM         Employees

GetEmployeesByManager

SELECT     EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country
FROM         Employees
WHERE ReportsTo = @ManagerID

GetEmployeeByEmployeeID

SELECT     EmployeeID, LastName, FirstName, Title, HireDate, ReportsTo, Country
FROM         Employees
WHERE EmployeeID = @EmployeeID
Adicionando Código Customizado ao DAL

O TableAdapters e DataTables adicionados ao DataSet são expressados como arquivos de Definição de Schema XML (Northwind.xsd). Você pode visualizar esta informação de schema clicando com o botão direito no arquivo Northwind.xsd no Solution Explorer e em seguida clicando em View Code.


Figura 31. O Arquivo (XSD) de Definição do Schema XML para o DataSet Northwind

Essa informacao de schema é traduzida em código C# ou Visual Basic em tempo de design quando compilad ou no runtime (se necessário). Para visualizar esse código gerado automaticamente, vá ao Class View e desça até as classe do TableAdapter ou do DataSet. Se você não visualizar o Class View em sua tela, vá ao menu View e selecione de lá, ou tecle Ctrl+Shift+C. A partir do Class VIew você pode ver as propriedades, métodos, e eventos das classes do DataSet e TableAdapter. Para ver o código para um método em particular, de um duplo-clique no nome do método no Class View ou clique com o botão direito nele e selecione Go To Definition.


Figura 32. Inspecione o Código Gerado Automaticamente Selecionando Go To Definition a partir do Class View

Enquanto o código gerado automaticamente pode salvar muito tmepo, o código é muito genérico e precisa ser customizado para suprir as necessidades únicas da aplicação. O risco de extender código gerado automaticamente é que a ferramente que gerou o código possa decidir a qualquer tempo "regerar" e substituir suas customizações. Com o novo conceito de classe parcial do .NET 2.0 facilmente dividimos uma classe através de vários arquivos. Isto nos permite adicionar nossos próprios métodos, propriedades e eventos as classes auto-geredas sem se preocupar com as substituticoes e customizações do Visual Studio.

Para demosntrar como customizar o DAL, vamos adicionar um método GetProducts( ) a classe SuppliersRow. A classe SuppliersRowrepresenta um registro simples na tabela Suppliers; cada fornecedor (supplier) pode fornercer zero ou muito produtos, assim GetProducts( ) irá retornar aqueles produtos de um fornecedor específico. Para isso crie um nova classe na pasta App_Code chamada SuppliersRow.cs e copie o seguinte código:
using System;
using System.Data;
using NorthwindTableAdapters;

public partial class Northwind
{
    public partial class SuppliersRow
    {
        public Northwind.ProductsDataTable GetProducts()
        {
            ProductsTableAdapter productsAdapter = new ProductsTableAdapter();
            return productsAdapter.GetProductsBySupplierID(this.SupplierID);
        }
    }
}
Esta classe parcial instrue o compilador que quando estiver gerando a classe Northwind.SuppliersRow para incluir o método GetProducts( ) que definimos. Se você der um build em seu projeto e em seguida retornar ao Class View, você verá que GetProducts( ) agora está lsitado como um método de Northwind.SuppliersRow.


Figura 33. O Método GetProducts( ) Agora é Parte da Classe Northwind.SuppliersRow

O método GetProducts( ) agora pode ser usado para enumerar os produtos de um fornecedor em particular, como demosntra o código a seguir:
NorthwindTableAdapters.SuppliersTableAdapter suppliersAdapter = new 
NorthwindTableAdapters.SuppliersTableAdapter();

// Pegue todos os fornecedores
Northwind.SuppliersDataTable suppliers = suppliersAdapter.GetSuppliers();

// Enumere os fornecedores
foreach (Northwind.SuppliersRow supplier in suppliers)
{
    Response.Write("Fornecedor: " + supplier.CompanyName);
    Response.Write("<ul>");

    // Liste os produtos para esse fornecedor
    Northwind.ProductsDataTable products = supplier.GetProducts();
    foreach (Northwind.ProductsRow product in products)
        Response.Write("<li>" + product.ProductName + "</li>");

    Response.Write("</ul><p></p>");
}
Esses dados podem também ser exibidos em qualquer controle web dados ASP.NET. A seguir, uma página que usa um controle GridView com dois campos:
  • Um BoundField que exibe o nome de cada fornecedor, e
  • Um TemplateField que contém um controle BulletedList que faz um bound aos resultados retornados pelo método GetProducts( ) de cada fornecedor.
Veremos como exibir um relatório tipo mestre-detalhe em tutoriais futuros. Por enquanto, o exemplo seguinte foi designado para ilustrar o método customizado adicionado a classe Northwind.SuppliersRow.
SuppliersAndProducts.aspx

<%@ Page Language="C#" AutoEventWireup="true" 
CodeFile="SuppliersAndProducts.aspx.cs" Inherits="SuppliersAndProducts" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html  >
<head runat="server">
    <title>Untitled Page</title>
    <link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h1>
            Fornecedores e seus produtos</h1>
        <p>
            <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" 
CssClass="DataWebControlStyle">
                <HeaderStyle CssClass="HeaderStyle" />
                <AlternatingRowStyle CssClass="AlternatingRowStyle" />
                <Columns>
                    <asp:BoundField DataField="CompanyName" HeaderText="Supplier" />
                    <asp:TemplateField HeaderText="Products">
                        <ItemTemplate>
                            <asp:BulletedList ID="BulletedList1" runat="server" DataSource="<%# 
((Northwind.SuppliersRow)((System.Data.DataRowView) Container.DataItem).Row).GetProducts() %>"
                                    DataTextField="ProductName">
                            </asp:BulletedList>
                        </ItemTemplate>
                    </asp:TemplateField>
                </Columns>
            </asp:GridView>
            </p>
    
    </div>
    </form>
</body>
</html>

SuppliersAndProducts.aspx.cs

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;

public partial class SuppliersAndProducts : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        SuppliersTableAdapter suppliersAdapter = new SuppliersTableAdapter();
        GridView1.DataSource = suppliersAdapter.GetSuppliers();
        GridView1.DataBind();
    }
}

Figura 34. O Nome da Companhia Fornecedora está Listada na Coluna da Esquerda, e Seus Produtos na Coluna da Direita
Daniel Braga Cavalcante

Daniel Braga Cavalcante - Graduado em Ciências da Computação pelo UNIPÊ, MBA em Tecnologia da Informação pelo UNIPÊ e Pós-Graduação em Desenvolvimento para Web pelo UNIBRATEC. Atualmente trabalha como consultor para entidades públicas municipais, e como desenvolvedor de sistemas autônomo.