Quem trabalha com o Report Viewer provavelmente já sabe que ele não permite a exibição de dados vindos de múltiplas tabelas em um único controle. Isso quer dizer que, se você quiser exibir dados de mais de uma tabela em um controle Tablix, você terá que partir para alguma alternativa.
A alternativa mais utilizada é a consolidação dos dados em uma única DataTable (ou classe) que servirá como fonte de dados para os nossos relatórios. Porém, essa não é a melhor das alternativas, uma vez que teremos que potencialmente construir DataSets e DataTables customizados para cada relatório do nosso sistema.
O que pouca gente conhece é que existe a expressão “Lookup” no Report Viewer, que nos permite justamente pegar informações de outras tabelas. No artigo de hoje eu vou mostrar para você como utilizar essa expressão. Você quer deixar os seus relatórios do Report Viewer mais simples? Então continue lendo e veja como a expressão “Lookup” pode simplificar consideravelmente os seus relatórios.
Entendendo o problema
Antes de apresentar a solução, vamos entender o problema? Para isso, vamos criar um exemplo onde temos que desenvolver um relatório que exibirá dados vindos de múltiplas tabelas. Temos inúmeros cenários onde isso se faz necessário, mas, para não complicarmos muito, preparei um exemplo bem simples para que vocês possam entender mais facilmente.
Imagine que a nossa tarefa seja gerar um relatório sobre uma entidade chamada “Pessoa” em um sistema de Recursos Humanos. Essa entidade, além das propriedades habituais, tem também uma ligação com a entidade “Empresa” (a pessoa trabalha em algum lugar) e outra ligação com a entidade “Cargo” (ela ocupa uma determinada posição nessa empresa).
Para simularmos essa estrutura, vamos começar criando um novo projeto do tipo “Windows Forms Application“. Dentro desse projeto, adicione um novo DataSet tipado (dando o nome de “DataSetPessoa“) com as seguintes tabelas, colunas e relacionamentos:
Aproveitando que temos um DataSet tipado, vamos dar um duplo clique para implementarmos um método que alimentará esse DataSet com alguns dados de exemplo. Obviamente, em um sistema “de verdade” você teria que ler esses dados do banco:
// C# partial class DataSetPessoa { public void PreencherComDadosDeExemplo() { var abcdev = this.Empresa.AddEmpresaRow("abcdev", "AA Lima Dev EPP", "99.999.999/9999-99"); var microsoft = this.Empresa.AddEmpresaRow("Microsoft", "Microsoft BR Ltda", "99.999.999/9999-99"); var google = this.Empresa.AddEmpresaRow("Google", "Google Brasil S/A", "99.999.999/9999-99"); var analista = this.Cargo.AddCargoRow("Analista de Sistemas", "XXX"); var arquiteto = this.Cargo.AddCargoRow("Arquiteto de Sistemas", "YYY"); var diretor = this.Cargo.AddCargoRow("Diretor Administrativo", "ZZZ"); this.Pessoa.AddPessoaRow("Andre", "Lima", new System.DateTime(1984, 1, 1), abcdev, analista, "999.999.99-99"); this.Pessoa.AddPessoaRow("Larissa", "Lima", new System.DateTime(1987, 2, 2), abcdev, diretor, "999.999.99-99"); this.Pessoa.AddPessoaRow("Fulano", "de Tal", new System.DateTime(1978, 3, 3), microsoft, arquiteto, "999.999.99-99"); this.Pessoa.AddPessoaRow("Paula", "Bellizia", new System.DateTime(1970, 4, 4), microsoft, diretor, "999.999.99-99"); this.Pessoa.AddPessoaRow("Fabio", "Coelho", new System.DateTime(1968, 5, 5), google, diretor, "999.999.99-99"); } }
' VB.NET Partial Class DataSetPessoa Public Sub PreencherComDadosDeExemplo() Dim Abcdev = Me.Empresa.AddEmpresaRow("abcdev", "AA Lima Dev EPP", "99.999.999/9999-99") Dim Microsoft = Me.Empresa.AddEmpresaRow("Microsoft", "Microsoft BR Ltda", "99.999.999/9999-99") Dim Google = Me.Empresa.AddEmpresaRow("Google", "Google Brasil S/A", "99.999.999/9999-99") Dim Analista = Me.Cargo.AddCargoRow("Analista de Sistemas", "XXX") Dim Arquiteto = Me.Cargo.AddCargoRow("Arquiteto de Sistemas", "YYY") Dim Diretor = Me.Cargo.AddCargoRow("Diretor Administrativo", "ZZZ") Me.Pessoa.AddPessoaRow("Andre", "Lima", New System.DateTime(1984, 1, 1), Abcdev, Analista, "999.999.99-99") Me.Pessoa.AddPessoaRow("Larissa", "Lima", New System.DateTime(1987, 2, 2), Abcdev, Diretor, "999.999.99-99") Me.Pessoa.AddPessoaRow("Fulano", "de Tal", New System.DateTime(1978, 3, 3), Microsoft, Arquiteto, "999.999.99-99") Me.Pessoa.AddPessoaRow("Paula", "Bellizia", New System.DateTime(1970, 4, 4), Microsoft, Diretor, "999.999.99-99") Me.Pessoa.AddPessoaRow("Fabio", "Coelho", New System.DateTime(1968, 5, 5), Google, Diretor, "999.999.99-99") End Sub End Class
E agora? Como poderíamos fazer para montarmos um relatório que lista as pessoas desse DataSet, porém, incluindo as informações da empresa onde ela trabalha e as informações do cargo que ela ocupa?
Primeira alternativa: juntando tudo em uma única tabela
A primeira alternativa para resolvermos essa situação (e a mais utilizada) é criarmos um novo DataSet (ou classe) juntando todas as colunas que queremos exibir no relatório em uma única tabela. Ou seja, no nosso caso, teríamos somente uma tabela com todas as informações da Pessoa, Empresa e Cargo.
Para implementarmos essa alternativa, vamos adicionar um segundo DataSet tipado no nosso projeto (dando o nome de “DataSetRelatorio“), que deverá conter a seguinte tabela:
Vamos também dar um duplo clique no DataSet para criarmos um método que receberá uma instância de “DataSetPessoa” e juntará tudo na tabela unificada:
// C# partial class DataSetRelatorio { public DataSetRelatorio(DataSetPessoa dsPessoa) { foreach (var pessoa in dsPessoa.Pessoa) { this.Pessoa.AddPessoaRow( pessoa.PessoaID, pessoa.Nome, pessoa.Sobrenome, pessoa.DataNascimento, pessoa.EmpresaID, pessoa.CargoID, pessoa.CPF, pessoa.EmpresaRow.NomeFantasia, pessoa.EmpresaRow.RazaoSocial, pessoa.EmpresaRow.CNPJ, pessoa.CargoRow.Nome, pessoa.CargoRow.Descricao); } } }
' VB.NET Partial Class DataSetRelatorio Public Sub DataSetRelatorio(DsPessoa As DataSetPessoa) For Each PessoaRow In DsPessoa.Pessoa Me.Pessoa.AddPessoaRow( PessoaRow.PessoaID, PessoaRow.Nome, PessoaRow.Sobrenome, PessoaRow.DataNascimento, PessoaRow.EmpresaID, PessoaRow.CargoID, PessoaRow.CPF, PessoaRow.EmpresaRow.NomeFantasia, PessoaRow.EmpresaRow.RazaoSocial, PessoaRow.EmpresaRow.CNPJ, PessoaRow.CargoRow.Nome, PessoaRow.CargoRow.Descricao) Next End Sub End Class
Com esse DataSet em mãos, podemos simplesmente criar um novo relatório baseado nessa DataTable unificada. Vamos dar o nome de “RelatorioPessoa” para esse novo relatório:
Dentro desse relatório, vamos adicionar um componente do tipo “Table“. Quando fazemos isso, o Visual Studio automaticamente nos perguntará as informações sobre o DataSet que deverá alimentar essa tabela. Nessa janela, vamos dar o nome de “DataSetPessoa” para o DataSet que será criado e vamos escolher a tabela “Pessoa” do nosso “DataSetRelatorio“:
Em seguida, vamos arrastar cada uma das colunas do DataSet para dentro da tabela:
O resultado final deverá ficar parecido com a imagem abaixo:
Por fim, vamos até o nosso formulário, onde arrastaremos um controle visualizador do Report Viewer para dentro do formulário e escolheremos o relatório que acabamos de criar:
No code-behind do formulário, vamos criar os DataSets que serão passados para o relatório:
// C# private void Form1_Load(object sender, EventArgs e) { var dsPessoa = new DataSetPessoa(); dsPessoa.PreencherComDadosDeExemplo(); var dsRelatorio = new DataSetRelatorio(dsPessoa); this.reportViewer1.LocalReport.DataSources.Clear(); this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", (DataTable)dsRelatorio.Pessoa)); this.reportViewer1.RefreshReport(); }
' VB.NET Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Dim DsPessoa As New DataSetPessoa() DsPessoa.PreencherComDadosDeExemplo() Dim DsRelatorio As New DataSetRelatorio(DsPessoa) Me.ReportViewer1.LocalReport.DataSources.Clear() Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", DirectCast(DsRelatorio.Pessoa, DataTable))) Me.ReportViewer1.RefreshReport() End Sub
Pronto! Execute a aplicação e confira o resultado:
Obviamente o relatório poderia ser melhor formatado, mas, como esse não é o foco desse artigo, resolvi deixa-lo com a formatação padrão.
Alternativa mais simples: múltiplas tabelas + expressão Lookup
Como vimos na seção anterior, para exibirmos dados de múltiplas tabelas no Report Viewer, nós podemos criar um novo DataSet (ou classe) juntando todos os dados em uma só estrutura. Apesar de essa alternativa resolver o problema, muitas vezes ela acaba sendo um tanto quanto custosa. Imagine se o nosso sistema tiver diversos relatórios e todos eles tiverem que exibir dados de múltiplas tabelas? Teríamos que criar um DataSet novo para cada relatório, o que seria possível, mas, impraticável.
Uma alternativa mais simples seria trabalharmos com vários DataSets no mesmo relatório, sendo que um DataSet apontaria para a tabela “principal” (no nosso caso a tabela “Pessoa“) e os outros DataSets seriam as tabelas de “Lookup” (no nosso caso as tabelas “Empresa” e “Cargo“). Então, através da função “Lookup“, nós conseguimos pegar dados das tabelas de “Empresa” e “Cargo” no nosso relatório (seria a mesma ideia do PROCV ou VLOOKUP do Excel).
Para vermos como ficaria essa alternativa, vamos criar uma cópia do “RelatorioPessoa“, dando o nome de “RelatorioPessoaMultiplasTabelas“. Dentro desse relatório, vamos excluir o DataSet que tínhamos criado no relatório original (o “DataSetPessoa“) e vamos adicionar três novos DataSets, cada um apontando para uma tabela do nosso DataSetPessoa:
Em seguida, vamos alterar o DataSource da nossa tabela, de forma que ele aponte para o DataSet “Pessoa“:
E agora é que vem o segredo. Como é que conseguimos pegar os dados da Empresa, uma vez que o DataSet da tabela não tem essas informações? Simples, através da expressão Lookup! Por exemplo, para pegarmos o nome fantasia da empresa, a expressão ficaria assim:
=Lookup(Fields!EmpresaID.Value, Fields!EmpresaID.Value, Fields!NomeFantasia.Value, "Empresa")
O primeiro parâmetro dessa expressão deve ser o nome do campo na tabela de origem (campo “EmpresaID” da tabela “Pessoa“). Já o segundo parâmetro corresponde ao nome do campo na tabela destino (no nosso caso, “EmpresaID“, que é o nome do campo chave na tabela “Empresa“). Ou seja, esses dois primeiros parâmetros indicam os campos de relacionamento entre a tabela origem e a tabela destino.
Em seguida, o terceiro parâmetro deve conter o nome que você quer pegar na tabela de destino. Nesse caso, queremos pegar o campo “NomeFantasia“. Por fim, no último parâmetro nós temos que indicar o nome da tabela de lookup (nesse caso, “Empresa“).
Fácil, não? Veja só como fica a expressão para os outros campos:
' Razão social: =Lookup(Fields!EmpresaID.Value, Fields!EmpresaID.Value, Fields!RazaoSocial.Value, "Empresa") ' CNPJ =Lookup(Fields!EmpresaID.Value, Fields!EmpresaID.Value, Fields!CNPJ.Value, "Empresa") ' Nome do Cargo =Lookup(Fields!CargoID.Value, Fields!CargoID.Value, Fields!Nome.Value, "Cargo") ' Descrição do Cargo =Lookup(Fields!CargoID.Value, Fields!CargoID.Value, Fields!Descricao.Value, "Cargo")
Agora que já temos o nosso novo relatório, vamos criar um novo formulário para exibi-lo (Form2). Como fizemos no primeiro formulário, nós temos que adicionar um controle exibidor do Report Viewer, só que dessa vez nós selecionaremos o nosso novo relatório (“RelatorioPessoaMultiplasTabelas“). No code-behind, nós não temos mais que criar um DataSetRelatorio, mas sim, temos que passar cada uma das tabelas para o relatório em DataSources diferentes:
// C# private void Form2_Load(object sender, EventArgs e) { var dsPessoa = new DataSetPessoa(); dsPessoa.PreencherComDadosDeExemplo(); this.reportViewer1.LocalReport.DataSources.Clear(); this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("Pessoa", (DataTable)dsPessoa.Pessoa)); this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("Empresa", (DataTable)dsPessoa.Empresa)); this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("Cargo", (DataTable)dsPessoa.Cargo)); this.reportViewer1.RefreshReport(); }
' VB.NET Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load Dim DsPessoa = New DataSetPessoa() DsPessoa.PreencherComDadosDeExemplo() Me.ReportViewer1.LocalReport.DataSources.Clear() Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("Pessoa", DirectCast(DsPessoa.Pessoa, DataTable))) Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("Empresa", DirectCast(DsPessoa.Empresa, DataTable))) Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("Cargo", DirectCast(DsPessoa.Cargo, DataTable))) Me.ReportViewer1.RefreshReport() End Sub
Por fim, vamos alterar o objeto de inicialização para que o nosso Form2 seja exibido (ao invés do Form1). Em projetos C#, fazemos isso no arquivo Program.cs:
Application.Run(new Form2());
Já no VB.NET, temos que alterar o “Startup Form” nas propriedades do projeto:
Pronto! Execute o projeto e veja que o resultado é idêntico ao primeiro relatório:
Concluindo
Quando estamos desenvolvendo relatórios para as nossas aplicações, não é tão difícil nos depararmos com uma situação em que precisamos de dados vindos de múltiplas tabelas. Como o Report Viewer não suporta a exibição de dados de mais de uma tabela no mesmo controle e também não tem o conceito de relacionamentos entre tabelas (como temos no Crystal Reports), a saída mais utilizada é a criação de um segundo DataSet (ou classe) onde todos os dados são consolidados em uma única DataTable.
Apesar dessa alternativa ser a mais utilizada, ela não é a mais ideal. O que pouca gente sabe é que nós podemos utilizar a expressão Lookup para pegarmos dados de outras tabelas no Report Viewer. Esse procedimento é muito mais simples do que termos que criar um DataSet consolidado para cada relatório.
No artigo de hoje você conferiu essas duas alternativas para a exibição de dados vindos de múltiplas tabelas no Report Viewer. Como você pode perceber, o resultado é idêntico, então, não pense duas vezes se você puder utilizar a expressão Lookup nos seus relatórios, que é muito mais prática.
E você, já passou por essa situação onde você tinha que exibir dados de múltiplas tabelas no Report Viewer? Se sim, como é que você resolveu? Você já conhecia a expressão Lookup? Conte-nos mais detalhes na caixa de comentários!
Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.
Até a próxima!
André Lima
Image by Jake Przespo used under Creative Commons
https://www.flickr.com/photos/jakeprzespo/4566115233/
The post Trabalhando com múltiplas tabelas no Report Viewer através da expressão Lookup appeared first on André Alves de Lima.