Pontos de dados

Brincando com o EF6 Alpha

Julie Lerman

 

Julie LermanNão há nada como um brinquedo novo em folha para brincar e, embora seja possível baixar compilações noturnas do Entity Framework 6 (EF6) conforme ele evolui, esperei a primeira versão em pacote alfa (lançada em 30 de outubro de 2012) para explorar e começar a brincar.

Se você estiver se perguntando: “Hein? Compilações noturnas?”, provavelmente não soube da notícia após o lançamento do EF5. O Entity Framework tornou-se um projeto de código-fonte aberto e as versões subsequentes estão sendo desenvolvidas abertamente (e comunitariamente) em entityframework.codeplex.com. Recentemente, escrevi um post no blog “Making your way around the Open Source Entity Framework CodePlex Site” (bit.ly/W9eqZS), cuja leitura eu recomendo antes de navegar nas páginas do CodePlex.

A nova versão percorrerá um longo caminho para tornar o EF mais flexível e extensível. Em minha opinião, os três recursos mais importantes que serão lançados para o EF na versão 6 são:

  1. Suporte para função e procedimento armazenado para Code First
  2. Suporte para o padrão Async/Await do .NET 4.5
  3. As APIs principais do Entity Framework que atualmente moram no Microsoft .NET Framework

Esse último ponto não só permite o suporte para enum e tipo espacial para aplicativos voltados para o .NET Framework 4, mas como o EF tem código-fonte aberto, também significa que todo o EF se beneficia de sua condição de código-fonte aberto.

Embora isso possa não ter o apelo amplo desses três recursos, há muitas outras funcionalidades significantes por vir também. Por exemplo:

  • As convenções Code First personalizadas extraídas antes da versão EF4.1 estão agora no EF6, com várias maneiras de implementar.
  • As migrações do Code First oferecem suporte a vários esquemas de bancos de dados.
  • Você pode definir configurações do Entity Framework em vez de defini-las em um arquivo web.config ou app.config (o que pode ser complicado).
  • A configuração baseada em código é possível graças ao novo suporte para extensibilidade com resolvedores de dependência.
  • Você pode personalizar como o Code First cria a tabela _Migrations­History para que seja mais dócil para uma variedade de provedores de bancos de dados.
  • As EF Power Tools estão sendo aperfeiçoadas e adicionadas ao Visual Studio EF Designer. Um aperfeiçoamento fornecerá um ótimo meio para escolher um fluxo de trabalho modelo, incluindo o Code First.

Obtendo o EF6 Alpha

Os desenvolvedores radicais podem se interessar em baixar as compilações noturnas. Se você preferir usar os pacotes lançados, poderá ter uma experiência de instalação suave usando o NuGet Package. Use o NuGet Package Manager e selecione "Include Prerelease" para obter o pacote EF6. Se você instalar a partir do Console do Package Manager, certifique-se de adicionar "-prerelease" ao final de seu comando install-package.

Observe que a versão de 10 de dezembro de 2012 que estou explorando (com a versão de arquivo 6.0.11025.0 e a versão de produto 6.0.0-alpha2-11210) não inclui o suporte para função ou procedimento armazenado nem a consolidação de ferramentas. Além disso, como este é um alfa bem inicial, presumo que alguns detalhes desses recursos mudarão com as novas versões. Embora os conceitos globais permaneçam, parte da sintaxe ou outros detalhes estão sujeitos a mudanças com base no feedback da comunidade. Graças ao exercício de trabalhar nesta coluna, também consegui fornecer algum feedback.

Async do .NET 4.5 no EF6

Aproveitar o processamento assíncrono no .NET Framework para evitar bloqueio enquanto ele aguarda o retorno de dados tem intimidado muitos desenvolvedores, especialmente quando usam aplicativos desconectado ou bancos de dados remotos. O processo Background Worker chegou no .NET Framework 2.0, mas ainda era complexo. O ADO.NET 2.0 ajudou um pouco com métodos como BeginExecuteQuery e EndExecuteQuery, mas o Entity Framework nunca teve nada parecido. Uma das grandes inclusões no .NET Framework 4.5 é o novo padrão assíncrono, que tem a capacidade de esperar por resultados de métodos que foram definidos como assíncronos, simplificando drasticamente o processamento assíncrono.

No EF6, uma série de métodos foram adicionados com suporte para o padrão assíncrono do .NET 4.5. Seguindo as orientações do novo padrão, todos os novos métodos têm Async acrescentado ao seus nomes, como SaveChangesAsync, FindAsync e Execute­SqlCommandAsync. No LINQ to Entities, um novo namespace chamado System.Data.Entity.IQueryableExtensions contém versões Async de muitos métodos LINQ, incluindo ToListAsync, First­Or­DefaultAsync, MaxAsync e SumAsync. E para carregar explicitamente os dados das entidades gerenciadas por um DbContext, o LoadAsync já está disponível.

O que segue é uma pequena experiência que realizei com esse recurso, primeiramente sem usar os métodos assíncronos e depois com eles. Eu realmente recomendo ler sobre o novo padrão assíncrono. “Asynchronous Programming with Async and Await (C# and Visual Basic)”, disponível em bit.ly/U8FzhP, é um bom ponto de partida.

Meu exemplo contém uma classe Casino que inclui uma classificação para o casino. Criei um método de repositório que irá encontrar determinado casino, incrementar sua classificação usando meu método UpdateRating (que é irrelevante para esta explicação e, portanto, não listado) e salvar a alteração no banco de dados:

public void IncrementCasinoRating(int id)
{
  using (var context = new CasinoSlotsModel())
  {
    var casino =  context.Casinos.Find(id);
    UpdateRating(casino);
    context.SaveChanges();
  }
}

Há dois pontos neste método onde um thread pode ser bloqueado. O primeiro é ao chamar Find, que faz com que o contexto pesquise em seu cache de memória o casino solicitado e consulte o banco de dados, se não for encontrado. O segundo é ao solicitar que o contexto salve os dados modificados no banco de dados.

Já estruturei o meu código de interface de usuário unicamente para demonstrar o comportamento relevante. A interface de usuário inclui um método que chama o IncrementCasinoRating do repositório e, quando é concluído, grava uma notificação no console:

private static void UI_RequestIncrement (SimpleRepository repo)
{
  repo.IncrementCasinoRating(1);
  Console.WriteLine("Synchronous Finish ");
}

Em outro método, eu aciono o teste chamando UI_ Increment­CasinoRating e sigo com outra notificação:

UI_RequestIncrement (repo);
Console.WriteLine(" After sync call");

Quando executo isso, vejo o seguinte no resultado do console:

Synchronous Finish
After sync call

Isso é porque tudo parou enquanto esperava o término de cada etapa em IncrementCasinoRating — encontrar o casino, atualizar a classificação e salvar no banco de dados.

Agora vou mudar o método de repositório para que ele use os novos métodos FindAsync e SaveChangesAsync. Seguindo o padrão assíncrono, também preciso usar o método assíncrono:

  • adicionando a palavra-chave async à assinatura
  • acrescentando Async ao nome do método
  • retornando de uma tarefa; se o método retornar resultados, você retornará a tarefa <resulttype>

No método, chamo os novos métodos Async — FindAsync e SaveChangesAsync — conforme prescrito, usando a palavra-chave await:

public async Task IncrementCasinoRatingAsync(int id)
{
  using (var context = new CasinoSlotsModel())
  {
    var casino=await context.Casinos.FindAsync(id);
    // Rest is delayed until await has received results
    UpdateRating(casino);
    await context.SaveChangesAsync();
    // Method completion is delayed until await has received results
  }
}

Como o método é marcado como async, assim que o primeiro await é atingido, o método retorna o controle para o processo de chamada. Mas vou precisar modificar esse método de chamada. Há um meio de cascata na criação do comportamento para o padrão assíncrono. Então, o método não só faz uma chamada especial para meu novo método assíncrono, mas ele próprio também precisa ser assíncrono porque está sendo chamado por outro processo:

private static async void UI_RequestIncrementAsync(
  SimpleRepository repo)
{
  await repo.IncrementCasinoRatingAsync(1);
  // Rest is delayed until await has received results
  Console.WriteLine(" Asynchronous Finish ");
}

Observe que estou usando await para chamar o método de repositório. Isso permite que o chamador saiba que este é um método assíncrono. Assim que await for atingido, o controle será retornado ao chamador. Também modifiquei o código de inicialização:

UI_RequestIncrementAsync(repo);
  Console.WriteLine(" After asynchronous call");

Agora, quando executo esse código, o método UI_RequestIncrementAsync retorna o controle para o chamador já que ele também está chamando o método de repositório. Isso significa que chegarei imediatamente na próxima linha de código de inicialização, que imprime "After asynchronous call". Quando o método de repositório termina de salvar no banco de dados, ele retorna uma tarefa para o método que o chamou, UI_RequestIncrementAsync, que por sua vez executa o restante do código, imprimindo uma mensagem no console:

After asynchronous call
Asynchronous Finish

Então, minha interface de usuário conseguiu terminar sem esperar o EF concluir o seu trabalho. Se o método de repositório tivesse retornado resultados, eles teriam aparecido na tarefa quando estivessem prontos.

Este pequeno exercício me ajudou a ver os novos métodos assíncronos em ação. Se você estiver elaborando aplicativos cliente ou desconectados que dependem de processamento assíncrono, é um grande benefício que o EF6 agora tem suporte para o novo padrão assíncrono com tal simplicidade.

Convenções personalizadas

O Code First tem um conjunto de convenções internas que orientam seu comportamento padrão quando ele cria um modelo juntamente com mapeamentos de banco de dados de suas classes. É possível substituir essas convenções por configurações explícitas usando DataAnnotations ou a API fluente. Nos primeiros betas do Code First, também era possível definir suas próprias convenções — por exemplo, uma convenção que define todas as strings para mapear campos de banco de dados com comprimento máximo de 50 caracteres. Infelizmente, a equipe não conseguiu deixar esse recurso em estado satisfatório sem reter o Code First; então, ele não teve sua versão final. Agora ele retornou no EF6.

Há várias maneiras de definir suas próprias convenções.

Usar uma convenção leve é o método mais simples. Isso permite que você especifique convenções fluentemente na sobrecarga de OnModelCreating do DbContext. Convenções leves são aplicadas a suas classes e limitam-se à configuração das propriedades que têm uma correlação direta no banco de dados, tais como o comprimento de MaxLength. Este é um exemplo de uma classe que não tem configurações especiais e, portanto, por padrão, seus campos de duas strings seriam mapeados para tipos de dados nvarchar(max) em um banco de dados do SQL Server:

public class Hotel
  {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
  }

Adicionei uma convenção leve no modelo especificando que a API deve verificar as propriedades de qualquer entidade que está processando e definir o MaxLength de strings para 50 caracteres:

modelBuilder.Properties<string>()
  .Configure(p => p.HasColumnType("nvarchar"));

Você pode ver na Figura 1 que o Code First garantiu que os campos Name e Description tenham um comprimento máximo de 50 caracteres.

A Custom Convention Made the Max Length of These nvarchars 50
Figura 1 Uma convenção personalizada definiu o comprimento máximo destes nvarchars em 50 caracteres

Você também pode definir uma convenção implementando uma interface existente, como a interface de convenção para manipular as propriedades de DateTime — a classe DateTimePropertyConfiguration no namespace System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive. A Figura 2 mostra um exemplo em que forcei o mapeamento das propriedades de DateTime para o tipo de data do SQL Server em vez da data e hora padrão. Note que este exemplo segue a orientação da Microsoft — não aplicarei minha configuração se o atributo (ColumnType, neste caso) já estiver configurado.

Figura 2 Mapeamento das propriedades de DateTime para o tipo de data do SQL Server

public  class DateTimeColumnTypeConvention :
  IConfigurationConvention<PropertyInfo, DateTimePropertyConfiguration>
  {
    public void Apply(
      PropertyInfo propertyInfo,
      Func<DateTimePropertyConfiguration> configuration)
    {
      // If ColumnType hasn't been configured ...
      if (configuration().ColumnType == null)
      {
        configuration().ColumnType = "date";
      }
    }
  }

As convenções têm uma ordem de hierarquia específica, razão pela qual você deve ter certeza de que o tipo de coluna ainda não esteja configurado antes de aplicar o novo ColumnType.

O construtor de modelos precisa saber como encontrar essa nova convenção. Veja aqui como fazer isso, novamente no método de sobrecarga OnModelCreating:

modelBuilder.Conventions.Add(new DateTimeColumnTypeConvention());

Existem outras duas maneiras de personalizar convenções. Um método permite criar atributos personalizados, que você pode usar em suas classes tão facilmente quanto DataAnnotations. O outro é mais granular: em vez de construir uma convenção de que depende do que o ModelBuilder entende de suas classes, esse método permite afetar os metadados diretamente. Você encontrará exemplos dos quatro estilos de convenções personalizadas na documentação do MSDN Data Developer Center, “Custom Code First Conventions” (msdn.microsoft.com/data/jj819164). Conforme a evolução do EF6, esse documento ganhará um link para uma versão mais atual ou será modificado de acordo com a versão mais recente.

Suporte a vários esquemas para migrações

O EF6 dá às migrações do Code First a capacidade de lidar com vários esquemas de bancos de dados. Para obter mais informações sobre esse recurso, confira o post detalhado que escrevi no blog pouco depois do lançamento do alpha: “Digging in to Multi-Tenant Migrations with EF6 Alpha” (bit.ly/Rrz1MD). Tenha em mente, contudo, que o nome do recurso mudou de "Migrações multilocatárias" para "Vários contextos por banco de dados".

Configurações baseadas em código

Você já pode especificar configurações relevantes para banco de dados para o Entity Framework em arquivos de configuração de aplicativos (app.config e web.config), livrando você da necessidade de fornecer as configurações na inicialização do aplicativo ou no construtor do contexto. Agora, no EF6, é possível criar uma classe que herda de uma nova classe DbConfiguration, onde você pode especificar detalhes como o provedor de banco de dados padrão do Code First, a estratégia de inicialização de banco de dados (por exemplo, DropCreateDatabaseIfModelChanges) e outros. Você pode criar essa classe DbConfiguration no mesmo projeto de seu contexto ou em um projeto separado, permitindo que vários contextos se beneficiem de uma classe de configuração única. A visão geral da configuração baseada em código em msdn.microsoft.com/data/jj680699 fornece exemplos das várias opções.

Agora o Core EF está no EF6 e tem código-fonte aberto também

Embora as APIs do Code First e do DbContext sempre estivessem desconectadas do ciclo de lançamentos do .NET, o núcleo do EF foi incorporado no .NET Framework. Esta é a principal funcionalidade — a API de ObjectContext, consultas, controle de alterações, o provedor de EntityClient e muito mais. É por isso que o suporte para enums e dados espaciais teve de esperar o lançamento do .NET Framework 4.5 — essas alterações tinham de ser feitas profundamente nas APIs centrais.

Com o EF6, todas essas APIs centrais foram agrupadas em um projeto de código-fonte aberto e serão implantadas através do pacote NuGet. É interessante ver os namespaces EF5 e EF6 lado a lado, como mostrado na Figura 3. Como você pode ver, no EF6, há muito mais namespaces.

EF6 Has Acquired the Namespaces of the EF Core APIs from the .NET Framework
Figura 3 O EF6 adquiriu os namespaces das APIs centrais do EF do .NET Framework

Suporte para enum e dados especiais para aplicativos .NET 4

Como mencionei anteriormente, um dos grandes benefícios de ter as APIs centrais no EF6 é que isso elimina parte da dependência das versões do .NET para recursos específicos do EF — mais notoriamente o suporte para enum e dados espaciais agregado ao EF no .NET Framework 4.5. Os aplicativos existentes voltados para o .NET Framework 4 com EF não conseguiram tirar proveito disso com o EF5. Agora essa limitação acabou porque o EF6 inclui o suporte para enum e dados especiais, e os recursos não são mais, portanto, dependentes do .NET Framework. A documentação existente sobre esses recursos pode ajudar você a usá-los em aplicativos voltados para o .NET Framework 4.

Ajude a conduzir a evolução do EF

Agora que o Entity Framework é um projeto de código-fonte aberto, os desenvolvedores podem ajudar a si mesmos e outras pessoas participando do design e desenvolvimento. Você pode compartilhar suas opiniões, comentários e feedback, seja lendo as especificações, participando de discussões e problemas, brincando com o último pacote NuGet ou baixando compilações noturnas e explorando-as. Você também pode contribuir com código, para um dos problemas listados no site em que ninguém está trabalhando ainda, ou algo próprio que você não pode viver sem no EF.

Julie Lerman é uma Microsoft MVP, mentora e consultora do .NET, que reside nas colinas de Vermont. Você pode encontrá-la fazendo apresentações sobre acesso a dados e outros tópicos do Microsoft .NET em grupos de usuários e conferências em todo o mundo. Seu blog está em thedatafarm.com/blog e ela é autora do livro “Programming Entity Framework” (2010), além das edições Code First (2011) e DbContext (2012), todos da O’Reilly Media. Siga-a no Twitter, em twitter.com/julielerman.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Glenn Condron