Criação de uma Camada de Acesso a Dados (VB)
por Scott Mitchell
Neste tutorial, começaremos desde o início e criaremos a DAL (Camada de Acesso a Dados), usando DataSets tipado, para acessar as informações em um banco de dados.
Introdução
Como desenvolvedores web, nossas vidas giram em torno de trabalhar com dados. Criamos bancos de dados para armazenar os dados, o código para recuperá-los e modificá-los e páginas da Web para coletar e resumir. Este é o primeiro tutorial de uma longa série que explorará técnicas para implementar esses padrões comuns no ASP.NET 2.0. Começaremos com a criação de uma arquitetura de software composta por uma DAL (Camada de Acesso a Dados) usando Conjuntos de Dados Tipados, uma BLL (Camada lógica de negócios) que impõe regras de negócios personalizadas e uma camada de apresentação composta por ASP.NET páginas que compartilham um layout de página comum. Depois que essa base de back-end for estabelecida, passaremos para relatórios, mostrando como exibir, resumir, coletar e validar dados de um aplicativo Web. Esses tutoriais são voltados para serem concisos e fornecem instruções passo a passo com muitas capturas de tela para orientá-lo visualmente. Cada tutorial está disponível nas versões C# e Visual Basic e inclui um download do código completo usado. (Este primeiro tutorial é bastante longo, mas o restante é apresentado em partes muito mais digestíveis.)
Para esses tutoriais, usaremos uma versão do Microsoft SQL Server 2005 Express Edition do banco de dados Northwind colocada no App_Data
diretório . Além do arquivo de banco de dados, a App_Data
pasta também contém os scripts SQL para criar o banco de dados, caso você deseje usar uma versão de banco de dados diferente. Se você usar uma versão SQL Server diferente do banco de dados Northwind, precisará atualizar a NORTHWNDConnectionString
configuração no arquivo do Web.config
aplicativo. O aplicativo Web foi criado usando o Visual Studio 2005 Professional Edition como um projeto de site baseado em sistema de arquivos. No entanto, todos os tutoriais funcionarão igualmente bem com a versão gratuita do Visual Studio 2005, Visual Web Developer.
Neste tutorial, começaremos desde o início e criaremos a DAL (Camada de Acesso a Dados), seguida pela criação da BLL (Camada lógica de negócios) no segundo tutorial e o trabalho no layout e na navegação da página no terceiro. Os tutoriais após o terceiro serão compilados sobre a base estabelecida nos três primeiros. Temos muito a abordar neste primeiro tutorial, então acione o Visual Studio e vamos começar!
Etapa 1: Criando um projeto Web e conectando-se ao banco de dados
Antes de podermos criar nossa DAL (Camada de Acesso a Dados), primeiro precisamos criar um site e configurar nosso banco de dados. Comece criando um novo site de ASP.NET baseado no sistema de arquivos. Para fazer isso, acesse o menu Arquivo e escolha Novo Site, exibindo a caixa de diálogo Novo Site. Escolha o modelo ASP.NET Site, defina a lista suspensa Localização como Sistema de Arquivos, escolha uma pasta para colocar o site e defina o idioma como Visual Basic.
Figura 1: Criar um novo arquivo System-Based site (clique para exibir a imagem em tamanho real)
Isso criará um novo site com uma Default.aspx
página ASP.NET, uma App_Data
pasta e um Web.config
arquivo.
Com o site criado, a próxima etapa é adicionar uma referência ao banco de dados no servidor do Visual Studio Explorer. Ao adicionar um banco de dados ao Servidor Explorer você pode adicionar tabelas, procedimentos armazenados, exibições e assim por diante de dentro do Visual Studio. Você também pode exibir dados de tabela ou criar suas próprias consultas manualmente ou graficamente por meio do Construtor de Consultas. Além disso, quando compilarmos os Conjuntos de Dados Tipados para o DAL, precisaremos apontar o Visual Studio para o banco de dados do qual os Conjuntos de Dados Tipados devem ser construídos. Embora possamos fornecer essas informações de conexão nesse momento, o Visual Studio preenche automaticamente uma lista suspensa dos bancos de dados já registrados no servidor Explorer.
As etapas para adicionar o banco de dados Northwind ao servidor Explorer dependem se você deseja usar o banco de dados SQL Server 2005 Express Edition na App_Data
pasta ou se você tem uma configuração de servidor de banco de dados do Microsoft SQL Server 2000 ou 2005 que você deseja usar.
Usando um banco de dados naApp_Data
pasta
Se você não tiver um servidor de banco de dados SQL Server 2000 ou 2005 para se conectar ou simplesmente quiser evitar a necessidade de adicionar o banco de dados a um servidor de banco de dados, poderá usar a versão SQL Server 2005 Express Edition do banco de dados Northwind localizado na pasta do App_Data
site baixado (NORTHWND.MDF
).
Um banco de dados colocado na App_Data
pasta é adicionado automaticamente ao servidor Explorer. Supondo que você tenha SQL Server 2005 Express Edition instalado em seu computador, deverá ver um nó chamado NORTHWND. MDF no servidor Explorer, que você pode expandir e explorar suas tabelas, exibições, procedimento armazenado e assim por diante (consulte a Figura 2).
A App_Data
pasta também pode conter arquivos do Microsoft Access.mdb
, que, como seus equivalentes SQL Server, são adicionados automaticamente à Explorer do Servidor. Se você não quiser usar nenhuma das opções de SQL Server, sempre poderá instalar o banco de dados e os aplicativos da Northwind Traders e entrar no App_Data
diretório. Tenha em mente, no entanto, que os bancos de dados do Access não são tão ricos em recursos quanto SQL Server e não foram projetados para serem usados em cenários de site. Além disso, alguns dos mais de 35 tutoriais utilizarão determinados recursos no nível do banco de dados que não têm suporte do Access.
Conectando-se ao banco de dados em um Servidor de Banco de Dados do Microsoft SQL Server 2000 ou 2005
Como alternativa, você pode se conectar a um banco de dados Northwind instalado em um servidor de banco de dados. Se o servidor de banco de dados ainda não tiver o banco de dados Northwind instalado, primeiro você deverá adicioná-lo ao servidor de banco de dados executando o script de instalação incluído no download deste tutorial.
Depois de instalar o banco de dados, vá para a Explorer servidor no Visual Studio, clique com o botão direito do mouse no nó Connections dados e escolha Adicionar Conexão. Se você não vir o servidor Explorer ir para a Explorer Exibir/Servidor ou pressione Ctrl+Alt+S. Isso abrirá a caixa de diálogo Adicionar Conexão, na qual você pode especificar o servidor ao qual se conectar, as informações de autenticação e o nome do banco de dados. Depois de configurar com êxito as informações de conexão do banco de dados e clicar no botão OK, o banco de dados será adicionado como um nó abaixo do nó Data Connections. Você pode expandir o nó de banco de dados para explorar suas tabelas, exibições, procedimentos armazenados e assim por diante.
Figura 2: Adicionar uma conexão ao banco de dados Northwind do servidor de banco de dados
Etapa 2: Criando a camada de acesso a dados
Ao trabalhar com dados, uma opção é inserir a lógica específica de dados diretamente na camada de apresentação (em um aplicativo Web, as páginas ASP.NET compõem a camada de apresentação). Isso pode assumir a forma de escrever ADO.NET código na parte de código da página ASP.NET ou usar o controle SqlDataSource da parte de marcação. Em ambos os casos, essa abordagem associa firmemente a lógica de acesso a dados com a camada de apresentação. A abordagem recomendada, no entanto, é separar a lógica de acesso a dados da camada de apresentação. Essa camada separada é chamada de Camada de Acesso a Dados, DAL para abreviar e normalmente é implementada como um projeto separado da Biblioteca de Classes. Os benefícios dessa arquitetura em camadas estão bem documentados (consulte a seção "Leituras adicionais" no final deste tutorial para obter informações sobre essas vantagens) e é a abordagem que adotaremos nesta série.
Todo o código específico para a fonte de dados subjacente, como a criação de uma conexão com o banco de dados, a emissão SELECT
de comandos , INSERT
, UPDATE
e DELETE
assim por diante, deve estar localizado no DAL. A camada de apresentação não deve conter referências a esse código de acesso a dados, mas deve, em vez disso, fazer chamadas para o DAL para todas e quaisquer solicitações de dados. As Camadas de Acesso a Dados normalmente contêm métodos para acessar os dados de banco de dados subjacentes. O banco de dados Northwind, por exemplo, tem Products
tabelas e Categories
que registram os produtos à venda e as categorias às quais pertencem. Em nosso DAL, teremos métodos como:
GetCategories(),
que retornará informações sobre todas as categoriasGetProducts()
, que retornará informações sobre todos os produtosGetProductsByCategoryID(categoryID)
, que retornará todos os produtos que pertencem a uma categoria especificadaGetProductByProductID(productID)
, que retornará informações sobre um produto específico
Esses métodos, quando invocados, se conectarão ao banco de dados, emitirão a consulta apropriada e retornarão os resultados. A forma como retornamos esses resultados é importante. Esses métodos podem simplesmente retornar um DataSet ou DataReader preenchido pela consulta de banco de dados, mas, idealmente, esses resultados devem ser retornados usando objetos fortemente tipados. Um objeto fortemente tipado é aquele cujo esquema é rigidamente definido em tempo de compilação, enquanto o oposto, um objeto de tipo flexível, é aquele cujo esquema não é conhecido até o runtime.
Por exemplo, o DataReader e o DataSet (por padrão) são objetos de tipo flexível, pois seu esquema é definido pelas colunas retornadas pela consulta de banco de dados usada para preenchê-los. Para acessar uma coluna específica de um DataTable com tipo flexível, precisamos usar a sintaxe como: DataTable.Rows(index)("columnName")
. A digitação solta do DataTable neste exemplo é exibida pelo fato de que precisamos acessar o nome da coluna usando uma cadeia de caracteres ou um índice ordinal. Um DataTable fortemente tipado, por outro lado, terá cada uma de suas colunas implementadas como propriedades, resultando em um código semelhante a: DataTable.Rows(index).columnName
.
Para retornar objetos fortemente tipados, os desenvolvedores podem criar seus próprios objetos de negócios personalizados ou usar Conjuntos de Dados Digitados. Um objeto de negócios é implementado pelo desenvolvedor como uma classe cujas propriedades normalmente refletem as colunas da tabela de banco de dados subjacente que o objeto de negócios representa. Um Conjunto de Dados Tipado é uma classe gerada para você pelo Visual Studio com base em um esquema de banco de dados e cujos membros são fortemente tipado de acordo com esse esquema. O Próprio Conjunto de Dados Tipado consiste em classes que estendem as classes DataSet, DataTable e DataRow ADO.NET. Além dos DataTables fortemente tipados, os Conjuntos de Dados Tipados agora também incluem TableAdapters, que são classes com métodos para preencher os DataTables do DataSet e propagar modificações dentro dos DataTables de volta para o banco de dados.
Observação
Para obter mais informações sobre as vantagens e desvantagens do uso de Conjuntos de Dados Tipados versus objetos de negócios personalizados, consulte Projetando componentes da camada de dados e passando dados por camadas.
Usaremos Conjuntos de Dados fortemente tipado para a arquitetura desses tutoriais. A Figura 3 ilustra o fluxo de trabalho entre as diferentes camadas de um aplicativo que usa Conjuntos de Dados Tipados.
Figura 3: Todo o código de acesso a dados é relegado à DAL (clique para exibir a imagem em tamanho real)
Criando um conjunto de dados tipado e um adaptador de tabela
Para começar a criar nosso DAL, começamos adicionando um Conjunto de Dados Digitado ao nosso projeto. Para fazer isso, clique com o botão direito do mouse no nó do projeto no Gerenciador de Soluções e escolha Adicionar um Novo Item. Selecione a opção Conjunto de Dados na lista de modelos e nomeie-a Northwind.xsd
como .
Figura 4: escolha adicionar um novo conjunto de dados ao seu projeto (clique para exibir a imagem em tamanho real)
Depois de clicar em Adicionar, quando solicitado a adicionar o DataSet à App_Code
pasta, escolha Sim. O Designer para o Conjunto de Dados Tipado será exibido e o Assistente de Configuração de TableAdapter será iniciado, permitindo que você adicione seu primeiro TableAdapter ao Conjunto de Dados Digitado.
Um Conjunto de Dados Tipado serve como uma coleção fortemente tipada de dados; ele é composto por instâncias datatable fortemente tipada, cada uma das quais, por sua vez, é composta por instâncias DataRow fortemente tipada. Criaremos uma DataTable fortemente tipada para cada uma das tabelas de banco de dados subjacentes com as quais precisamos trabalhar nesta série de tutoriais. Vamos começar com a criação de uma DataTable para a Products
tabela.
Tenha em mente que DataTables fortemente tipado não incluem nenhuma informação sobre como acessar dados de sua tabela de banco de dados subjacente. Para recuperar os dados para preencher a DataTable, usamos uma classe TableAdapter, que funciona como nossa Camada de Acesso a Dados. Para nossa Products
DataTable, o TableAdapter conterá os métodos GetProducts()
, GetProductByCategoryID(categoryID)
e assim por diante que invocaremos da camada de apresentação. A função do DataTable é servir como os objetos fortemente tipados usados para passar dados entre as camadas.
O Assistente de Configuração do TableAdapter começa solicitando que você selecione com qual banco de dados trabalhar. A lista suspensa mostra esses bancos de dados no servidor Explorer. Se você não adicionou o banco de dados Northwind ao servidor Explorer, clique no botão Nova Conexão neste momento para fazer isso.
Figura 5: Escolher o Banco de Dados Northwind na Lista de Drop-Down (Clique para exibir a imagem em tamanho real)
Depois de selecionar o banco de dados e clicar em Avançar, você será perguntado se deseja salvar o cadeia de conexão no Web.config
arquivo. Ao salvar o cadeia de conexão você evitará que ele seja embutido em código nas classes TableAdapter, o que simplifica as coisas se as informações cadeia de conexão forem alteradas no futuro. Se você optar por salvar o cadeia de conexão no arquivo de configuração, ele será colocado na <connectionStrings>
seção , que pode ser criptografada opcionalmente para melhorar a segurança ou modificada posteriormente por meio da nova página de propriedades do ASP.NET 2.0 na Ferramenta de Administração de GUI do IIS, que é mais ideal para administradores.
Figura 6: Salvar a cadeia de Web.config
conexão em (clique para exibir a imagem em tamanho real)
Em seguida, precisamos definir o esquema para o primeiro DataTable fortemente tipado e fornecer o primeiro método para nosso TableAdapter usar ao preencher o DataSet fortemente tipado. Essas duas etapas são realizadas simultaneamente criando uma consulta que retorna as colunas da tabela que queremos refletir em nossa DataTable. No final do assistente, forneceremos um nome de método para essa consulta. Depois que isso for feito, esse método poderá ser invocado de nossa camada de apresentação. O método executará a consulta definida e preencherá uma DataTable fortemente tipada.
Para começar a definir a consulta SQL, primeiro devemos indicar como queremos que o TableAdapter emita a consulta. Podemos usar uma instrução SQL ad hoc, criar um novo procedimento armazenado ou usar um procedimento armazenado existente. Para estes tutoriais, usaremos instruções SQL ad hoc.
Figura 7: Consultar os dados usando uma instrução SQL Ad Hoc (clique para exibir a imagem em tamanho real)
Neste ponto, podemos digitar a consulta SQL manualmente. Ao criar o primeiro método no TableAdapter, você normalmente deseja que a consulta retorne as colunas que precisam ser expressas na DataTable correspondente. Podemos fazer isso criando uma consulta que retorna todas as colunas e todas as linhas da Products
tabela:
Figura 8: Insira a consulta SQL na caixa de texto (clique para exibir a imagem em tamanho real)
Como alternativa, use o Construtor de Consultas e construa graficamente a consulta, conforme mostrado na Figura 9.
Figura 9: Criar a consulta graficamente por meio do Editor de Consultas (clique para exibir a imagem em tamanho real)
Depois de criar a consulta, mas antes de passar para a próxima tela, clique no botão Opções Avançadas. Em Projetos de Site, "Gerar instruções Inserir, Atualizar e Excluir" é a única opção avançada selecionada por padrão; se você executar esse assistente em uma Biblioteca de Classes ou em um Projeto do Windows, a opção "Usar simultaneidade otimista" também será selecionada. Deixe a opção "Usar simultaneidade otimista" desmarcada por enquanto. Examinaremos a simultaneidade otimista em tutoriais futuros.
Figura 10: selecione apenas a opção Gerar instruções Inserir, Atualizar e Excluir (Clique para exibir a imagem em tamanho real)
Depois de verificar as opções avançadas, clique em Avançar para prosseguir para a tela final. Aqui, é solicitado que selecione quais métodos adicionar ao TableAdapter. Há dois padrões para preencher dados:
- Preencha uma DataTable com essa abordagem, um método é criado que usa um DataTable como um parâmetro e o preenche com base nos resultados da consulta. O ADO.NET classe DataAdapter, por exemplo, implementa esse padrão com seu
Fill()
método . - Retornar uma DataTable com essa abordagem, o método cria e preenche a DataTable para você e a retorna como o valor retornado pelos métodos.
Você pode fazer com que o TableAdapter implemente um ou ambos os padrões. Você também pode renomear os métodos fornecidos aqui. Vamos deixar as duas caixas de seleção marcadas, mesmo que usaremos apenas o último padrão ao longo desses tutoriais. Além disso, vamos renomear o método bastante genérico GetData
para GetProducts
.
Se marcada, a caixa de seleção final, "GenerateDBDirectMethods", criará Insert()
os métodos , Update()
e Delete()
para o TableAdapter. Se você deixar essa opção desmarcada, todas as atualizações precisarão ser feitas por meio do único Update()
método do TableAdapter, que usa o Typed DataSet, um DataTable, um único DataRow ou uma matriz de DataRows. (Se você tiver desmarcado a opção "Gerar instruções Insert, Update e Delete" das propriedades avançadas na Figura 9, a configuração dessa caixa de seleção não terá efeito.) Vamos deixar essa caixa de seleção marcada.
Figura 11: Alterar o Nome do Método de GetData
para GetProducts
(Clique para exibir a imagem em tamanho real)
Conclua o assistente clicando em Concluir. Depois que o assistente for fechado, retornaremos ao DataSet Designer que mostra a DataTable que acabamos de criar. Você pode ver a lista de colunas na Products
DataTable (ProductID
, ProductName
e assim por diante), bem como os métodos do ProductsTableAdapter
(Fill()
e GetProducts()
).
Figura 12: a Products
DataTable e ProductsTableAdapter
foram adicionadas ao Conjunto de Dados Tipado (clique para exibir a imagem em tamanho real)
Neste ponto, temos um Conjunto de Dados Tipado com uma única DataTable (Northwind.Products
) e uma classe DataAdapter fortemente tipada (NorthwindTableAdapters.ProductsTableAdapter
) com um GetProducts()
método . Esses objetos podem ser usados para acessar uma lista de todos os produtos do código, como:
Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
Dim products as Northwind.ProductsDataTable
products = productsAdapter.GetProducts()
For Each productRow As Northwind.ProductsRow In products
Response.Write("Product: " & productRow.ProductName & "<br />")
Next
Esse código não exigiu que escrevamos um bit de código específico de acesso a dados. Não foi necessário instanciar nenhuma ADO.NET classes, não precisamos nos referir a nenhuma cadeia de conexão, consultas SQL ou procedimentos armazenados. Em vez disso, o TableAdapter fornece o código de acesso a dados de baixo nível para nós.
Cada objeto usado neste exemplo também é fortemente tipado, permitindo que o Visual Studio forneça intelliSense e verificação de tipo em tempo de compilação. E o melhor de todos os DataTables retornados pelo TableAdapter pode ser associado a ASP.NET controles da Web de dados, como GridView, DetailsView, DropDownList, CheckBoxList e vários outros. O exemplo a seguir ilustra a associação da DataTable retornada pelo GetProducts()
método a um GridView em apenas três linhas de código no Page_Load
manipulador de eventos.
AllProducts.aspx
<%@ Page Language="VB" AutoEventWireup="true" CodeFile="AllProducts.aspx.vb"
Inherits="AllProducts" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<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.vb
Imports NorthwindTableAdapters
Partial Class AllProducts
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.Load
Dim productsAdapter As New ProductsTableAdapter
GridView1.DataSource = productsAdapter.GetProducts()
GridView1.DataBind()
End Sub
End Class
Figura 13: a lista de produtos é exibida em um GridView (clique para exibir a imagem em tamanho real)
Embora este exemplo exija que escrevamos três linhas de código no manipulador de eventos da Page_Load
página ASP.NET, em tutoriais futuros, examinaremos como usar o ObjectDataSource para recuperar declarativamente os dados do DAL. Com o ObjectDataSource, não precisaremos escrever nenhum código e também obteremos suporte para paginação e classificação!
Etapa 3: Adicionar métodos parametrizados à camada de acesso a dados
Neste ponto, nossa ProductsTableAdapter
classe tem apenas um método, GetProducts()
, que retorna todos os produtos no banco de dados. Embora a capacidade de trabalhar com todos os produtos seja definitivamente útil, há momentos em que queremos recuperar informações sobre um produto específico ou todos os produtos que pertencem a uma categoria específica. Para adicionar essa funcionalidade à camada de acesso a dados, podemos adicionar métodos parametrizados ao TableAdapter.
Vamos adicionar o GetProductsByCategoryID(categoryID)
método . Para adicionar um novo método ao DAL, retorne ao dataset Designer, clique com o botão direito do ProductsTableAdapter
mouse na seção e escolha Adicionar Consulta.
Figura 14: Right-Click no TableAdapter e escolha Adicionar Consulta
Primeiro, somos avisados sobre se queremos acessar o banco de dados usando uma instrução SQL ad hoc ou um procedimento armazenado novo ou existente. Vamos optar por usar uma instrução SQL ad hoc novamente. Em seguida, nos perguntam que tipo de consulta SQL gostaríamos de usar. Como queremos retornar todos os produtos que pertencem a uma categoria especificada, queremos escrever uma instrução SELECT
que retorne linhas.
Figura 15: Optar por criar uma instrução SELECT
que retorna linhas (clique para exibir a imagem em tamanho real)
A próxima etapa é definir a consulta SQL usada para acessar os dados. Como queremos retornar apenas os produtos que pertencem a uma categoria específica, uso a mesma SELECT
instrução de GetProducts()
, mas adiciono a seguinte WHERE
cláusula: WHERE CategoryID = @CategoryID
. O @CategoryID
parâmetro indica ao assistente TableAdapter que o método que estamos criando exigirá um parâmetro de entrada do tipo correspondente (ou seja, um inteiro anulável).
Figura 16: Insira uma consulta para retornar apenas produtos em uma categoria especificada (clique para exibir a imagem em tamanho real)
Na etapa final, podemos escolher quais padrões de acesso a dados usar, bem como personalizar os nomes dos métodos gerados. Para o padrão fill, vamos alterar o nome para FillByCategoryID
e para o retornar um padrão de retorno DataTable (os GetX
métodos), vamos usar GetProductsByCategoryID
.
Figura 17: Escolher os nomes para os métodos TableAdapter (clique para exibir a imagem em tamanho real)
Depois de concluir o assistente, o Designer DataSet inclui os novos métodos TableAdapter.
Figura 18: Os produtos agora podem ser consultados por categoria
Reserve um momento para adicionar um GetProductByProductID(productID)
método usando a mesma técnica.
Essas consultas parametrizadas podem ser testadas diretamente no Designer DataSet. Clique com o botão direito do mouse no método em TableAdapter e escolha Visualizar Dados. Em seguida, insira os valores a serem usados para os parâmetros e clique em Visualizar.
Figura 19: Os produtos que pertencem à categoria bebidas são mostrados (clique para exibir a imagem em tamanho real)
Com o GetProductsByCategoryID(categoryID)
método em nosso DAL, agora podemos criar uma página ASP.NET que exibe apenas esses produtos em uma categoria especificada. O exemplo a seguir mostra todos os produtos que estão na categoria Bebidas, que têm um CategoryID
de 1.
Beverages.aspx
<%@ Page Language="VB" AutoEventWireup="true" CodeFile="Beverages.aspx.vb"
Inherits="Beverages" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<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.vb
Imports NorthwindTableAdapters
Partial Class Beverages
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.Load
Dim productsAdapter As New ProductsTableAdapter
GridView1.DataSource =
productsAdapter.GetProductsByCategoryID(1)
GridView1.DataBind()
End Sub
End Class
Figura 20: Esses produtos na categoria Bebidas são exibidos (clique para exibir a imagem em tamanho real)
Etapa 4: Inserindo, atualizando e excluindo dados
Há dois padrões comumente usados para inserir, atualizar e excluir dados. O primeiro padrão, que chamarei de padrão direto do banco de dados, envolve a criação de métodos que, quando invocados, emitem um INSERT
comando , UPDATE
ou DELETE
para o banco de dados que opera em um único registro de banco de dados. Esses métodos normalmente são passados em uma série de valores escalares (inteiros, cadeias de caracteres, boolianos, DateTimes e assim por diante) que correspondem aos valores a serem inseridos, atualizados ou excluídos. Por exemplo, com esse padrão para a Products
tabela, o método delete usaria um parâmetro inteiro, indicando o ProductID
do registro a ser excluído, enquanto o método insert levaria uma cadeia de caracteres para o ProductName
, um decimal para o UnitPrice
, um inteiro para o UnitsOnStock
e assim por diante.
Figura 21: Cada solicitação de inserção, atualização e exclusão é enviada ao banco de dados imediatamente (clique para exibir a imagem em tamanho real)
O outro padrão, ao qual me referirei como o padrão de atualização em lote, é atualizar um DataSet inteiro, DataTable ou uma coleção de DataRows em uma chamada de método. Com esse padrão, um desenvolvedor exclui, insere e modifica os DataRows em uma DataTable e, em seguida, passa esses DataRows ou DataTable para um método de atualização. Esse método enumera os DataRows passados, determina se eles foram modificados, adicionados ou excluídos (por meio do valor da propriedade RowState do DataRow) e emite a solicitação de banco de dados apropriada para cada registro.
Figura 22: Todas as alterações são sincronizadas com o banco de dados quando o método update é invocado (clique para exibir a imagem em tamanho real)
O TableAdapter usa o padrão de atualização em lote por padrão, mas também dá suporte ao padrão direto do BD. Como selecionamos a opção "Gerar instruções Insert, Update e Delete" nas Propriedades Avançadas ao criar nosso TableAdapter, o ProductsTableAdapter
contém um Update()
método , que implementa o padrão de atualização em lote. Especificamente, o TableAdapter contém um Update()
método que pode ser passado para Typed DataSet, um DataTable fortemente tipado ou um ou mais DataRows. Se você deixou a caixa de seleção "GenerateDBDirectMethods" marcada ao criar pela primeira vez o TableAdapter, o padrão direto do BD também será implementado por meio Insert()
dos métodos , Update()
e Delete()
.
Ambos os padrões de modificação de dados usam as propriedades , e do InsertCommand
TableAdapter para emitir seus INSERT
comandos , UPDATE
e DELETE
para o banco de DeleteCommand
dados. UpdateCommand
Você pode inspecionar e modificar as InsertCommand
propriedades , UpdateCommand
e DeleteCommand
clicando no TableAdapter no Designer DataSet e indo para o janela Propriedades. (Verifique se você selecionou o TableAdapter e se o ProductsTableAdapter
objeto é o selecionado na lista suspensa no janela Propriedades.)
Figura 23: o TableAdapter tem InsertCommand
as propriedades , UpdateCommand
e DeleteCommand
(clique para exibir a imagem em tamanho real)
Para examinar ou modificar qualquer uma dessas propriedades de comando de banco de dados, clique na CommandText
subpropriedade , que abrirá o Construtor de Consultas.
Figura 24: Configurar as INSERT
instruções , UPDATE
e DELETE
no Construtor de Consultas (clique para exibir a imagem em tamanho real)
O exemplo de código a seguir mostra como usar o padrão de atualização em lote para dobrar o preço de todos os produtos que não foram descontinuados e que têm 25 unidades em estoque ou menos:
Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
Dim products As Northwind.ProductsDataTable = productsAdapter.GetProducts()
For Each product As Northwind.ProductsRow In products
If Not product.Discontinued AndAlso product.UnitsInStock <= 25 Then
product.UnitPrice *= 2
End if
Next
productsAdapter.Update(products)
O código a seguir ilustra como usar o padrão direto do BD para excluir programaticamente um produto específico, atualizar um e, em seguida, adicionar um novo:
Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
productsAdapter.Delete(3)
productsAdapter.Update( _
"Chai", 1, 1, "10 boxes x 20 bags", 18.0, 39, 15, 10, false, 1)
productsAdapter.Insert( _
"New Product", 1, 1, "12 tins per carton", 14.95, 15, 0, 10, false)
Criando métodos personalizados de inserção, atualização e exclusão
Os Insert()
métodos , Update()
e Delete()
criados pelo método direto do BD podem ser um pouco complicados, especialmente para tabelas com muitas colunas. Observando o exemplo de código anterior, sem a ajuda do IntelliSense, não está particularmente claro qual Products
coluna de tabela mapeia para cada parâmetro de entrada para os Update()
métodos e Insert()
. Pode haver ocasiões em que queremos apenas atualizar uma ou duas colunas ou desejar um método personalizado Insert()
que, talvez, retorne o valor do campo (incremento automático) do IDENTITY
registro recém-inserido.
Para criar esse método personalizado, retorne à Designer DataSet. Clique com o botão direito do mouse no TableAdapter e escolha Adicionar Consulta, retornando ao assistente TableAdapter. Na segunda tela, podemos indicar o tipo de consulta a ser criada. Vamos criar um método que adiciona um novo produto e, em seguida, retorna o valor do registro ProductID
recém-adicionado. Portanto, opte por criar uma INSERT
consulta.
Figura 25: Criar um método para adicionar uma nova linha à Products
tabela (clique para exibir a imagem em tamanho real)
Na tela seguinte, o InsertCommand
é CommandText
exibido. Aumente essa consulta adicionando SELECT SCOPE_IDENTITY()
ao final da consulta, que retornará o último valor de identidade inserido em uma IDENTITY
coluna no mesmo escopo. (Consulte a documentação técnica para obter mais informações sobre SCOPE_IDENTITY()
e por que você provavelmente deseja usar SCOPE_IDENTITY() em vez de @@IDENTITY.) Certifique-se de terminar a INSERT
instrução com ponto e vírgula antes de adicionar a instrução SELECT
.
Figura 26: Aumentar a consulta para retornar o SCOPE_IDENTITY()
valor (clique para exibir a imagem em tamanho real)
Por fim, nomeie o novo método InsertProduct
.
Figura 27: Definir o nome do novo método como InsertProduct
(clique para exibir a imagem em tamanho real)
Ao retornar ao DataSet Designer você verá que o ProductsTableAdapter
contém um novo método, InsertProduct
. Se esse novo método não tiver um parâmetro para cada coluna na Products
tabela, é provável que você tenha esquecido de terminar a INSERT
instrução com ponto e vírgula. Configure o InsertProduct
método e verifique se você tem um ponto e vírgula delimitando as INSERT
instruções e SELECT
.
Por padrão, os métodos de inserção emitem métodos não consulta, o que significa que eles retornam o número de linhas afetadas. No entanto, queremos que o InsertProduct
método retorne o valor retornado pela consulta, não o número de linhas afetadas. Para fazer isso, ajuste a InsertProduct
propriedade do ExecuteMode
método para Scalar
.
Figura 28: Alterar a ExecuteMode
propriedade para Scalar
(clique para exibir a imagem em tamanho real)
O código a seguir mostra esse novo InsertProduct
método em ação:
Dim productsAdapter As New NorthwindTableAdapters.ProductsTableAdapter()
Dim new_productID As Integer = Convert.ToInt32(productsAdapter.InsertProduct( _
"New Product", 1, 1, "12 tins per carton", 14.95, 10, 0, 10, false))
productsAdapter.Delete(new_productID)
Etapa 5: Concluindo a camada de acesso a dados
Observe que a ProductsTableAdapters
classe retorna os CategoryID
valores e SupplierID
da Products
tabela, mas não inclui a CategoryName
coluna da Categories
tabela ou da CompanyName
coluna da Suppliers
tabela, embora essas sejam provavelmente as colunas que queremos exibir ao mostrar informações do produto. Podemos aumentar o método inicial do TableAdapter, GetProducts()
, para incluir os CategoryName
valores de coluna e CompanyName
, que atualizarão o DataTable fortemente tipado para incluir essas novas colunas também.
No entanto, isso pode apresentar um problema, pois os métodos do TableAdapter para inserir, atualizar e excluir dados são baseados nesse método inicial. Felizmente, os métodos gerados automaticamente para inserção, atualização e exclusão não são afetados por subconsultas na SELECT
cláusula . Ao tomar cuidado para adicionar nossas consultas a Categories
e Suppliers
como subconsultas, em vez JOIN
de s, evitaremos a necessidade de refazer esses métodos para modificar dados. Clique com o botão direito do GetProducts()
mouse no método no ProductsTableAdapter
e escolha Configurar. Em seguida, ajuste a SELECT
cláusula para que ela se pareça com:
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: Atualizar a instrução SELECT
do GetProducts()
método (clique para exibir a imagem em tamanho real)
Depois de atualizar o GetProducts()
método para usar essa nova consulta, a DataTable incluirá duas novas colunas: CategoryName
e SupplierName
.
Figura 30: a Products
DataTable tem duas novas colunas
Reserve um momento para atualizar a SELECT
cláusula no GetProductsByCategoryID(categoryID)
método também.
Se você atualizar a GetProducts()
SELECT
sintaxe usando JOIN
o DataSet Designer não poderá gerar automaticamente os métodos para inserir, atualizar e excluir dados de banco de dados usando o padrão direto do BD. Em vez disso, você precisará criá-los manualmente da mesma forma que fizemos com o InsertProduct
método anteriormente neste tutorial. Além disso, você precisará fornecer manualmente os InsertCommand
valores de propriedade , UpdateCommand
e DeleteCommand
se quiser usar o padrão de atualização em lote.
Adicionando os TableAdapters restantes
Até agora, examinamos apenas o trabalho com um único TableAdapter para uma única tabela de banco de dados. No entanto, o banco de dados Northwind contém várias tabelas relacionadas com as quais precisaremos trabalhar em nosso aplicativo Web. Um Conjunto de Dados Tipado pode conter várias DataTables relacionadas. Portanto, para concluir nosso DAL, precisamos adicionar DataTables para as outras tabelas que usaremos nestes tutoriais. Para adicionar um novo TableAdapter a um DataSet Tipado, abra o Designer DataSet, clique com o botão direito do mouse no Designer e escolha Adicionar/TableAdapter. Isso criará um novo DataTable e TableAdapter e o orientará pelo assistente que examinamos anteriormente neste tutorial.
Reserve alguns minutos para criar os métodos e TableAdapters a seguir usando as consultas a seguir. Observe que as consultas no ProductsTableAdapter
incluem as subconsultas para obter os nomes de categoria e fornecedor de cada produto. Além disso, se você estiver acompanhando, já adicionou os ProductsTableAdapter
métodos e GetProductsByCategoryID(categoryID)
da GetProducts()
classe.
ProductsTableAdapter
GetProducts:
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
GetProductsByCategoryID:
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 WHERE CategoryID = @CategoryID
GetProductsBySupplierID:
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 WHERE SupplierID = @SupplierID
GetProductByProductID:
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 WHERE ProductID = @ProductID
CategoriesTableAdapter
GetCategories:
SELECT CategoryID, CategoryName, Description FROM Categories
GetCategoryByCategoryID:
SELECT CategoryID, CategoryName, Description FROM Categories WHERE CategoryID = @CategoryID
FornecedoresTableAdapter
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
Figura 31: o conjunto de dados Designer depois que os quatro TableAdapters tiverem sido adicionados (clique para exibir a imagem em tamanho real)
Adicionando código personalizado ao DAL
Os TableAdapters e DataTables adicionados ao Conjunto de Dados Digitados são expressos como um arquivo de definição de esquema XML (Northwind.xsd
). Você pode exibir essas informações de esquema clicando com o botão direito do Northwind.xsd
mouse no arquivo no Gerenciador de Soluções e escolhendo Exibir Código.
Figura 32: o arquivo XSD (Definição de Esquema XML) para o Conjunto de Dados Digitado northwinds (clique para exibir a imagem em tamanho real)
Essas informações de esquema são convertidas em código C# ou Visual Basic em tempo de design quando compiladas ou em runtime (se necessário), momento em que você pode percorrê-la com o depurador. Para exibir esse código gerado automaticamente, vá para a Exibição de Classe e faça drill down para as classes TableAdapter ou Typed DataSet. Se você não vir o Modo de Exibição de Classe na tela, vá para o menu Exibir e selecione-o nela ou clique em Ctrl+Shift+C. Na Exibição de Classe, você pode ver as propriedades, os métodos e os eventos das classes Typed DataSet e TableAdapter. Para exibir o código de um método específico, clique duas vezes no nome do método no Modo de Exibição de Classe ou clique com o botão direito do mouse nele e escolha Ir para Definição.
Figura 33: Inspecionar o código gerado automaticamente selecionando Ir para Definição na Exibição de Classe
Embora o código gerado automaticamente possa ser um ótimo tempo de salvamento, o código geralmente é muito genérico e precisa ser personalizado para atender às necessidades exclusivas de um aplicativo. No entanto, o risco de estender o código gerado automaticamente é que a ferramenta que gerou o código possa decidir que é hora de "regenerar" e substituir suas personalizações. Com o novo conceito de classe parcial do .NET 2.0, é fácil dividir uma classe em vários arquivos. Isso nos permite adicionar nossos próprios métodos, propriedades e eventos às classes geradas automaticamente sem precisar se preocupar com o Visual Studio substituindo nossas personalizações.
Para demonstrar como personalizar o DAL, vamos adicionar um GetProducts()
método à SuppliersRow
classe . A SuppliersRow
classe representa um único registro na Suppliers
tabela; cada fornecedor pode fornecer zero a muitos produtos, portanto GetProducts()
, retornará esses produtos do fornecedor especificado. Para fazer isso, crie um novo arquivo de classe na App_Code
pasta chamada SuppliersRow.vb
e adicione o seguinte código:
Imports NorthwindTableAdapters
Partial Public Class Northwind
Partial Public Class SuppliersRow
Public Function GetProducts() As Northwind.ProductsDataTable
Dim productsAdapter As New ProductsTableAdapter
Return productsAdapter.GetProductsBySupplierID(Me.SupplierID)
End Function
End Class
End Class
Essa classe parcial instrui o compilador que, ao criar a Northwind.SuppliersRow
classe, inclua o GetProducts()
método que acabamos de definir. Se você compilar seu projeto e retornar ao Modo de Exibição de GetProducts()
Classe, verá agora listado como um método de Northwind.SuppliersRow
.
Figura 34: O GetProducts()
método agora faz parte da Northwind.SuppliersRow
classe
O GetProducts()
método agora pode ser usado para enumerar o conjunto de produtos para um fornecedor específico, como mostra o seguinte código:
Dim suppliersAdapter As New NorthwindTableAdapters.SuppliersTableAdapter()
Dim suppliers As Northwind.SuppliersDataTable = suppliersAdapter.GetSuppliers()
For Each supplier As Northwind.SuppliersRow In suppliers
Response.Write("Supplier: " & supplier.CompanyName)
Response.Write("<ul>")
Dim products As Northwind.ProductsDataTable = supplier.GetProducts()
For Each product As Northwind.ProductsRow In products
Response.Write("<li>" & product.ProductName & "</li>")
Next
Response.Write("</ul><p> </p>")
Next
Esses dados também podem ser exibidos em qualquer um dos ASP. Controles da Web de dados do NET. A página a seguir 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 associado aos resultados retornados
GetProducts()
pelo método para cada fornecedor.
Examinaremos como exibir esses relatórios master detalhes em tutoriais futuros. Por enquanto, este exemplo foi projetado para ilustrar usando o método personalizado adicionado à Northwind.SuppliersRow
classe .
SuppliersAndProducts.aspx
<%@ Page Language="VB" CodeFile="SuppliersAndProducts.aspx.vb"
AutoEventWireup="true" Inherits="SuppliersAndProducts" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<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>
Suppliers and Their Products</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="<%# CType(CType(Container.DataItem, System.Data.DataRowView).Row, Northwind.SuppliersRow).GetProducts() %>"
DataTextField="ProductName">
</asp:BulletedList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</p>
</div>
</form>
</body>
</html>
SuppliersAndProducts.aspx.vb
Imports NorthwindTableAdapters
Partial Class SuppliersAndProducts
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles Me.Load
Dim suppliersAdapter As New SuppliersTableAdapter
GridView1.DataSource = suppliersAdapter.GetSuppliers()
GridView1.DataBind()
End Sub
End Class
Figura 35: o nome da empresa do fornecedor está listado na coluna esquerda, seus produtos à direita (clique para exibir a imagem em tamanho real)
Resumo
Ao criar um aplicativo Web, criar o DAL deve ser uma de suas primeiras etapas, ocorrendo antes de começar a criar sua camada de apresentação. Com o Visual Studio, criar um DAL baseado em Conjuntos de Dados Tipado é uma tarefa que pode ser realizada em 10 a 15 minutos sem escrever uma linha de código. Os tutoriais que avançarão se basearão nesse DAL. No próximo tutorial , definiremos várias regras de negócios e veremos como implementá-las em uma camada lógica de negócios separada.
Programação feliz!
Leitura Adicional
Para obter mais informações sobre os tópicos discutidos neste tutorial, consulte os seguintes recursos:
- Criando um DAL usando TableAdapters e DataTables fortemente tipado no VS 2005 e ASP.NET 2.0
- Criando componentes da camada de dados e passando dados por camadas
- Criptografando informações de configuração em aplicativos ASP.NET 2.0
- Visão geral de TableAdapter
- Trabalhando com um Conjunto de Dados Tipado
- Usando o acesso a dados Strongly-Typed no Visual Studio 2005 e ASP.NET 2.0
- Como estender métodos TableAdapter
Treinamento em vídeo sobre tópicos contidos neste Tutorial
- Camadas de acesso a dados em aplicativos do ASP.NET
- Como associar manualmente um conjunto de dados a um Datagrid
- Como trabalhar com conjuntos de dados e filtros de um aplicativo ASP
Sobre o autor
Scott Mitchell, autor de sete livros do ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Ele pode ser contatado em mitchell@4GuysFromRolla.com. ou através de seu blog, que pode ser encontrado em http://ScottOnWriting.NET.
Agradecimentos Especiais
Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores deste tutorial foram Ron Green, Hilton Giesenow, Dennis Patterson, Liz Shulok, Abel Gomez e Carlos Santos. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, solte-me uma linha em mitchell@4GuysFromRolla.com.
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de