Vamos agora desenvolver nosso projeto de teste que será um Console Application.
Como foi dito no início do artigo, iremos utilizar o conceito de três camadas (Model, Business e Data Access). Dessas três camadas, iremos codificar apenas a Model e a Business, pois utilizaremos nossa classe DAL Genérica.
No seu Visual Studio 2008, dentro da nossa solução, crie mais um projeto de nome “TestProject” do tipo Console Application.
Vamos primeiramente montar a estrutura de pastas deste projeto:

Ilustração 12
– Estrutura de pastas do projeto de teste
Dentro do
projeto temos uma pasta BO (anacrônico para Business Objects), onde ficarão as
classes da camada Business, e temos uma pasta onde ficarão as classes da camada
Model (ou Entities). Temos também um App.Config, que conterá as strings de
conexão e algumas chaves na seção appconfig. A classe Program.cs será o Console
Application propriamente dito.
Vamos
iniciar a codificação pelo App.Config:
xml version="1.0" encoding="utf-8" ?>
appSettings>
connectionStrings>
configuration>
Este é o
nosso arquivo de configuração. Ele é bem simples. Possui apenas a chave “current.connection” na seção <appSettings>, necessária para o funcionamento da nossa classe DAL
genérica. Essa chave aponta para a string de conexão que será a mais utilizada
pela nossa aplicação.
Na seção <connectionStrings> temos duas conection strings, uma que será a principal
“sqlServerConn”, que é a que utilizaremos no nosso primeiro exemplo, e outra de
nome “sqlServerConn2” que utilizaremos mais tarde.
Agora
precisamos adicionar as referências dos outros projetos da solução ao projeto
de teste. Clique com o botão direto do mouse no projeto de teste, depois clique
em “add reference” na aba “Projects” selecione todos os projetos e clique em
“OK”.

Ilustração 13
– Referências a serem adicionadas ao projeto de teste
Agora
que temos todas as referências que precisamos, vamos codificar nossa classe
model. No nosso BD temos apenas uma tabela e cinco procedures. Vamos então
criar uma classe para representar essa tabela e utilizar os Atributos
customizados que construimos, para mapear as propriedades dessa classe com os campos
da tabela e com os parâmetros das Stored Procedures.
Clique
com o botão direito do mouse na pasta “Model” e adicione uma nova classe de
nome “Produto.cs”.
Vale
lembrar que, por ser uma classe da camada Model, esta classe não conterá nenhum
método, somente propriedades. Primeiramente adicione os seguintes “usings”:
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using GenDAL.Library.Attributes;
using GenDAL.Library.BaseClasses;
Logo após
declaramos a namespace da classe model. Se não estiver assim, corrija:
namespace TestProject.Model
{
}
Vamos
agora declarar a classe:
[AttProcNameSelectNoFilter("spSELProd_S")]
[AttDataAccessType(DataAccesType.Both)]
public class Produto : BaseModelClass
{
}
Utilizamos
dois de nossos atributos customizados - AttProcNameSelectNoFilter que serve para indicar qual é a
procedure que realiza um Select sem filtro e AttDataAccessType
que serve para indicar
qual será o tipo de acesso a dados que a classe DAL realizará. No caso deixamos
setado para Both, que quer dizer que a classe DAL poderá acessar o BD via SP ou
via execução de SQL. Repare que a classe herda de BaseModelClass.
Essa herança é
necessária para conseguirmos utilizar a classe DAL genérica e esta utilize o método de validação de uso dos
atributos que criamos.
Vamos
agora criar nossos campos privados que serão o reflexo dos campos da tabela e
dos parâmetros esperados pelas SPs que criamos:
#region Fields
private int _id;
private string _nome;
private string _url;
private string _desc;
private decimal _preco;
private string _imagem;
private byte[] _imagemByte;
#endregion
Vamos
agora criar as propriedades de acesso aos campos privados da nossa classe. Aqui
utilizaremos os nossos atributos customizados para mapear essas propriedades
com os campos da tabela e com os parâmetros das SPs.
#region Properties
[AttParamProcDB(ProcedureTypes.Delete, "spDELProd_D", "@pProdId", DbType.Int32, ParameterDirection.Input)]
[AttParamProcDB(ProcedureTypes.Update, "spUPDProd_U", "@pProdId", DbType.Int32, ParameterDirection.Input)]
[AttParamProcDB(ProcedureTypes.SelectFilter, "spSELProd_SF", "@pProdId", DbType.Int32, ParameterDirection.Input)]
[AttParamProcDB(ProcedureTypes.Insert, "spINSProd_I", "@outProdId", DbType.Int32, ParameterDirection.Output, 50)]
[AttFieldDB("prod_id")]
public int Id
{
get { return _id; }
set { _id = value; }
}
[AttParamProcDB(ProcedureTypes.Update, "spUPDProd_U", "@pProdNome", DbType.String, ParameterDirection.Input)]
[AttParamProcDB(ProcedureTypes.Insert, "spINSProd_I", "@pProdNome", DbType.String, ParameterDirection.Input)]
[AttFieldDB("prod_nome")]
public string Nome
{
get { return _nome; }
set { _nome = value; }
}
[AttParamProcDB(ProcedureTypes.Update, "spUPDProd_U", "@pProdUrl", DbType.String, ParameterDirection.Input)]
[AttParamProcDB(ProcedureTypes.Insert, "spINSProd_I", "@pProdUrl", DbType.String, ParameterDirection.Input)]
[AttFieldDB("prod_url")]
public string Url
{
get { return _url; }
set { _url = value; }
}
[AttParamProcDB(ProcedureTypes.Update, "spUPDProd_U", "@pProdDesc", DbType.String, ParameterDirection.Input)]
[AttParamProcDB(ProcedureTypes.Insert, "spINSProd_I", "@pProdDesc", DbType.String, ParameterDirection.Input)]
[AttFieldDB("prod_desc")]
public string Desc
{
get { return _desc; }
set { _desc = value; }
}
[AttParamProcDB(ProcedureTypes.Update, "spUPDProd_U", "@pProdPreco", DbType.Decimal, ParameterDirection.Input)]
[AttParamProcDB(ProcedureTypes.Insert, "spINSProd_I", "@pProdPreco", DbType.Decimal, ParameterDirection.Input)]
[AttFieldDB("prod_preco")]
public decimal Preco
{
get { return _preco; }
set { _preco = value; }
}
[AttParamProcDB(ProcedureTypes.Update, "spUPDProd_U", "@pProdImg", DbType.String, ParameterDirection.Input)]
[AttParamProcDB(ProcedureTypes.Insert, "spINSProd_I", "@pProdImg", DbType.String, ParameterDirection.Input)]
[AttFieldDB("prod_img")]
public string Imagem
{
get { return _imagem; }
set { _imagem = value; }
}
[AttParamProcDB(ProcedureTypes.Update, "spUPDProd_U", "@pProdImgByte", DbType.Binary, ParameterDirection.Input)]
[AttParamProcDB(ProcedureTypes.Insert, "spINSProd_I", "@pProdImgByte", DbType.Binary, ParameterDirection.Input)]
[AttFieldDB("prod_img_byte")]
public byte[] ImagemByte
{
get
{
if (_imagemByte == null)
{
_imagemByte = new byte[0];
}
return _imagemByte;
}
set { _imagemByte = value; }
}
#endregion
Repare
que para usarmos o atributo AttParamProcDB precisamos informar o tipo de procedure,
o nome da procedure, o nome do parâmetro que será mapeado com a propriedade, o
tipo de dado do parâmetro e a direção do parâmetro. Para utilizarmos o atributo
AttFieldDB precisamos declarar apenas o nome do campo da tabela. Com isso mapeamos
nossa classe com a tabela Produto do BD assim como com as procedures que farão
manipulações nessa tabela. Em outras palavras fizemos um mapeamento
Objeto/Relacional
Por
último vamos construir um construtor vazio:
#region Constructor
public Produto()
{
}
#endregion
Vamos
agora construir nossa classe da camada Business. Vale lembrar que a classe Business, não deve
possuir nenhum código de acesso a dados, deve ter apenas a inteligência de negócio
da aplicação. Todo código de acesso a dados estará encapsulado na nossa classe
DAL genérica.
Clique
com o botão direito do mouse na pasta BO e adicione uma nova classe de nome
“ProdutoBO”.
Vamos
começar com as referências que precisamos:
using System;
using System.Collections.Generic;
using GenDAL.Library.DataBaseLib.GenericDAL;
using TestProject.Model;
Precisamos
de apenas 4 referências. As 2 primeiras são do próprio .Net. As duas últimas são dos nossos projetos.
Vamos
declarar a namespace e a classe:
namespace TestProject.BO
{
public class ProdutoBO : GenericDAL
{
}
}
Na
declaração da classe dizemos que ProdutoBO herdará da nossa classe DAL
genérica. Lembrando que ao declarar essa herança, precisamos passar um Type
Parameter. Esse type parameter precisa ser uma implementação da nossa interface
“IBaseModel” e precisa ter um construtor vazio. Então temos que passar a classe
model “Produto” que herda de BaseModelClass, que é uma implementação de
IBaseModel e possui um construtor vazio.
Pronto
com isso teremos acesso aos métodos de acesso a dados da classe DAL, não
precisando codificar nenhuma classe DAL. Na reaIidade, o uso desta técnica
deixa a classe DAL genérica preparada para atender às exigências da classe
business que estamos construindo. A classe DAL, por sua vez, possui a
funcionalidade de ler algumas informações contidas em nossos atributos a
respeito da classe model corrente. Com essas informações a classe DAL consegue
“tomar a decisão” de quais métodos executar para atender às exegências da
classe Business que a esta consumindo. Isso nos deixa livre para focarmos nas
regras de negócio do nosso projeto.
Vamos
ao primeiro método da nossa classe Business. Esse método acessará a procedure
que realiza um select sem filtros e retornara um List do tipo Produto, relativo
ao resultado da busca dessa procedure:
public List GetListaSemFiltro()
{
List retorno = null;
using (base.TransactionScope())
{
retorno = base.ExecuteProcSelectNoFilter();
}
return retorno;
}
Um
detalhe interessante é o conceito de “TransactionScope”. Definimos
anteriormente que todo acesso a dados deverá estar dentro de um contexto de
transação, como evidenciado no bloco de comando:
using (base.TransactionScope())
{
retorno = base.ExecuteProcSelectNoFilter();
}
Outro
detalhe interessante é que a utilização de type parameters, faz o compilador em
tempo de design entender quais são os tipos de retorno e os tipos de parâmetros
utilizados nos métodos da classe DAL genérica. Para verificar, deixe o cursor
do mouse sobre a declaração do método:

Ilustração 14
– Exemplo de como o compilador interpreta o uso de Type Parameters
O Visual
Studio mostra o retorno do método como sendo um List
Vamos criar
um novo método que fará duas operações dentro de um TransactionScope:
public void TestaInsertUpdateTrans(Produto pObj)
{
using (base.TransactionScope())
{
this.ExecuteProcInsert(pObj);
pObj.Nome = "teste Transaction";
this.ExecuteProcUpdate(pObj);
}
}
Este
método chama as procedures de insert e um update dentro de uma mesma transação.
Claro que isto é apenas um exemplo, pois numa situação real, seria muito improvável
fazer um update logo após um insert. O intuito aqui é demostrar que com o conceito
de TransactionScope, temos condições de realizar todos as nossas operações
dentro de transações, sem nos preocuparmos em criar os objetos do ado.net de
transação e conexão.
No próximo artigo, codificaremos a classe program.cs para iniciarmos os testes.