Entity Framework Core 1

Introdução

A Entity Framework é a tecnologia para acesso a dados recomendada pela Microsoft. A sua próxima versão (disponível a partir de Junho de 2016) será a quinta e promete ser revolucionária em vários sentidos. Este artigo irá revelar o que precisa de saber sobre ela.

História

A Entity Framework foi lançada em conjunto com o Service Pack 1 da framework .NET versão 3.5, em meados de 2009. No início, dividiu atenções com o LINQ to SQL, outra framework de acesso a dados lançada ao mesmo tempo; por esta, no entanto, ser mais simples e limitada unicamente a SQL Server, cedo foi deixada para trás, passando a Microsoft a recomendar a utilização da Entity Framework para todos os cenários de acesso a bases de dados relacionais.

Com a primeira versão da Entity Framework, a Microsoft iniciou uma aventura num mercado onde já existiam outros participantes, sendo o mais notório o NHibernate. Como framework ORMObject-Relational Mapper –, a Entity Framework encontrava-se então muito longe do que estes outros participantes ofereciam, beneficiando, no entanto, de bom suporte a LINQ, tecnologia então nos seus primórdios, e que se viria a revelar essencial.

A segunda encarnação foi introduzida com o .NET 4.0 e trouxe consigo o suporte opcional a Plain Old CLR Objects (POCOs) – simples classes .NET sem necessidade de herdar de uma classe específica da framework, carregamento implícito (lazy loading) e a possibilidade de personalizar os modelos T4 usados para gerar as classes, quer quando o modelo era importado da base de dados, quer quando este era definido no editor integrado no Visual Studio.

Com a terceira versão lançada, 4.1, a Microsoft passou a propor um novo fluxo para o desenvolvimento: começar com o código (modelo POCO) e a partir daí gerar a base de dados. Esta abordagem adequa-se bem à metodologia de desenvolvimento conhecida por Domain-Driven Design (DDD), a qual advoga em primeiro lugar a representação por meio de classes de todos os conceitos do domínio de negócio, sendo a persistência um aspecto que, embora claramente importante, deve ser o mais transparente possível, não devendo limitar as opções de desenho do modelo de domínio. Passaram a existir convenções para definir grande parte da tradução de e para o modelo relacional, sendo desnecessário configurar todos os detalhes explicitamente. O novo API DbContext, grandemente simplificador relativamente ao ObjectContext, revelou-se extremamente popular, apesar de algumas ausências, totalmente aceitáveis numa primeira versão, e o mesmo se passou com a nova funcionalidade de migrações, poderosa, que trouxe para o mundo das bases de dados as funcionalidades de controlo de versões, tão bem conhecidas dos programadores. Passou a falar-se de Entity Framework Code First.

Brevemente a Microsoft disponibilizou uma actualização – 5 – que introduziu tipos de dados enumerados e geo-espaciais ao modelo Code First, bem como a possibilidade de chamar funções SQL transparentemente em pesquisas LINQ através de métodos estáticos em classes .NET.

Finalmente, a versão 6 veio culminar o processo de maturação da Entity Framework como ferramenta ORM de pleno direito, com suporte a intercepção e logging de chamadas, uso de stored procedures para operações Create-Update-Delete (CUD), possibilidade de definição de convenções personalizadas, e, uma das funcionalidades mais apreciadas, a possibilidade de efectuar a maior parte das operações de forma assíncrona. Muitas das funcionalidades previamente introduzidas foram também alvo de melhoramentos, com destaque para as migrações. Parecíamos estar no bom caminho.

Eis senão quando a Microsoft veio baralhar completamente o jogo, com o anúncio da sua nova versão, designada inicialmente por 7, e, mais tardiamente, por Core 1, nome que ficou.

Entity Framework: evolução
Figura 1: Evolução da Entity Framework

Funcionalidades Adicionadas ou Alteradas

Novo Código

A EF Core foi totalmente escrita de raiz. Esta decisão teve a ver com o desejo da Microsoft de se ver livre de código “legado”, muito do qual praticamente não era usado – alguém usa Entity-SQL ou o ObjectContext desde a saída do Code First? – mas também com a necessidade de uma profunda alteração que permitisse a implementação de novas funcionalidades radicalmente incompatíveis com a anterior arquitectura. Esta abordagem, com méritos, veio, no entanto, causar algumas limitações à versão inicial, 1.0, como veremos a seguir.

Instalação

À semelhança das anteriores versões, a EF Core irá ser disponibilizada exclusivamente através do Nuget. A diferença é que, ao invés de um único pacote Nuget, teremos de instalar vários, consoante as necessidades:

Tabela 1: Pacotes Nuget
PacoteDescrição
Microsoft.EntityFrameworkCoreClasses base da Entity Framework
Microsoft.EntityFrameworkCore.ToolsOutras funcionalidades (migrações, p.ex.)

NoSQL

O mundo em 2015 e 2016, no que respeita às Tecnologias de Informação, é diferente do que era em 2009. Por um lado, o movimento No SQL ou Not-Only SQL, ganhou grande preponderância – passou a ser normal, numa empresa, a utilização de vários tipos de tecnologias para armazenamento de dados, mesmo concorrentemente, para responder a diferentes necessidades. Se a Entity Framework no início correspondia ao que as pessoas esperavam, pode argumentar-se que já não é assim. Revelando atenção às novas tendências, a Microsoft tornou a nova versão da Entity Framework agnóstica em relação às fontes de dados de uma forma inédita: passou a suportar não apenas bases de dados relacionais mas potencialmente qualquer fonte de dados, incluindo NoSQL, através de um mecanismo de providers. Foram anunciadas duas provas de conceito, na forma de providers para Azure Table Storage e para o muito popular serviço de cache distribuída Redis, mas este anúncio fica, para já, pela intenção, já que a versão inicial, Core 1.0, não irá incluir esta funcionalidade. A ideia é promissora – usar a mesma API para efectuar consultas a potencialmente qualquer fonte de dados – mas teremos de esperar para ver.

Múltiplas Plataformas

Sendo construída de raiz com suporte ao .NET Core, a versão de .NET multi-plataforma, a Entity Framework Core irá, assim, correr nos sistemas operativos Linux, MacOS, FreeBSD e Windows, bem como em dispositivos móveis usando Windows 10 e aplicações desenvolvidas para a Windows Store. Tal não exclui, no entanto, a utilização em aplicações .NET 4.6 ou superior, a qual continuará a ser, por agora e durante muito tempo, largamente dominante.

Configuração de Fornecedores de Dados

É substancialmente mais fácil a configuração de fornecedores de dados, passando a existir um método virtual na classe DbContext exclusivamente para esse efeito: trata-se do método OnConfiguring:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
   optionsBuilder.UseSqlServer(@"Data Source=.\SQLEXPRESS; Integrated Security=SSPI; Initial Catalog=Blogs;")
   base.OnConfiguring(optionsBuilder);
}
Código 1: Configuração do fornecedor SQL Server

Este exemplo ilustra a utilização do fornecedor SQL Server. Outro exemplo, agora para o fornecedor em memória (requer a instalação do pacote Microsoft.EntityFrameworkCore.InMemory):

optionsBuilder.UseInMemoryDatabase();
Código 2: Configuração do fornecedor In-Memory

Para que possamos usar um fornecedor, será necessário instalar o seu pacote Nuget. Os pacotes incluídos na versão 1 da Entity Framework Core são:

Tabela 2: Fornecedores de acesso
NomeDescrição
Microsoft.EntityFrameworkCore.SqlServerFornecedor de acesso para SQL Server
Microsoft.EntityFrameworkCore.SQLiteFornecedor de acesso para SQLite
Microsoft.EntityFrameworkCore.InMemoryFornecedor de acesso in-memory (para testes)
Npgsql.EntityFrameworkCore.PostgreSQLFornecedor de acesso para PostgreSQL
EntityFrameworkCore.SqlServerCompact35
EntityFrameworkCore.SqlServerCompact40
Fornecedores de acesso para SQL Server Compact 3.5 e 4.0
*.DesignFornecedores para geração de entidades a partir de uma base de dados relacional (p.ex., Microsoft.EntityFrameworkCore.SqlServer.Design)

Para cada um destes existe um método de extensão específico para a classe DbContextOptionsBuilder, o qual aceita diferentes parâmetros, tais como a connection string a usar (o in-memory não aceita nenhum).

Podemos ver a lista de fornecedores sempre actualizada em https://docs.efproject.net/en/latest/providers/index.html.

Geração de Identificadores

Historicamente, a EF apenas suportava duas estratégias para geração de chaves primárias:

  • IDENTITY, do SQL Server;
  • Chaves fornecidas manualmente (p.ex., GUIDs geradas por código, ou quaisquer outras chaves de tipos de dados básicos que fossem unívocas).

A nova versão vem finalmente trazer novidades a este respeito, nomeadamente, pela introdução do algoritmo High-Low. Este algoritmo, bem conhecido dos utilizadores de NHibernate, permite a geração de chaves primárias pela Entity Framework e não pela base de dados. Como não há bela sem senão, a Microsoft implementou este algoritmo com recurso às sequências do SQL Server 2012, pelo que não será suportado em versões anteriores, e o mecanismo no qual assenta não é verdadeiramente modular. Cai assim por terra o sonho de um mecanismo de geração de chaves primárias independente da base de dados.

A forma de configurar o uso de sequências para geração de identificadores globalmente é a seguinte (no método OnModelCreating):

modelBuilder.ForSqlServerUseSequenceHiLo();
Código 3: Configuração global para uso de sequências para gerar identificadores

Mas é também possível defini-lo entidade a entidade:

modelBuilder
   .Entity<Blog>()
   .Property(b => b.BlogId)
   .ForSqlServerUseSequenceHiLo();
Código 4: Configuração de sequências para gerar identificadores para uma entidade

Geração de SQL Optimizada

A Microsoft gaba-se de ter optimizado a geração de SQL, no caso da utilização de bases de dados relacionais. Este era, de facto, um dos calcanhares de Aquiles das versões anteriores. Adicionalmente, passou a ser possível enviar ao mesmo tempo múltiplos comandos INSERT, UPDATE e DELETE, ao invés de um por cada, respectivamente, inserção, actualização ou apagamento, o que, só por si, deve melhorar o desempenho sensivelmente. Além disso, a reescrita do gerador de SQL introduz outras melhorias, no sentido de produzir SQL mais optimizado.

Um exemplo de SQL a inserir três registos e a retornar as chaves geradas (IDENTITY) pode ser:

INSERT INTO [Blog] ([Name], [Url], [Description])
OUTPUT INSERTED.[BlogId]
VALUES (@p0, @p1, @p2), (@p3, @p4, @p5), (@p6, @p7, @p8);
Código 5: SQL para inserção de dados em bloco

Execução de Funções no Lado do Cliente

O LINQ introduz validação de queries no momento da compilação; no entanto, esta validação não inclui chamadas de métodos de extensão: estas aparentarão ser válidas até que o código corra, altura em que a EF tentará executá-los na base de dados, caso estes métodos tenham sido marcados para tal. Se não, com as versões anteriores da EF, isto resulta numa excepção, mas tal não é o caso da EF Core: pura e simplesmente, as chamadas serão efectuadas no lado do cliente, após os resultados terem sido materializados, de forma transparente:

var formattedEntities = (from blog in ctx.Blogs
                         select blog.Format())
                        .ToList();
Código 6: Execução de funções no lado do cliente

Neste exemplo, o método Format irá ser executado após os registos terem sido retornados da base de dados e os objectos materializados. Qualquer método de extensão pode ser usado, apenas não na cláusula where, como é facilmente compreensível.

Outro exemplo, usar uma função client-side para transformar uma coluna de forma a que possa ser ordenada:

var sortedEntities = (from blog in ctx.Blogs
                      select blog
                      orderby blog.Name.Soundex())
                     .ToList();
Código 7: Execução de funções no lado do cliente

A ordenação, neste caso, apenas será aplicada no lado do cliente, já que a Entity Framework não sabe nada sobre o método de extensão Soundex (mero exemplo, este método não existe).

Mistura de SQL com LINQ

Passa a ser possível combinar queries LINQ com SQL. Um exemplo ilustrativo:

var itemsContainingTerm = ctx.Blogs
                             .FromSql("SELECT * FROM dbo.SearchBlogs (@p0)", ".net")
                             .OrderBy(b => b.Name)
                             .ToList();
Código 8: Mistura de LINQ com SQL

Parece magia, mas a Entity Framework é capaz de combinar o resultado produzido pelo stored procedure SearchBlogs (poderia ser um SELECT, claro está) e aplicar ordenação pelo campo indicado (Name), resultando no SQL seguinte (simplificado):

EXEC sp_executesql 'SELECT [b].BlogId, [b].Name, [b].Url, [b].Description FROM (SELECT * FROM dbo.SearchBlogs (@p0)) AS [b] ORDER BY [b].Name'

Repare-se que o SQL passado ao método FromSql suporta parâmetros. O único cuidado a ter com este SQL é que esteve deverá retornar colunas compatíveis com a entidade que se pretende devolver.

Propriedades Sombra

Passou a ser possível configurar propriedades de uma entidade que, tendo correspondência em colunas na base de dados (ou numa fonte de dados NoSQL), não possuem equivalência na classe .NET. Ou seja, são possivelmente incluídas em SELECTs, UPDATEs e INSERTs, apesar de não serem “visíveis” nas classes POCO. Um exemplo óbvio seria a adição de colunas de “auditoria” – “criado por”, “criado em”, “modificado por”, “modificado em”. Por não serem visíveis no código, serão, em teoria, mais difíceis de interferir com. O código para configurar tal seria algo como o seguinte:

public interface IShadowAuditable { }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
   foreach (var e in modelBuilder.Model.GetEntityTypes().Where(e => typeof(IShadowAuditable).GetTypeInfo().IsAssignableFrom(e.ClrType.GetTypeInfo()))
   {
      modelBuilder
         .Entity(e.ClrType)
         .Property<string>("CreatedBy")
         .IsRequired();
      modelBuilder
         .Entity(e.ClrType)
         .Property<DateTime>("CreatedOn")
         .IsRequired();
      modelBuilder
         .Entity(e.ClrType)
         .Property<string>("UpdatedBy")
         .IsRequired();
      modelBuilder
         .Entity(e.ClrType)
         .Property<DateTime>("UpdatedOn")
         .IsRequired();
   }

   base.OnModelCreating(modelBuilder);
}
Código 9: Configuração de propriedades sombra

Já a utilização seria:

public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
   foreach (var e in this.ChangeTracker.Entries().Where(e => typeof(IShadowAuditable).GetTypeInfo().IsAssignableFrom(e.Entity.GetType().GetTypeInfo())))
   {
      if (e.State == EntityState.Added)
      {
         e.Property("CreatedBy")
            .CurrentValue = WindowsIdentity.GetCurrent().Name;
         e.Property("CreatedOn")
            .CurrentValue = DateTime.UtcNow;
      }

      e.Property("UpdatedBy")
         .CurrentValue = WindowsIdentity.GetCurrent().Name;
      e.Property("UpdatedOn")
         .CurrentValue = DateTime.UtcNow;
   }
   return base.SaveChanges(acceptAllChangesOnSuccess);
}
Código 10: Actualização de propriedades sombra

Finalmente, é também possível efectuar queries LINQ sobre propriedades sombra, recorrendo a uma sintaxe algo bizarra:

var blogsCreatedToday = (from blog in ctx.Blogs
                         where EF.Property<DateTime>(blog, "CreatedOn") == DateTime.Today)
                        .ToList();
Código 11: Queries sobre propriedades sombra

Anexar Entidades em Cascata

Sempre foi possível “anexar” entidades a um contexto Entity Framework diferente daquele que as carregou. Por exemplo, entidades devolvidas por um web service (saber se um web service deve ou não devolver entidades é uma questão que não será discutida aqui) podem ser posteriormente anexadas a um contexto a residir num servidor diferente e serem tratadas como originarias desse contexto. A novidade introduzida na nova versão consiste em poder definir, para cada entidade relacionada com a entidade a anexar, qual o seu estado, do ponto de vista do novo contexto. Vejamos o que acontecia se anexássemos uma entidade com uma colecção de outras entidades associadas:

BlogContext ctx = /* ... */;
Blog blog = /* ... */;
ctx.Entry(blog).State = EntityState.Unchanged;
Código 12: Alteração do estado de uma entidade e todos os seus descendentes

Todos os elementos Post da colecção Posts da instância da classe Blog seriam, assim, marcados como não modificados. Imaginemos agora que possuíamos um grafo muito complexo onde, para cada entidade nele presente pretendíamos especificar o seu estado. Para esse cenário, foi criado o API TrackGraph:

BlogContext ctx = /* ... */;
Blog blog = /* ... */;
ctx.ChangeTracker.TrackGraph(blog, (node) =>
{
   if (node.Entry.Entity is Post)
   {
      //marcar o post como inalterado
      node.Entry.State = EntityState.Unchanged;
      //se a data do post for anterior a hoje
      if (((Post) node.Entry.Entity).Timestamp.Date < DateTime.Today)
      {
         //colocar uma indicação que será partilhada por todos os filhos
         node.NodeState = true;
      }
   }
   else if (node.Entry.Entity is Tag)
   {
      //usa a indicação colocada anteriormente
      if (node.NodeState == true)
      {
         node.Entry.State = EntityState.Deleted;
      }
      else
      {
         node.Entry.State = EntityState.Unchanged;
      }
   }
});
Código 13: Utilização do TrackGraph

Ou seja, o TrackGraph sabe como percorrer todas as relações a partir de uma entidade raiz e permite que adicionemos a nossa lógica para definição dos estados encontrados.

Geração de Entidades a Partir da Base de Dados

A Entity Framework possibilita a geração de entidades a partir da base de dados (claro está, apenas para bases de dados relacionais), sem necessidade do Visual Studio. Para tal, é necessário ter presente o fornecedor de acesso pretendido (p.ex., Microsoft.EntityFrameworkCore.SqlServer) e também a sua versão “design”, a qual contém a funcionalidade para geração de entidades (Microsoft.EntityFrameworkCore.SqlServer.Design). A sua utilização é simples:

dotnet ef dbcontext scaffold "Data Source=.\SQLEXPRESS; Initial Catalog=Blog; Integrated Security=SSPI" "Microsoft.EntityFrameworkCore.SqlServer"
Código 14: Geração de entidades a partir da base de dados

Claro está, este exemplo assume um servidor de base de dados SQL Server Express local com o nome SQLEXPRESS, uma base de dados chamada Blog e autenticação integrada do Windows. O leitor deverá alterar estes dados de acordo com o seu cenário concreto.

Outras Funcionalidades

Importa também referir que a nova Entity Framework possibilita também, na senda das versões anteriores, uma elevada extensibilidade: grande parte dos seus componentes pode ser trocada por outros. É possível, nomeadamente, injectar fornecedores de log, de geração de SQL, e muitos outros. Este tópico é vasto, pelo que fica apenas um exemplo de configuração de log para a consola:

public class ConsoleLoggerFactory : ILoggerFactory
{
   public void AddProvider(ILoggerProvider provider)  { }
   public void Dispose() { }
   public ILogger CreateLogger(string categoryName)
   {
      //fazer log de tudo para a consola
      return new ConsoleLogger(categoryName, (facility, level) => true, true);
   }
}

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
   optionsBuilder.UseLoggerFactory(new ConsoleLoggerFactory());
   optionsBuilder.UseSqlServer(@"Data Source=.\SQLEXPRESS; Integrated Security=SSPI; Initial Catalog=Blogs;")
   base.OnConfiguring(optionsBuilder);
}
Código 15: Configuração do fornecedor de log

Funcionalidades Ainda Não Suportadas

Nem tudo, no entanto, é positivo. Para não atrasar ainda mais a data de lançamento (já bastante atrasada), a versão 1.0 não inclui um número de funcionalidades que se encontravam nas versões anteriores:

  • Estratégias de mapeamento de heranças: apenas Table Per Type Hierarchy / Single Table Inheritance é suportada, não Table Per Type / Class Table Inheritance nem Table Per Concrete Type / Concrete Table Inheritance;
  • Carregamento implícito (lazy loading) e explícito (explicit loading) não são suportados; é necessário indicar, aquando da execução de uma query LINQ as entidades a trazer, por meio de chamadas a Include;
  • Relações muitos-para-muitos: podem ser emulados recorrendo a duas relações um-para-muitos;
  • Tipos complexos (complex types) também não existem na nova versão;
  • Mapeamento de inserções, alterações e apagamentos por procedimentos armazenados (stored procedures);
  • Agrupamentos (GROUP BY) na base de dados, ao invés de em memória;
  • A capacidade de associar entidades a vistas;
  • Estratégias para tentar re-executar comandos aquando de perda de ligação também não existirão;
  • A possibilidade de adicionar convenções personalizadas;
  • Intercepção de queries e comandos SQL;
  • Filtrar colecções de entidades a carregar;
  • Conversões de tipos de dados, por exemplo, de String para XML;
  • Como foi dito no início, todo o suporte a NoSQL não foi ainda incluído nesta primeira versão.

Funcionalidades Removidas

Foram removidas as seguintes funcionalidades, a título definitivo:

  • Todos os APIs dependentes de ObjectContext (introduzido na versão 1.0 da Entity Framework) foram removidos. Isto significa que não existe mais Entity SQL ou os eventos SavingChanges e ObjectMaterialized, mas também entidades com estado auto-gerido (self-tracking entities);
  • O interface IDatabaseInitializer<T> e todas as suas implementações (CreateDatabaseIfNotExists, DropCreateDatabaseAlways, DropCreateDatabaseIfModelChanges); a alternativa passa por usar exclusivamente migrações; também deixa de ser possível a alimentação de registos iniciais (seed);
  • Deixam de existir migrações automáticas;
  • A validação de dados de acordo com o API ComponentModel.DataAnnotations deixou de ser efectuada;
  • A abordagem model first deixa de ser suportada, apenas database first ou code first.

Conclusão

A nova versão da Entity Framework não deverá ficar para a história por si, mas pelo que possibilita. Na verdade, é a própria Microsoft a admiti-lo: esta nova versão não é a recomendada para uso profissional, devendo essa escolha recair sobre a Entity Framework 6.x. De facto, são tantas as lacunas que quase parece impossível que o gigante do software tenha optado por a tornar disponível, com tanta pompa e circunstância. No entanto, se pensarmos no suporte a múltiplas plataformas e na possibilidade (futura…) de usar o mesmo API quer para fontes de dados relacionais quer para não relacionais (Azure Table Storage, Redis, outras), a coisa muda de figura. Para sermos honestos, há algumas funcionalidades que merecem a nossa atenção desde já, mas, para uma utilização “a sério”, resta-nos esperar pela próxima versão. A curiosidade já aperta!

Referências

  1. https://github.com/aspnet/EntityFramework
  2. https://github.com/aspnet/EntityFramework/wiki/Roadmap
  3. http://ef.readthedocs.io/en/latest
  4. https://data.uservoice.com/forums/72025-entity-framework-feature-suggestions
  5. https://blogs.msdn.microsoft.com/dotnet/