Entity Framework Core 1

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