O grande problema ao criarmos relatórios do Report Viewer com DataSets tipados é que, caso nós utilizemos DataAdapters vinculados às nossas DataTables, existem grandes chances dos dados do relatório virem de um banco de dados incorreto quando executarmos a aplicação no computador dos nossos clientes. Isso pode ser facilmente contornado alterando manualmente a string de conexão no computador do cliente ou até mesmo alterando a string de conexão via código em tempo de execução.
Mas, que tal passarmos um DataSet não tipado para o Report Viewer? Dessa forma nós ficaríamos totalmente flexíveis na hora de carregarmos os dados dos nossos relatórios. Nós poderíamos, por exemplo, carregar os dados através de um TableAdapter ou DataReader. Poderíamos também carregar os dados de algum outro lugar que não fosse o banco de dados (um arquivo texto, uma planilha do Excel, etc).
No artigo de hoje eu vou mostrar como você pode alterar a string de conexão dos DataSets tipados em tempo de execução (caso você deseje trabalhar com DataSets tipados). Além disso, eu vou ensinar também como passar DataSets não tipados para o Report Viewer.
Criando o relatório com DataSet tipado ou classe
Infelizmente, as únicas maneiras (nativas) de criarmos relatórios no Report Viewer é através de um DataSet tipado ou classe (além das outras opções mais exóticas, como através de serviços ou listas do SharePoint). Dessa forma, mesmo que a gente decida passar um DataSet não tipado para o Report Viewer em tempo de execução, nós teremos que obrigatoriamente criar um DataSet tipado ou classe para conseguirmos desenhar a estrutura do relatório. Existe também uma gambiarra onde editamos o arquivo rdlc manualmente, mas esse truque eu vou deixar para um próximo artigo.
Dito isso, vamos começar o nosso projeto de exemplo criando um novo projeto do tipo “Windows Forms Application“. Poderia ser WPF ou poderia ser web e o resultado seria o mesmo, mas vamos trabalhar com Windows Forms para facilitar a demonstração.
Dentro desse projeto, vamos analisar as duas possibilidades que temos para desenharmos o nosso relatório: DataSet tipado ou classe. A primeira opção (DataSet tipado) é a mais óbvia. Vamos adicionar um novo DataSet tipado ao projeto, dando o nome de “DataSetPessoa“:
Dentro desse DataSet tipado, vamos adicionar uma nova DataTable, chamada “Pessoa“, contendo as colunas “Id“, “Nome” e “Sobrenome“, sendo que a coluna “Id” é auto-incremento e chave primária:
E com isso nós temos o DataSet que nós poderíamos utilizar para criar o nosso relatório. Porém, essa não é a sistemática que a maioria das pessoas utiliza na criação de DataSets tipados. O mais comum é, ao invés de criar as DataTables manualmente, os programadores acabam arrastando as tabelas de um banco de dados existente para dentro do DataSet. O problema disso é que a string de conexão fica gravada no arquivo app.config do projeto, e muitas pessoas têm dificuldade em mudar posteriormente essa string de conexão em tempo de execução, fazendo com que o relatório pegue os dados do banco errado.
Para entendermos esse cenário, vou criar um outro DataSet tipado (“DataSetPessoa2“) e, na janela “Server Explorer“, eu vou criar uma conexão com um banco de dados do Microsoft Access já existente (você pode baixar o arquivo mdb aqui). Esse banco já possui uma tabela chamada “Pessoa” com a estrutura que precisamos para o nosso relatório, então eu vou arrastar essa tabela para dentro do DataSet tipado:
O resultado disso tudo é uma DataTable contendo um DataAdapter atachado, que tem todas as informações necessárias para carregar os dados daquele banco que selecionamos no Server Explorer:
Por fim, a terceira maneira de termos uma estrutura de dados para desenharmos o nosso relatório é através de uma classe. Vamos, então, adicionar uma nova classe no nosso projeto, dando o nome de “Pessoa“:
// C# public class Pessoa { public int Id { get; set; } public string Nome { get; set; } public string Sobrenome { get; set; } }
' VB.NET Public Class Pessoa Public Property Id As Integer Public Property Nome As String Public Property Sobrenome As String End Class
Nesse ponto, não esqueça de compilar o projeto, senão essa classe não será detectada pelo Report Viewer nos próximos passos.
É importante frisar que nós não precisamos de toda essa parafernalha no nosso projeto. Nós só temos que escolher uma das três opções que eu apresentei acima: ou DataSet tipado com a tabela criada manualmente, ou DataSet tipado com a tabela vindo de um banco de dados existente ou uma classe de dados. Não faz sentido utilizar mais de uma sistemática no mesmo projeto. Eu só fiz dessa forma nesse artigo para mostrar todas as possibilidades existentes.
Agora chegou a hora de, finalmente, criarmos o nosso relatório. Vamos adicionar um novo relatório do Report Viewer no nosso projeto, dando o nome de “RelatorioPessoa“:
Nesse relatório, vamos adicionar uma nova fonte de dados, clicando com o botão direito em “Datasets” e escolhendo a opção “Add Dataset“:
Na janela Dataset Properties, o Report Viewer automaticamente detectará os dois DataSets tipados que temos no nosso projeto (“DataSetPessoa” e “DataSetPessoa2“):
Se quiséssemos trabalhar com a classe de dados (ao invés dos DataSets tipados), teríamos que clicar em “New“, escolher a opção “Object” como tipo do DataSource e depois encontraríamos a classe “Pessoa” na lista de classes do projeto:
Porém, ao invés disso, vamos trabalhar com o modelo que a maioria das pessoas utiliza e que, infelizmente, é o modelo que traz mais problemas: o DataSet tipado que tem uma DataTable com adapter atachado (no nosso caso, o “DataSetPessoa2“):
Agora que já temos o DataSet no relatório, vamos adicionar uma tabela bem simples que listará as informações da tabela “Pessoa“:
Pronto! Esse será o relatório que utilizaremos no exemplo deste artigo. É claro que poderíamos trabalhar com uma estrutura bem mais complexa e um design bem mais detalhado, porém a ideia do artigo é mostrar o envio dos dados para o relatório, e não o relatório em si. Se você quiser dicas para criar relatórios mais complexos, dá uma olhada no meu histórico de artigos sobre o Report Viewer que tem bastante coisa interessante já publicada.
Alterando a string de conexão do DataSet tipado em tempo de execução
Com o relatório finalizado, vamos ajustar o formulário do nosso projeto para exibirmos o relatório com os seus respectivos dados. A primeira coisa que temos que fazer é adicionarmos um controle do Report Viewer no formulário, docka-lo (para que ele ocupe o espaço todo do formulário) e temos também que escolher o relatório que será exibido:
Como criamos o relatório apontando para o DataSet tipado que tinha um adapter atachado, ao escolhermos esse relatório no controle, o Visual Studio criará automaticamente no formulário uma instância do DataSet, um BindingSource e uma instância do Adapter:
E, se olharmos no code-behind do formulário, no evento “Load“, o Visual Studio já colocou o código para fazer o carregamento do DataSet utilizando o Adapter. E qual é o problema disso? O problema é que o Adapter utilizará a string de conexão que está armazenada no arquivo app.config da aplicação. Veja só como ficou a string de conexão no projeto que eu criei:
Essa string de conexão criada pelo Visual Studio está errada e, se eu tentar executar o projeto, veja só o erro que eu vou acabar recebendo:
Obviamente, na hora de fazermos o deployment desse projeto, esse caminho do arquivo será inválido também. E isso pode acontecer não só com o Microsoft Access, mas com qualquer banco de dados (quando trabalhamos com o SQL Server, o nome da instância pode ser diferente no cliente, por exemplo).
A solução nesse caso é alterarmos a string de conexão em tempo de execução. E como é que podemos fazer isso? É simples. Só precisamos abrir o arquivo de configuração (utilizando a classe ConfigurationManager), alteramos a string de conexão e salvamos o arquivo novamente. Porém, um detalhe importante é que essa classe fica no assembly System.Configuration, que não é referenciado por padrão quando criamos um novo projeto. Portanto, precisamos adicionar manualmente a referência para esse assembly:
Adicionada a referência, podemos utilizar a classe ConfigurationManager para alterar a nossa string de conexão. Veja como fica o código de forma que a string de conexão aponte para o arquivo Banco1.mdb no mesmo diretório da aplicação:
// C# private void AlterarStringDeConexao() { var config = System.Configuration.ConfigurationManager.OpenExeConfiguration(System.Configuration.ConfigurationUserLevel.None); var connectionStrings = config.ConnectionStrings; foreach (System.Configuration.ConnectionStringSettings connectionString in connectionStrings.ConnectionStrings) { connectionString.ConnectionString = string.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}\\Banco1.mdb", Environment.CurrentDirectory); } config.Save(System.Configuration.ConfigurationSaveMode.Modified); System.Configuration.ConfigurationManager.RefreshSection("connectionStrings"); }
' VB.NET Private Sub AlterarStringDeConexao() Dim Config = System.Configuration.ConfigurationManager.OpenExeConfiguration(System.Configuration.ConfigurationUserLevel.None) Dim ConnectionStrings = Config.ConnectionStrings For Each ConnectionString As System.Configuration.ConnectionStringSettings In ConnectionStrings.ConnectionStrings ConnectionString.ConnectionString = String.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0}\Banco1.mdb", Environment.CurrentDirectory) Next Config.Save(System.Configuration.ConfigurationSaveMode.Modified) System.Configuration.ConfigurationManager.RefreshSection("connectionStrings") End Sub
Nota: obviamente, caso a sua aplicação utilize mais de uma string de conexão ao mesmo tempo, você teria que lidar com essa especificidade nesse método.
Agora nós só temos que adicionar a chamada para esse método antes do carregamento da DataTable:
// C# private void Form1_Load(object sender, EventArgs e) { AlterarStringDeConexao(); this.PessoaTableAdapter.Fill(this.DataSetPessoa2.Pessoa); this.reportViewer1.RefreshReport(); }
' VB.NET Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load AlterarStringDeConexao() Me.PessoaTableAdapter.Fill(Me.DataSetPessoa2.Pessoa) Me.ReportViewer1.RefreshReport() End Sub
Ao executarmos novamente o projeto, veremos que os dados serão carregados corretamente através do arquivo mdb especificado na string de conexão:
Criando e passando um DataSet não tipado
Muito bem. Nós já vimos como criar o relatório com DataSet tipado (ligado ou não a um banco de dados via TableAdapter) e classe de dados. Já vimos também como carregar o relatório através de um DataSet tipado que contenha um Adapter, inclusive alterando a string de conexão em tempo de execução. Agora chegou a hora de vermos como podemos carregar esse relatório que criamos utilizando um DataSet não tipado.
Um DataSet não tipado é um DataSet criado diretamente via código. Para o caso do Report Viewer, nós nem precisamos de um DataSet em si, mas somente, uma DataTable (um DataSet seria mais ou menos um agrupamento de DataTables). Dessa forma, a primeira coisa que temos que fazer é criarmos uma DataTable com as respectivas colunas:
// C# var dataTable = new DataTable(); dataTable.Columns.Add("Id", typeof(int)); dataTable.Columns.Add("Nome"); dataTable.Columns.Add("Sobrenome");
' VB.NET Dim DataTable = New DataTable() DataTable.Columns.Add("Id", GetType(Integer)) DataTable.Columns.Add("Nome") DataTable.Columns.Add("Sobrenome")
Nota: os nomes das colunas da DataTable devem bater exatamente com os nomes das colunas no DataSet do relatório, inclusive as letras maiúsculas e minúsculas.
Em seguida, temos que carregar essa tabela de alguma maneira. Num sistema “de verdade“, você teria que carregar os dados do banco de dados, provavelmente utilizando um TableAdapter ou DataReader. Como esse não é o foco do artigo (não importa de onde você esteja pegando os dados), eu vou simplesmente criar algumas linhas manualmente:
// C# dataTable.Rows.Add(1, "André", "Lima"); dataTable.Rows.Add(2, "Fulano", "de Tal"); dataTable.Rows.Add(3, "Beltrano", "da Silva"); dataTable.Rows.Add(4, "Zé", "Ninguém");
' VB.NET DataTable.Rows.Add(1, "André", "Lima") DataTable.Rows.Add(2, "Fulano", "de Tal") DataTable.Rows.Add(3, "Beltrano", "da Silva") DataTable.Rows.Add(4, "Zé", "Ninguém")
Por fim, a única coisa que está faltando é passarmos essa DataTable para o nosso relatório. Veja só como fazemos isso:
// C# this.reportViewer1.LocalReport.DataSources.Clear(); this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", dataTable));
' VB.NET Me.ReportViewer1.LocalReport.DataSources.Clear() Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", DataTable))
Atenção! O nome do DataSet (primeiro parâmetro do construtor da classe ReportDataSource) deve bater exatamente com o nome definido no DataSet dentro do relatório, inclusive letras maiúsculas e minúsculas.
Pronto! Execute o projeto e veja que o relatório será exibido corretamente. Aqui vai o resultado do código completo da criação da DataTable e carregamento do relatório:
// C# private void Form1_Load(object sender, EventArgs e) { var dataTable = new DataTable(); dataTable.Columns.Add("Id", typeof(int)); dataTable.Columns.Add("Nome"); dataTable.Columns.Add("Sobrenome"); dataTable.Rows.Add(1, "André", "Lima"); dataTable.Rows.Add(2, "Fulano", "de Tal"); dataTable.Rows.Add(3, "Beltrano", "da Silva"); dataTable.Rows.Add(4, "Zé", "Ninguém"); this.reportViewer1.LocalReport.DataSources.Clear(); this.reportViewer1.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", dataTable)); this.reportViewer1.RefreshReport(); }
' VB.NET Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Dim DataTable = New DataTable() DataTable.Columns.Add("Id", GetType(Integer)) DataTable.Columns.Add("Nome") DataTable.Columns.Add("Sobrenome") DataTable.Rows.Add(1, "André", "Lima") DataTable.Rows.Add(2, "Fulano", "de Tal") DataTable.Rows.Add(3, "Beltrano", "da Silva") DataTable.Rows.Add(4, "Zé", "Ninguém") Me.ReportViewer1.LocalReport.DataSources.Clear() Me.ReportViewer1.LocalReport.DataSources.Add(New Microsoft.Reporting.WinForms.ReportDataSource("DataSetPessoa", DataTable)) Me.ReportViewer1.RefreshReport() End Sub
Baixe o projeto de exemplo
Para baixar o projeto de exemplo desse artigo, assine a minha newsletter. Ao fazer isso, além de ter acesso ao projeto, 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 no final do artigo.
Concluindo
A principal dificuldade que os programadores enfrentam no desenvolvimento de relatórios com o Report Viewer é acertar no carregamento dos dados. Muitas vezes, ao utilizarmos DataSets tipados, a string de conexão acaba apontando para o lugar errado.
No artigo de hoje você aprendeu a alterar a string de conexão em tempo de execução (para o caso de você estar utilizando DataSets tipados com Adapters) e aprendeu também a passar um DataSet não tipado para o Report Viewer. Dessa forma você tem total flexibilidade na hora de carregar os dados dos seus relatórios.
O que achou? Gostou desse artigo? Escreva um comentário deixando a sua opinião e compartilhe esse artigo com algum amigo que possa se beneficiar desse aprendizado!
Até a próxima!
André Lima
Image by Juhan Sonin used under Creative Commons
https://www.flickr.com/photos/juhansonin/5135576565
The post Passando um DataSet não tipado para o Report Viewer appeared first on André Alves de Lima.