Utilizando o DataReader Assincronamente

Na versão 2.0 do .NET Framework, a Microsoft incluiu uma série de novas funcionalidades em sua API de acesso a dados, o ADO.NET. Entre elas, podemos destacar o código genérico, MARS, Bulk-Copy e execução assíncrona de comandos e consultas.

O que tínhamos disponível naquela época é a implementação através do modelo assíncrono do .NET, que era implementado utilizando um par de métodos BeginXXX/EndXXX. Sendo assim, o método ExecuteReader passou a ter os métodos BeginExecuteReader e EndExecuteReader, enquanto o método ExecuteNonQuery, ganhou os métodos BeginExecuteNonQuery e EndExecuteNonQuery.

Da mesma forma, para ficar alinhado a nova forma de se trabalhar assincronamente nas linguagens, a execução assíncrona de comandos e consultas no ADO.NET 4.5 sofreu algumas mudanças, para seguir o modelo baseado em Tasks. Além das mudanças em nível das interfaces das classes, um detalhe importante é que não é mais necessário definir o flag Asynchronous Processing para True no arquivo de configuração, algo que sempre era descoberto somente depois que a aplicação estava em execução.

Para iniciar, a classe que representa a conexão (SqlConnection/DbConnection) fornecem a versão assíncrona do método Open, que é o OpenAsync. Este método retorna uma Task, o que a torna “aguardável”, e com isso, podemos utilizar a keyword await para que a abertura possa ser realizada de forma assíncrona. Abaixo o código ilustra o uso deste método:

private async static Task Executar()
{
    using (var conn = new SqlConnection(“…”))
    {
        await conn.OpenAsync();

        //…
    }
}

Como já era de se esperar, os mesmos métodos fornecidos na versão 2.0 do ADO.NET para processamento assíncrono, ganharam na versão baseada em Tasks na versão 4.5. No caso do ExecuteReader, temos o ExecuteReaderAsync. Já para o método ExecuteNonQuery, temos o ExecuteNonQueryAsync e, finalmente, para o ExecuteScalar, existe o ExecuteScalarAsync.

Todos estes métodos tratam-se da nova versão assíncrona, que basicamente retornam um objeto do tipo Task, que representa a tarefa que está sendo executada assincronamente. E, qualquer exceção que eventualmente ocorra dentro do processo assíncrono, ela será retornada/acessada através da Task que foi retornada pelo método. Abaixo temos um exemplo de como ler os dados através de DataReader, utilizando este novo modelo assíncrono:

private async static Task Executar()
{
    using (var conn = new SqlConnection(“…”))
    {
        await conn.OpenAsync();

        using (var cmd = new SqlCommand(“SELECT * FROM Cliente”, conn))
            using (var dr = await cmd.ExecuteReaderAsync())
                while (await dr.ReadAsync())
                    if (!await dr.IsDBNullAsync(1))
                        Console.WriteLine(dr.GetString(1));
    }
}

Acima utilizamos o método ExecuteReaderAsync, mas ao percorrer o result-set retornado, utilizamos o – também novo – método ReaderAsync, que é a versão assíncrona, também baseada em Task,  do método Read do DataReader. Esse método em conjunto os métodos NextResultAsync, IsDBNullAsync e GetFieldValueAsync<T>, fornecem um controle muito mais refinado aos dados que estão sendo extraídos, pois quando olhamos um pouco mais de perto os internals de cada um deles, percebemos que a versão síncrona pode custar caro, prejudicando assim a escalabilidade.

Além disso, todos os métodos que vimos até aqui, possuem um segundo overload que suporta o cancelamento da tarefa custosa que está sendo executada. Para controlar o cancelamento, eles fazem uso da estrutura CancellationToken, e que podemos criar e informar ao invocar o método. Com uma pequena mudança na assinatura do método de exemplo que criamos acima (Executar), ele passará a receber o token que controla e propaga a notificação de cancelamento. Uma vez que o mesmo é repassado às tarefas que são executadas internamente, periodicamente o token é consultado para ver se o cancelamento foi ou não solicitado. A mudança é ligeira:

private async static Task Executar(CancellationToken ct)
{
    using (var conn = new SqlConnection(“…”))
    {
        await conn.OpenAsync(ct);

        using (var cmd = new SqlCommand(“SELECT * FROM Cliente”, conn))
            using (var dr = await cmd.ExecuteReaderAsync(ct))
                while (await dr.ReadAsync(ct))
                    if (!await dr.IsDBNullAsync(1, ct))
                        Console.WriteLine(dr.GetString(1));
    }
}

Como percebemos, para preparar o método para permitir o cancelamento, é receber no parâmetro um CancellationToken, e propagá-lo para os métodos internos. Abaixo, estamos consumindo o método Executar que criamos, só que agora estamos passando um token que que será cancelado em dois segundos. Se depois deste tempo o método não concluir, uma exceção será disparada, informando que a tarefa foi cancelada.

var cts = new CancellationTokenSource();

try
{
    cts.CancelAfter(TimeSpan.FromSeconds(2));
    Executar(cts.Token).Wait();
}
catch (Exception ex)
{
    //trata exceção
}

Nome da Aplicação na ConnectionString

Muitas vezes temos várias aplicações que consomem o mesmo banco de dados. Cada uma dessas aplicações atuam em uma porção de dados, realizando uma tarefa específica. Nestes casos, é muito comum eles precisarem dos mesmos dados, e não há nada errado nisso.

Como há várias aplicações penduradas em cima de uma mesma base de dados, utilizando a mesma string de conexão, e na maioria das vezes, o mesmo usuário e senha, é difícil monitorar as requisições que estão sendo executadas no SQL Server, pois fica difícil identificar qual a origem (aplicação) da consulta/comando.

O Activity Monitor do SQL Server, ferramenta que nos permite monitorar em tempo real as conexões que estão abertas no momento, fornece várias colunas que detalham as informações de um processo específico. Entre essas colunas, temos uma chamada Application. Essa coluna, na configuração padrão da string de conexão para o SQL Server, sempre exibirá .Net Sql Client Data Provider.

Você pode customizar isso, através do atributo Application Name, que pode ser colocado na própria string de conexão, assim como podemos visualizar no código abaixo. Isso pode variar de acordo com cada aplicação, descrevendo para o monitor qual a aplicação que está aguardando ou executando o comando/consulta, independentemente se estiver utilizando um ORM ou não.

Initial Catalog=DBTeste;Data Source=.;Integrated Security=true;Application Name=Aplicacao01

Finalmente, depois dessa configuração devidamente realizada, se começarmos a realizar chamadas para o banco de dados SQL Server, essa configuração será levada até ele, e ao abrir a console de monitoramento, podemos visualizar tal informação, e rapidamente identificar qual é a aplicação que está realizando-a. A imagem abaixo ilustra a configuração em ação:

Expondo Tipos POCO através do WCF

Ao construir serviços WCF, podemos expor tipos que vão além dos tipos tradicionais (tais como strings, inteiros, etc.), ou seja, podemos recorrer a construção de tipos customizados, onde construimos classes com suas propriedades que determinam os dados que serão carregados entre o cliente e o serviço.

Algumas vezes essas classes são construídas com o intuito de atender a requisição e/ou resposta das operações de um determinado serviço, mas que internamente, são consultas que fazemos no banco de dados e estamos retornando ao cliente, ou ainda, são comandos que devemos executar, também no banco de dados, para persitir alguma informação.

Se optarmos pelo uso do Entity Framework, podemos utilizar o modelo de geração POCO, onde as classes geradas são consideradas “puras”, ou seja, não dependem de nenhuma classe do framework de persistência. Isso tornam as classes simples, apenas com as propriedades que são, em princípio, mapeadas para colunas da base de dados. Sendo assim, muitos podem optar pela exposição direta destas classes no contrato de um serviço WCF, evitando assim a criação de outras classes (DTOs) para efetuar o transporte das informações.

Mas expor diretamente essas classes podem acarretar alguns problemas. A questão é que entidades que foram geradas utilizando o modelo POCO, possuem um comportamento durante a execução diferente das entidades que são geradas pelo modelo padrão do Entity Framework. Quando utilizamos o modelo padrão, as entidades herdam diretamente da classe EntityObject, que fornece toda a infraestrutura necessária para efetuar o rastreamento das mudanças e dá também o suporte ao lazy-loading. Quando utilizamos classes POCO, esses recursos continuam sendo oferecidos, mas são implementados de forma diferente.

Neste caso, o Entity Framework gera proxies dinâmicos para interceptar os acessos às propriedades das entidades que estão sendo manipuladas, e com isso, todos os recursos acima, continuam ainda sendo suportados. Abaixo temos o objeto Type da classe que foi gerada dinamicamente.

{System.Data.Entity.DynamicProxies.Cliente_B38C1C71074F1A0CDCF11548021F55AF4BAC2116057847A45ACD8E600D02BB90}

Só que ao expor esse tipo de objeto diretamente através do WCF, vamos nos deparar com uma exceção durante a execução. Isso se deve ao fato de que o WCF não é capaz serializar/deseralizar tipos que não são conhecidos pelo mesmo. Ao construir o contrato para um serviço WCF, você pode mencionar os tipos das classes geradas efetivamente pelo EDMX, mas durante a execução, o tipo acima será criado e, consequentemente, será entregue para a infraestrutura do WCF, ele irá se certificar de que o tipo é conhecido no contrato, e como não é, a exceção será disparada.

Há algumas alternativas para evitar esse problema. A primeira delas é desabilitar a criação do proxy dinâmico através da propriedade boleana ProxyCreationEnabled, que está acessível através do contexto do Entity Framework, assim como é exibido no código abaixo. O problema é que ao fazer isso, entidades criadas no padrão POCO não serão capazes de fornecer as funcionalidades de rastreamento de mudanças e lazy-loading.

public class Servico : IContrato
{
    public Cliente Recuperar(int id)
    {
        using (DBTestesEntities1 ctx = new DBTestesEntities1())
        {
            ctx.ContextOptions.ProxyCreationEnabled = false;

            return (from c in ctx.Cliente where c.ClienteId == id select c).Single();
        }
    }
}

A outra opção é a criação de um behavior que pode ser aplicado em nível de operação, alterando o serializador padrão do WCF, que é o DataContractSerializer, para o serializador ProxyDataContractResolver. Este serializador, que está debaixo do namespace System.Data.Objects, ajuda na resolução dos tipos que são criados dinamicamente pelo Entity Framework em “tipos concretos”, recorrendo ao método estático GetObjectType, exposto através da classe ObjectContext, que dado o Type do proxy dinâmico, ele retorna o tipo correspondente POCO, informando ao WCF que Type correto para que ele possa ser capaz de serializá-lo. Abaixo temos um exemplo da implementação e aplicação deste behavior:

public class ProxyDataResolverAttribute : Attribute, IOperationBehavior
{
    public void ApplyDispatchBehavior(OperationDescription opDescription, DispatchOperation dop)
    {
        opDescription
            .Behaviors
            .Find<DataContractSerializerOperationBehavior>()
            .DataContractResolver = new ProxyDataContractResolver();
    }

    //outros métodos
}

[ServiceContract]
public interface IContrato
{
    [OperationContract]
    [ProxyDataResolver]
    Cliente Recuperar(int id);
}

E, finalmente, uma terceira opção, seria a utilização de DTOs (Data Transfer Objects), onde você pode definir os objetos que quer trafegar entre as partes, sem a necessidade de se preocupar com detalhes de uma tecnologia específica que esteja utilizando nos bastidores e, principalmente, de ter a possibilidade de desacoplar complementamente o que representa os seus dados/domínio daquilo que quer expor para os clientes/parceiros.

Detalhes do uso de IQueryable

Como sabemos, a Microsoft incluiu nas linguagens .NET a capacidade de se escrever queries para interrogar coleções, de forma bem semelhante ao que fazemos quando desejamos trabalhar com alguma base de dados. Esse recurso ganhou o nome de LINQ, e várias funcionalidades e mudanças essas linguagens sofreram para conseguir acomodá-lo.

Para manipular um conjunto de informações, temos que ser capazes de executar duas tarefas: escrever/executar a query e iterar pelo resultado, caso ele exista. Parte disso já é suportado pelo .NET, através da interface IEnumerable<T>, que fornece um enumerador (representado pela interface IEnumerator<T>) para iterar por alguma coleção. A parte faltante (escrita) foi implementada também como uma interface e adicionada ao .NET Framework. Para isso, passamos a ter a interface IQueryable e IQueryable<T>. Com esses interfaces, somos capazes de expressar uma query através de uma AST (Abstract Syntax Tree), e mais tarde, traduzí-la para alguma fonte de dados específica.

Hoje temos os providers: LINQ To SQL, LINQ To XML e LINQ To Entities (Entity Framework). Cada um desses providers traduzem a query para uma fonte de dados específica, tais como: SQL Server (T-SQL), XML (XPath), ou até mesmo, Oracle (PL-SQL). A interface IQueryable<T> herda diretamente de IEnumerable<T>, para assim agregar a funcionalidade de iteração do resultado. A principal propriedade que ela expõe é a Expression, que retorna a instância da classe Expression, utilizada como raiz da expression tree. A imagem abaixo ilustra a hierarquia entre elas:

Existem duas classes dentro do assembly System.Core.dll, debaixo do namespace System.Linq, e que se chamam: Enumerable e Queryable. São duas classes estáticas que, via extension methods, agregam funcionalidades aos tipos IEnumerable<T> e IQueryable<T>, respectivamente. Grande parte dessas funcionalidades (métodos), refletem operadores para manipulação da query, tais como: Where, Sum, Max, Min, etc. A principal diferença entre essas duas classes é que enquanto a classe Enumerable recebe delegates como parâmetro, a classe Queryable recebe a instância da classe Expression (que envolve um delegate), que será utilizada para montar a query, e mais tarde, ser traduzida e executada pelo provider. Abaixo temos a assinatura do método Where para as duas classes:

namespace System.Data.Linq
{
    public static class Enumerable
    {
        public static IEnumerable<TSource> Where<TSource>(
            this IEnumerable<TSource> source, Func<TSource, bool> predicate)
        {
            //…
        }
    }

    public static class Queryable
    {
        public static IQueryable<TSource> Where<TSource>(
            this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
        {
            //…
        }
    }
}

Ambas interfaces postergam a execução da query até o momento que você precisa realmente iterar pelo resultado. Da mesma forma, como IQueryable<T> herda de IEnumerable<T>, você pode definí-la como retorno de uma função, e mais tarde, iterar pelo resultado. Só que utilizar uma ou outra, tem um comportamento bastante diferente, que pode prejudicar a performance. Para exemplificar, vamos considerar um método que retorna as categorias da base de dados, e depois disso, vamos aplicar um filtro para retornar somente aquelas categorias cujo Id seja maior que 2. Com isso, escrevemos o seguinte código:

class Program
{
    private static DBTestesEntities ctx = new DBTestesEntities();

    static void Main(string[] args)
    {
        var query = RecuperarCategorias().Where(c => c.Id > 2);

        foreach (var item in query)
            Console.WriteLine(item.Descricao);
    }

    static IEnumerable<Dados> RecuperarCategorias()
    {
        return from c in ctx.Categorias select c;
    }
}

Repare que depois de executar o método RecurarCategorias, invocamos o método de estensão Where, e neste caso, fornecido pela classe Enumerable, já que o retorno da função é do tipo IEnumerable<T>. Ao compilar a aplicação, o compilador faz o parse da query LINQ e a transforma em dados, fazendo com que a query seja representada por objetos do tipo Expression. Abaixo temos o código decompilado, e podemos perceber que a expressão lambda que passamos para o método Where, está sendo utilizada para filtrar o resultado.

internal class Program
{
    private static DBTestesEntities ctx = new DBTestesEntities();

    private static void Main(string[] args)
    {
        IEnumerable<Categoria> query = RecuperarCategorias().Where<Categoria>(delegate (Categoria c) {
            return c.Id > 2;
        });

        foreach (Categoria item in query)
            Console.WriteLine(item.Descricao);
    }

    private static IEnumerable<Categoria> RecuperarCategorias()
    {
        ParameterExpression CS$0$0001;
        return ctx.Categorias.Select<Categoria, Categoria>(
            Expression.Lambda<Func<Categoria, Categoria>>(CS$0$0001 = Expression.Parameter(typeof(Categoria), “c”), 
            new ParameterExpression[] { CS$0$0001 }));
    }
}

O grande detalhe aqui é que as estensões para a interface IEnumerable<T>, recebem delegates como parâmetro, o que quer dizer que o filtro será aplicado do lado cliente, ou seja, ao invocar o método RecuperarCategorias, todas elas (categorias) serão retornadas, carregadas em memória, e depois aplicado o filtro, “descartando” aqueles elementos que não se enquadram no critério. Abaixo temos a query que chegou até o SQL Server:

SELECT
    [Extent1].[Id] AS [Id], 
    [Extent1].[Descricao] AS [Descricao]
FROM [dbo].[Categorias] AS [Extent1]

Agora, se o retorno da função RecuperarCategorias for do tipo IQueryable<Categoria>, o comportamento é totalmente diferente. O simples fato de alterar o tipo de retorno, é o suficiente para a compilação ser substancialmente diferente. No código abaixo temos esse código para ilustrar. Note que depois de invocar o método RecuperarCategorias, já temos o método Where na sequência. A diferença é que o método Where aqui é oriundo da classe Queryable, que ao invés de receber um delegate, recebe uma Expression, obrigando o compilador a reescrever o nosso código, transformando o filtro “c => c.Id > 2” em uma instância da classe Expression, expressando aquele mesmo critério.

internal class Program
{
    private static DBTestesEntities ctx = new DBTestesEntities();

    private static void Main(string[] args)
    {
        ParameterExpression CS$0$0000;
        IQueryable<Categoria> query = RecuperarCategorias().Where<Categoria>(
            Expression.Lambda<Func<Categoria, bool>>(
                Expression.GreaterThan(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(Categoria), “c”), 
                (MethodInfo) methodof(Categoria.get_Id)), Expression.Constant(2, typeof(int))), 
            new ParameterExpression[] { CS$0$0000 }));

        foreach (Categoria item in query)
            Console.WriteLine(item.Descricao);
    }

    private static IQueryable<Categoria> RecuperarCategorias()
    {
        ParameterExpression CS$0$0001;
        return ctx.Categorias.Select<Categoria, Categoria>(
            Expression.Lambda<Func<Categoria, Categoria>>(CS$0$0001 = Expression.Parameter(typeof(Categoria), “c”), 
            new ParameterExpression[] { CS$0$0001 }));
    }
}

Apesar da ligeira mudança em tempo de compilação, isso permite o processamento remoto da query. Utilizar IQueryable<T> neste cenário, irá possibilitar o build-up de queries, ou seja, a medida que você vai adicionando critérios, operadores, etc., ele irá compondo a query principal e, finalmente, quando precisar iterar pelo resultado, apenas uma única query será disparada no respectivo banco de dados, já fazendo todo o filtro por lá, retornando menos dados. Isso pode ser comprovado visualizando a query que foi encaminhada para o banco de dados depois da alteração acima:

SELECT
    [Extent1].[Id] AS [Id], 
    [Extent1].[Descricao] AS [Descricao]
FROM [dbo].[Categorias] AS [Extent1]
WHERE [Extent1].[Id] > 2

Evento de Tecnologia em Hortolândia

No próximo dia 01 de junho (terça-feira) acontecerá um evento na UNASP Hortolândia (Centro Universitário Adventista de São Paulo), na cidade de Hortolândia. Para quem é de Campinas e região, este evento acontecerá na sede da universidade, localizada na Rua Pr. Hugo Gegembauer, 265, Parque Hortolândia, à partir das 19:45. Eu palestrarei sobre o Visual Studio 2010 e o .NET Framework 4.0. É importante dizer que a palestra será de nível 100, que além de abordar superficialmente algumas das principais funcionalidades desta nova versão, falaremos um pouco também sobre algumas tecnologias que já existem e estão à disposição desde as versões anteriores. Abaixo temos a descrição da palestra:

Título: Visual Studio 2010 e .NET Framework 4.0
Palestrante: Israel Aece
Descritivo: Esta palestra tem a finalidade de exibir um “tour” sobre a plataforma .NET, e a principal ferramenta que utilizamos para desenvolver aplicações para ela, que é o Visual Studio. A ideia é demonstrar rapidamente a história e a evolução do .NET Framework até a versão 4.0, incluindo assuntos como as linguagens de programação (C# e VB.NET), acesso à dados e aplicações para internet (WebForms, MVC e Silverlight). Logo após, vamos analisar de forma superficial as novidades que estão sendo incluídas na IDE do Visual Studio 2010 e que dão suporte ao desenvolvimento de qualquer tipo de aplicação sobre a plataforma .NET.

Community Launch em Campinas

No próximo dia 20 de março acontecerá um evento chamado de Community Launch. Esse evento será espalhado por todo o Brasil, onde cada região criará o evento e abordará os principais produtos da Microsoft que estarão sendo lançados neste ano de 2010, a saber: Visual Studio 2010, Windows Server 2008 R2SQL Server 2008 R2.

Para quem é de Campinas e região, este evento acontecerá na Universidade UNIP, localizada no bairro Swift, à partir das 09:00 da manhã. Caso você não saiba exatamente onde ela está localizada, consulte este endereço para saber como chegar até lá. Entre as várias palestras, eu sou o responsável por falar sobre Visual Studio 2010 e .NET Framework 4.0. É importante dizer que a palestra será de nível 100, que além de abordar superficialmente algumas das principais funcionalidades desta nova versão, falaremos um pouco também sobre algumas tecnologias que já existem e estão à disposição desde as versões anteriores. Abaixo temos a descrição da palestra:

Título: Visual Studio 2010 e .NET Framework 4.0
Palestrante: Israel Aece
Descritivo: Esta palestra tem a finalidade de exibir um “tour” sobre a plataforma .NET, e a principal ferramenta que utilizamos para desenvolver aplicações para ela, que é o Visual Studio. A ideia é demonstrar rapidamente a história e a evolução do .NET Framework até a versão 4.0, incluindo assuntos como as linguagens de programação (C# e VB.NET), acesso à dados e aplicações para internet (WebForms, MVC e Silverlight). Logo após, vamos analisar de forma superficial as novidades que estão sendo incluídas na IDE do Visual Studio 2010 e que dão suporte ao desenvolvimento de qualquer tipo de aplicação sobre a plataforma .NET.

IMPORTANTE: Apesar do evento ser gratuito, para poder participar é necessário efetuar o cadastro aqui.

Usando LINQ To SQL com WCF

Para aqueles que trabalham com LINQ To SQL e querem expor as entidades geradas pela ferramenta via WCF, terá que tomar alguns cuidados. Quando essas entidades não mantém um relacionamento, algo que é bem díficil, você consegue retorná-las a partir das operações que o serviço irá disponibilizar.

O problema começa a aparecer quando você deseja enviar para os clientes, entidades que possuem relacionamentos com outras entidades. Um exemplo clássico e que ilustra bem o cenário, é quando temos categorias e produtos, onde cada produto deverá pertencer à somente uma única categoria. Neste caso, temos uma relação direta entre elas. Mas o ponto que torna isso difícil é que o LINQ To SQL cria uma relação bidirecional entra elas. Isso quer dizer que a entidade Categoria terá uma propriedade chamada Produtos, que como o próprio nome diz, retorna a coleção dos produtos daquela categoria; além disso, a classe Produto expõe uma propriedade chamada Categoria, que retorna a instância de uma categoria em qual o produto está contido.

Quando falamos em orientação à objetos, isso é perfeitamente válido e comum. O problema ocorre quando você tenta serializar essa estrutura a partir do WCF. Como eu comentei neste outro post, o WCF tem um comportamento diferente quando há referências circulares, e não conseguirá fazer a serialização porque ele ficará em uma espécie de loop, pois com há referência bidirecional, ao serializar uma categoria, ela serializa os respectivos produtos, e para cada produto a sua respectiva categoria, e para esta categoria os seus produtos, e por aí vai. O serviço roda sem maiores problemas, mas você terá uma exceção quando quando o cliente tentar acessá-lo.

Analisando a imagem abaixo, podemos visualizar a estrutura das classes que compõem o exemplo, e logo ao lado, você pode reparar nas propriedades do arquivo (superfície) DBML, verá que existe uma propriedade chamada Serialization, que pode receber apenas dois valores: None e Unidirectional. O primeiro deles permite que as propriedades das entidades sejam serializadas de acordo com as regras impostas pelo serializador padrão do WCF, que graças a possibilidade de serializar qualquer propriedade, mesmo que elas não estejam decoradas com os atributos DataContractAttribute e DataMemberAttribute (POCO). A segunda opção, Unidirectional, faz com que ele somente consiga serializar o relacionamento em uma única direção para evitar as referências circulares. No nosso caso, teremos a entidade Categoria com a propriedade Produtos, mas a classe Produto não terá uma propriedade que define a sua categoria.

Alterando a opção de serialização para Unidirectional, a forma que você efetua a consulta também terá que mudar. Isso fará com que o LINQ To SQL não consiga trazer os dados (produtos) relacionados aquela categoria, algo que é transparente quando estamos utilizando o LINQ To SQL diretamente. Para conseguir fazer com que os dados relacionados também sejam carregados, temos que recorrer a classe DataLoadOptions, como é mostrado abaixo:

public Categoria[] RecuperarCategorias()
{
    using (DBContextDataContext ctx = new DBContextDataContext())
    {
        DataLoadOptions opts = new DataLoadOptions();
        opts.LoadWith<Categoria>(c => c.Produtos);
        ctx.LoadOptions = opts;

        return (fromin ctx.Categorias select c).ToArray();
    }
}

Mas como disse acima, isso funcionará mas você perderá a navegação bidirecional. Felizmente, podemos recorrer à propriedade boleana IsReference, que é exposta pelo atributo DataContractAttribute, definindo isso na classe Categoria. Isso permitirá a criação da navegação bidirecional, mas há um trabalho manual a ser feito para que isso funcione. Quando você muda a propriedade Serialization para None, nenhuma das propriedades é decorada com o atributo DataContractAttribute/DataMemberAttribute; já se definir essa propriedade para Unidirectional, as propriedades que são problemáticas, não estarão decoradas com o atributo DataMemberAttribute.

Sendo assim, o exemplo final fica como é mostrado abaixo, conseguindo ter no cliente, a navegação bidirecional. Obviamente que alguns membros foram omitidos por questões de espaço.

[Table(Name = “dbo.Categoria”)]
[DataContract(IsReference = true)]
public partial class Categoria
{
    [DataMember]
    [Column(…)]
    public int CategoriaId

    [DataMember]
    [Column(…)]
    public string Nome

    [DataMember]
    [Association(…)]
    public EntitySet<Produto> Produtos
}

[Table(Name = “dbo.Produto”)]
public partial class Produto
{
    [Column(…)]
    public int ProdutoId

    [Column(…)]
    public int CategoriaId

    [Column(…)]
    public string Nome

    [Association(…)]
    public Categoria Categoria
}

DataBinding em WPF

Já ouvimos falar muito sobre o termo DataBinding. Como sabemos, trata-se de um mecanismo para associar a informação de uma determinada origem à um determinado destino, criando uma dependência unidirecional ou bidirecional. Muitas vezes este termo está associado à alguma fonte de dados, que desejamos exibir suas respectivas informações na tela de uma aplicação qualquer.

Cada tecnologia implementa isso de uma forma diferente, com seus benefícios e possíveis limitações. O ASP.NET traz essa funcionalidade, onde você pode facilmente ligar uma fonte de dados, mas tem algumas limitações por conta de ser HTTP, que não mantém estado. Já o Windows Forms, fornece uma forma de databinding muito mais rico em termos de funcionalidades. A finalidade deste artigo é apresentar o databinding no WPF, exibindo suas funcionalidades, que facilitarão a forma com que lidamos com manipulações de UI.

A Microsoft incorporou no WPF uma forma muito mais evoluída para efetuar databinding, não se limitando apenas a vincular uma fonte de dados à controles, mas também permitindo que qualquer objeto preencha outro, incluindo controles. Isso quer dizer que poderemos definir o valor de uma propriedade de um controle com o valor de uma outra propriedade e de um outro controle. Tudo isso eliminando grande parte do código imperativo requerido pelo Windows Forms, pois a partir de agora, poderemos recorrer a código declarativo (XAML) para especificar essas “amarrações”.

Grande parte da responsabilidade para fazer tudo isso funcionar, é a classe Binding, que está debaixo do namespace System.Windows.Data. Ela é responsável por manter o “canal de comunicação” entre a origem e o destino, e além disso, expõe uma série de propriedades que nos permite customizar o comportamento dessa comunicação. Entre as principais propriedades, temos:

  • ElementName: define o nome do elemento que servirá como fonte. Utilize esta propriedade quando desejar preencher uma outra propriedade com o valor de um controle do WPF.
  • Mode: determina a direção das informações.
  • NotifyOnSourceUpdated: valor boleano indicando se o evento SourceUpdated é disparado quando alguma atualização na fonte das informações ocorrer.
  • NotifyOnTargetUpdated: valor boleano indicando se o evento SourceUpdated é disparado quando alguma atualização no destino das informações ocorrer.
  • Path: espefica o nome da propriedade que será exibida.
  • RelativeSource: especifica uma fonte de forma relativa à posição do objeto atual.
  • Source: define o nome do objeto que servirá como fonte. Utilize esta propriedade quando desejar preencher com uma instância de um objeto.
  • XPath: a mesma finalidade da propriedade Path, mas define uma expressão XPath quando a fonte de informações for um arquivo Xml.

Note que nas propriedades acima, nós não temos uma propriedade que especifica qual propriedade no destino será carregada. Isso se deve, porque você aplicará a sintaxe de binding diretamente dentro da propriedade que você quer preencher. O exemplo abaixo mostra como podemos proceder para preencher um conteúdo de um controle Label com o texto de um Button:

<Button Name=”button1″ Content=”Texto do Botão” />
<Label Name=”label1″ Content=”{Binding ElementName=button1, Path=Content}” />

Alternativamente, você pode achar essa sintaxe um pouco ilegível, principalmente quando você tiver situações mais complexas. Se desejar, você pode recorrer à uma segunda forma de configurar o databinding, de forma hierárquica, onde você irá aninhar as configurações como um Xml tradicional, através de sub-elementos. O código abaixo ilustra esta segunda técnica:

<Button Name=”button1″ Content=”Texto do Botão” />
<Label Name=”label1″>
    <Label.Content>
        <Binding ElementName=”button1″ Path=”Content” />
    </Label.Content>
</Label>

Quando o modelo declarativa não é uma solução, pois você precisa dinamicamente determinar os databindings, você pode ainda utilizar o código C#/VB.NET para configurá-los. Tudo o que precisamos fazer é instanciar a classe Binding que falamos acima, e configurar as propriedades necessárias para que isso funcione, e que neste exemplo simples serão ElementName e Path. ElementName vai receber uma string com o nome do controle que servirá como origem, enquanto a propriedade Path, receberá a instância da classe PropertyPath, que em seu construtor você deverá especificar o nome da propriedade no objeto de origem, que quer que seja enviado para o destino.

Binding b = new Binding();
b.ElementName = “button1”;
b.Path = new PropertyPath(“Content”);

this.label1.SetBinding(Label.ContentProperty, b);

Depois da instância da classe Binding configurada, utilizamos o método SetBinding do controle de destino, que no caso do exemplo é o Label. Além do Binding, esse método recebe a dependency property que receberá o valor da origem.

Carregando um Objeto

Acima vimos como podemos utilizar o databinding de uma forma diferente da qual estamos acostumado, que é através de controles de UI. Mas, o cenário mais comum é quando precisamos preencher um, ou vários controles de UI, com propriedades de um objeto. Eventualmente você tem um objeto que foi construído pela aplicação, e você precisa exibí-lo no formuário, distribuindo suas propriedades pelos controles do mesmo. O databinding também ajuda nisso, onde você pode especificar o tipo da classe, e o próprio WPF o instancia e, consequentemente, preenche os controles interessados, e tudo isso sendo feito declarativamente.

Ao invés de utilizar a propriedade ElementName, vamos agora recorrer à propriedade Source, que deve ser utilizada quando a origem se tratar de um objeto. Para exemplificar, vamos criar a instância da classe dentro dos resources do formulário, que nada mais é que uma coleção que pode armazenar qualquer tipo de objeto. Teremos uma classe simples chamada de Configuracao, contendo uma propriedade chamada Url. A instância desta classe será criada pelo WPF e estará armazenada estaticamente dentro dos recursos locais daquele formulário.

A sintaxe de binding agora consiste em configurar a propriedade Source com a instância criada e nomeada como “config”. Continuamos a utilizar a propriedade Path, mas agora ela deverá refletir a propriedade do objeto que será preenchida pelo controle. Como podemos perceber, a propriedade Text do TextBox irá exibir a propriedade Url:

<Window x:Class=”Teste.Window2″
    xmlns:local=”clr-namespace:Teste”>
    <Window.Resources>
        <local:Configuracao x:Key=”config” />
    </Window.Resources>
    <Grid name=”grid1″>
        <TextBox Name=”textBox1″ Text=”{Binding Source={StaticResource config}, Path=Url}” />
    </Grid>
</Window>

Suponhamos que temos vários controles e cada um receberá o valor de uma propriedade diferente. Ao invés de ficar repetindo a instância do objeto (config), podemos utilizar a propriedade DataContext. Essa propriedade nos permite compartilhar a mesma fonte por vários controles, e cada controle que o utilizará, apenas deve indicar qual propriedade ele estará vinculado. Repare que vinculamos o config à propriedade DataContext do controle Grid, e os controles inerentes à eles apenas mencionam qual propriedade cada um deles quer utilizar, sem a necessidade de especificar a propriedade Source.

<Window x:Class=”Teste.Window2″
    xmlns:local=”clr-namespace:Teste”>
    <Window.Resources>
        <local:Configuracao x:Key=”config” />
    </Window.Resources>
    <Grid name=”grid1″ DataContext=”{StaticResource config}”>
        <TextBox Name=”textBox1″ Text=”{Binding Path=Url}” />
        <TextBox Name=”textBox2″ Text=”{Binding Path=Timeout}”/>
    </Grid>
</Window>

Um detalhe importante aqui, é que se um controle não especificar nenhuma das propriedades de Binding (Source, RelativeSource ou Element), o WPF procura por algum elemento que possui a propriedade DataContext definida na árvore visual do formulário, e encontrando-o, tentará extrair o valor dele. Essa propriedade também pode ser configurada de via código, caso a instância do objeto precise de alguma manipulação adicional antes de ser usada pelo WPF.

this.grid1.DataContext = new Configuracao();

ObjectDataProvider

Como vimos acima, podemos criar a instância do objeto via código. Geralmente recorremos a essa técnica quando precisamos customizar a criação deste objeto. Mas o WPF fornece uma opção, que nos permite customizar de forma declarativa, algumas opções que podem ser utilizadas durante a criação deste objeto.

Entre essas opções, podemos definir alguns parâmetros para um construtor, fazer o databinding através de um método que retorna a instância do objeto, entre outras opções. Suponhamos que a partir de agora o construtor da classe Configuracao recebe como parâmetro uma string com a Url. Se tentar rodar a aplicação sem qualquer alteração, uma exceção será lançada dizendo que a classe não possui nenhum construtor público sem parâmetros. O ObjectDataProvider irá nos ajudar, permitindo especificar o valor deste parâmetro durante a criação:

<Window x:Class=”Teste.Window2″
    xmlns:system=”clr-namespace:System;assembly=mscorlib”
    xmlns:local=”clr-namespace:Teste”>
    <Window.Resources>
        <ObjectDataProvider x:Key=”config” ObjectType=”{x:Type local:Configuracao}”>
            <ObjectDataProvider.ConstructorParameters>
                <system:String>http://wwww.israelaece.com</system:String&gt;
            </ObjectDataProvider.ConstructorParameters>
        </ObjectDataProvider>
    </Window.Resources>
    <Grid DataContext=”{StaticResource config}”>
        <TextBox Name=”textBox1″ Text=”{Binding Path=Url}” />
        <TextBox Name=”textBox2″ Text=”{Binding Path=Timeout}”/>
    </Grid>
</Window>

RelativeSource

Já falamos das formas de databinding ElementName e Source, mas ainda nos resta uma terceira forma: a RelativeSource. Essa forma permite especificar como fonte, um objeto que está relativamente posicionado em relação ao objeto corrente. Essa opção traz três propriedades: Mode, AncestorType e AncestorLevel. A primeira delas recebe uma entre as seguintes opções:

  • FindAncestor: refere-se à um ancestral na árvore visual, que está acima em relação ao controle corrente. Essa opção exige que as propriedades AncestorType e AncestorLevel sejam definidas. A propriedade AncestorType define o tipo de elemento que será procurado, enquanto a propriedade AncestorLevel determina o nível de profundidade da busca.
  • PreviousData: permite referenciar o item anterior de uma coleção que está sendo exibida. Útil em templates.
  • Self: permite referenciar qualquer propriedade do objeto corrente.
  • TemplatedParent: permite referenciar um objeto que está envolvido em uma template.

O exemplo abaixo ilustra o uso do FindAncestor. Note que especificamos através do AncestorType o tipo de controle que desejamos procurar e o AncestorLevel, um número inteiro que determina até quantos níveis acima a busca será realizada. Neste caso, estamos interessados em exibir como o texto do botão o valor da propriedade Name de um Grid, e como estamos definindo o nível 2, ele irá apresentar o valor “g1” no botão.

<Window>
    <Grid Name=”g1″>
        <Grid Name=”g2″>
            <Button Name=”button1″>
                <Button.Content>
                    <Binding Path=”Name”>
                        <Binding.RelativeSource>
                            <RelativeSource Mode=”FindAncestor” AncestorType=”Grid” AncestorLevel=”2″ />
                        </Binding.RelativeSource>
                    </Binding>
                </Button.Content>
            </Button>
        </Grid>
    </Grid>
</Window>

Binding.Mode e Binding.UpdateSourceTrigger

Vimos até agora como podemos efetuar a ligação entre a origem e o destino das informações, mas não se resume a isso. Uma das característica do databinding permite também customizar que possíveis alterações sejam efetuadas para refletí-las tanto na origem quanto no destino. A propriedade Mode da classe Binding nos permite configurar como responder à essas ações, onde devemos escolher uma entre as cinco opções expostas pelo enumerador BindingMode:

  • Default: especifica que o binding utilizará o modo padrão estipulado pelo destino.
  • OneTime: especifica que o binding deve atualizar o destino quando a aplicação inicia ou quando os dados mudam, mas não deve atualizar o alvo quando subsequentes alterações são feitas na origem.
  • OneWay: especifica que o binding atualizará o destino quando a origem mudar. Alterações no destino não terão efeito na origem.
  • OneWayToSource: especifica que o binding atualizará a origem quando o destino mudar. Alterações na origem não terão efeito no destino.
  • TwoWay: especifica que as alterações feitas tanto na origem quanto no destino serão atualizadas automaticamente.

Outra propriedade que também é exposta pela classe Binding é a UpdateSourceTrigger, e que é utilizada quando a propriedade Mode é definida como OneWayToSource ou TwoWay. Essa propriedade determina como e quando a atualização das informações será realizada. Essa propriedade também receberá a informação oriunda de um enumerador, chamado UpdateSourceTrigger, onde as possíveis opções são:

  • Default: indica que a atualização será de acordo com o valor definido pela propriedade de destino, que muitas vezes é PropertyChanged. Propriedades que são editáveis pelo usuário, como a propriedade Text do TextBox, define o padrão como sendo LostFocus.
  • PropertyChanged: a fonte é atualizada quando a propriedade do destino é alterada.
  • LostFocus: a fonte é atualizada quando a propriedade de destino é alterada e quando o objeto perde o foco.
  • Explicit: a atualização da fonte será realizada quando você invocar explicitamente o método UpdateSource da classe Binding.

Coleções

Muitas vezes temos coleções de objetos que desejamos exibir através de controles, como por exemplo, ListBox. Da mesma forma que vimos anteriormente, para coleções, podemos proceder de forma semelhante, mas por se tratar de coleções, temos algumas novas propriedades que são exclusivas para esse cenário.

Controles que são considerados databound, expõe as seguintes propriedades DisplayMemberPath, ItemsSource e ItemTemplate. A primeira delas define o nome da propriedade do objeto que será exibida. Já a segunda representa a coleção que contém os itens que serão definidos com fonte das informações. Finalmente, a propriedade ItemTemplate, como o próprio nome diz, nos permite criar uma forma diferenciada para exibição de cada item da coleção. Dado uma coleção de clientes, onde cada elemento é representado por uma instância da classe Cliente, podemos fazer o seguinte:

<Window x:Class=”Teste.Window3″
    xmlns:local=”clr-namespace:Teste”>
    <Window.Resources>
        <local:ColecaoDeClientes x:Key=”cc” />
    </Window.Resources>
    <Grid>
        <ListBox
            Name=”listBox1″
            ItemsSource=”{Binding Source={StaticResource cc}}”
            DisplayMemberPath=”Nome” />
    </Grid>
</Window>

O binding de coleções não está restrito à controles databound. Você pode também vincular uma coleção à um controle do tipo Label, mas como já era de se esperar, apenas o primeiro elemento será exibido. Para que se consiga navegar pelos elementos da coleção, você precisa recorrer à um mecanismo exposto pelo WPF que permite essa navegação.

Quando uma coleção é utilizada através do databinding, o WPF cria nos bastidores um objeto que implementa a interface ICollectionView (namespace System.ComponentModel). Essa interface disponibiliza membros que permite gerenciar a navegação, ordenação e agrupamento das informações. Para extrair este objeto, podemos utilizar o seguinte código:

ColecaoDeClientes cc = new ColecaoDeClientes();
ICollectionView view = CollectionViewSource.GetDefaultView(cc);

Entre os vários membros expostos por essa interface, temos: MoveCurrentToNext, MoveCurrentToPrevious, CurrentItem, etc. Não há o que comentar sobre cada um deles, pois são autoexplicativos. De posse da instância deste navegador, podemos navegar pelos registros de forma simples, sem precisar manualmente armazenar e incrementar ou decrementar índices na medida que o usuário for solicitando a visualização de um novo registro.

Data Templates

Muitas vezes, a visualização padrão fornecida por um controle databound não nos atende, ou por questões visuais ou porque a informação precisa ser customizada/formatada para cada item. Felizmente o WPF separa a funcionalidade do controle da sua visualização, permitindo que se customize completamente a aparência, sem perder ou ter que reescrever a funcionalidade de iteração de elementos.

Como o próprio nome diz, as data templates permite customizar a aparência de um controle, configurando como queremos que ele seja exibido. Tradicionalmente o ListBox exibe cada item um abaixo do outro, sem qualquer customização. Mas e se quisermos que cada elemento seja exibido como um TextBox, e dentro da propriedade Text termos a propriedade Nome vinculada? Abaixo podemos atingir esse objetivo com as data templates, onde customizamos o ListBox e para cada item, exibimos o valor da propriedade Nome dentro da propriedade Text de um TextBox. Repare que neste caso não utilizamos a propriedade DisplayMemberPath do ListBox, pois isso foi delegado ao template, para que ele determine onde e como mostrará o valor. Para quem já trabalhou com ASP.NET, mais precisamente com os controles DataList e Repeater, notará uma grande semelhança aqui.

<Window x:Class=”Teste.Window3″
    xmlns:local=”clr-namespace:Teste”>
    <Window.Resources>
        <local:ColecaoDeClientes x:Key=”cc” />
    </Window.Resources>
    <Grid>
        <ListBox Name=”listBox1″ ItemsSource=”{Binding Source={StaticResource cc}}”>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBox Text=”{Binding Path=Nome}” />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

ADO.NET

Para preencher os controles do formulário com dados de um banco de dados, você pode recorrer as mesmas técnicas apresentadas aqui. DataSets possuem uma forma tranquila para uso em databinding, mesmo no WPF. Você pode utilizar a propriedade DataContext que vimos acima, definindo para ela a instância do DataSet/DataTable com os dados carregados de um banco de dados qualquer, e a partir daí, utiliza-se a mesma sintaxe para exibição dessas informações no formulário.

Já quando trabalhamos com tecnologias mais recentes, como o LINQ To SQL ou Entity Framework, eles sempre geram coleções, e estas estariam vinculadas aos controles que fossem exibí-las.

Conclusão: Este artigo demonstrou superficialmente todo o poder do databinding do WPF. Vimos como podemos utilizar as mais variadas formas de preencher um controle, não somente com dados de um banco de dados, mas também com informações que são geradas por outros controles ou até mesmo por outros objetos. Esse novo modelo de trabalho facilita bastante a forma como efetuamos a ligação das informações, algo que é um pouco mais complicado em seu principal concorrente, o Windows Forms.

Serialização de Datasets

Vira e mexe alguém entra em contato comigo para discutir sobre – os populares – DataSets. Como todos sabem, ele foi construído para ambientes desconectados, que permite criar uma espécie de “banco de dados em memória”, possibilitando ao usuário chegar de manhã na empresa, carregar os dados que ele vai trabalhar durante todo o dia, e depois sair à campo.

Durante o tempo que ele estiver fora, dificilmente terá acesso à rede da empresa, o que obriga a persistir os dados fisicamente, para quando chegar no(s) cliente(s), conseguir restaurar essas informações e manipulá-las como se ele estivesse trabalhando localmente. Dependendo do volume de informações que é carregado neste DataSet, o arquivo final pode ser muito grande, e dependendo do tipo de dispositivo que está utilizando, isso pode ser prejudicial.

Hoje um ex-aluno me enviou um e-mail dizendo que passava por um problema semelhante, onde ele precisava diminuir o tamanho final do arquivo que continha os dados. O problema é que ele estava utilizando a serialização em Xml, que naturalmente é maior do que a serialização binária. Isso se deve-se ao fato de que Xml é o padrão para interoperabilidade, e se fosse trafegá-lo através de Web Services, então obrigatoriamente ele deve ser serializado desta forma.

Do contrário, você pode optar pelo padrão binário. Como neste caso a interoperabilidade não é necessária, já que a finalidade é apenas ter um arquivo menor salvo no disco, podemos adotar esta técnica. Para isso, a partir da versão 2.0 do ADO.NET, a Microsoft disponibilizou uma pequena funcionalidade no DataSet, que modifica-o durante o processo de serialização. Abaixo o exemplo de como podemos proceder:

DataSet ds = new DataSet();
CarregarDados(ds);

ds.RemotingFormat = SerializationFormat.Binary;

using (FileStream fs = File.Create(“Dados.bin”))
    new BinaryFormatter().Serialize(fs, ds);

Com este exemplo, um DataSet que em Xml tem 1MB, caiu para 280KB. Você não é obrigado a utilizar a propriedade RemotingFormat, mas se omití-la, verá que o resultado não será tão expressivo como. Quando você define esta propriedade, ele customizará a serialização, transformando o schema e a instância do DataSet atual em um formato mais otimizado e comprimido. Atente-se aqui, pois se o DataSet for muito pequeno (quantidade de linhas/colunas), o resultado pode ser igual ou até mesmo maior que o Xml.

Mais uma vez, se possível reescreva e tente optar por alguma outra alternativa que não sejam os DataSets. Quando você persite-o, independentemente se for Xml ou binário, ele armazena muito mais informações do que o aquilo que realmente você precisa, pois lembre-se: ele é um “banco de dados em memória”. Para mais informações sobre serialização, consulte o capítulo 06 deste livro.

Problemas em arquivos de dados

Até agora não entendi o motivo, mas repentinamente os arquivos que representam as estruturas de classes do LINQ To SQL e do Entity Framework deixaram de funcionar, ou melhor, o Visual Studio .NET deixou de exibir graficamente a estrutura de classes. O Server Explorer deixou de ser exibido; quando tentava criar um novo arquivo EDMX, o wizard simplesmente desaparecia; e quando tentava criar e/ou carregar um arquivo do LINQ To SQL, a seguinte mensagem era exibida: The operation could not be completed. The custom tool ‘MSLinqToSQLGenerator’ failed.  Could not retrieve the current project.

Depois de algumas pesquisas, cheguei à um blogue que dizia para excluir as sub-chaves que existiam dentro do seguinte path: HKEY_CURRENT_USERSoftwareMicrosoftVisualStudio9.0Packages. Basicamente o que tinha ali é uma chave chamada SkipLoading, que estava definida como “1”. Antes de excluir, eu simplesmente mudei para “0”, e tudo voltou a funcionar.