Banco de Dados - SQL Server

Introdução ao LINQ - Parte 2 (LINQ to SQL)

Este artigo tem o objetivo de apresentar o funcionamento básico do LINQ to SQL, desenvolvendo uma aplicação utilizando o Visual Studio 2008 que permita realizar operações SELECT, INSERT, UPDATE e DELETE utilizando esta ferramenta, no banco de dados Northwind.

por Rodolfo Paoni



Este artigo tem o objetivo de apresentar o funcionamento básico do LINQ to SQL, desenvolvendo uma aplicação utilizando o Visual Studio 2008 que permita realizar operações SELECT, INSERT, UPDATE e DELETE utilizando esta ferramenta, no banco de dados Northwind.

Tecnologias utilizadas:

Visual Studio 2008 Express (VB 2008 Express);

.NET Framework 3.5;

SQL Server 2005 Express;

Banco de dados exemplo Northwind.

Para começar, uma dica bastante interessante para se aprofundar nesta tecnologia é o LINQPad. Trata-se de uma ferramenta gratuita que pode ser encontrada em

http://www.linqpad.net/ e que normalmente é utilizada para testes e para nos familiarizarmos com a sintaxe.

Abaixo apresento um breve exemplo, utilizando o banco Northwind do SQL 2005 Express:

From c In Categories Join p In Products On c.CategoryID Equals p.CategoryID Where c.CategoryName= "Condiments" Select c.CategoryName, p.ProductName, p.UnitPrice, p.UnitsInStock

Nesta consulta, faço uma junção (JOIN) entre as tabelas Categories e Products, onde relaciono as tabelas utilizando o identificador da categoria, logo especificando o nome da mesma (neste caso “Condiments”) e por fim, seleciono algumas colunas.

01

Resultado da execução da query.

Como vemos, a interface é muito simples. Basta definirmos o tipo como VB Expression (Poderia ter sido definido C# Expression, no caso de utilizarmos uma expressão em C#) e executarmos a consulta.

Se quiséssemos obter todas as informações, ou seja, não especificando quais campos da tabela desejamos retornar na consulta, poderíamos omitir o SELECT

From c In Categories Join p In Products On c.CategoryID Equals p.CategoryID Where c.CategoryName= "Condiments"

Neste caso o SELECT ficaria implícito e obteríamos o seguinte resultado:

02

Repare que o resultado da consulta abrange todos os campos resultantes da junção das tabelas e nos mostra um resultado bastante detalhado, onde cada linha possui a categoria e o respectivo produto desta categoria, sendo que neste caso uma categoria pode ter 1 ou vários produtos (1-N), como por exemplo, a categoria 2, detalhada acima.

Sugiro estudar os exemplos que vem incluídos no LINQPad, pois estes englobam comandos para leitura, atualização, inclusão e deleção em banco de dados e além disso estão bem comentados.

Apesar dos assistentes do Visual Studio 2008 encapsularem muitas funcionalidades do LINQ to SQL, deixando-as transparentes ao desenvolvedor, resolvi revisar alguns conceitos de mapeamento O/R, que são utilizados internamente pelo LINQ to SQL.

O mapeamento O/R tem como objetivo básico aproximar o modelo de representação dos dados no banco de dados (modelo relacional) com o modelo de representação de dados na aplicação (objetos).

Basicamente quando usamos ADO.NET, utilizamos por exemplo DataSets alimentados por DataAdapters para representar as tabelas do banco de dados em um modelo desconectado.

Com a introdução dos DataSets tipados houve a melhoria de não precisarmos mais utilizar um objeto do tipo DataSet, mas sim utilizar um objeto do tipo que definimos no Schema XML.

Quando utilizamos classes de negócio que encapsulam funcionalidades e trabalham com  componentes bean, por exemplo, geralmente representamos as tabelas do banco de dados  em classes e cada propriedade representa uma coluna da tabela. Quando necessitamos inserir, por exemplo, um novo cliente em uma tabela cliente, no caso de um sistema em 3 camadas, geralmente chamamos a Bll  (Business Logical Layer) e o método correspondente.

Dim c As New Cliente()

c.Nome = "Rodolfo Paoni"

c.Profissao = "Programador"

Bll.InserirCliente(c)

Este exemplo é puramente ilustrativo, mas o que ocorre neste caso é que a estrutura da tabela é vista pela aplicação como um conjunto de propriedades e não nos preocupamos com detalhes sobre a conexão com o banco de dados e etc. Mas repare um detalhe: Manter essa transparência não é fácil e é esse o objetivo do LINQ to SQL:

Mapear as tabelas como classes, campos como propriedades e procedures como métodos, mantendo os relacionamentos devidos entre as tabelas e respeitando as constraints (Chaves primárias e Chaves estrangeiras).

Imagine por exemplo, no banco de dados exemplo do SQL Server 2005, o Northwind, e as tabelas Categories e Products. Trata-se de um relacionamento 1-N, ou seja, uma categoria pode ter 1 ou vários produtos. Quando mapeamos essas 2 tabelas do banco de dados utilizando o LINQ to SQL, na classe Product (mapeada da tabela Products) há uma propriedade Category que representa a qual categoria o produto  pertence. Na classe Category (mapeada da tabela Categories) há uma coleção de objetos Product pertencentes àquela categoria. Isso tudo é feito de maneira transparente e aumenta grandiosamente a produtividade do desenvolvimento.

OK, agora vamos à prática. Crie um novo projeto no Visual Studio 2008 chamado linqToSQL (ou qualquer outro nome de sua preferência).

Adicione um novo form chamado frmNorthwind e inclua um controle DataGridView  e 4 buttons de acordo que fique desta maneira:

01

Agora crie uma conexão com o banco de dados Northwind , clique com o botão direito em cima do projeto e adicione um DataContext chamado Northwind.dbml

03

Arraste a tabela Customers para o descritor Object Relational Mapping , obtendo o seguinte resultado:

01.JPG

Agora declare a instância do DataContext:

Dim db As New NorthwindDataContext()

Em seguida inclua o seguinte código no evento Click de cada botão:

    Private Sub btnSelect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSelect.Click

        CarregarDados()

    End Sub

    Private Sub btnInsert_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnInsert.Click

        Try

            Dim c As New Customer With {.Address = "Floriano", .City = "Petrópolis", .ContactName = "Rodolfo Paoni", .CompanyName = "LNCC", .ContactTitle = "RPAONI", .Country = "Brazil", .CustomerID = "PAONI", .Fax = "999", .Phone = "999", .PostalCode = "9999", .Region = "LEST"}

            db.Customers.InsertOnSubmit(c)

            db.SubmitChanges()

            CarregarDados()

            MessageBox.Show("Cliente inserido com sucesso")

        Catch ex As Exception

            MessageBox.Show("Erro na inclusão do cliente: " & ex.Message)

        End Try

    End Sub

    Private Sub btnUpdate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnUpdate.Click

        Try

            Dim cust = (From c In db.Customers Where c.ContactName = "Rodolfo Paoni").Single

            cust.ContactName = "Rodolfo Paoni Viçoso"

            db.SubmitChanges()

            CarregarDados()

            MessageBox.Show("Cliente atualizado com sucesso")

        Catch ex As Exception

            MessageBox.Show("Erro na atualização do cliente: " & ex.Message)

        End Try       

    End Sub

    Private Sub btnDelete_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDelete.Click

        Try

            Dim cust = (From c In db.Customers Where c.ContactName = "Rodolfo Paoni").Single

            db.Customers.DeleteOnSubmit(cust)

            db.SubmitChanges()

            CarregarDados()

            MessageBox.Show("Cliente excluído com sucesso")

        Catch ex As Exception

            MessageBox.Show("Erro na exclusão do cliente: " & ex.Message)

        End Try       

    End Sub

    Private Sub CarregarDados()

        Try

‘Preenchendo o DataGridView

            dgSource.DataSource = From cust In db.Customers Order By cust.CustomerID

        Catch ex As Exception

            MessageBox.Show("Erro ao carregar dados: " & ex.Message)

        End Try        

    End Sub

O que temos basicamente é um DataContext chamado NorthwindDataContex,  que é usado para persistir as entidades  do banco de dados, assim como seus relacionamentos. Para cada tabela adicionada na janela Object Relation Mapping uma representação no DataContext  será criada. Poderemos então acessar classes como se fossem tabelas e propriedades como se fossem colunas. No próximo artigo apresentarei como utilizar Stored Procedures e mapeá-las como métodos.

No método CarregarDados  simplesmente carrego todos os clientes existentes. Repare que o tipo gerado por essa consulta é uma coleção de Customer. O tipo dessa coleção é descoberto em tempo de execução.

Para inserir um cliente, inicializo o objeto utilizando um novo recurso do .NET Framework 3.5 chamado Object Initiazers (Inicializadores de objetos), onde podemos definir as propriedades do objeto no momento da declaração, simplificando a leitura e não havendo necessidade de sobrecarregarmos o construtor da classe. Logo, insiro o cliente na coleção de clientes do meu DataContext e submeto as mudanças do mesmo ao banco de dados.

Para atualizar, busco um cliente específico, assegurando que é um único objeto do tipo Customer e não uma coleção, pelo operador Single. A partir daí mudo o título do contato e submeto as alterações ao banco de dados. Para quem já utilizou DataSets, seria uma idéia parecida com a de submeter as alterações feitas no mesmo ao banco de dados por meio do método Update de DataAdapter.

E por fim, para excluir, seleciono um único cliente e o excluo da coleção de clientes do meu DataContext  e por fim, submeto as alterações ao banco de dados.

Inserindo um novo cliente:

Atualizando um cliente:

No próximo artigo, apresentarei como desenvolver a mesma aplicação, utilizando Stored Procedures.

Até a próxima!

Rodolfo Paoni

Rodolfo Paoni - Rodolfo Paoni - Desenvolvedor .NET - Twitter: @rodolfopaoni