Criando uma camada de dados com o .NET 2.0
Nesta página
Introdução
Primeira etapa : Criando uma camada de acesso a dados simples
Segunda Etapa : Criando um método personalizado
Terceira Etapa : Implementando uma regra de negócio personalizada
Quarta Etapa : Implementando regras nos métodos padrões
Quinta Etapa : Preparando o componente para uso em aplicações windows
Sexta Etapa : Criando a aplicação Windows
Sétima Etapa : Implementando regras de negócio para o ambiente Windows e Web
Conclusões finais
Introdução
Anteriormente falamos sobre a criação de uma camada de dados no artigo em http://www.bufaloinfo.com.br/artigos/artigo281104.asp . O que explicamos neste artigo continua válido para o .NET 2.0, com mudanças mínimas. Assim sendo esta forma que apresentamos anteriormente ainda é válida, mas as mudanças existentes no .NET 2.0 fizeram com que esta não seja sempre a melhor opção.
No .NET 2.0 temos um volume maior de recursos para aumentar a produtividade na interface. Entre esses recursos temos o ObjectDataSource, que permite fazermos um vinculo de dados entre a interface e um objeto, tudo feito de forma visual.
Neste ponto começam nossos problemas. Os componentes de negócio produzidos pela metodologia anterior não se encaixam com o ObjectDataSource, o que faz com que tenhamos que dispensar alguns dos novos recursos que geram alta produtividade no .NET 2.0.
Mas temos alternativas, é disso que vamos tratar neste artigo. Vamos criar uma camada de negócios e dados utilizando o .NET 2.0 e vamos compara-la com o que criamos anteriormente no .NET 1.1
Primeira etapa : Criando uma camada de acesso a dados simples
Crie um novo webSite
Adicione um novo projeto do tipo ClassLibrary, vamos chama-lo de libDados
Na libDados, utilize o add new item para adicionar um novo dataset chamado dsProdutos
Pela ToolBox, adicione um novo TableAdapter
Utilize a seguinte query para montar o TableAdapter :
SELECT ProductID, ProductName, UnitPrice, UnitsInStock FROM Products
Nas advanced options, desmarque a opção optimistic concurrency.
Na tela para escolha dos métodos a serem gerados, deixe as opções default marcadas.
O TableAdapter é um novo objeto que acompanha as dataTables e é criado de forma personalizada para cada dataTable, conforme desejarmos.
O TableAdapter gera métodos para realizar a leitura e gravação de dados na base de dados.
Clique com o botão direito sobre o Fill do TableAdapter e selecione a opção Add Query
Selecione o tipo de query como "Update"
Altere a query de Update, mudando o parâmetro de @Original_productId para @ProductID
O método de update inicialmente gerado junto do Fill é feito para receber dois parâmetros chave, no nosso exemplo, ProductID e @ProductID. Isso não é adequado e dificulta o vinculo deste método com objetos visuais. Criando um método a parte como este que acabamos de criar, resolvemos o problema.
No site web, crie uma referência para o projeto libDados
Adicione um ObjectDataSource na página default.aspx
Configure o objectDataSource, apontando para o TableAdapter
Observe que o TableAdapter não só é visível como possui os métodos adequados para se interligar automaticamente com o ObjectDataSource - exceto no caso do Update
Altere o método de Update para o nosso método Atualizar
Nas propriedades do objectDataSource, altere a propriedade OldValuesParametersFormatString deixando apenas como "{0}"
Adicione uma dataGrid, vincule-a ao ObjectDataSource e teste, incluindo a edição na grid.
Resultado: Neste ponto temos a interface da aplicação fazendo todo o acesso a dados através de um componente, o tableAdapter, sem acesso direto ao banco.
Uma das muitas questões que ficam pendentes é se essa estrutura nos trará os benefícios de manutenção que temos como objetivo ao utilizar uma estrutura em camadas. Na sequencia de exemplos a seguir vamos responder a esta pergunta.
Segunda Etapa : Criando um método personalizado
Vamos criar em nossa aplicação o recurso de atualizar o preço dos produtos com base em um percentual informado e vamos fazer isso através de nosso objeto de negócios.
Crie uma nova query no tableAdapter
Defina o tipo da query como de Update
Utilize a seguinte query de Update :
Update Products set UnitPrice = Unitprice + (unitprice *@percentual/100)
Defina o nome do método como AtualizarPreco
Na página, adicione um novo ObjectDataSource
Configure o ObjectDataSource apontando para nosso TableAdapter
Selecione como método de Update o nosso AtualizarPreco
Insira uma textbox e um botão na página
Chamaremos a textbox de txtPercentual e o botão de cmdAtualizarPreco. Você pode também inserir um requiredFieldValidator.
Configure a propriedade UpdateParameters do ObjectDataSource, aponte para a textbox.
Programe o click do botão da seguinte forma :
39 Protected Sub cmdAtualizarPreco_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdAtualizarPreco.Click 40 41 ObjectDataSource2.Update() 42 GridView1.DataBind() 43 44 End Sub
A instruçào Update provoca a atualização na base de dados, sendo que o objectDataSource já tem o parâmetro configurado - a textbox - enquanto que o dataBind da gridView faz um refresh nos dados.
Com isso vemos que podemos criar métodos personalizados para realizar tarefas personalizadas no banco. Mas até o momento a realização de tarefas do componente é feita diretamente com o banco, exatamente como um componente de acesso a dados. Como fazer então para implementar regras de negócio que exijam mais código ?
Terceira Etapa : Implementando uma regra de negócio personalizada
Vamos implementar uma regra de validação personalizada para o percentual de aumento. Será uma regra simples, mas demonstrará como regras diversas podem ser implementadas com o TableAdapter
Utilize o Add New Item e adicione uma nova classe
Troque o nome da classe para ProductsTableAdapter
Crie o nameSpace dsProdutosTableAdapters e deixe a classe dentro deste nameSpace
Os tableAdapters são sempre criados como Partial Class, isso nos permite extender as funcionalidades do TableAdapter criando uma outra metade para ele, como estamos fazendo.
No TableAdapter, altere a propriedade Modifiers de nosso método AtualizarPreco para Private
Não existe Partial Method, não podemos alterar o funcionamento do método que já criamos, mas podemos oculta-lo tornando-o private e criar um novo método.
Observe que mesmo definindo o método como private nós podemos chama-lo, pois estamos criando uma partial Class, ou seja, estamos trabalhando dentro da própria classe productsTableAdapter, então o escopo private ainda é acessível.
Altere o nome do método de AtualizarPreco para privAtualizarPreco
O nome do método público precisa ser mantido igual para que a interface não tenha que mudar, então alteramos o nome do método privado.
Crie um novo método AtualizarPreco com a mesma assinatura e com a seguinte implementação :
46 Public Sub AtualizarPreco(ByVal Percentual As Decimal)
47
48 If Percentual > 50 Then
49 Throw New ApplicationException("Percentual muito alto")
50 End If
51
52 Me.privAtualizarPreco(Percentual)
53
54 End Sub
Não existe nenhuma restrição técnica que determine que a assinatura precisa ser igual. De fato em alguns casos você desejará fazer uma assinatura diferente, quando o próprio método de negócio será responsável por gerar algumas das informações que serão gravadas.
Mas no caso de manutenção, quando a interface gráfica já existe e está vinculada ao método existente, se a assinatura for alterada a interface para de funcionar.
No client, apenas para deixar a interface agradável, vamos adicionar um tratamento de erro na atualização :
56 Protected Sub cmdAtualizarPreco_Click(ByVal sender As Object,
ByVal e As System.EventArgs) Handles cmdAtualizarPreco.Click
57
58 Try
59 ObjectDataSource2.Update()
60 Catch ex As Exception
61 Me.ClientScript.RegisterClientScriptBlock(Me.GetType(),
"xxx", "alert('" & ex.Message & "')", True)
62
63 End Try
64 GridView1.DataBind()
65
66 End Sub
Com isso adicionamos uma nova regra de negócio e obtivemos exatamente o que se espera de um desenvolvimento em camadas : Facilidade de manutenção. Foi possível adicionar esta regra sem que a interface client tivesse que ser alterada.
Quarta Etapa : Implementando regras nos métodos padrões
Quando criamos o TableAdapter foram criados os métodos Fill e GetData. Junto desses métodos, foram criados métodos de atualização (insert/update/delete), sendo que nos exemplos anteriores chegamos a substituir o método de update por um método nosso, personalizado.
A questão é como fazer para implementar regras de negócio nestes métodos.
Assim como alteramos o modifers de nosso método personalizado, no exemplo anterior, poderiamos fazer o mesmo com estes métodos. Mas existe um problema : Junto ao Fill e GetData, só podemos alterar o modifier dos dois, Fill e GetData. Não podemos alterar o Modifier dos métodos de atualização que foram gerados junto do Fill e GetData.
Nesse caso o melhor é adotarmos um padrão : Não utilizar os métodos de atualização criados junto com o Fill/GetData e sim criar nossas próprias querys de atualização a parte. Então, durante o wizard para criação do Fill, desmarcamos a opção GenerateDbDirectMethods.
Com isso podemos criar nossas próprias querys de atualização (utilizando o Add Query) e implementar regras de negócio da mesma forma que fizemos nos exemplos anteriores.
Quinta Etapa : Preparando o componente para uso em aplicações windows
As aplicações windows possuem uma importante diferença em relação a aplicações web que precisa ser considerada para a montagem deste componente : Enquanto as aplicações web atualizam registros individualmente, as aplicações windows atualizam (e aqui falo de insert/update/delete) blocos de registros utilizando o dataSet.
O TableAdapter possui diferentes métodos de atualização exatamente prevendo as diferentes formas de atualização entre aplicações web e windows. Para aplicações web o TableAdapter possui os DbDirectMethods, enquanto que para aplicações Windows o TableAdapter gera métodos de update que recebem como parâmetro a DataTable ou DataSet e fazem a atualização de todos os dados no banco. São 2 métodos de Update em overloads.
Esses métodos, porém, nos causarão problema : Não temos o controle do modifers destes métodos, pois são gerados juntamente com o Fill/GetData. Também não temos uma forma direta de "desligar" a geração destes métodos, como fizemos com os DbDirectMethods.
É importante lembrar neste ponto que o TableAdapter mantém um DataAdapter - o mesmo dataAdapter que utilizávamos na versão 1.1 - configurado com as querys que serão executadas no banco.
A solução para "desligar" a geração dos métodos de atualização para o ambiente windows é desligando a geração do insert/update/delete nas advanded options da geração do método Fill. O problema é que isso desliga também a configuração do DataAdapter interno para a realização de atualizações na base.
Nesse caso somos obrigados a criar não só o método de atualização, mas um método que faça a configuração do DataAdapter com as querys de atualização. Para isso podemos utilizar nosso velho amigo CommandBuilder, que na versão 2.0 aprendeu alguns truques novos.
Veja como fica o código em nosso productsTableAdapter :
68 Public Sub AtualizarProdutos(ByVal dados As dsProdutos.ProductsDataTable) 69 70 InicializarAdapter() 71 Me._adapter.Update(dados) 72 End Sub 73 74 75 Private Sub InicializarAdapter() 76 Dim cb As SqlClient.SqlCommandBuilder 77 78 Me._adapter.SelectCommand = Me.CommandCollection(0) 79 80 cb = New SqlClient.SqlCommandBuilder(Me._adapter) 81 cb.ConflictOption = ConflictOption.CompareAllSearchableValues 82 83 Me._adapter.DeleteCommand = cb.GetDeleteCommand 84 Me._adapter.UpdateCommand = cb.GetUpdateCommand 85 Me._adapter.InsertCommand = cb.GetInsertCommand 86 End Sub
No inicializarAdapter utilizamos o commandBuilder para fazer a configuração do dataAdapter, enquanto que no AtualizarProdutos tomamos o cuidado de chamar o inicializarAdapter antes de fazer a atualização. A variável "_adapter" é private, mas nós estamos fazendo uma partial class, portanto ela ainda encontra-se dentro do escopo.
Observe a propriedade ConflictOption, no CommandBuilder. Na versão anterior a forma de tratamento de conflitos não era configurável, isso mudou na versão 2.0, com essa propriedade.
Neste exemplo está sendo determinado que a clausula where das instruções update e delete contenham todos os campos, de forma que se houverem atualizações simultâneas os registros não sejam sobrescritos, não havendo perda de dados.
No método AtualizarProdutos podemos ainda implementar outros recursos com relação a gravação, tal como gravação transacional, controle de campos auto-numeração, etc. Veja em http://www.bufaloinfo.com.br/dicas.asp?cod=611 , http://www.bufaloinfo.com.br/dicas.asp?cod=630 e http://www.bufaloinfo.com.br/dicas.asp?cod=657
Sexta Etapa : Criando a aplicação Windows
Adicione na solução um novo projeto Windows
Defina este projeto como startUp Project
Adicione uma referência para a libDados
Abra a janela de Data Sources em Data->Show Data Sources
Adicione um novo data source
No tipo, escolha "Object"
Na seleção de objetos, aponte para o dsProducts na libDados
Bem diferente da aplicação web, aqui apontamos para o DataSet e não para o objeto que possui os métodos de negócio.
Arraste a tabela Products que apareceu na janela Data Sources para dentro do formulário
Verifique se ela está com a opção "Details" selecionada como formato de geração.
Clique com o botão direito no ProductsBindNavigator e selecione "Edit Items"
Habilite o botão SaveItem
Codifique o form_load para carregar os dados para o dataSet
88 Dim obj As New libDados.dsProdutosTableAdapters.productsTableAdapter 89 90 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 91 92 obj.Fill(DsProdutos.Products) 93 End Sub
Com um duplo clique no botão salvar, que encontra-se no topo do formulário, codifique a gravação dos dados :
96 Private Sub ProductsBindingNavigatorSaveItem_Click(ByVal sender As System.Object,
ByVal e As System.EventArgs) Handles ProductsBindingNavigatorSaveItem.Click
97
98 ProductsBindingSource.EndEdit()
99 obj.AtualizarProdutos(DsProdutos.Products)
100 MsgBox("Todos os produtos foram atualizados")
101 End Sub
Observe o uso do EndEdit para garantir a gravação do registro atual, caso ele esteja em edição.
Sétima Etapa : Implementando regras de negócio para o ambiente Windows e Web
O grande desafio da implementação de regras é evitar que o código dessas regras seja duplicado.
No ambiente web, utilizamos os métodos de atualização direta, recebendo cada campo como um parâmetro. No ambiente windows, recebemos uma dataTable que pode conter diversos registros. Formatos diferentes de dados. Portanto nosso desafio é validar ambos sem duplicar a lógica de validação.
Precisaremos então ter um método privado de validação de dados. Este método privado de validação será chamado tanto da atualização direta como da atualização com dataTables.
Porém na implementação temos que escolher uma entre duas opções :
-
O método que recebe a dataTable pode chamar o método de validação uma vez para cada registro atualizado, passando os valores dos campos ou
-
O método de atualização direta cria um dataSet e dataTable e gera uma nova linha, passando esta dataTable para o método de validação
Assim sendo, em um dos casos precisa haver uma conversão do formato dos dados.
Veja um exemplo a seguir. Neste caso convertemos os dados do dataSet, chamando a validação linha a linha :
104 Private Sub ValidarDados(ByVal productName As String, ByVal unitPrice As Decimal,
ByVal UnitsInStock As Integer, ByVal ProductId As Integer)
105
106 If unitPrice > 1000 Then
107 Throw New ApplicationException("O preço é muito alto")
108 End If
109 End Sub
110
111 Public Sub AtualizarProdutos(ByVal dados As dsProdutos.ProductsDataTable)
112 InicializarAdapter()
113
114 Dim dt As DataTable
115 dt = dados.GetChanges()
116
117 For Each dr As dsProdutos.ProductsRow In dt.Rows
118 ValidarDados(dr.ProductName, dr.ProductID, dr.UnitsInStock, dr.ProductID)
119
120 Next
121
122 Me._adapter.Update(dados)
123 End Sub
124
125 Public Sub InserirDados(ByVal productid As Integer,
ByVal productName As String, ByVal unitPrice As Decimal, ByVal unitsInStock As Integer)
126
127 Me.ValidarDados(productName, unitPrice, unitsInStock, productid)
128
129 Me.privInserirDados(productid, productName, unitPrice, unitsInStock)
130
131 End Sub
Conclusões finais
|
Modelo em Camadas versão 1.1 |
Modelo em Camadas versão 2.0 |
|---|---|
|
Permite a troca de servidor de banco sem necessidade de manutenção (excetuando-se querys SQL) e mantendo a melhor performance possível |
Fica vinculado a um único servidor de banco |
|
Independe da ferramenta de desenvolvimento |
Vinculo direto com a ferramenta de desenvolvimento |
|
Trabalha de forma não tipada, permitindo a inclusão/exclusão de campos dinamicamente apenas com manutenção no banco e interface |
Por trabalhar de forma tipada, exige a manutenção e recompilação da camada de negócios em caso de mudança nos campos |
|
Não se integra com a interface gráfica, exigindo considerável codificação na interface, além da própria codificação na camada de negócios |
Totalmente integrado com a interface, não só evita o código no desenvolvimento do client como também na camada de negócios |
Como você pode observar neste quadro comparativo, o modelo que criamos para a versão 1.1 tem várias vantagens em relação ao novo modelo que acabei de apresentar.
Mas a única desvantagem pode ser decisiva : o modelo anterior não se integra com a nova interface gráfica, enquanto que o novo evita até mesmo boa parte da codificação nas camadas de componentes.
Boa parte das aplicações no .NET 2.0 podem ser produzidas neste novo modelo com vantagens para a produtividade e manutenção. Certamente apenas algumas, as maiores e com necessidades específicas, precisarão utilizar os recursos mais versáteis do modelo anterior.
Dennes Torres MCAD,MCSD,MCSE,MCDBA