Quantcast
Channel: Relatórios – André Alves de Lima
Viewing all articles
Browse latest Browse all 61

Gerando relatórios do Report Viewer com Entity Framework

$
0
0

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

The post Gerando relatórios do Report Viewer com Entity Framework appeared first on André Alves de Lima.


Viewing all articles
Browse latest Browse all 61