André Alves de Lima

Talking about Software Development and more…

Gerando relatórios do Report Viewer com Entity Framework

Não é segredo para ninguém que eu acho o Report Viewer uma ferramenta muito interessante para a geração de relatórios em aplicativos desenvolvidos com o .NET Framework. Eu já escrevi muitos artigos sobre o Report Viewer e inclusive já compilei uma grande parte do meu conhecimento em um e-book sobre essa ferramenta.

Entretanto, a grande maioria dos artigos sobre o Report Viewer só mostra a ligação de dados através de um DataSet tipado. E como é que fica o pessoal que utiliza o Entity Framework? Você já deve ter ouvido falar desse ORM da Microsoft, não é mesmo?

Os poucos artigos (em inglês) que mostram a utilização do Report Viewer com Entity Framework abordam exemplos muito simples, utilizando somente uma entidade. No artigo de hoje vou mostrar para você como alimentar um relatório mestre/detalhe do Report Viewer utilizando uma query customizada entre várias tabelas vindas do Entity Framework.

Criando o modelo de dados

Para montarmos um relatório mestre/detalhe, normalmente precisamos de uma hierarquia de tabelas no nosso modelo de dados. Resolvi escolher um exemplo clássico com múltiplas tabelas, que é modelo de gerenciamento de pedidos.

Nesse modelo, temos uma classe Pedido e uma classe ItemPedido. Cada pedido tem um ou mais itens de pedido. Além disso, o pedido está vinculado a um cliente e o item de pedido está vinculado a um produto. Confira no diagrama abaixo o relacionamento entre essas classes:

O exemplo desse artigo utilizará um projeto do tipo “Windows Forms Application“. Dito isso, a primeira coisa que temos que fazer é criar um projeto desse tipo.

Uma vez criado o projeto, vamos adicionar a referência ao Entity Framework 6, utilizando o NuGet. Para isso, vá até a tela de gerenciamento de pacotes do NuGet e escolha a opção para instalar o pacote do Entity Framework (que normalmente é o primeiro da lista):

Com o Entity Framework instalado no nosso projeto, podemos partir para a criação das nossas classes de Cliente, Produto, Pedido e ItemPedido. Adicionaremos essas classes dentro de uma nova pasta no nosso projeto, chamada “Models“:

Veja a seguir o código de cada uma dessas classes:

    public class Cliente
    {
        [System.ComponentModel.DataAnnotations.Key]
        [System.ComponentModel.DataAnnotations.Schema.DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
        public int ClienteId { get; set; }

        public string Nome { get; set; }
    }

    public class Produto
    {
        [System.ComponentModel.DataAnnotations.Key]
        [System.ComponentModel.DataAnnotations.Schema.DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
        public int ProdutoId { get; set; }

        public string Descricao { get; set; }
        public double Preco { get; set; }
    }

    public class Pedido
    {
        [System.ComponentModel.DataAnnotations.Key]
        [System.ComponentModel.DataAnnotations.Schema.DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
        public int PedidoId { get; set; }

        public int ClienteId { get; set; }
        public virtual Cliente Cliente { get; set; }
        public DateTime DataPedido { get; set; }
        public virtual ICollection<ItemPedido> ItensPedido { get; set; }
    }

    public class ItemPedido
    {
        [System.ComponentModel.DataAnnotations.Key]
        [System.ComponentModel.DataAnnotations.Schema.DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
        public int ItemPedidoId { get; set; }

        public int PedidoId { get; set; }
        public virtual Pedido Pedido { get; set; }
        public int ProdutoId { get; set; }
        public virtual Produto Produto { get; set; }
        public double Quantidade { get; set; }
        public double ValorTotal { get; set; }
    }

Note que a estrutura dessas classes é bem simples, uma vez que o foco do artigo não é criar uma super-estrutura de dados, mas sim, mostrar como podemos utilizar essa estrutura de múltiplas classes para alimentar o nosso relatório do Report Viewer. Também não vou entrar nos detalhes do Entity Framework neste artigo, uma vez que já existem inúmeros artigos sobre esse tema.

Após adicionarmos as classes do modelo, temos que criar uma classe de contexto do Entity Framework. Para isso, crie uma nova classe no projeto, chamada “Contexto“, que deverá ter o seguinte conteúdo:

    public class Contexto : System.Data.Entity.DbContext
    {
        public System.Data.Entity.DbSet<Models.Cliente> Clientes { get; set; }
        public System.Data.Entity.DbSet<Models.Produto> Produtos { get; set; }
        public System.Data.Entity.DbSet<Models.Pedido> Pedidos { get; set; }
        public System.Data.Entity.DbSet<Models.ItemPedido> ItensPedido { get; set; }
    }

Finalmente, vamos até o code-behind do formulário que o Visual Studio criou automaticamente com o projeto Windows Forms para adicionarmos o código que populará alguns dados em cada uma das tabelas (Cliente, Produto, Pedido e ItensPedido):

        public FormRVComEF()
        {
            InitializeComponent();

            PopularBancoDeDadosSeNecessario();
        }

        private void PopularBancoDeDadosSeNecessario()
        {
            using (var contexto = new Contexto())
            {
                Models.Cliente clienteMicrosoft = contexto.Clientes.FirstOrDefault(cliente => string.Compare(cliente.Nome, "Microsoft", StringComparison.InvariantCultureIgnoreCase) == 0);
                Models.Cliente clienteGoogle = contexto.Clientes.FirstOrDefault(cliente => string.Compare(cliente.Nome, "Google", StringComparison.InvariantCultureIgnoreCase) == 0);
                Models.Produto produtoMouse = contexto.Produtos.FirstOrDefault(produto => string.Compare(produto.Descricao, "Mouse", StringComparison.InvariantCultureIgnoreCase) == 0);
                Models.Produto produtoTeclado = contexto.Produtos.FirstOrDefault(produto => string.Compare(produto.Descricao, "Teclado", StringComparison.InvariantCultureIgnoreCase) == 0);

                if (clienteMicrosoft == null)
                {
                    clienteMicrosoft = new Models.Cliente() { Nome = "Microsoft" };
                    contexto.Clientes.Add(clienteMicrosoft);
                }
                if (clienteGoogle == null)
                {
                    clienteGoogle = new Models.Cliente() { Nome = "Google" };
                    contexto.Clientes.Add(clienteGoogle);
                }
                if (produtoMouse == null)
                {
                    produtoMouse = new Models.Produto() { Descricao = "Mouse", Preco = 75 };
                    contexto.Produtos.Add(produtoMouse);
                }
                if (produtoTeclado == null)
                {
                    produtoTeclado = new Models.Produto() { Descricao = "Teclado", Preco = 55 };
                    contexto.Produtos.Add(produtoTeclado);
                }
                if (!contexto.Pedidos.Any())
                {
                    var pedido = new Models.Pedido() { Cliente = clienteMicrosoft, DataPedido = DateTime.Now };
                    contexto.Pedidos.Add(pedido);
                    var itemPedido = new Models.ItemPedido() { Pedido = pedido, Produto = produtoMouse, Quantidade = 3, ValorTotal = 3 * produtoMouse.Preco };
                    contexto.ItensPedido.Add(itemPedido);
                    itemPedido = new Models.ItemPedido() { Pedido = pedido, Produto = produtoTeclado, Quantidade = 1, ValorTotal = 1 * produtoMouse.Preco };
                    contexto.ItensPedido.Add(itemPedido);

                    pedido = new Models.Pedido() { Cliente = clienteGoogle, DataPedido = DateTime.Now.AddDays(-7) };
                    contexto.Pedidos.Add(pedido);
                    itemPedido = new Models.ItemPedido() { Pedido = pedido, Produto = produtoMouse, Quantidade = 1, ValorTotal = 1 * produtoMouse.Preco };
                    contexto.ItensPedido.Add(itemPedido);
                    itemPedido = new Models.ItemPedido() { Pedido = pedido, Produto = produtoTeclado, Quantidade = 7, ValorTotal = 7 * produtoMouse.Preco };
                    contexto.ItensPedido.Add(itemPedido);
                }

                contexto.SaveChanges();
            }
        }

Pronto. Com isso temos o modelo criado e o contexto inicializado com alguns dados. Se observarmos o banco de dados, veremos que o Entity Framework terá criado as tabelas correspondentes a cada classe do nosso modelo:

Ajustando o formulário de preview

Agora que já temos o banco de dados, vamos preparar os dados que deverão ser exibidos no relatório. Como queremos fazer um relatório mestre/detalhe (com as informações do pedido no mestre e as informações dos itens de pedido no detalhe), temos duas opções: ou trabalhamos com agrupamentos (agrupando os dados pelo ID do Pedido) ou trabalhamos com sub-relatórios.

Para trabalharmos com agrupamentos, precisamos passar para o relatório todos os dados de forma “desnormalizada” em uma fonte de dados única (onde cada linha da fonte de dados teria as informações de um item de pedido, juntamente com todas as informações do pedido correspondente).

Por outro lado, se quisermos trabalhar com sub-relatórios, teríamos que passar duas fontes de dados para o relatório: uma representando os pedidos e outra representando os itens de pedido.

Quando me deparo com essa situação, normalmente eu escolho o primeiro caminho (agrupamentos), uma vez que o relatório fica muito mais simples de ser gerado e também tem o fato de agrupamentos terem uma performance melhor que sub-relatórios no Report Viewer.

Dessa forma, nesse artigo eu vou mostrar somente como fazer esse relatório utilizando agrupamentos. Caso você opte por utilizar sub-relatórios, tenho certeza que você conseguirá adaptar o exemplo sem muitas dificuldades.

O grande problema das fontes de dados do Report Viewer é o fato de que não é possível criarmos uma fonte de dados dinâmica. O que eu quero dizer com isso é que não é possível adicionarmos uma fonte de dados no relatório e especificarmos manualmente as colunas dessa fonte de dados. Toda fonte de dados do Report Viewer deve ser baseada em um banco de dados, serviço, objeto ou lista do SharePoint.

Por causa disso, antes de prosseguirmos, teremos que criar uma nova classe no nosso projeto que servirá de representação para a estrutura de dados que alimentará o relatório. Para isso, crie uma nova pasta no projeto (chamada “Report“) e adicione uma nova classe chamada “DadosRelatorio“:

    public class DadosRelatorio
    {
        public int PedidoId { get; set; }
        public DateTime DataPedido { get; set; }
        public int ClienteId { get; set; }
        public string NomeCliente { get; set; }
        public int ProdutoId { get; set; }
        public string DescricaoProduto { get; set; }
        public double PrecoProduto { get; set; }
        public double Quantidade { get; set; }
        public double ValorTotal { get; set; }
    }

Note que essa classe possui basicamente todas as propriedades das quatro classes que criamos anteriormente. Em outras palavras, ela representa um item de pedido, juntamente com todas as outras propriedades relacionadas (descrição do produto, preço, nome do cliente, etc).

No caso desse exemplo, teremos somente um relatório, por isso dei o nome de “DadosRelatorio” para essa classe. Em um sistema verdadeiro, provavelmente você terá múltiplos relatórios, portanto, caso os seus relatórios sejam baseados em mais de uma tabela, você terá que criar múltiplas classes desse tipo (por exemplo, “DadosRelatorioFornecedor“, “DadosRelatorioFatura“, etc).

Uma vez tendo criado a classe dos dados do relatório, vamos até o design do formulário para adicionarmos um controle do Report Viewer (dê o nome de “reportViewer” para esse controle e fixe-o para que ele ocupe o tamanho todo do formulário):

Antes de continuar com a próxima seção deste artigo, compile o projeto. Se você não compilar o projeto, existe a chance que o Report Viewer não reconheça a classe “DadosRelatorio” que criamos anteriormente.

Desenhando o relatório

Com o formulário de preview preparado para a exibição do relatório, agora só falta o mais importante: o relatório em si! Para resolver esse problema, vamos adicionar um novo relatório chamado “RelatorioPedido” na pasta “Report“.

A primeira coisa que vamos fazer após termos adicionado o relatório é criar a fonte de dados. Para isso, vá até a janela de dados do relatório (“Report Data“), clique com o botão direito em “Datasets” e escolha a opção “Add Dataset“:

Na tela de escolha do tipo de Dataset, escolha a opção “Object” e clique em “Next“:

Feito isso, na tela de escolha do tipo do objeto, expanda o namespace do seu projeto e encontre a classe “DadosRelatorio” que criamos anteriormente e clique em “Finish“:

Nota: caso a classe “DadosRelatorio” não apareça na lista, é porque você não seguiu as instruções e esqueceu de compilar o projeto antes de prosseguir. Dessa forma, cancele a operação, compile o projeto e repita os passos apresentados acima.

Finalmente, dê o nome de “DadosRelatorio” para o Dataset que será criado e clique em “OK“:

Agora que já temos a fonte de dados preparada, adicione um componente do tipo “Table” no relatório e configure o seu Dataset (nas propriedades do componente) apontando para o Dataset “DadosRelatorio” que criamos anteriormente:

Feito isso, vamos adicionar um grupo pelo ID do Pedido. Para isso, vá até “Row Groups” e escolha a opção “Add Group / Parent Group“. Não esqueça de marcar as opções para gerar o cabeçalho e rodapé do grupo. Isso facilitará bastante o ajuste do layout da tabela:

Outra configuração interessante que podemos ajustar no grupo é a quebra de página. Se você quiser que cada pedido fique em uma página diferente, vá até as propriedades do grupo e marque a opção “Between each instance of a group” na categoria de “Page Breaks“:

Após isso, o próximo passo que temos que seguir é deletarmos a coluna que foi criada anteriormente e ajustarmos o layout da tabela:

Se você quiser, você pode adicionar um cabeçalho no relatório com o título “Pedido“, ou até mesmo com o logotipo da sua empresa ou cliente:

Ajustes finais no formulário de preview

Você está preparado(a) para a parte final? Agora que já temos o nosso relatório desenhado, a única coisa que está faltando é escolhermos esse relatório no controle do Report Viewer e popularmos o relatório com os dados vindos do nosso contexto do Entity Framework.

Vá até o designer do formulário e escolha o relatório que criamos anteriormente:

Feito isso, vamos até o code-behind e, no evento “Load” do formulário, vamos criar um “IEnumerable de DadosRelatorio” pegando os dados de todas as tabelas envolvidas com os itens de pedido através de uma LINQ query. Com o resultado em mãos, basta adicionarmos o resultado da LINQ query como fonte de dados do relatório:

        private void FormRVComEF_Load(object sender, EventArgs e)
        {
            using (var contexto = new Contexto())
            {
                var dadosRelatorio = (from itemPedido in contexto.ItensPedido
                                      select new Report.DadosRelatorio()
                                      {
                                          PedidoId = itemPedido.Pedido.PedidoId,
                                          DataPedido = itemPedido.Pedido.DataPedido,
                                          ClienteId = itemPedido.Pedido.Cliente.ClienteId,
                                          NomeCliente = itemPedido.Pedido.Cliente.Nome,
                                          ProdutoId = itemPedido.Produto.ProdutoId,
                                          DescricaoProduto = itemPedido.Produto.Descricao,
                                          PrecoProduto = itemPedido.Produto.Preco,
                                          Quantidade = itemPedido.Quantidade,
                                          ValorTotal = itemPedido.ValorTotal
                                      }).ToArray();

                this.reportViewer.LocalReport.DataSources.Clear();
                this.reportViewer.LocalReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("DadosRelatorio", dadosRelatorio));
            }

            this.reportViewer.RefreshReport();
        }

Execute a aplicação e veja o resultado:

Concluindo

O Report Viewer é uma excelente ferramenta para geração de relatórios disponibilizada diretamente pela Microsoft. Em uma outra categoria completamente diferente, o Entity Framework é um ORM muito poderoso, também disponibilizado diretamente pela Microsoft.

Nesse artigo você aprendeu a utilizar de forma combinada essas duas ferramentas importantíssimas que deve estar presente na caixa de ferramentas de qualquer desenvolvedor de aplicações desktop na plataforma Microsoft.

E você, já precisou popular relatórios do Report Viewer com Entity Framework? Você seguiu essa metodologia? Deixe a sua opinião nos comentários!

Antes de me despedir, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado, ficará sabendo em primeira mão sobre o artigo da próxima semana e receberá também dicas “bônus” que eu só compartilho por e-mail. Além disso, você já deve ter percebido que eu recebo muitas sugestões de temas e eu costumo dar prioridade às sugestões vindas de inscritos da minha newsletter. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

Image by Pixabay used under Creative Commons
https://pixabay.com/en/calculator-calculation-insurance-1044173/

Newsletter do André Lima

* indicates required



Powered by MailChimp

21 thoughts on “Gerando relatórios do Report Viewer com Entity Framework

  • Rodrigo Otávio Belo disse:

    Olá novamente André…

    Seu site é sensacional, muito obrigado pela iniciativa de ajudar!!!

    Abrass…

  • AIRTON BARROS disse:

    André,

    Difícil encontrar um profissional com tanta vontade de buscar o perfeccionismo.

    Disponibiliza o conteúdo para todas as pessoas, não importa se comprou seu produto ou não.

    Como Deus só tem Um, diria quase perfeito.

    Agradecido pelo artigo.

    • andrealveslima disse:

      Poxa Airton, muito obrigado! Fico feliz que você esteja gostando do conteúdo.. São comentários como estes que me motivam a continuar produzindo mais e mais conteúdo..

      Abraço!
      André Lima

  • Rodrigo Otávio Belo disse:

    Bom dia André

    Coloca algo sobre gráficos ai.. :)

    • andrealveslima disse:

      Olá Rodrigo, obrigado pelo comentário!

      O tema “gráficos” está na minha lista.. Vamos ver se eu consigo escrever e publicar no segundo trimestre deste ano..

      Abraço!
      André Lima

  • Ednilson do Valle disse:

    Olá André!

    Parabéns pelo excelente artigo! Você consegue deixar simples os mais complexos assuntos com muita didática e maestria. Obrigado pelas valiosas dicas a nós disponibilizadas e por compartilhas seu conhecimento! Que Deus ilumine sempre!

    Grande abraço!

  • […] semanas atrás eu escrevi um artigo mostrando como alimentar relatórios do Report Viewer com o Entity Framework. Aí eu parei e pensei: será que dá para utilizar o Entity Framework com o Crystal Reports […]

  • […] outros artigos onde eu mostro mais detalhes sobre a geração de relatórios como, por exemplo, este aqui onde eu explico como criar um relatório mestre/detalhe com Report Viewer + Entity Framewo…. Ah, e claro, além disso tem o meu livro sobre Report Viewer também, caso você queira se […]

  • […] Nota: não é obrigatório o uso de DataSets com o Report Viewer. Se você já tiver o arquivo .rdlc gerado utilizando um outro tipo de fonte de dados, você pode utilizá-lo sem problema na sua Web API. Por exemplo, é possível criarmos os relatórios .rdlc utilizando as classes de domínio do nosso projeto, como mostrei neste artigo sobre o Report Viewer com o Entity Framework. […]

  • JAIR SOUZA disse:

    Olá, ótimos post você tem.
    Estou iniciando em reports, tenho um form1 onde o usuário digita um parâmetro em um textbox, a partir deste parâmetro abre outro form2 com o reportviewer mostrando o relatório, eu quero que só abra o form2, se existir dados para o report mostrar,
    sabes como fazer isto ?
    Desde agora agradeço!

    • andrealveslima disse:

      Olá Jair, muito obrigado pelo comentário!

      Como é que você está fazendo o carregamento dos dados? Basicamente você teria que, antes de chamar o segundo formulário com o relatório, você checa no seu repositório se existem dados para serem exibidos no relatório.. Fica difícil de dar uma explicação mais detalhada sem ver exatamente como está a arquitetura do seu projeto.. Será que você poderia passar mais detalhes do seu código? Se preferir, me manda por e-mail: contato [arroba] andrealveslima [ponto] com [ponto] br

      Abraço!
      André Lima

  • Thiago Lima disse:

    Andre, boa tarde.
    Você não sabe o favor que me fez com esse seu post. Muito obrigado por tamanho altruísmo.

  • Rodrigo de Medeiros disse:

    Prezado André,

    Primeiro gostaria de parabenizá-lo pelo blog! É excelente.

    André, estou com um problema ao utilizar o Visual Studio 2017 Community, com Entity Framework (code first) com Banco de dados Oracle XE, onde ao tentar criar um relatório seguindo este tutorial, quando tento adicionar o “dataset” a janela “Data Source Configuration Wizard”, onde eu poderia escolher a opção “Object”, não aparece, é como o VS pulasse essa etapa e já abre a segunda tela “Dataset Properties”. Não sei se tem relação com a versão do VS ou o fato de está utilizando Oracle, mas até não conseguir identificar o que poderia ser, se você puder me ajudar será de grande ajudar, Abraço!

    • andrealveslima disse:

      Olá Rodrigo, muito obrigado pelo comentário!

      Deixa eu te perguntar uma coisa.. Que tipo de projeto você está trabalhando? Windows Forms mesmo? O projeto onde você está tentando adicionar o relatório é uma Class Library?

      Abraço!
      André Lima

      • Rodrigo de Medeiros disse:

        Olá André,

        Estou usando AngularJs com WebApi. O projeto é Web, uso o WebApi para retornar o relatório em PDF baseado em um post que você publicou sobre o assunto.

        Tem relação com tipo de projeto? Obrigado pelo retorno!

        Abraço!

        • andrealveslima disse:

          Olá Rodrigo!

          Sim, tem relação com o tipo do projeto! Em projetos web você não consegue criar object data source.. Só em projetos desktop ou class library.. Nesse caso, você teria duas opções:

          1) Criar um novo projeto do tipo “Class Library” onde você armazenará os relatórios (pelo menos enquanto você estiver desenhando os relatórios)

          2) Manter os relatórios no projeto web, porém, definir a estrutura dos campos diretamente no arquivo rdlc utilizando a ferramenta que eu desenvolvi chamada “Report Viewer DataSet Editor” (mais informações neste outro artigo)

          Abraço!
          André Lima

  • Guilherme disse:

    Boa tarde, segui seu passo a passo, porém ao criar um report na minha controller encontrei um problema. É o seguinte, quando seleciono a opção para criar um novo dataset ele não me mostra a tela choose data source type, ele entra diretamente na opção do banco de dados. Porém não quero criar um dataset a partir do banco de dados. Saberia se há alguma configuração responsável por isso?

    • andrealveslima disse:

      Olá Guilherme!

      Isso normalmente acontece ao criar o relatório diretamente dentro do projeto MVC.. Se você criar um projeto separado (class library) com os relatórios, você obterá as outras opções de Data Source para o relatório..

      Outra opção (caso você queira continuar com os relatórios dentro do projeto MVC), é utilizar a ferramenta que eu criei, chamada Report Viewer DataSet Editor:

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

      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 *