André Alves de Lima

Talking about Software Development and more…

Passando um DataSet não tipado para o Report Viewer

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

Newsletter do André Lima

* indicates required



Powered by MailChimp

13 thoughts on “Passando um DataSet não tipado para o Report Viewer

  • fernando lima disse:

    Muito bom, vai ser útil! Obrigado.

  • mauricio disse:

    E quando há dois datasets dentro do datasource? como que eu adiciono esses campos dentro de um text box? valeu

    • andrealveslima disse:

      Olá Mauricio!

      Quando existem dois DataSets no relatório você precisa fazer a mesma coisa duas vezes, uma para cada DataSet do relatório.. Ou seja, você faria uma chamada a DataSources.Add passando o nome e DataTable do primeiro DataSet e outra chamada passando o nome e DataTable do segundo DataSet..

      Já quanto à sua segunda pergunta, eu não consegui entender muito bem.. Como assim adicionar esses campos dentro de um TextBox? Que campo? Que TextBox?

      Abraço!
      André Lima

  • Fernanda disse:

    Olá, você poderia me explicar qual é a mudança feita de quem utiliza o banco de dados MySQL Worckbench para a conexão de fonte de dados e o servidor? Pois no meu projeto não aparece a opção para se conectar a fonte de dados e provedor, será que o problema está na versão do Visual Studio?

  • Jhonata disse:

    Ola André tudo bem?
    Gostei de suas dicas.
    Estou com uma duvida, gostaria de saber se no seu blog, tem alguma seção ensinando como eu imprimo um DataGridViewer(Preenchido pelo usuário) utilizando o Reportviewer. Pois eu só encontro relatórios que o datagrid já vem preenchido do banco de dados, minha duvida é como eu vou passar esse datagrid preenchido pelo usuário para o ReportViewer.
    Obrigado desde já. Estou Utilizando o C# Windows Forms

    • andrealveslima disse:

      Olá Jhonata, muito obrigado pelo comentário!

      Como é que você está alimentando o DataGridView? Com uma DataTable? Se for, basta você passar essa DataTable para o Report Viewer através do método DataSources.Add.. O maior problema não é alimentar o relatório, mas sim, a confecção do relatório em si.. Você precisará criar um DataSet tipado (ou classe) com a estrutura da tabela que será utilizada no relatório para conseguir desenhá-lo..

      O meu próximo artigo será mais ou menos sobre esse tema.. Ele deve sair lá pelo dia 30.. Fique atento às novas publicações para não perder.. ;)

      Abraço!
      André Lima

    • andrealveslima disse:

      Olá Jhonata, tudo tranquilo?

      Aquele artigo que eu comentei com você sobre a geração de relatórios do Report Viewer sem DataSet saiu nessa semana.. Dá uma olhada aqui:

      Definindo a estrutura de campos do Report Viewer sem DataSet ou classe

      Abraço!
      André Lima

  • Jean Carlos disse:

    olá André, tudo Bem?
    eu gostaria de saber como eu coloco múltiplos relatórios (mesmo relatório, mas com informações diferentes via parâmetros) no ReportViewer. Exemplo: tenho 30 funcionários, e através do relatório matriz repeti-lo com a informação de cada funcionário(no caso 30 relatórios) repetidamente com a informação funcional de cada um, de forma que apareça esses 30 relatórios na tela do ReportViewer.
    eu tentei fazer via foreach passando os parâmetros, mas só aparece no ReportViewer um único relatório, com o ultimo funcionário.

    • andrealveslima disse:

      Olá Jean!

      Nesse caso fica impraticável você fazer dessa maneira (com parâmetros).. O ideal é criar um DataSet, montar um único relatório utilizando esse DataSet e depois você passa a informação de todos esses 30 funcionários de uma vez só para esse relatório, que pode ser agrupado por funcionário caso necessário..

      Uns tempos atrás eu até mostrei como juntar o conteúdo de dois relatórios diferentes em um só:

      Juntando dois relatórios do Report Viewer em um só

      Mas, nesse seu caso, esse exemplo não se aplica.. Ficaria inviável fazer isso com 30 relatórios.. Além disso, essa quantidade provavelmente não é fixa..

      Concluindo: não utilize parâmetros para isso.. Parta para um DataSet (ou classe de dados) com a informação dos 30 funcionários ao mesmo tempo, agrupando pelo ID deles.. Acredito que esta apresentação que eu fiz uns tempos atrás possa te ajudar nessa tarefa:

      Relatórios mestre/detalhe com Report Viewer

      Abraço!
      André Lima

  • Thiago Rafael Teixeira Nogueira disse:

    Olá André, teria como montar algum exemplo para geração de relatórios com instruções SQL, por exemplo: querto todos os clientes que seja de uma determinada cidade

    • andrealveslima disse:

      Olá Thiago!

      Para eu entender melhor, deixa eu te fazer umas perguntas.. Você já sabe carregar essas informações em um DataTable? Ou seja, você já consegue carregar de um banco SQL os clientes de uma determinada cidade?

      Se sim, aí não tem segredo.. É só usar o mesmo esquema que eu mostrei neste mesmo artigo onde você comentou para passar a DataTable carregada para o relatório..

      Agora, se você não sabe como carregar os dados na DataTable, você precisa dar uma estudada melhor em ADO.NET..

      Qual das duas situações você se encaixa?

      Abraço!
      André Lima

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *