Pontos de dados

Movendo projetos existentes para o EF 5

Julie Lerman

 

Julie LermanEntre as alterações feitas no Microsoft .NET Framework 4.5 há várias modificações e melhorias nas APIs principais do Entity Framework. Muito notável é a nova maneira como o Entity Framework armazena automaticamente suas consultas LINQ to Entities em cache, removendo o custo de desempenho da conversão de uma consulta em SQL quando ela é usada repetidamente. Esse recurso é chamado de Consultas Compiladas Automaticamente, e você pode ler mais sobre isso e sobre outras melhorias de desempenho na postagem do blog da equipe, "Pré-estreia: melhorias no desempenho do Entity Framework 5", em bit.ly/zlx21L. Um bônus desse recurso é que ele é controlado pela API do Entity Framework no .NET Framework 4.5, de forma que mesmo os aplicativos do .NET 4 que usam o Entity Framework se beneficiarão "gratuitamente" quando executados em máquinas com o .NET 4.5 instalado.

Outros novos recursos úteis internos na API principal requerem alguma codificação de sua parte, incluindo suporte para enums, tipos de dados especiais e funções com valor de tabela. O designer do EDM (Entity Data Model, Modelo de dados de entidade) do Visual Studio 2012 tem alguns recursos também, incluindo a capacidade de criar diferentes exibições do modelo.

Atualmente, executo a maior parte de minha codificação relacionada ao EF usando a API DbContext, que é fornecida junto com recursos de Code First, separadamente do .NET Framework. Esses recursos são a maneira como a Microsoft melhora o Entity Framework de maneira mais fluida e frequente e estão contidos em uma única biblioteca denominada EntityFramework.dll, que você pode instalar em seus projetos via NuGet.

Para tirar proveito do suporte a enum e a outros recursos adicionados ao EF no .NET Framework 4.5, você precisará da versão compatível do EntityFramework.dll, EF 5. A primeira versão deste pacote é a versão de número 5.

Tenho vários aplicativos que usam o EF 4.3.1. Essa versão inclui o suporte à migração introduzido no EF 4.3, além de alguns ajustes secundários que foram adicionados logo em seguida. Nesta coluna, mostrarei como mover um aplicativo que está usando o EF 4.3.1 para o EF 5 para tirar proveito do novo suporte a enum do .NET 4.5. Estas etapas também se aplicam a projetos que estão usando o EF 4.1, 4.2 ou 4.3.

Começarei com uma solução de demonstração simples que tem um projeto para DomainClasses, outro para DataLayer e um que é um aplicativo de console, conforme mostrado na Figura 1.

The Existing Solution That Uses EF 4.3.1
Figura 1 A solução existente que usa o EF 4.3.1

Essa solução foi criada no Visual Studio 2010 usando o .NET Framework 4 e a versão EF 4.3.1 do EntityFramework.dll.

O projeto DomainClasses tem duas classes incluídas em um único arquivo, mostrado na Figura 2, usando um tema popular para código de exemplo: Twitter. As classes são Tweeter e Tweet.

Figura 2 As classes de domínio originais

using System.ComponentModel.DataAnnotations;
namespace DataPointsDemo.DomainClasses
{
  public class Tweeter
  {
    public Tweeter()
    {
      Tweets = new List<Tweet>();
    }
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    [MaxLength(10),Column("ExperienceCode")]
    public string Experience { get; set; }
    [MaxLength(30), MinLength(5)]
    public string UserName { get; set; }
    [RegularExpression(@"(\w[-._\w]*\w@\w[-._\w]*\w\.\w{2,3})")]
    public string Email { get; set; }
    public string Bio { get; set; }
    public DateTime CreateDate { get; set; }
    public byte[] Avatar { get; set; }
    public ICollection<Tweet> Tweets { get; set; }
    public string AliasPlusName
    { get { return Name + "(" + UserName + ")"; } }
  }
  public class Tweet
  {
    public int Id { get; set; }
    public DateTime CreateDate { get; set; }
    public string Content { get; set; }
    [Range(1, 5),Column("RatingCode")]
    public int Rating { get; set; }
    public Tweeter Alias { get; set; }
    public int AliasId { get; set; }
  }
}

Esse projeto usa Data Annotations não apenas para adicionar validações (como RegularExpression) mas também para definir parte da configuração — MaxLength, MinLength e Column. A última, Column, especifica o nome da coluna na tabela do banco de dados para a qual os campos Experience e Rating são mapeados.

Todos os três projetos fazem referência ao EntityFramework.dll (versão 4.3.1). Normalmente, mantenho o EntityFramework.dll e qualquer conhecimento do banco de dados fora de minhas classes de domínio, mas, neste exemplo, resolvi inclui-los para fins de demonstração. Os atributos MaxLength, MinLength e Column estão no mesmo namespace que as validações (System.ComponentModel.DataAnnotations), mas fazem parte do assembly EntityFramework.

Também notável nas classes de domínio é o fato de que tenho duas propriedades que imploram o uso de enums: Tweeter.Experience, que depende de uma cadeia de caracteres para seu valor, e Tweet.Rating, que usa um valor numérico. O desenvolvedor é quem decide a codificação dessas classes para garantir que os usuários tenham os valores adequados disponíveis para elas. Por que nenhuma enum? Porque a API principal do Entity Framework no .NET Framework 4 não dá suporte a enums. Mas como esse era o recurso mais solicitado para o Entity Framework e agora faz parte do .NET Framework 4.5 (e tem suporte do Code First no EF 5), eu posso usar enums. Portanto, vamos atualizar a solução.

Embora eu tenha aberto minha solução no Visual Studio 2012 RC, ela ainda está direcionada para o .NET 4. A primeira coisa que preciso fazer é direcionar meus três projetos para o .NET 4.5, o que posso fazer na janela Properties de cada projeto (consulte a Figura 3). Isso precisa ser feito um de cada vez, portanto, se você tiver muitos projetos, poderá usar um script para ser executado diretamente em relação aos arquivos de projeto.

Changing a .NET Framework 4 Project to .NET Framework 4.5
Figura 3 Alterando um projeto do .NET Framework 4 para o .NET Framework 4.5

É importante executar esta etapa antes de atualizar para o EF 5. Aprendi isso com dificuldade e explicarei brevemente porque.

Quando os projetos estão direcionados para o .NET Framework 4.5, você pode atualizar para o EF 5. Como vários projetos usam esse assembly, você desejará gerenciar os pacotes da solução inteira em vez de atualizar um projeto de cada vez. A opção Manage NuGet Packages está disponível no menu de contexto da solução no Gerenciador de Soluções. Isso abrirá a interface do usuário do gerenciador de pacotes. À esquerda, selecione Updates. No painel do meio, se você tiver uma versão atual do gerenciador de pacotes, verá uma caixa suspensa com as opções Stable Only e Include Prerelease. Se, como seu, você estiver fazendo isso antes da versão completa do .NET 4.5 e do EF 5, precisará selecionar Include Prerelease. Em minha solução específica, o EntityFramework é o único pacote que precisa de atualização, portanto, isso é o que está sendo mostrado, como você pode ver na Figura 4. Se você preferir trabalhar no console do gerenciador de pacotes, poderá digitar "Install-Package EntityFramework –prerelease", mas precisará fazer isso separadamente para cada projeto.

Finding the Entity Framework 5 Prerelease Update
Figura 4 Localizando a atualização de pré-lançamento do Entity Framework 5

No assistente, depois de disparar a atualização do pacote, o NuGet solicitará a você quais projetos deseja atualizar. Embora todos os meus projetos usem o Entity Framework 4.3.1, atualizarei apenas o ConsoleApplication e o DataLayer, portanto, cancelarei a seleção de DomainClasses. Você pode observar a caixa de status para ver as etapas que estão sendo executadas. Quando a atualização for concluída, basta fechar o gerenciador de pacotes.

Um pacote, duas DLLs

A atualização para o EF 5 afetou os dois projetos de algumas maneiras. Primeiro, a versão 4.3.1 do EntityFramework.dll foi substituída pela 5. Você deve verificar isso em todos os projetos que atualizar. Isso demonstra porque é importante alternar para o .NET Framework 4.5 antes de executar a etapa de atualização de pacotes. O pacote do EF 5 contém duas DLLs. Uma é da versão 5, que contém todos os recursos da API DbContext API e do Code First e é compatível com o .NET 4.5. O outro arquivo é da versão 4.4. Esse é o arquivo que permanece compatível com o .NET 4. Com a inclusão dessa DLL no pacote, a equipe evitou a necessidade de manter dois pacotes do NuGet separados para você se preocupar. Depois da liberação da versão EF 5, você instalará o mesmo pacote do EF 5 sempre que desejar suporte a DbContext ou a Code First. O pacote garantirá que você tem a versão correta instalada em seu projeto, quer o projeto seja do .NET 4 ou do .NET 4.5.

Na primeira vez que fiz essa atualização, eu não tinha atualizado meus projetos para o .NET 4.5 antes da atualização do EF. Eu não consegui que os novos recursos funcionassem e fiquei muito confusa. Então observei que a versão do EntityFramework.dll era 4.4, o que me deixou mais confusa. Finalmente, naveguei para os arquivos de pacote da solução e vi que eu tinha dois pacotes e entendi meu erro.

A atualização do EF 5 também modificou o arquivo app.config no projeto Console e criou um arquivo app.config no projeto DataLayer. Como minha solução original deixava o Code First usar seu comportamento padrão para detectar automaticamente o banco de dados relevante, eu não tinha nenhuma cadeia de caracteres de conexão ou informações de fábrica de conexões no arquivo de configuração. A instalação do EF 5 adicionou a seguinte seção na seção <entityFramework> do arquivo:

<defaultConnectionFactory
  type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework">
  <parameters>
    <parameter
       value="Data Source=(localdb)\v11; Integrated Security=True;
              MultipleActiveResultSets=True" />
  </parameters>
</defaultConnectionFactory>

Além disso, atualizou a referência do app.config ao assembly do EF para refletir o novo número da versão.

Os projetos que não tiverem nenhum arquivo de configuração obterão um novo app.config com a configuração padrão do EF 5. É por isso que o projeto DataLayer tem um app.config depois da atualização. Mas eu não preciso de um arquivo de configuração naquele projeto, portanto, eu simplesmente o excluo.

E o projeto DomainClasses?

Ao atualizar, eu ignorei o projeto com as classes de domínio. A única razão pela qual eu precisava do EntityFramework.dll na versão anterior de minha solução era para ter acesso às Data Annotations que eram específicas ao EF. Agora elas foram movidas para o assembly do .NET Framework 4.5, System.ComponentModel.DataAnnotations.dll, para se unirem às outras anotações de dados. Portanto, eu não preciso mais fazer referência ao EF naquele projeto. Na verdade, agora posso desinstalar a referência ao EntityFramework do projeto. Em vez de usar a interface do usuário do gerenciador de pacotes, prefiro abrir a janela do console do gerenciador de pacotes, garantir que meu destino é o projeto DomainClasses e digitar "uninstall-package entityframework" para remover o pacote daquele projeto.

Porém, há mais uma etapa. A abertura do arquivo com as classes revela um aviso do compilador para as três anotações de dados que estou focalizando. Originalmente, elas estavam no namespace System.ComponentModel.DataAnnotations como parte do EntityFramework.dll. Mas, no assembly do .NET onde elas residem agora, elas foram movidas para um sub-namespace. Portanto, preciso adicionar mais um usando a instrução na parte superior do arquivo de código.

using System.ComponentModel.DataAnnotations.Schema;

Com isso, o compilador fica feliz e eu também, porque removi a dependência do Entity Framework nas classes que não têm nada a ver com o acesso de dados. Ainda tenho uma aversão pessoal a colocar os atributos que definem o esquema do banco de dados em minhas classes de domínio e, geralmente, confio nas configurações fluentes da API do Entity Framework para essas tarefas. No entanto, em um pequeno projeto, acho que as anotações de dados são convenientes e rápidas de usar.

Cruzando versões

A equipe do EF cobriu a possibilidade de você instalar o EF 4.3.x em um projeto direcionado para o .NET Framework 4.5. Se você fizer isso (intencional ou acidentalmente), um arquivo de texto será apresentado no IDE listando os problemas conhecidos com o uso do EF 4.x em um projeto do .NET 4.5 e recomendando a instalação do EF 5. Quando o EF 5 se tornar estável e for o pacote padrão, a probabilidade dos desenvolvedores de cometer esse erro desaparecerá.

Alternando para enums, Sim!

Com tudo isso estabelecido, posso modificar minhas classes de domínio para me livrar da alternativa ruim e usar enums para as propriedades Rating e Experience. Estas são as duas novas enums. Observe que especifiquei valores para uma, mas não para a outra, para que você possa ver como o EF trata os dois cenários:

public enum TweetRating
{
  Suxorz = 0,
  WorksForMe = 1,
  WatchOutAPlusK = 2
}
public enum TwitterExperience
{
  Newbie, BeenAround, Ninja
}

Com as enums estabelecidas, posso modificar as propriedades da seguinte maneira:

[Column("ExperienceCode")]
public TwitterExperience Experience { get; set; }
[Column("RatingCode")]
public TweetRating Rating { get; set; }

Observe que não preciso mais dos atributos para especificar o intervalo e o comprimento da propriedade. O mais importante, lembre-se de que estou fazendo essa alteração sem me preocupar com possíveis dados existentes em meu banco de dados de demonstração. Para fazer uma alteração como essa em um aplicativo que esteja em produção, você precisará se preparar antecipadamente para a alteração dos dados. Estou alterando completamente o significado de Experience no banco de dados e também alterei aleatoriamente o intervalo de classificação de tweet de 1-5 para 0-2.

Depois de usar as migrações do Code First para atualizar o banco de dados, a coluna Tweeter.ExperienceCode foi alterada do tipo de dados nvarchar para int. Por padrão, o C# e o Visual Basic interpretarão a enum como um inteiro e começarão a enumeração com 0. Portanto, o Code First mapeará os valores da enum para um tipo de dados int no banco de dados. É possível especificar a enum como sendo de outro tipo (dentro dos limites das enums do C# e do Visual Basic) e o Code First honrará isso. Por exemplo, a definição de uma enum como long resultará em propriedades que são mapeadas para um tipo de dados bigint. Mas, por padrão, você sempre obterá um inteiro começando com 0. Em meu exemplo, no banco de dados, Newbie será representado por 0, BeenAround por 1 e Ninja por 2. Se você achar que há alguma possibilidade de que no futuro poderá desejar remover qualquer um dos membros da enum, reordene-os ou adicione novos membros além dos do final. Você deve atribuir valores explícitos para eles como fiz na enum TweetRating. Isso facilita a alteração da enum sem alterar nenhum desses valores acidentalmente. Não se esqueça de que o banco de dados armazenará apenas o valor numérico, portanto, se você acabar alterando o valor na enum, isso alterará efetivamente o significado de seus dados ... que quase sempre é, como diz o guru do C#, Jon Skeet, "uma coisa ruim".

A Figura 5 mostra código que cria uma nova instância de Tweeter junto com uma Tweet, as duas usando as enums. Depois de salvar esses dados, o banco de dados mostra o valor de ExperienceCode igual a 1 e de Rating igual a 2.

Figura 5 Criando um novo gráfico de uma Tweeter e de uma Tweet

var alias = new Tweeter
  {
    Name = "Julie",
    UserName = "Julie",
    Bio = "Mom of Giantpuppy",
    CreateDate = DateTime.Now,
    Experience = TwitterExperience.BeenAround,
    Tweets = new List<Tweet>{new Tweet
               {
                 Content = "Oh how I love that Giantpuppy",
                 CreateDate = DateTime.Now,
                 Rating = TweetRating.WatchOutAPlusK
               }}
  };

É possível usar as enums em consultas. O Entity Framework cuidará da transformação da enum no valor int no SQL e da transformação dos valores int retornados para o valores enum originais. Por exemplo, esta é uma consulta LINQ que usa uma enum no predicado Where:

context.Tweeters.Where(t => t.Experience == 
  TwitterExperience.Ninja)

No T-SQL resultante, o valor do predicado Where é 2.

Mudança mais tranquila para o EF 5

Já estou ouvindo os desenvolvedores expressarem entusiasmo para portar suas soluções existentes do Entity Framework para o EF 5 para se beneficiar das enums e dos dados espaciais. Mudar os projetos do EF 4 para o EF 5 não é um bicho de sete cabeças, mas houve problemas suficientes para resolver que fizeram com que eu achasse a transição um pouco irritante nas primeiras vezes. Espero que esta coluna facilite a mudança para você.

Gosto do fato de que o único pacote do NuGet para suporte ao Code First e ao DbContext fornece DLLs compatíveis para o .NET 4 e o .NET 4.5. Mesmo que eu esteja usando um EDMX, ainda inicio todos os novos projetos com DbContext e, portanto, 10% de meus projetos agora dependem do pacote do NuGet do Entity Framework.

Lembre-se de que os aplicativos do EF 4 que são executados em computadores com o .NET Framework 4.5 instalado, se beneficiarão das melhorias de desempenho, portanto, mesmo que você não mude para o Visual Studio 2012 imediatamente, seus usuários ainda poderão sentir um pouco de amor pelas melhorias no núcleo do Entity Framework no .NET Framework 4.5.

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: Arthur Vickers