Janeiro de 2017

Volume 32 - Número 1

Data Points - EF Core 1.1: Um pouco sobre o que eu mais gosto

Por Julie Lerman

Julie LermanEnquanto escrevo esta coluna (em novembro de 2016), o EF (Entity Framework) Core 1.1 acaba de ser lançado. Entre as versões 1.0 e 1.1, aconteceram algumas coisas importantes. Em particular, o patch 1.0.1 corrigiu alguns bugs críticos que foram descobertos quando a versão 1.0 foi lançada. Para obter uma lista detalhada das correções, você pode ler as notas de versão em bit.ly/2fl5xNE. Embora a maioria das correções se relacione à forma com como as consultas LINQ são transformadas em SQL, também há correções para o construtor do modelos, a persistência via SaveChanges e as migrações.

O EF Core 1.1 fornece correções de bugs adicionais, muito mais aprimoramentos na forma como as consultas LINQ são interpretadas e grandes ganhos de desempenho. Porém, também há novos recursos, alguns que existiam no EF6, mas não foram incluídos no EF Core 1.0, impedindo que muitos desenvolvedores considerem o EF Core, e outros que são completamente novos. Neste artigo, quero apresentar uma lista rápida das alterações e destacar algumas que são de grande interesse para mim. Também fornecerei links para os muitos recursos valiosos que uso para acompanhar a evolução do EF Core. Para começar, muitas dessas informações são abordadas nos documentos (docs.microsoft.com/ef) e nas postagens do blog da equipe (bit.ly/2ehZBUB).

Em particular, você deve conhecer o blog de Arthur Vickers. Arthur é um desenvolvedor sênior que faz parte da equipe desde o início do suporte a POCO no EF e teve um papel fundamental na modernização do EF. Ele escreveu uma ótima série de postagens sobre o EF Core 1.1 em blog.oneunicorn.com.

A Microsoft planeja incluir muitos recursos do EF6 no EF Core, mas nem todos eles. Há uma série de novos recursos planejados para versões futuras. No momento, o EF Core 1.1 se concentra em atualizações para tornar o EF Core mais satisfatório para um maior número de desenvolvedores. Aqui estão os recursos mais importantes.

Recursos do EF6 agora no EF 1.1

O método DbSet.Find, que foi disponibilizado com a introdução da API DbContext no EF 4.1, não foi incluído na primeira iteração do EF Core, o que desagradou a muitos desenvolvedores. O método Find não é só uma maneira conveniente de consultar uma entidade com eficiência com base em seu valor-chave. Primeiro ele verifica se a entidade já está na memória e está sendo rastreada pelo EF. Se a entidade não for encontrada no cache de DbContext, o EF executará uma consulta FirstOrDefault no banco de dados para recuperar a entidade. Isso costumava ser uma consulta SingleOrDefault no EF6 e versões anteriores. O design do Find para evitar o acesso desnecessário ao banco de dados também é um benefício de desempenho. Se você é como eu e quer entender o funcionamento, confira o código na classe EntityFinder no GitHub  (bit.ly/2e9V0Uu).

A outra grande diferença é que o Find agora está em um serviço do EntityFinder. Não é apenas um método implementado diretamente em uma classe interna DbSet, como no EF6. O EF Core é composto de centenas de serviços para encapsular tarefas específicas. Em suas demonstrações no vídeo do Channel 9 sobre o EF Core 1.1 (bit.ly/2fHOCsN), Rowan Miller mostra como usar os serviços diretamente e como substituí-los.

Na atualização 1.1, também foi incorporado ao EF Core o recurso EF6, conhecido como resiliência de conexão, que fornece suporte para lidar facilmente com problemas de conexão transitória que podem ocorrer quando se trabalha com um banco de dados remoto, como o Banco de Dados SQL do Azure. O método de extensão EnableRetryOnFailure do provedor de Banco de Dados SQL pode ser definido em DbContext.OnConfiguring ou na chamada AddDbcontext em Startup.cs se você está usando injeção de dependência do Núcleo ASP.NET. EnableRetryOnFailure chama SqlServerRetryingExecutionStrategy, que herda do tipo ExecutionStrategy. Você pode criar e definir seu próprio ExcecutionStrategy, e outros provedores também podem predefinir suas próprias configurações ExecutionStrategy. Se você não está familiarizado com esse recurso, e;e é semelhante ao EF6 DbExecutionStrategy, que expliquei de forma aprofunda em meu curso da Pluralsight, "EF6 Ninja Edition" (bit.ly/PS_EF6).

O EF sempre forneceu três maneiras de carregar dados relacionados. Uma delas, o carregamento adiantado, é habilitada pelo método DbSet.Include para recuperar gráficos de dados em uma única consulta. Por outro lado, as duas outras carregam dados relacionados depois que os objetos principais já estão na memória e enquanto o DbContext que os está rastreando ainda está no escopo. O carregamento lento carrega automaticamente os dados relacionados sob demanda, enquanto no carregamento explícito você instrui explicitamente o EF a carregar os dados relacionados. Include foi o primeiro a ser implementado no EF Core e foi disponibilizado com o EF Core 1.0. Ele tem até algumas melhorias interessantes em relação às versões do EF6 e anteriores. Agora o carregamento explícito com o método Load foi adicionado ao EF Core 1.1. O carregamento lento ainda não tem suporte, mas terá em algum momento.

As APIs do rastreador de alterações do DbContext permitem que você acesse diretamente as informações do rastreador de alterações, por exemplo, obtendo ou definindo o estado de uma entidade com o método DbContext.Entry (someObject).State. O EF6 forneceu controle adicional com novos métodos como GetDatabaseValues, CurrentValues e OriginalValues. Agora esses métodos estão disponíveis no EF Core 1.1.

Novos recursos no EF 1.1

O EF Core está repleto de recursos que nunca existiram em versões anteriores. Aqui está uma breve lista de exemplos: envio em lote durante SaveChanges, chaves estrangeiras exclusivas, o maravilhoso provedor InMemory para testes, processamento inteligente de consultas LINQ e mapeamentos fluentes mais inteligentes e simples.

O EF 1.1 oferece alguns novos recursos adicionais. Como grande fã do DDD (Domain-Driven Design), há um recurso que me agrada particularmente: o suporte para coleções encapsuladas.

Campos Mapeados e Coleções Encapsuladas O EF Code First só dá suporte ao mapeamento para propriedades que têm um getter e um setter, mesmo que o setter seja privado. Para navegação em coleções, a propriedade tinha que ser ICollection. Se você quer restringir como os valores das propriedades são populados, a capacidade de encapsular a propriedade é crucial. Dessa forma, você pode forçar qualquer pessoa que usa sua classe a usar um método público para garantir que as regras de negócios referentes à propriedade sejam respeitadas. O EF6 e as versões anteriores permitem encapsular propriedades escalares tornando o setter privado. Porém, não há uma maneira de encapsular realmente as coleções e impedir que alguém as modifique diretamente.

Com o EF 1.1, você pode mapear diretamente para campos, bem como para propriedades IEnumerable. A nova capacidade de mapear campos e não apenas propriedades permite que você use uma abordagem mais direta do que ocultar o setter e também dá suporte a algumas formas adicionais de encapsular valores escalares. Aqui está uma propriedade, DueDate, que tem um getter, mas não um setter, juntamente com o campo _dueDate, que está vinculado à propriedade:

private DateTime _dueDate;
public DateTime DueDate {
  get { return _dueDate;
  }
}

A única maneira de definir DueDate é chamar o método CalculateDueDate, que modifica o campo:

private void CalculateDueDate() {
  _dueDate=Start.AddDays(DaysLoaned);
}

O EF Core 1.1 requer um mapeamento explícito em DbContext para informar ao EF que ele pode usar o campo _dueDate para mapear para o banco de dados, por exemplo, ao retornar os resultados de uma consulta. Você deve usar os métodos Property (e , opcionalmente, HasField) da API para especificar que o campo _dueDate é um substituto para a propriedade DueDate. Nesse caso, como o nome do campo, _dueDate, segue uma convenção do EF, não preciso usar o método HasField, mas o adicionei para que você possa vê-lo:

protected override void OnModelCreating(ModelBuilder modelBuilder) {
  modelBuilder.Entity<BookLoan>().Property(bl => bl.DueDate)
  .HasField(“_dueDate”);
}

Embora você pudesse usar setters privados antes que o mapeamento de campo estivesse disponível, não havia uma maneira de encapsular coleções para impedir que alguém manipulasse diretamente uma coleção por meio dos métodos Add ou Remove. Não é uma questão de ocultar o setter. É necessário ocultar os métodos de coleção. Uma abordagem comum no DDD é tornar a propriedade um IEnumerable. No entanto, no EF6 e nas versões anteriores, você só pode mapear para um tipo de ICollection; contudo, IEnumerable não é um ICollection.

Inicialmente, verifiquei se você poderia usar o recurso de campo de mapeamento, mas isso não é possível no EF 1.1, pois ele só dá suporte ao mapeamento para propriedades escalares. Isso deve mudar na próxima versão do EF Core, que permitirá o mapeamento para propriedades de navegação. Porém, quando mencionei isso no Twitter certa vez, Arthur Vickers me disse que, na verdade, é possível mapear para IEnumerables. Eu não tinha notado isso no EF Core. Portanto, agora posso encapsular e proteger completamente uma coleção, forçando os usuários de minha API a usar um método para modificar a coleção. Aqui está um exemplo de onde posso adicionar novas instâncias de BookLoan a um livro sempre que ele é emprestado, garantindo que o período de empréstimo seja incluído:

private List<BookLoan> _bookLoans;
public IEnumerable<BookLoan> BookLoans {
  get { return _bookLoans; }
}
public void BookWasLoaned(int daysLoaned){
  _bookLoans.Add(new BookLoan(DateTime.Today, 14));
}

Este é um padrão para obter o encapsulamento aproveitando o mapeamento IEnumerable. Porém, recomendo a leitura da postagem no blog de Arthur (bit.ly/2fVzIfN) para obter mais detalhes, incluindo como fazer isso sem um campo de suporte (como o campo _bookLoans), planos para aprimoramentos e algumas dicas para conferir.

Suporte para recursos específicos de bancos de dados O EF Core foi projetado para permitir que os provedores deem suporte mais facilmente a recursos específicos do repositório de dados. Por exemplo, o provedor do SQL Server para o EF Core dá suporte a tabelas com otimização de memória do SQL Server para aplicativos em que há uma taxa de transferência incrivelmente alta. Para especificar que uma entidade é mapeada para esse tipo especial de tabela, o provedor do SQL Server tem um método de extensão que você pode usar ao configurar seu modelo com a API Fluente:

modelBuilder.Entity<Book>().ForSqlServerIsMemoryOptimized();

Isso afetará não só o modo como o código gera primeiro o script de criação de tabelas, mas também afetará a forma como o EF gera comandos para inserir dados no banco de dados. Você pode ver uma demonstração interessante disso no vídeo do Channel 9 mencionado anteriormente, em que Rowan Miller demonstra recursos do EF 1.1.

Shay Rojansky, que criou o provedor PostgreSQL para o EF Core, escreveu um artigo sobre como o EF Core permitia o suporte a recursos especiais do PosgreSQL, como tipos de matrizes. Leia-o em bit.ly/2focZKQ.

Acesso mais fácil aos serviços

Como destaquei anteriormente sobre o serviço EntityFinder, o EF Core é composto por centenas de serviços. No vídeo do Channel 9, Rowan demonstra como acessar e usar esses serviços diretamente no código. Além disso, você pode substituir um serviço por sua própria personalização. Com o EF 1.1, é fácil fazer isso com um método de substituição simples que pode ser usado no OnConfiguring. Você pode substituir um serviço por outro que herda desse serviço ou implementa a mesma interface. Não há uma lista específica de serviços. Essas são apenas classes nas várias APIs do EntityFrameworkCore. Um exemplo simples de entender é a criação de uma classe que herda de um mapeador de tipo de banco de dados, como SqlLiteTypeMapper do provedor Sqlite, e a adição de uma nova regra de mapeamento de tipos. Verifique se a regra pode ser transmitida pelo banco de dados. (Algumas conversões mais inteligentes estarão disponíveis em uma versão futura do EF Core.) Em seguida, em OnConfiguring, configure a regra de substituição:

optionsBuilder.ReplaceService<SqliteTypeMapper,MySqliteTypeMapper>();

Por que não substituí o serviço EntityFinder? Primeiro, porque gosto da maneira como ele funciona. Porém, em segundo lugar, porque é uma classe genérica, o que torna mais complicada a criação de uma nova versão dela, e optei por deixar essa tarefa de lado por enquanto. Embora ReplaceService tenha sido criado para facilitar a substituição de serviços internos, você precisa se lembrar de que os componentes internos do EF Core podem mudar e, assim, sua substituição pode ser problemática. Você pode identificá-los facilmente porque eles estão em namespaces que terminam com a palavra Internal. Da Microsoft: "Geralmente, evitamos dividir as alterações em namespaces não .Internal em versões secundárias e patches."

Vale ressaltar (porque isso causou uma certa preocupação na época) uma correção para um problema de desempenho relacionado a consultas assíncronas que usavam o método Include, que foi descoberto apenas quando a versão 1.0 estava prestes a ser lançada. Ele foi resolvido rapidamente (confira bit.ly/2faItD1 se estiver interessado nos detalhes), com um aumento de 70% no desempenho, e também faz parte da versão 1.1.

Conclusão

O EF Core tem muitos recursos sofisticados. É incrivelmente importante saber o que está disponível, o que não está, o que está por vir e o que nunca fará parte do EF Core. A documentação do EF Core sobre sua relação com o EF6 (bit.ly/2fxbVVj) também é um recurso útil. A questão é: você decide quando o EF Core é ideal para você e para seus aplicativos.

Para a maioria do trabalho que faço, o EF Core 1.1 tem as APIs e os recursos de que preciso. Como um praticante do DDD, a capacidade de encapsular coleções é crucial para mim, embora ainda esteja esperando que os mapeamentos de tipos complexos sejam incluídos para que possa usar objetos de valor em meus modelos. Esse recurso está previsto para a próxima versão do EF Core. Também fiquei empolgado em descobrir recentemente que o EF Core realizou um de meus desejos: definir um DbContext somente leitura (não rastreamento). Não tinha percebido que isso foi adicionado no início e fazia parte da versão 1.0 do EF Core. Você pode ler mais a respeito na postagem em meu blog em bit.ly/2f75l8m.


Julie Lerman é MVP da Microsoft, mentora e consultora do .NET, que reside nas colinas de Vermont. Você pode encontrá-la em apresentações sobre acesso de dados ou sobre outros tópicos .NET em grupos de usuários e conferências em todo o mundo. Ela escreve no blog thedatafarm.com/blog e é autora do "Programming Entity Framework", bem como de uma edição do Code First e do DbContext, todos da O'Reilly Media. Siga-a no Twitter em @julielerman e confira seus cursos da Pluralsight em juliel.me/PS-Videos.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Rowan Miller


Discuta esse artigo no fórum do MSDN Magazine