Continuando a falar de LINQ to SQL, montei uma página simples, com um gridview e uma consulta com um join às tabelas SalesOrderHeaders e Contact. A idéia é exibir os pedidos e o contato colocado no pedido.

Tudo funcionou muito bem, com pouquíssimo código e sem ter que ficar me preocupando com strings de conexão, sem precisar compor código SQL, tudo na base do objeto, em suma: lindo (não fosse o problema do qual falei ontem, seria maravilhoso). É o tipo de código que todo mundo gosta de usar em  palestras: parece que tudo foi feito para te dar o máximo de produtividade.

Montei um diagrama LINQ to SQL bem simples, arrastando e soltando as referidas tabelas:

linqtosqlAW

Vejam o resultado abaixo, na página SemPerformance.aspx:

semperf

O código da aspx é o seguinte:

    1 <%@ Page Language="vb" AutoEventWireup="false" CodeBehind="SemPerformance.aspx.vb" Inherits="LINQToSQLLoadWith.SemPerformance" %>

    2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    3 <html xmlns="http://www.w3.org/1999/xhtml" >

    4 <head runat="server">

    5     <title>Sem Performance</title>

    6 </head>

    7 <body>

    8     <form id="form1" runat="server">

    9     <div>

   10         <asp:GridView ID="gvOrders" runat="server" AutoGenerateColumns="False">

   11             <Columns>

   12                 <asp:BoundField DataField="SalesOrderID" HeaderText="Order ID" />

   13                 <asp:BoundField DataField="OrderDate" HeaderText="Order Date"

   14                     DataFormatString="{0:d}"  />

   15                 <asp:TemplateField HeaderText="Customer">

   16                     <ItemTemplate>

   17                         <asp:Label ID="Label1" runat="server" Text='<%# Eval("Contact.FirstName") & " " & Eval("Contact.LastName") %>'></asp:Label>

   18                     </ItemTemplate>

   19                 </asp:TemplateField>

   20                 <asp:BoundField DataField="TotalDue" HeaderText="Total Due"

   21                     DataFormatString="{0:F}" />

   22             </Columns>

   23         </asp:GridView>

   24     </div>

   25     </form>

   26 </body>

   27 </html>

O código VB por trás da página é o seguinte:

    1 Public Partial Class SemPerformance

    2     Inherits System.Web.UI.Page

    3 

    4     Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    5         If Not Page.IsPostBack Then

    6             Dim orders = GetOrders(0, 10)

    7             gvOrders.DataSource = orders

    8             gvOrders.DataBind()

    9         End If

   10     End Sub

   11 

   12     Public Function GetOrders(ByVal page As Integer, ByVal pageSize As Integer) As IEnumerable(Of SalesOrderHeader)

   13 

   14         Dim dc As New AdventureWorksDataContext

   15 

   16         Dim query = From so In dc.SalesOrderHeaders _

   17                     Take pageSize * (page + 1) Skip page * pageSize

   18         Return query

   19 

   20     End Function

   21 

   22 End Class

Tudo certo, certo? Errado. COmo vocês viram, estou fazendo uma query no LINQ somente à tabela SalesOrerHeaders. Por causa so relacionamento entre as duas tabelas, existe uma propriedade Contact nas linhas desta tabela, que me retorna o objeto Contact referido. Notem que na linha 17 do código ASPX estou chamando esta propriedade, e solicitando as colunas FirstName e LastName. Fiquei curioso para saber como o LINQ to SQL iria lidar com isso, afinal, a chamada ao banco só acontece na última hora (chamada de recuperação preguiçosa - lazy), ou seja, somente quando chamamos DataBind. Abri o SQL Profiler para espiar, e vejam o que vi:

sqlprofiler

Um monte de solicitações ao banco... Abaixo as 3 primeiras instruções: 

    1 SELECT TOP (10) [t0].[SalesOrderID], [t0].[RevisionNumber], [t0].[OrderDate], [t0].[DueDate], [t0].[ShipDate], [t0].[Status], [t0].[OnlineOrderFlag], [t0].[SalesOrderNumber], [t0].[PurchaseOrderNumber], [t0].[AccountNumber], [t0].[CustomerID], [t0].[ContactID], [t0].[SalesPersonID], [t0].[TerritoryID], [t0].[BillToAddressID], [t0].[ShipToAddressID], [t0].[ShipMethodID], [t0].[CreditCardID], [t0].[CreditCardApprovalCode], [t0].[CurrencyRateID], [t0].[SubTotal], [t0].[TaxAmt], [t0].[Freight], [t0].[TotalDue], [t0].[Comment], [t0].[rowguid], [t0].[ModifiedDate]

    2 FROM [Sales].[SalesOrderHeader] AS [t0]

    3 go

    4 exec sp_executesql N'SELECT [t0].[ContactID], [t0].[NameStyle], [t0].[Title], [t0].[FirstName], [t0].[MiddleName], [t0].[LastName], [t0].[Suffix], [t0].[EmailAddress], [t0].[EmailPromotion], [t0].[Phone], [t0].[PasswordHash], [t0].[PasswordSalt], [t0].[AdditionalContactInfo], [t0].[rowguid], [t0].[ModifiedDate]

    5 FROM [Person].[Contact] AS [t0]

    6 WHERE [t0].[ContactID] = @p0',N'@p0 int',@p0=378

    7 go

    8 exec sp_executesql N'SELECT [t0].[ContactID], [t0].[NameStyle], [t0].[Title], [t0].[FirstName], [t0].[MiddleName], [t0].[LastName], [t0].[Suffix], [t0].[EmailAddress], [t0].[EmailPromotion], [t0].[Phone], [t0].[PasswordHash], [t0].[PasswordSalt], [t0].[AdditionalContactInfo], [t0].[rowguid], [t0].[ModifiedDate]

    9 FROM [Person].[Contact] AS [t0]

   10 WHERE [t0].[ContactID] = @p0',N'@p0 int',@p0=216

 

Na verdade o "monte" de solicitações eram na verdade 11. Porque 11? porque a primeira era um select simples, feito quando eu chamei Databind contra um IEnumerable(Of SalesOrderHeader) (na verdade uma System.Data.Linq.Table(Of SalesOrderHeader)). Os outros 10 foram feitos cada vez que o binding de uma linha encontrava a expressão Contact.LastName, quando era necessário preencher esta linha de Contact. Após a chamada de LastName, a linha já estava na memória, e Contact.FirstName vinha de lá. Como cada linha passa pelo binding sozinha, foram 10 chamadas...

Como resolver isso? Se você estiver com uma latência muito alta até o servidor, essas outras 10 chamadas vão pesar. O jeito é utilizar uma propriedade do Datacontext chamada LoadOptions, do tipo System.Data.Linq.DataLoadOptions. Esse tipo possui um método com a seguinte assinatura: LoadWith(Of T)(ByVal expression As System.Linq.Expressions.Expression(Of System.Func(Of T, Object))). Ou seja, é um método que aceita uma expressão do tipo de uma lambda (ou seja, um delegate, ou seja, um "ponteiro" para uma outra função). Depois eu conto aqui o que é uma expressão, que, simplificando é um tipo novo do LINQ (e sem ele não existe LINQ como o conhecemos). Por enquanto, pense que o método aceita um outro método ao ser chamado. Note também que ele é tipado, ou seja, é um método de algum tipo. Ele serve para dizer ao datacontext que, quando carregar algum tipo (leia tabela - no nosso caso SalesOrderHeader), que deve carregar outro tipo junto (no nosso caso Contact). O código fica assim:

   12     Public Function GetOrders(ByVal page As Integer, ByVal pageSize As Integer) As IEnumerable(Of SalesOrderHeader)

   13 

   14         Dim dc As New AdventureWorksDataContext

   15 

   16         Dim dlo As New Data.Linq.DataLoadOptions

   17         dlo.LoadWith(Of SalesOrderHeader)(Function(o) o.Contact)

   18         dc.LoadOptions = dlo

   19 

   20         Dim query = From so In dc.SalesOrderHeaders _

   21                     Take pageSize * (page + 1) Skip page * pageSize

   22         Return query

   23 

   24     End Function

Foram adicionadas as linhas em amarelo. Assim, estou dizendo ao datacontext o seguinte: "quando você carregar o tipo SalesOrderHeader, carregue com ele (LoadWith) o tipo Contact" (linha 17).  Todo o resto do código não muda.

O resultado era exatamente o que eu estava buscando. O profiler não me deixa mentir:

sqlprofilercomperf

Abaixo o código executado:

 

    1 SELECT TOP (10) [t0].[SalesOrderID], [t0].[RevisionNumber], [t0].[OrderDate], [t0].[DueDate], [t0].[ShipDate], [t0].[Status], [t0].[OnlineOrderFlag], [t0].[SalesOrderNumber], [t0].[PurchaseOrderNumber], [t0].[AccountNumber], [t0].[CustomerID], [t0].[ContactID], [t0].[SalesPersonID], [t0].[TerritoryID], [t0].[BillToAddressID], [t0].[ShipToAddressID], [t0].[ShipMethodID], [t0].[CreditCardID], [t0].[CreditCardApprovalCode], [t0].[CurrencyRateID], [t0].[SubTotal], [t0].[TaxAmt], [t0].[Freight], [t0].[TotalDue], [t0].[Comment], [t0].[rowguid], [t0].[ModifiedDate], [t1].[ContactID] AS [ContactID2], [t1].[NameStyle], [t1].[Title], [t1].[FirstName], [t1].[MiddleName], [t1].[LastName], [t1].[Suffix], [t1].[EmailAddress], [t1].[EmailPromotion], [t1].[Phone], [t1].[PasswordHash], [t1].[PasswordSalt], [t1].[AdditionalContactInfo], [t1].[rowguid] AS [rowguid2], [t1].[ModifiedDate] AS [ModifiedDate2]

    2 FROM [Sales].[SalesOrderHeader] AS [t0]

    3 INNER JOIN [Person].[Contact] AS [t1] ON [t1].[ContactID] = [t0].[ContactID]

 

Uma única viagem ao servidor. Era isso que eu buscava. Excelente!


Postado na(s) categoria(s) LINQ pelo Giovanni Bassi em 24 de março de 2008 às 20:37 | Tags: , ,

Ninguém avaliou. Dê sua nota!

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Estou escrevendo um artigo para a .Net Magazine e resolvi usar em um dos exemplos a persistência "pré-fabricada" do LINQ to SQL. Até aí tudo bem, mas qual não foi minha surpresa ao perceber que não há muito suporte de LINQ to SQL para programação multi-camadas. Já repararam que todos os exemplos que vemos com LINQ to SQL estão usando sempre 2 camadas (apresentação e banco de dados)?

Descobri que algumas pessoas já estão reclamando disso a algum tempo. Alguns artigos que li sugerem o uso de uma camada adicional de abstração, algo sugerido pelo próprio Scott Guthrie da Microsoft em seu blog, onde ele também comenta que vai fazer um post sobre n-camadas e LINQ to SQL, que até agora eu não vi (ele comenta novamente, dessa vez no próprio post, aqui).

Isso tudo é muito estranho. Uma das coisas que senti falta foi a possibilidade de separar as entidades de negócio do código de acesso a dados, algo que o Visual Studio 2008 já oferece para os Datasets tipados:

DatasetEmOutroProjetoVS2008  

Porque não oferecer o mesmo suporte ao LINQ to SQL? Procurei bastante, mas não encontrei nada parecido no Designer:

LINQToSQLSemOutroProjetoVS2008 

Outro problema encontrado foi a utilização de um objeto modificado com outra instância de datacontext. Tudo indica que não funciona...

Vou procurar um pouco mais e ver no que dá. Se encontrar solução coloco por aqui.


Postado na(s) categoria(s) LINQ pelo Giovanni Bassi em 22 de março de 2008 às 22:33 | Tags: , , ,

4.0 ponto(s). Avaliado por 1 pessoas

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Logo depois de eu escrever o post anterior sobre XML Axis Properties e LINQ eu fico sabendo desse novo projeto da Microsoft, ainda em Alpha, mas liberado para a comunidade: Linq To XSD. Como o projeto ainda está muito no começo, esse nome pode não ficar.

Ele oferece uma solução de X/O Mapping, ou seja, mapeamento XML para Objeto. Ele cria objetos para o seu XML. E o legal é que ele o faz a partir de um schema XSD, e mantém suas restrições, tipos e outros detalhes. Por exemplo, vocês devem ter notado que tive que fazer cast para recuperar uma propriedade booleana do XML:

   10         Dim blnLogLigado As Boolean = CBool(consultaXML.<app>.@logLigado)

Com LINQ to XSD isso não precisaria ser feito. Veja como fica:

   14         static void Main(string[] args)

   15         {

   16 

   17             var ArquivoXML = Path.Combine(

   18                 Directory.GetParent

   19                 (

   20                     Path.GetDirectoryName

   21                     (

   22                         Assembly.GetExecutingAssembly().Location

   23                     )

   24                 ).Parent.FullName,

   25                 @"configuracoes.xml");

   26             var config = algumacoisa.configuracoes.Load(ArquivoXML);

   27             Console.WriteLine(@"O id da aplicação é ""{0}"" e o log está {1}.",

   28                 config.app.appId.ToString(),

   29                 config.app.logLigado ? "ligado" : "desligado");

   30 

   31             Console.WriteLine("Caminhos:");

   32 

   33             foreach (var caminho in config.caminhosDePesquisa.caminho)

   34             {

   35                 Console.WriteLine(@"O endereço ""{0}"" está {1}.",

   36                     caminho.enderecoFisico,

   37                     caminho.habilitado ? "habilitado" : "desabilitado");

   38             }   

   39         }

O problema foi corrigido. Note que os dados AppID e LogLigado já estão convertidos para o tipo correto (linhas 28 e 29).

Ah, é legal observar, já que falei do VB no post anterior, que isso praticamente resolve o problema de acesso ao XML que o C# tinha antes, apesar de ainda estarem em fase Alpha e sem previsão de lançamento.

E como eu não tinha mostrado ainda o resultado final, segue abaixo:

ResultadoLinqToXSD

Se quiserem saber mais comentem aqui no blog que eu dou uma aprofundada. 


Postado na(s) categoria(s) LINQ pelo Giovanni Bassi em 27 de fevereiro de 2008 às 20:23 | Tags: ,

Ninguém avaliou. Dê sua nota!

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Precisei recentemente montar uma aplicação que fosse configurável, e que não fizesse acesso a nenhuma base de dados estruturada. A idéia era permitir que o usuário, um power user, fizesse as configurações, que eram poucas, sozinho. Como venho de olho no LINQ, e especificamente no LINQ to XML, não pensei duas vezes: vou configurar tudo em um XML simples e lê-lo com LINQ.

Segue o passo a passo. Foram pouquíssimas linhas de código e tempo de desenvolvimento. Você gasta mais tempo abobado com a capacidade da ferramenta do que com o desenvolvimento em si. Acaba sendo um bom exemplo de leitura de XML com LINQ.

  1. Crie um projeto, pode ser um console;
  2. Crie o XML com os dados que você vai precisar. Meu exemplo ficou assim:

        1 <?xml version="1.0" encoding="utf-8" ?>

        2 <configuracoes xmlns="algumacoisa">

        3   <app appId="1" logLigado="true" />

        4   <caminhosDePesquisa>

        5     <caminho enderecoFisico="C:\config" habilitado="true" />

        6     <caminho enderecoFisico="C:\Windows" habilitado="false" />

        7   </caminhosDePesquisa>

        8 </configuracoes>

  3. Crie o schema, clicando no menu XML > Create Schema. Salve o arquivo do schema no diretório da aplicação e o inclua no projeto.
  4. Inclua o atributo targetNamespace no Schema. O valor tanto faz, escolha um valor de namespace que você gosta, pode ser o nome do seu cachorro, da namorada, o que for. O schema vai ficar assim:

        1 <?xml version="1.0" encoding="utf-8"?>

        2 <xs:schema targetNamespace="algumacoisa" attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">

        3   <xs:element name="configuracoes">

        4     <xs:complexType>

        5       <xs:sequence>

        6         <xs:element name="app">

        7           <xs:complexType>

        8             <xs:attribute name="appId" type="xs:unsignedByte" use="required" />

        9             <xs:attribute name="logLigado" type="xs:boolean" use="required" />

       10           </xs:complexType>

       11         </xs:element>

       12         <xs:element name="caminhosDePesquisa">

       13           <xs:complexType>

       14             <xs:sequence>

       15               <xs:element maxOccurs="unbounded" name="caminho">

       16                 <xs:complexType>

       17                   <xs:attribute name="enderecoFisico" type="xs:string" use="required" />

       18                   <xs:attribute name="habilitado" type="xs:boolean" use="required" />

       19                 </xs:complexType>

       20               </xs:element>

       21             </xs:sequence>

       22           </xs:complexType>

       23         </xs:element>

       24       </xs:sequence>

       25     </xs:complexType>

       26   </xs:element>

       27 </xs:schema>


  5. Altere seu XML para utilizar o namespace que você escolheu. A linha do elemento base fica assim (note que ao digitar xmlns e o igual, a opção do namespace que você digitou no targetnamespace do schema já fica disponível, escolha ela): 

        1 <?xml version="1.0" encoding="utf-8" ?>
       

  6. Vá até o arquivo Module1.vb, e importe o namespace do XML. Note que ao digitar "Imports" ele já te dá a opção de importar um namespace xml, e ao digitar o "=" após "xmlns", o namespace que você escolheu já aparece como opção. Fica assim:

        1 Imports <xmlns="algumacoisa">

  7. Na Sub Main abra o arquivo com a novíssimo classe XDocument, do namespace System.Xml.Linq lhe ajudando:

        4     Sub Main()

        5 

        6         Dim arquivo = IO.Path.Combine(IO.Directory.GetParent(My.Application.Info.DirectoryPath).Parent.FullName, "Configuracoes.xml")

        7         Dim XMLDoc = XDocument.Load(arquivo)

       17 

       18     End Sub


  8. Crie então o início do código Linq de consulta. Como você importou o namespace xml, quando digitar o primeiro ponto depois de XMLDoc você verá a possibilidade de utilizar as XML Axis properties. Veja na figura abaixo como o intelisense ajuda e em seguida o código:

    XMLAxisProperty 

        8         Dim consultaXML = From config In XMLDoc.<configuracoes>


  9. Faça a consulta dos atributos logLigado e Id (note o uso do "@" para obter o atributo):

        9         Dim appID As Integer = CInt(consultaXML.<app>.@appId)

       10         Dim blnLogLigado As Boolean = CBool(consultaXML.<app>.@logLigado)

       11         Console.WriteLine("O id da aplicação é ""{0}"" e o log está {1}.", appID.ToString(), If(blnLogLigado, "ligado", "desligado"))

  10. Consulte então os elementos de caminho de pesquisa:

       12         Console.WriteLine("Caminhos:")

       13         For Each caminho In consultaXML.<caminhosDePesquisa>.<caminho>

       14             Console.WriteLine("O endereço ""{0}"" está {1}.", caminho.@enderecoFisico, If(CBool(caminho.@habilitado), "habilitado", "desabilitado"))

       15         Next

 

Você deve ter percebido também nesse exemplo o uso do novo operador ternário If. Eu estou achando esse operador muito útil. Ele ajuda a deixar o código bem mais limpo. Usei também bastante inferência de tipo. Ah... como eu adoro digitar menos...

E como a comunidade de desenvolvimento adora se provocar, e o debate C# versus VB não termina nunca, vou aproveitar para contribuir: XML Axis properties são uma exclusividade do VB, não há nada nem de perto parecido no C#. Assim como XML Literals. Way cool!


Postado na(s) categoria(s) LINQ pelo giovanni bassi em 27 de fevereiro de 2008 às 01:17 | Tags: , ,

Ninguém avaliou. Dê sua nota!

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Quem é Giovanni Bassi

Giovanni Bassi Sou uma pessoa apaixonada por tecnologia e especificamente por .Net. Gerencio uma fábrica de software, gosto muito de arquitetura e engenharia de software, publico artigos e edito a .Net Magazine. Dou umas palestras e cursos de vez em quando, e quando dá tempo eu respiro um pouco. Mais detalhes nesta página.

Selos

Web Days 2008

MCPD

MCSD

.Net Magazine

Calendário

«  agosto 2008  »
seteququsedo
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567
Ver detalhamento de posts no calendário

Postagens recentes