Skip Navigation Links



Translate this page now :



»Programação
»Programação.NET
»Banco de Dados
»Webdesign
»Office
» Certificações Microsoft 4
»Treinamentos4
»Programação 4
»Webdesign«
»Office & User Tips«
»Grupos de UsuĆ”rios
»CĆ©lulas AcadĆŖmicas«
intcontpiada : 118
Viciado
Você já está cadastrado e participa do grupo de usuários de sua cidade ? Se não, comente o porque.
 
 
FaƧa um pequeno teste com 10 questƵes de VB
.:.
Teste seus conhecimentos em Visual Basic, SQL Server e ASP 3.0 com nossas provas on-line
.:.
Aprimore seus conhecimentos em programaĆ§Ć£o com nosso treinamento on-line de lĆ³gica de programaĆ§Ć£o
.:.
Veja nosso calendƔrio de treinamentos
Gostou da PƔgina?
Então

para um amigo!

Pesquisa personalizada
Pesquisar Dicas:

 







Criando uma camada de dados com o .NET 2.0

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





Envie seus comentįrios sobre este artigo

Nome :

E-mail :

Comentários :


Avise-me quando houverem novos comentįrios nesta pįgina

Veja abaixo os comentários já enviados :

Nome : Ronaldo Biagini E-Mail : biagini@tutopia.com.br
Olá. Preciso de ajuda quanto a terceira etapa.

No caso não sei onde a nova classe deverá ser criada, e também não consigo acessar o método private "privAtualizarPreco" dessa nova classe.
Outra coisa é o namespace. Como eu devo criá-lo? Como uma pasta ou simplesmente renomeando o namespace da classe criada?

Desde já, agradeço a sua ajuda.
Nome : Dennes Torres E-Mail : dennes@bufaloinfo.com.br
Oi !

Conforme o 1o item, utilize o Add new item e adicione uma nova classe ao projeto.

Já a criação do namespace é feita com as instruções namespace e end namespace, que ficarão neste mesmo arquivo criado.

[]'s
Nome : Fabio E-Mail : fdb@hotmail.com
Olá,

Também estou com dúvidas na terceira etapa.
Não consigo acessar o método private "privAtualizarPreco" da classe ProductsTableAdapter.
Erro:'privAtualizarPreco' is not a member of 'libDados.dsProdutosTableAdapters.ProductsTableAdapter'

Obrigado pela ajuda.
Nome : Fábio E-Mail : fdb@hotmail.com
OK, ja resolvi meu problema.
Grato
Nome : ziFzWUFDoQOl E-Mail : info@ipafiemmefassa.org
desde que entrei na fddclaaue de direito vi a necessidade de ler,fiz dois semestre como nao tenho tempo e condicoes de pagar,peguei o habito de ler,leio ate 3livros por semana,fora os jornais diarios,tenho amigos que me emprestam livros pois trabalho num restaurantes e clientes me trazem os livros"meus amigos"comento com meus colegas sobre alguma historia que li e aumento e eles ficam fascinado por isso surgiu minha vontade de escrever livro,ou algum,obrigado pelas dicas ainda me vera com um grande escritor ou umlivro de sucesso.ate breve
Nome : 1 E-Mail : 1
-1'
Nome : -1' E-Mail : 1
1
Nome : 1 E-Mail : -1'
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : tM5oTol3z E-Mail : 9ztcvf81z@yahoo.com
Ole1 amigos, eu tive um peueqno problema.Ao instalar o Access Manager, um plugin para habilitar e desabilitar e1rea do wordpress para usue1rios especedficos ou um grupo este mesmo plugin removeu de todos os admins a funcionalidade de adicionar ou remover plugin, melhor, ao acessar o link /wp-admin/plugins.php aparece a seguinte mensagem "Sem permissf5es suficientes para acessar esta pe1gina." tenho todo o acesso administrativo exceto esta e1rea de plugins.Adiantando possedveis apoios de caros amigos, je1 tentei ir em phpmyadmin no meu servidor uolhost mais o meu banco de dados ne3o e9 acessedvel devido ter sido instalado direto do site da uolhost e ne3o por um ftp, problema este que me impossibilita de fazer a desativae7e3o do plugin via phpmyadmin, gostaria que se algue9m pudesse me auxilar buscando uma maneira de voltar a ter o acesso a e1rea exclusiva de plugins do wordpress agradeceria, he1, je1 tentei de1 a permisse3o 777 via filezilla e nada tambe9m e excluir a pasta referente ao plugin access manager via ftp e nada tambe9m, e antes que algue9m pergunte, o mesmo plugin ne3o tem a fune7e3o de HABILITAR acesso a aba PLUGINS ou similar.Grato se algue9m poder ajudar, passei o me1ximo de informae7e3o possedvel Responder 08/05/2012
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : -1' E-Mail : 1
1
Nome : 1 E-Mail : -1'
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : -1' E-Mail : 1
1
Nome : 1 E-Mail : -1'
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : -1'
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : -1' E-Mail : 1
1
Nome : 1 E-Mail : -1'
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
-1'
Nome : -1' E-Mail : 1
1
Nome : 1 E-Mail : -1'
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
-1'
Nome : -1' E-Mail : 1
1
Nome : 1 E-Mail : -1'
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : tHWs9yLJG E-Mail : w6mrcvvu@mail.com
Anche il Fatto ha titolato su questo tono. Ma non ci credo, nemmeno un minuto. Mai scambiare i desideri con la realtà. Li tiene insieme il collante più tenace che esista: la convenienza, appunto. Fino a quando c’è il Berlusca, non ci saranno movimenti in questa zona. I fuochi d&;2817#artificio cominceranno quando morirà, perché prima non molla, a meno di cataclismi non troppo probabili (anche se alla prossima asta dei bot voglio vedere chi compra in perdita). Michele Gardini

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Conheça mais sobre o nosso site :

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::



Quer saber mais?
Faça um curso na Búfalo Informática, Treinamento e Consultoria e
Prepare-se para o Mercado!
Veja o que a Búfalo tem para você.

ļæ½ BĆŗfalo InformĆ”tica, Treinamento e Consultoria - Rua Ɓlvaro Alvim, 37 Sala 920 - CinelĆ¢ndia - Rio de Janeiro / RJ
Tel.: (21)2262-1368 (21) 9240-5134 (21) 9240-7281 e-Mail:
contato@bufaloinfo.com.br