segunda-feira, 23 de junho de 2008

Linq to SQL - Trabalhando com bancos de dados

Click here to see this post in English

Neste post vou continuar a mostrar sobre a tecnologia Linq. Desta vez veremos como ela é usada para acessar e manipular dados em tabelas de bancos de dados relacionais.

Para habilitar Linq para acessar bancos de dados nós precisamos primeiro estabelecer relações entre os objetos de negócio do nosso software e as tabelas do banco de dados. Para isso usamos mapeamento de classe/abtributos.

Este mapeamento é simples: Nós criamos uma classe que representa uma tabela do banco de dados e nesta classe nós indicamos qual tabela do banco de dados ela representa. Nas propriedades desta classe nós marcamos quais propriedades representam quais campos da tabela.

Na versão atual Linq suporta apenas o MS SQL Server, mas há desenvolvedores por aí que já estão trabalhando no suporte a outros bancos também.

Para começar vamos mostrar uma tabela no banco de dados: Produtos

CREATE TABLE [dbo].[Produtos](
[ProdutoID] [uniqueidentifier] ROWGUIDCOL
NOT NULL CONSTRAINT [DF_Produtos_ProdutoID] DEFAULT (newid()),
[Descricao] [nvarchar](100) COLLATE Latin1_General_CI_AS NOT NULL,
[Preco] [decimal](10, 2) NOT NULL,
[Saldo] [int] NOT NULL,
[Versao] [timestamp] NOT NULL
)


Atenção especial à coluna Versao, do tipo timestamp. Esta coluna armazena a versão do registro. Ela é útil para efeitos de atualizações otimistas e concorrência e Linq faz ótimo uso dela.

Antes de continuar insira alguns registros nesta tabela.

Agora vamos codificar uma classe que faça uso desta tabela. Para utilizar Linq devemos adicionar referência no projeto para o assembly System.Data.Linq. Abaixo segue o código da classe:

using System;
using System.Data.Linq.Mapping;

[Table(Name="Produtos")]
class Produto {

[Column(IsPrimaryKey = true,
IsDbGenerated = true,
AutoSync = AutoSync.OnInsert)]
public Guid ProdutoID { get; set; }

[Column(CanBeNull = false)]
public string Descricao { get; set; }

[Column(CanBeNull = false)]
public decimal Preco { get; set; }

[Column(CanBeNull = false)]
public int Saldo { get; set; }

[Column(IsDbGenerated = true,
IsVersion = true, AutoSync = AutoSync.Always)]
public System.Data.Linq.Binary Versao { get; set; }
}


Agora vamos criar uma query Linq nesta tabela do banco. Até aqui nós definimos o mapeamento da entidade e dos campos, mas nós não definimos nada sobre o banco de dados. Como faremos isso?

Há uma classe em Linq chamada DataContext. Podemos pensar nela como se fosse um objeto de conexão com um banco de dados, mas o DataContext vai além disso. Ele é responsável pela conexão com o banco de dados, montagem e execução das queries, tratamento dos resultados, cuida das operações de atualização dos registros e até cuida de transações e concorrência de registros!

Para isso só devemos instanciá-lo e ele faz tudo automaticamente! Ou seja, nós não precisamos mais criar códigos para abrir conexão, fechar conexão, montar queries em string, tratar resultados... Vamos ver na prática:

using System;
using System.Data.Linq;
using System.Linq;

class Program {
static void Main(string[] args) {

var connStr = @"Data Source=.\SQLEXPRESS;Initial Catalog=DadosSql;User ID=felipe;Password=123";

using (var db = new DataContext(connStr)) {
var Produtos = db.GetTable<Produto>();

var qry =
from p in Produtos
orderby p.Descricao
select p;

foreach (var prod in qry) {
Console.WriteLine("{0}, {1}, {2}", prod.Descricao,
prod.Preco,
prod.Saldo);
}
}
}
}


Vamos repassar:

- Criamos um DataContext para conectar ao nosso banco de dados;
- Criamos uma variável local para ser a definição da nossa tabela mapeada;
- Fazemos a query extraindo dados desta "tabela";
- Exibimos os resultados.

A parte mais legal é: Será que o DataContext vai trazer a tabela Produtos inteira quando passar pela linha de código onde criamos a variável Produtos? A resposta é não! Então, quando o banco vai na tabela Produtos? Na linha onde criamos a qry? Também não! Ora, então onde é?

Uma das características mais legais do Linq to SQL é a habilidade de executar a query no banco apenas no momento em que seus dados são utilizados. No nosso caso, no loop foreach. Aquela query produziu a variável qry do tipo IOrderedQueryable que, parece, é uma lista. Tem até foreach! Mas ela não guarda as informações nela no momento em que é instanciada. Quando o software "requer" os dados desta "lista", ela produz uma instrução SQL select e traz do banco os resultados, transformando-os em uma coleção de instâncias da classe Produto, através do mapeamento. Isso é chamado Deferred execution.

Como iríamos fazer para abrir conexão com o banco de dados, criar uma query parametrizada e transformar um resultset de datarows em uma lista de instâncias de Produto? E para todas as outras tabelas?

Para reforçar o que foi dito acima, vamos ver o log do DataContext para ver o que ele faz e como faz. Vou adicionar algumas linhas de código ao nosso exemplo anterior e ele vai ficar assim:

using System;
using System.Data.Linq;
using System.Linq;

class Program {
static void Main(string[] args) {

var connStr = @"Data Source=.\SQLEXPRESS;Initial Catalog=DadosSql;User ID=felipe;Password=123";

using (var db = new DataContext(connStr)) {
db.Log = Console.Out;

Console.WriteLine("** Pegando a tabela Produtos");
var Produtos = db.GetTable<Produto>();

Console.WriteLine("** Criando a query");
var qry =
from p in Produtos
where p.Descricao.Contains("a")
orderby p.Descricao
select p;

Console.WriteLine("** Começando a iteração");
foreach (var prod in qry) {
Console.WriteLine("{0}, {1}, {2}", prod.Descricao,
prod.Preco,
prod.Saldo);
}
}
}
}


Olha aqui a saída do console:

** Pegando a tabela Produtos
** Criando a query
** Começando a iteração
SELECT [t0].[ProdutoID], [t0].[Descricao], [t0].[Preco], [t0].[Saldo], [t0].[Ver
sao]
FROM [Produtos] AS [t0]
WHERE [t0].[Descricao] LIKE @p0
ORDER BY [t0].[Descricao]
-- @p0: Input NVarChar (Size = 3; Prec = 0; Scale = 0) [%a%]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

Caderno Universitário 500 folhas, 7,90, 50
Caneta BIC, 1,99, 25
Lapiseira 0,7mm, 5,00, 10
Pasta polionda A4, 2,40, 6


A query que o DataContext produziu foi parametrizada, em vez de ter os valores concatenados. Isso é muito bom, porque o SGBD faz o plano de execução e deixa ele em cache, para que outras consultas que apenas tenham parâmetros diferentes usem o plano de execução que já foi calculado, trazendo melhor performance.

Com isso chegamos ao fim da nossa primeira visão sobre Linq to SQL e em outros posts vamos ver sobre as oeprações de CRUD com Linq to SQL. Até lá e muito obrigado.

Nenhum comentário:

Postar um comentário

 
BlogBlogs.Com.Br