sexta-feira, 12 de novembro de 2010

PLINQ é sempre a melhor alternativa?

Essa semana resolvi fazer um comparativo entre LINQ e PLINQ, porém ao final da execução do projeto percebi que o PLINQ sempre levou cerca de 1 segundo a mais de processamento. Bem ao parar para analisar direito e pesquisar no MSDN 1  percebi onde estava meu erro.
Existem alguns pontos que devem ser observados quanto ao PLINQ:
1.       Ele sempre melhora a desempenho? Não!
2.       PLINQ sempre processa em paralelo? Não!
O PLINQ realmente tende a ser mais performático que o LINQ, porém deve-se observar que nem sempre será. Mas então, onde eu errei no meu teste?
O meu erro foi executar um teste com uma instrução de LINQ normal que não exigia muitos cálculos nem sequer permitia a sua execução em paralelo. O PLINQ nem sempre irá melhorar a desempenho do código, para tal é necessário estudar sua viabilidade, um exemplo prático de onde isso se aplica é na utilização de Entity FrameWork e LINQ to SQL, porque tentar utilizar processamento paralelo no programa se o real processamento está no banco? O SGBD quem deve ter paralelismo neste caso.
O PLINQ é extremamente útil na utilização em objetos, ai sim temos um poderoso aliado para ganho de desempenho na execução das aplicações em computadores multiprocessados. Ainda assim, para que o PLINQ execute em paralelo, será necessário que obedeça algumas regras básicas. A documentação1 do PLINQ descreve alguns itens que são levados em consideração como Queries com Where indexado, ou Skip, Take, concat, além de outros.
Para demonstrar de maneira rápida a utilização do PLINQ iremos implementar um PLINQ em uma classe LINQ to Sql e em um DataSet. Ao consumir a DataContext (Linq to SQL) veremos que o processamento tanto em LINQ quanto em PLINQ será muito proximo. No segundo, como o DataSet é um objeto na memória veremos certa diferença.
O banco de exemplo foi criado para este artigo e sua tabela terá 3 campos: um integer, um string e um DateTime. Como nosso objetivo é teste de performance, é bom que a base tenha uma quantidade considerável de registros, para tal, nosso script realiza o insert de 1.000.000 registros na base conforme listagem abaixo.

--Criação do banco
Create database TestePLINQ
GO
--Uso do DataBase
use TestePLINQ
GO

--Tabela de Conteúdo
create table tabelaTeste (
    id int not null
    ,dataCriacao datetime null
    ,texto varchar(200) null
)
GO

--Procedure para popular a tabela
Create procedure FcInclui
AS
BEGIN
    declare @incremental as integer
   
    select @incremental = (select IsNull(MAX(id),1) from tabelaTeste)
   
    while @incremental < 1000000
    BEGIN
         insert into TestePLINQ.dbo.tabelaTeste(id,dataCriacao,texto) values (@incremental,GETDATE(),NEWID())
         print @incremental
         set @incremental = @incremental + 1
    END;
END;
GO
--execução da procedure
exec FcInclui

Nosso primeiro teste será com o LinqToSql2 para comparação entre LINQ e PLINQ, o que nesse caso o PLINQ não deve trazer ganho de desempenho. Utilizaremos 2 cenários neste teste, 1) realizando consulta PLINQ simples com um count, o que nos permite paralelismo; 2) utilizando Take que, segundo a documentação oficial no MSDN por padrão leva o PLINQ a executar em modo linear.
O projeto utilizado será simples, um Windows Form Application com um botão “Iniciar teste” e 3 Labels, 1 com a hora de inicio da execução, 1 com a hora final e o ultimo com o tempo total de execução em segundos.
Ao utilizar o PLINQ com o count obtivemos cerca de 775 milissegundos de execução (Fig 1), enquanto que, com o LINQ tivemos aproximadamente 363 milissegundos (Fig 2). A escolha do count neste caso se dá pelo fato de ele exigir o processamento dos dados diretamente na query, além de permitir que o PLINQ processe em paralelo.
    //PLINQ com Count
            var dadosLinqToSql = (from t in testePlinq.tabelaTestes.AsParallel()
                                  select t).Count();

            //LINQ com Count
            var dadosLinqToSql = (from t in testePlinq.tabelaTestes
                                  select t).Count();




Figura 1 Tempo de execução com PLINQ



Figura 2 Tempo de execução com LINQ


Já ao utilizar o PLINQ com o Take, temos um tempo de processamento mais próximo entre LINQ e PLINQ, uma vez que o PLINQ tende a processar a requisição de forma linear. Ao se observar as figuras 3 e 4 podemos ver a diferença menor de processamento.

                     //PLINQ com Take
            var dadosLinqToSql = (from t in testePlinq.tabelaTestes.AsParallel()
                                  select t).Take(50000);

            //LINQ com Take
            var dadosLinqToSql = (from t in testePlinq.tabelaTestes
                                  select t).Take(50000);

Figura 3 PLINQ com Take


Figura 4 LINQ com Take


Agora nosso teste será com um DataSet, por se tratar de um objeto na memória o PLINQ tende a nos oferecer muito mais performance. A idéia é simples, fazer uma classe que execute o select na base e retorne um DataSet para ser consumido por LINQ, para isso foi criada a classe abaixo.

public class TabelaTeste
    {
        public static DataSet GetTabelaTeste() {
            using (SqlConnection sqlConn = new SqlConnection(@"Data Source=MESSASMACHINE\MESSASSQL;Initial Catalog=TestePLINQ;Integrated Security=True"))
            {
                //declaração DataSet Retorno
                DataSet dataSetTeste = new DataSet();
                //declaração DataAdapter
                SqlDataAdapter sqlAdapter = new SqlDataAdapter();

                //Objeto SqlCommand
                SqlCommand sqlCommand = new SqlCommand();
                //Adiciona o objeto de conexão ao sqlCommand
                sqlCommand.Connection = sqlConn;
                //Adiciona o a query ao commandText
                sqlCommand.CommandText = @"select id,texto,dataCriacao
                                           from tabelaTeste";

                //adiciona a query ao Adapter
                sqlAdapter.SelectCommand = sqlCommand;
                //Preenche o DataSet
                sqlAdapter.Fill(dataSetTeste);

                return dataSetTeste;
            }
        }
    }


Ao analisarmos o resultado da execução com o DataSet veremos um ganho de desempenho com o PLINQ, além de vermos o balanceamento de carga entre os núcleos, enquanto o LINQ sobrecarrega somente um núcleo, veja imagens 5 e 6.


Figura 5 PLINQ com DataSet



Figura 6 Linq com DataSet

Novamente, ao utilizarmos o PLINQ com o Take, vemos que as diferenças entre sua execução e o LINQ diminuem, por sua opção de processamento linear, conforme figuras 7 e 8.










Figura 7 PLINQ com Take no dataSet




Figura 8 LINQ com Take no DataSet





Como até mesmo a própria Microsoft afirma, é necessário avaliar a situação em que pretende empregar o PLINQ, nem sempre ele será satisfatório, se antes era coisa de DBA estudar performance de query, agora cabe ao desenvolvedor também pensar no assunto, vale a pena acessar o MSDN e revisar sobre o PLINQ, como qualquer recurso feito para agregar qualidade ao software, se mal empregado poderá não servir de nada, ou até piorar a situação.

                   2 trabalhando com LINQ to SQL:  http://msdn.microsoft.com/en-us/library/bb384396.aspx

sábado, 6 de novembro de 2010

Trabalhando com Reports Dinamicamente

Além de suas funcionalidades primordiais para manutenção de dados, sistemas costumam exigir uma área gerencial que permita a visualização dos dados manipulados. Para tal é comum utilizar ferramentas de relatório do .NET, tais como Crystal Reports ou Reports.
Reports associados com seu Report Viewer devem ser conectados a base de dados de maneira que aceite os filtros necessários para atender aos critérios do cliente. Quando pesquisamos por conteúdo que demonstre a utilização desta ferramenta, é comum encontrar tutoriais e/ou artigos que demonstrem sua utilização, porém com um velho vicio de desenvolvedores em ASP.NET ou Windows Form, o famoso “clica e arrasta”. São poucos os artigos que ensinem o leitor a popular a criar, popular e vincular um DataSet ao Report de forma dinâmica.
O objetivo deste artigo é demonstrar de maneira simples como criar um DataSet de maneira programática e vinculá-lo ao Report. Neste exemplo utilizaremos a base de exemplo da Microsoft AdventureWorks. O Report irá listar os empregados e seus respectivos cargos, a figura 1 mostra o Database Diagram do SQL Server com as tabelas que utilizaremos. Para fazer download do AdventureWorks acesse o link http://sqlserversamples.codeplex.com/.

Figura 1 Diagrama da Base utilizada

Nosso projeto será simples, terá uma classe de acesso a dados, uma página de filtros e uma página contendo o ReportViewer, além do Report.rdlc é claro.
Para iniciar abra o Visual Studio e vá a novo Projeto -> ASP.NET WebSite, conforme figura 2.

Figura 2 Criando o Projeto

A figura 3 mostra a tela de criação do projeto, como utilizaremos o servidor interno do Visual Studio para executar o projeto, recomendo deixar selecionado File System na caixa de seleção Web Location, assim podemos indicar um diretório físico para o projeto, que demos o nome de “PopulandoReport”.


Figura 3 Salvando o Projeto

O primeiro passo para implementar nosso projeto é acessar a base de dados e retornar os dados que queremos, como o objetivo desse artigo é demonstrar a utilização dinâmica do ReportViewer, nossa classe de acesso a dados será simples e ficará na pasta App_Code do projeto. Para criar a classe EmpregadoDataAccess, clique com o botão direto do mouse sobre o projeto -> Add New Item e escolha Class, assim como exibido na figura  4.

Figura 4 Adicionando a Classe EmpregadoDataAccess




Ao clicar em Add o Visual Studio irá sugerir que seja criada a pasta App_Code ao projeto e adicione a nossa classe dentro dela, clique em sim e a classe será criada  normalmente. Note que a estrutura da classe é simples, teremos um construtor e um método: GetEmployees.
A listagem 1 apresenta o código da classe, note que utilizamos a instrução using para garantir que o Garbage Collector entre em ação para limpar os recursos utilizados. Por boas práticas não recomendamos a utilização da connectionstring direto no código fonte e sim, no Web.Config da aplicação, como este é apenas um código de exemplo será utilizado diretamente. Outro ponto importante é a utilização de parâmetros na query, parâmetros além de serem boa prática, também agregam segurança, reduzindo os riscos de Sql Injection.

Listagem 1 Classe EmpregadoDataAccess

Em nossa página default serão adicionados um DropDownList, o qual popularemos com alguns cargos, um Button para o submit e o ReportViewer, para geração do Report. Para adicionar o ReportViewer expanda a  toolbox do visual Studio, clique em Reporting, então clique sobre  ReportViewer e arraste para dentro da página, a figura 5 exibe o controle exatamente o controle a ser selecionado. Neste ponto é recomendada a utilização do “Clica e Arrasta”, pois conforme pode ser visto nas figuras 6 e 7, o visual Studio adiciona as referências necessárias para utilização de Reports na Página e no Web.Config.

Figura 5 Selecionando o Report Viewer

Figura 6 Registrando o Assembly para utilização de reports

Figura 7 Assembly registrado no Web.Config




Por questão de organização iremos adicionar duas pastas ao nosso projeto: 1) report – irá abrigar o nosso Report.rdlc e; 2) xsd – onde será salvo o nosso DataSet gerado dinamicamente. Então sem mais delongas vamos a .cs de nossa Default.aspx para inciarmos a programação. Antes de abrir a default.aspx.cs dê dois cliques sobre o botão para que o visual Studio adicione automaticamente o evento necessário para a pesquisa de dados do relatório.
Dentro do evento gerado iremos receber o valor selecionado no DropDownList de cargos e instanciar nossa Classe EmpregadoDataAccess. O próximo passo é criar um DataSet que recebe o retorno do método GetEmployess, veja a Listagem 2.

Listagem 2 Estrutura Default.aspx.cs

O próximo passo é adicionar logo abaixo da linha de criação do dataset, a instrução para que seja criado o DataSet físico no projeto, para isso utilizamos o método WriteXmlSchema da classe DataSet, veja Listagem 3. Execute o projeto pressionando Ctrl + F5, logo após vá a pasta xsd e pressione F5 para atualizar seu conteúdo.

Listagem 3 Criando o DataSet

Abra o DataSet gerado e note que ele possui somente uma Table, correspondente ao resultado de nossa query, os campos da Table são exatamente os chamados no select, assim como vemos na Figura 8. É importante lembrar que o método WriteXmlSchema é necessário ser executado somente uma vez, para geração do xml, após recomendo que comente a linha com a chamada do método.

Figura 8 DataSet Gerado

O próximo passo é trabalhar nosso Report, para isso clique com o botão direito sobre a pasta Report-> Add New Item... Report. Em nosso projeto, chamaremos o report de ReportEmpregado, confira na Figura 9.

Figura 9 Adicionando o Report

Com o Report aberto expanda ToolBox e arraste uma Table, o visual Studio irá solicitar vinculação de um dataset, a figura 10 exibe a tela de vinculação. Escolha nosso DataSet criado para vincular a Table.  Note que se estiver utilizando o Visual Studio 2008, o procedimento para vincular o DataSet ao Report é diferente, para isso vá em Report, na barra de ferramentas, selecione DataSources e escolha o datasource desejado.

Figura 10 Vinculando DataSet

Em sua Table adicione os campos e seus valores, basta clicar sobre a célula, selecionar Expression e então utilizar a Expressão =Fields![NomeCampo].Value, onde [NomeCampo] corresponde ao nome do campo que deseja exibir, conforme a Figura 11. O report possui inúmeras funções que possibilitam a manipulação de dados, como somar, contar, formatação de dados, vale a pena conferir.

Figura 11 Adicionando valores às colunas

Voltando ao nosso evento no código da Default.aspx.cs adicionaremos algumas linhas de código, para vincular o dataset e o report ao ReportViewer da pagina, assim poderemos executar nossa pesquisa e visualizar o relatório. A primeira coisa a fazer é criar um objeto do tipo ReportDataSource e vinculá-lo ao DataSet, utilizaremos duas propriedades do objeto: Name e Value. Name receberá o nome dado ao DataSet no momento da criação da Table no report, em nosso projeto foi DataSetEmployees. Value receberá o objeto de nosso DataSet já populado anteriormente, na Listagem 4 podemos ver esta alteração.

Listagem 4 Criando o ReportDataSource

A seguir criamos uma string indicando o endereço do Report. Feito isto estamos finalizando nosso projeto, agora basta atribuir os objetos ao ReportViewer da Página. Para atribuir o datasource ao ReportViewer utilizaremos o método Add da ReportDataSourceCollection do Report, this.ReportViewer1.LocalReport.DataSources.Add. Por se tratar de uma Collection podemos atribuir quantos ReportDataSource quisermos. O endereço do report é adicionado por meio da propriedate ReportPath, this.ReportViewer1.LocalReport.ReportPath, veja a Listagem 5 para maior detalhe.

Listagem 5 Adicionando parâmetros ao ReportViewer

Agora basta executar o projeto e ver o resultado, a figura 12 exibe o relatório sendo gerado corretamente. Cada vez que clicamos no botão consultar nosso filtro é aplicado.

Listagem 6 Visualização do Relatório

Para criação de consultas personalizadas e criação de relatórios com Report sem precisar adicionar query diretamente nas propriedades do DataSet para vinculá-lo. A possibilidade de parametrizar o report dinamicamente via código facilita na hora de criar o DataSet e trabalhar com diferentes reports em um só ReportViewer, eliminando a necessidade de criar varias paginas para cada report. Lembre-se que o ReportViewer possui varias propriedades e vale a pena explorá-las.