Criação de uma DAL com Typed DataSets
Do tutorial: “Creating a Data Access Layer” de Scott Mitchell
http://www.asp.net/data-access/tutorials/creating-a-data-access-layer-cs
1. Abrir o Projecto Base
Duplo clique no ficheiro .sln
Já está adicionada a base de dados NORTHWIND.MDF ao App_Data Alternativa: criação desta versão inicial do programa (Projecto Base) (Step 1 do Tutorial)
A.Criar uma Solução
File > New Project… > Other Project Types > Visual Studio Solutions > Blank Solution > TutorialDALeBLL
B.Adicionar à solução uma Aplicação Web
Na Solução : Add > New Web Site… > ASP.NET Empty Web Site WebSite
C.Adicionar à Aplicação Web a base de dados NORTHWND.MDF: No Projecto WebSite Add ASP.NET Folder > App_Data
No App-Data Add Existing Item… > NORTHWND.MDF
D.Adicionar à Aplicação Web 4 páginas: AllProducts.aspx
Beverages.aspx NewProduct.aspx
Colocar em cada página um cabeçalho, escrever o texto, seleccioná-lo, e clicar em Heading 1 <h1>.
A seguir ao cabeçalho, colocar um controlo GridView, e em GridView Tasks, AutoFormat… seleccionar um esquema de formatação.
E.Adicionar à solução uma Class Library para a camada de acesso a dados: DAL Na Solução : Add > New Project… > (Visual C#) Class Library DAL Apagar a classe Class1.cs automaticamente adicionada à Class Library DAL.
F. Adicionar à Aplicação Web uma referência para a Class Library DAL
2. Adicione um Typed DataSet (Step 2 do Tutorial)
Na Class library DAL Add New Item… > DataSet Northwind.xsd (Adiciona o dataSet à class library DAL)
O ficheiro Northwind.xsd abre em modo Design, visualizando-se a janela DataSet Designer.
O dataSet fortemente tipado adicionado ao Projecto DAL não inclui informação sobre como aceder aos dados de qualquer tabela da base de dados. Essa informação irá estar contida nos tableAdapters.
Um dataSet tipado é composto de instâncias dataTable tipadas, cada uma das quais é composta de instâncias dataRow tipadas. Para colocar cada instância dataTable no dataSet é necessário um objecto de uma classe TableAdapter. Cada tableAdapter possui vários métodos para selecção de dados de uma dada tabela da base de dados e também métodos para actualização desses dados. Os dados retribuídos por cada método povoam um dataTable dentro do dataSet.
3. Adicione TableAdapters
Verifique o conteúdo do ficheiro web.config, o qual não contém qualquer elemento connectionStrings.
No Server Explorer, expandir a base de dados e arrastar 3 tabelas da base de dados, Categories, Products e Suppliers:
Categories(CategoryID, CategoryName, Description, Picture) Products(ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued)
Suppliers(SupplierID, CompanyName, ContactName, ContactTitle, Address, City, Region, PostalCode, Country, Phone, Fax, HomePage)
Verifique agora o conteúdo do ficheiro app.config criado automaticamente na class library, o qual já contém o elemento connectionStrings, com uma connectionString para a base de dados.
O DataSet Designer cria 3 tableAdapters, um para cada tabela, com os nomes CategoriesTableAdapter
PoductsTableAdapter SuppliersTableAdapter.
Estes tableAdapters são criados dentro do namespace NorthwindTableAdapters. Estas classes TableAdapter permitem criar objectos que representam a ligação e comandos usados para retribuir e gravar dados.
Cada um dos tableAdapters criados automaticamente pelo DataSet Designer já possui 2 métodos para retribuir os dados das respectivas tabelas, com os seguintes nomes: Fill – este método recebe um dataTable como parâmetro e preenche-o com os dados da tabela da base de dados.
GetData - este método não recebe qualquer parâmetro e retorna um dataTable povoado com os dados da tabela da base de dados.
Cada tableAdapter também cria os métodos Insert, Update e Delete para inserir, actualizar e apagar registos individuais na respectiva tabela.
Na Class Library DAL faça
Build > Build Solution
4. Visualize o Código gerado automaticamente
Este passo destina-se apenas a visualizar o código gerado automaticamente pelo Visual Studio para as classes DataSet tipada e TableAdapters:
Visualizando directamente com Class View no Visual Studio
Os TableAdapters e DataTables adicionados ao Typed DataSet são expressos em XML Schema Definition.
Pode visualizar este documento seleccionando Northwind.xsd no Solution Explorer e escolhendo Open With… XML (Text) Editor.
Para ver o código C# gerado automaticamente faça View > Class View. Pode ver propriedades, métodos e eventos das classes Typed DataSet e TableAdapter. Para ver o código de um método particular faça duplo clique no nome do método ou seleccione Go To Definition a partir do nome do método.
Neste caso de um dataSet adicionado a uma classe library, o código C# gerado está no ficheiro Northwind.Designer.cs mostrado na janela do Solution Explorer.
5. Liste todos os registos da tabela Products
Para preencher as tabelas do dataSet Northwind, o DataSet Designer criou tableAdapters, com os nomes CategoriesTableAdapter,
ProductsTableAdapter, e SupliersTableAdapter.
Estes tableAdapters são criados dentro de um namespace com o nome
NorthwindTableAdapters.
No WebSite adicionar uma referência para a Class library: Add Reference… > DAL (Projects)
Build > Build Solution
Dá erro na página SuppliersAndProducts.aspx. Excluí-la do Projecto Build > Build Solution
Adicione à página AllProducts.aspx o seguinte código no Page_Load: protected void Page_Load(object sender, EventArgs e) { DAL.NorthwindTableAdapters.ProductsTableAdapter taProd = new DAL.NorthwindTableAdapters.ProductsTableAdapter(); GridView1.DataSource = taProd.GetData(); GridView1.DataBind(); //DAL.Northwind.ProductsDataTable dtProd = // new DAL.Northwind.ProductsDataTable(); //taProd.Fill(dtProd); //GridView1.DataSource = dtProd; //GridView1.DataBind(); }
Adicione mais uma página (ListarProdutos.aspx) ao Web Site e coloque o seguinte código no Page_Load:
protected void Page_Load(object sender, EventArgs e) { DAL.NorthwindTableAdapters.ProductsTableAdapter taProd =
new DAL.NorthwindTableAdapters.ProductsTableAdapter(); DAL.Northwind.ProductsDataTable tabelaProd = taProd.GetData();
foreach (DAL.Northwind.ProductsRow row in tabelaProd) Response.Write("Id = " + row.ProductID +
" Nome do Produto = " + row.ProductName + "<br />"); }
6. Liste todos os registos da tabela Products da Categoria Beverages CategoryID = 1
Adicione os métodos parametrizados (Step 3 do Tutorial) GetProductsByCategoryID(IdCategoria), e
GetProductsByProductID(IdProduto)
Estes métodos devem ser adicionados ao tableAdapter ProductsTableAdapter. No DataSet Designer, com o botão direito do rato na secção ProductsTableAdapter seleccione: Add > Query…
Use SQL Statements Next SELECT which returns rows Next
Complete a instrucção SQL a usar para aceder aos dados, adicionando a cláusula WHERE
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued FROM dbo.Products
WHERE CategoryID = @CategoryID
O parâmetro @CategoryID indica que o método necessita de um parâmetro de entrada
do tipo CategoryID.
No último passo escolhe-se o padrão de acesso a usar (Fill e/ou GetData) assim como o nome dos métodos. Altere os nomes para:
FillProductsByCategoryID, e GetProductsByCategoryID.
Depois de premir Finish o DataSet Designer inclui os novos métodos no tableAdapter. Teste estes métodos listando numa página (Beverages.aspx) todos os produtos da categoria Beverages.
protected void Page_Load(object sender, EventArgs e) { DAL.NorthwindTableAdapters.ProductsTableAdapter taProd = new DAL.NorthwindTableAdapters.ProductsTableAdapter(); GridView1.DataSource = taProd.GetProductsByCategoryID(); GridView1.DataBind();
}
Adicione o método GetProductsByProductID(int ProductID) usando a mesma técnica.
7. Inserir, Actualizar e Apagar Dados (Step 4 do Tutorial) Existem 2 padrões para inserir, actualizar e apagar dados:
• Um padrão cria métodos INSERT, UPDATE e DELETE em que a execução de cada método altera apenas um único registo da base de dados.
• O outro padrão cria métodos UPDATE com um parâmetro - um DataSet, um DataTable ou uma colecção de DataRows - em que a execução de um qualquer destes método actualiza os dados da tabela na base de dados, podendo alterar múltiplos registos da base de dados.
Para a tabela Products a classe ProductsTableAdapter possui os seguintes métodos:
public int Update(DataSet1.ProductsDataTable dataTable) public int Update(DataSet1 dataSet)
public int Update(DataRow[] dataRows) public int Delete(int Original_ProductID)
public int Insert(string ProductName, int SupplierID,
int CategoryID, string QuantityPerUnit, decimal UnitPrice, short UnitsInStock, short UnitsOnOrder, short ReorderLevel, bool Discontinued)
public int Update(string ProductName, int SupplierID,
int CategoryID, string QuantityPerUnit, decimal UnitPrice, short UnitsInStock, short UnitsOnOrder, short ReorderLevel, bool Discontinued, int Original_ProductID)
Estes métodos podem ser inspeccionados e modificados clicando no TableAdapter apresentado no DataSet Designer e seleccionando Properties.
Na janela Properties seleccionar o tableAdapter no drop-down list.
Expandir os métodos DeleteCommand, InsertCommand, SelectCommand, e UpdateCommand para visualizar a sub-propriedade CommandText.
Para modificar clicar na Propriedade CommandText, surgindo a janela Query Builder.
Teste estes métodos adicionando uma página (AlterarPrecosProdutos.aspx) que permita alterar o preço de todos os produtos da Categoria 2:
Double factor = . . . ;
DAL.NorthwindTableAdapters.ProductsTableAdapter taProd =
new DAL.NorthwindTableAdapters.ProductsTableAdapter(); DAL.Northwind.ProductsDataTable tabelaProd = taProd.GetData();
foreach (DAL.Northwind.ProductsRow row in tabelaProd)
if (row.CategoryID == 2) Response.Write(
row.ProductName + " Preco:" + row.UnitPrice + "<br />");
foreach (DAL.Northwind.ProdutosRow row in tabelaProd)
if (row.CategoryID == 2) row.UnitPrice *= factor; // Actualiza a tabela Produtos
taProd.Update(tabelaProd);
foreach (DAL.Northwind.ProductsRow row in tabelaProd)
if (row.CategoryID == 2) Response.Write(
row.ProductName + " Preco:" + row.UnitPrice + "<br />");
Teste o método Insert, construindo uma interface gráfica adequada na página NewProduct.aspx:
protected void Button1_Click(object sender, EventArgs e) {
string nomePoduto = TextBox1.Text;
int idFornecedor = int.Parse(TextBox2.Text); int idCategoria = int.Parse(TextBox3.Text); string quantidade = TextBox4.Text;
decimal precoUnitario = decimal.Parse(TextBox5.Text); short stock = short.Parse(TextBox6.Text);
short quantEncomendada = short.Parse(TextBox7.Text); short nivel = short.Parse(TextBox8.Text);
bool descontinuado = false;
DAL.NorthwindTableAdapters.ProductsTableAdapter tableAdapterProd =
new DAL.NorthwindTableAdapters.ProductsTableAdapter();
int n = tableAdapterProd.Insert(nomePoduto, idFornecedor, idCategoria, quantidade, precoUnitario, stock, quantEncomendada, nivel, descontinuado);
TextBox10.Text = n.ToString(); }
8. Criar Métodos Insert, Update e Delete específicos
Vamos criar um método para inserir um registo e retornar o valor do campo IDENTITY auto-gerado pela base de dados.
No DataSet Designer, com o botão direito do rato na secção ProductsTableAdapter seleccione: Add > Query…
Use SQL Statements Next
INSERT Next
Termine a instrução SQL com ponto e vírgula e acrescente no fim: SELECT SCOPE_IDENTITY(), para retornar o último valor identity.
INSERT INTO [dbo].[Products] ([ProductName], [SupplierID], [CategoryID], [QuantityPerUnit], [UnitPrice], [UnitsInStock],
[UnitsOnOrder], [ReorderLevel], [Discontinued]) VALUES (@ProductName, @SupplierID, @CategoryID, @QuantityPerUnit, @UnitPrice, @UnitsInStock, @UnitsOnOrder, @ReorderLevel, @Discontinued);
SELECT SCOPE IDENTITY();
Function Name: InserirProduto Finish.
Verificar que o tableAdapter ProductsTableAdapter contém um novo método, InserirProduto.
Por omissão, os métodos Insert são executados pelo método ExecuteNonQuery, o qual retorna o número de linhas afectadas. Contudo nós pretendemos que o método
InserirProduto retorne o valor retornado pela query, e não o número de linhas afectadas. Para isso temos de alterar a propriedade ExecuteMode de NonQuery para Scalar.
Teste este método, utilizando a interface gráfica da página NewProduct.aspx
protected void Button1_Click(object sender, EventArgs e) {
string nomePoduto = TextBox1.Text;
int idFornecedor = int.Parse(TextBox2.Text); int idCategoria = int.Parse(TextBox3.Text); string quantidade = TextBox4.Text;
short stock = short.Parse(TextBox6.Text);
short quantEncomendada = short.Parse(TextBox7.Text); short nivel = short.Parse(TextBox8.Text);
bool descontinuado = false;
DAL.NorthwindTableAdapters.ProductsTableAdapter tableAdapterProd =
new DAL.NorthwindTableAdapters.ProductsTableAdapter();
int n = tableAdapterProd.InserirProduto(nomePoduto, idFornecedor, idCategoria, quantidade, precoUnitario, stock, quantEncomendada, nivel, descontinuado);
TextBox10.Text = n.ToString(); }
9. Uso de Subqueries na cláusula Select
O tableAdapter ProductsTableAdapter retorna os valores CategoryID da tabela Products, mas não inclui o CategoryName (Nome da Categoria) da tabela Categories, embora esta coluna seja mais significativa para mostrar informações de cada produto. Podemos aumentar o método GetData() gerado automaticamente para incluir os valores de CategoryName.
Se usarmos um JOIN para modificar o SELECT do método GetData() o DataSet Designer não será capaz de gerar automaticamente os métodos Insert, Update, e Delete usados para alterar registos individuais na Base de Dados. Teremos que os criar
manualmente tal como fizemos com o método InserirProduto().
Também teremos de escrever os valores das propriedades InsertCommand,
UpdateCommand, e DeleteCommand para usar o método Update do tableAdpter para alterar múltiplos resgistos a partir de um DataSet.
Se usarmos Subqueries na cláusula Select os métodos gerados automaticamente não serão afectados.
Seleccione os métodos Fill e GetData do tableAdapter ProductsTableAdapter e prima Configure…
A seguir ajuste a cláusula Select
SELECT ProductID, ProductName, SupplierID, CategoryID, QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued FROM dbo.Products
Para:
SELECT ProductID, ProductName, SupplierID, CategoryID,
(SELECT CategoryName FROM Categories WHERE Categories.CategoryID = Products.CategoryID) as CategoryName,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued FROM dbo.Products
10. Adicionar Código Específico ao Código Gerado Automaticamente
Por vezes o código gerado automaticamente necessita de ser alterado para fornecer as necessidades de uma aplicação.
Se alterássemos directamente o código, essas alterações poderiam ser pagadas por uma nova geração automática de código efectuada pelo Visual Studio.
Com o conceito de classes parciais é fácil separar uma classe por múltiplos ficheiros. Isto permite-nos adicionar código às classes geradas automaticamente.
Vamos adicionar o método GetProducts() à classe SuppliersRow. A classe
SuppliersRow representa um único registo na tabela Suppliers. O método GetProducts() deverá retornar os produtos de um fornecedor (registo da tabela Suppliers) particular. Para isso crie uma nova classe (ficheiro SuppliersRow.cs) no directório DAL e adicione o seguinte código:
namespace DAL {
public partial class Northwind {
public partial class SuppliersRow {
public Northwind.ProductsDataTable GetProducts() {
NorthwindTableAdapters.ProductsTableAdapter tableAdapterProd =
new NorthwindTableAdapters.ProductsTableAdapter();
return tableAdapterProd.GetProductsBySupplierID(this.SupplierID); }
} } }
Adicione o método GetProductsBySupplierID ao tableAdapter ProductsTableAdapter.
Recompile a class library DAL.
Teste o método GetProducts() da classe CategoriesRow.
Utilize a página SuppliersAndProducts.aspx para listar os nomes dos Suppliers e os nomes dos Produtos de cada Supplier. Inclua novamente a página no Projecto. Esta página usa um GridView com 2 campos:
• Um BoundField que mostra o nome da Categoria.
• Um TemplateField que contém o controlo BulletedList que lista os resultados retornados pelo método GetProducts() para cada Suplier.
Alterar:
DataSource='<%#
((DAL.Northwind.SuppliersRow)((System.Data.DataRowView) Container.DataItem).Row).GetProducts() %>'
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='<%# ((DAL.Northwind.SuppliersRow) ((System.Data.DataRowView)Container.DataItem).Row).GetProducts() %>' DataTextField="ProductName"> </asp:BulletedList> </ItemTemplate> </asp:TemplateField> </Columns> </asp:GridView>
Na página SuppliersAndProducts.aspx para listar os nomes dos Suppliers coloque:
public partial class SuppliersAndProducts : System.Web.UI.Page {
protected void Page_Load(object sender, EventArgs e) {
DAL.NorthwindTableAdapters.SuppliersTableAdapter taSuppliers = new DAL.NorthwindTableAdapters.SuppliersTableAdapter();
GridView1.DataSource = taSuppliers.GetData(); GridView1.DataBind();
} }
Criação de uma BLL com Typed DataSets
Do tutorial: “Creating a Business Logic Layer” de Scott Mitchell
http://www.asp.net/data-access/tutorials/creating-a-business-logic-layer-cs
11. Adicionar à solução uma Class Library para a camada de lógica de negócio: BLL
Na Solução : Add > New Project… > (Visual C#) Class Library BLL Apagar a classe Class1.cs automaticamente adicionada à Class Library BLL. Adicionar à Class Library BLL uma referência para a Class Library DAL
12. Adicionar classes à BLL
BLL Add New Item… > Class CategoriesBLL.cs
ProductsBLL.cs SuppliersBLL.cs
CategoriesBLL.cs
public class CategoriesBLL {
private NorthwindTableAdapters.CategoriesTableAdapter taCat = null;
protected NorthwindTableAdapters.CategoriesTableAdapter Adapter {
get {
if (taCat == null)
taCat = new NorthwindTableAdapters.CategoriesTableAdapter(); return taCat;
} }
public Northwind.CategoriesDataTable GetCategories() {
return Adapter.GetCategories(); }
}
ProductsBLL.cs
public class ProductsBLL {
private NorthwindTableAdapters.ProductsTableAdapter taProd = null;
protected NorthwindTableAdapters.ProductsTableAdapter Adapter {
get {
if (taProd == null) taProd =
new NorthwindTableAdapters.ProductsTableAdapter();
return taProd; }
}
public Northwind.ProductsDataTable GetProducts() {
return Adapter.GetProducts(); }
public Northwind.ProductsDataTable GetProductByProductID(
int productID) { return Adapter.GetProductByProductID(productID);
}
public Northwind.ProductsDataTable GetProductsByCategoryID(
int categoryID) { return Adapter.GetProductsByCategoryID(categoryID);
}
public Northwind.ProductsDataTable GetProductsBySupplierID(
int supplierID) { return Adapter.GetProductsBySupplierID(supplierID);
}
public bool InsertProduct(string productName, int supplierID, int categoryID, string quantityPerUnit, decimal unitPrice, short unitsInStock, short unitsOnOrder, short reorderLevel, bool discontinued) {
int rowsAffected = Adapter.Insert(productName, supplierID, categoryID, quantityPerUnit, unitPrice, unitsInStock, unitsOnOrder, reorderLevel, discontinued);
// Retorna true se exactamente umalinha é inserida, senao false return rowsAffected == 1;
}
SuppliersBLL.cs
public class SuppliersBLL {
private NorthwindTableAdapters.SuppliersTableAdapter taSup = null;
protected NorthwindTableAdapters.SuppliersTableAdapter Adapter {
get {
if (taSup == null) taSup =
new NorthwindTableAdapters.SuppliersTableAdapter();
return taSup; }
}
public Northwind.SuppliersDataTable GetSuppliers() {
return Adapter.GetSuppliers(); }
}
13. Alterar o Código das Páginas aspx
Apagar a Referência da Aplicação Web à Class Library DAL: remova o directório Bin existente na Aplicação Web WebSite.
Adicionar à Aplicação Web uma referência para a Class Library BLL. O código da páginas aspx deve invocar funcionalidades da Camada BLL.
AllProducts.aspx
public partial class AllProducts : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { ProductsBLL produtos = new ProductsBLL();
GridView1.DataSource = produtos.GetProducts(); GridView1.DataBind();
} }
Beverages.aspx
public partial class Beverages : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { ProductsBLL produtos = new ProductsBLL();
GridView1.DataSource = produtos.GetProductsByCategoryID(1); GridView1.DataBind();
} }
NewProduct.aspx
public partial class NewProduct : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { }
protected void Button1_Click(object sender, EventArgs e) { string nomePoduto = TextBox1.Text;
int iDFornecedor = int.Parse(TextBox2.Text); int idCategoria = int.Parse(TextBox3.Text); string quantidade = TextBox4.Text;
decimal precoUnitario = decimal.Parse(TextBox5.Text); short stock = short.Parse(TextBox6.Text);
short quantEncomendada = short.Parse(TextBox7.Text); short nivel = short.Parse(TextBox8.Text);
bool descontinuado = false;
ProductsBLL produtos = new ProductsBLL(); bool b =
produtos.InsertProduct(nomePoduto, iDFornecedor, idCategoria, quantidade, precoUnitario, stock, quantEncomendada, nivel, descontinuado);
TextBox10.Text = b.ToString(); }
}