Nova funcionalidade do Entity Framework Core 2.0

Introdução As atualizações recentes no Entity Framework Core 2.0 foram bem recebidas na comunidade, com várias artigos disponíveis em blogs como o deste.

A versão 2.0 introduziu vários novos recursos que completaram uma linha de produtos anteriormente anunciada. Vamos explorar essas novidades. O conteúdo completo desta postagem pode ser encontrado aqui.

Novidades em Entidades

2.1 Divisão de Tabelas

Agora é possível mapear múltiplos tipos de entidade para a mesma tabela compartilhando a coluna primária, onde cada linha representa duas ou mais entidades.

A configuração para identificar relações através da divisão de tabelas (onde as propriedades de chave estrangeira formam a chave primária) deve ser realizada em todas as entidades compartilhando a tabela:

modelBuilder.Entity<Product>()
    .HasOne(e => e.Details).WithOne(e => e.Product)
    .HasForeignKey<ProductDetails>(e => e.Id);
modelBuilder.Entity<Product>().ToTable("Products");
modelBuilder.Entity<ProductDetails>().ToTable("Products");

2.2 Tipo Possessivo

Os tipos de entidade possuídos podem comaprtilhar o mesmo tipo CLR, mas, como não são reconhecidos pelo CLR, eles devem ser navegados a partir de outro tipo de entidade. O tipo que define a navegação é o proprietário. Quando um proprietário é consultado, por padrão, todos os tipos serão incluídos.

Por convenção, um chave primária sombra será criada para o tipo possuído e ele será mapeado à mesma tabela que o proprietário. Usar tipos possuídos é similar aos tipos complexos do EF6, onde os tipos complexos permitem organizar propriedades escalares dentro de um tipo de entidade não escalar. Assim como entidades, tipos complexos são compostos por propriedades escalares ou outros tipos complexos.

modelBuilder.Entity<Order>().OwnsOne(p => p.OrderDetails, cb =>
{
    cb.OwnsOne(c => c.BillingAddress);
    cb.OwnsOne(c => c.ShippingAddress);
});

public class Order
{
    public int Id { get; set; }
    public OrderDetails OrderDetails { get; set; }
}

public class OrderDetails
{
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

public class StreetAddress
{
    public string Street { get; set; }
    public string City { get; set; }
}

2.3 Filtros de Consulta no Nível do Modelo (Entidade)

Esta funcionalidade permite definir condições de consulta LINQ diretamente nos tipos de entidade no modelo metadados (geralmente em OnModelCreating). Esses filtros são aplicados automaticamente a qualquer consulta LINQ envolvendo esses tipos de entidade, incluindo referências indiretas de entidade, como por meio de inclusões (Include) ou navegação direta.

Boa prática: uso em soft delete e design de banco de dados multitenant, reduzindo significativamente a quantidade de código.

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
    public int TenantId { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>().HasQueryFilter(
            p => !p.IsDeleted
            && p.TenantId == this.TenantId );
    }
}

2.4 Mapeamento de Funções Escalar do Banco de Dados

Este recurso é muito útil porque sabemos que nossos bancos de dados geralmente têm funções integradas ou que podemos definir algumas funções escalares.

Com esta característica, podemos chamar facilmente essas funções diretamente no LINQ e ter suas consultas traduzidas para SQL.

Código exemplo:

public class BloggingContext : DbContext
{
    [DbFunction]
    public static int PostReadCount(int blogId)
    {
        throw new Exception();
    }
}

Em seguida, podemos usar essa função diretamente em consultas LINQ, como abaixo:

var query =
    from p in context.Posts
    where BloggingContext.PostReadCount(p.Id) > 5
    select p;

É importante notar:

  • Ao gerar SQL, o nome do método será usado como o nome da função (neste caso, a função definida pelo usuário), mas o nome e o esquema podem ser substituídos durante o registro da função.
  • Atualmente, apenas funções escalares são suportadas.
  • A criação da função mapeada deve ser feita manualmente no banco de dados, pois as migrações do EF Core não criam essa função.

Aprimoramentos de Desempenho

2.1 Pooling de DbContext

No ASP.NET Core, usamos o Entity Framework Core ao registrar o tipo personalizado de DbContext no sistema de injeção de dependência, obtendo uma instância desse tipo através dos parâmetros de construtor de controladores. Isso significa que uma nova instância de DbContext é criada para cada solicitação.

Para resolver isso, a versão 2.0 introduziu uma nova maneira de registrar o tipo personalizado de DbContext no serviço de injeção de dependência, introduzindo um pool de instâncias reutilizáveis de DbContext. Para usar o pooling de DbContext, registre o tipo personalizado de DbContext usando AddDbContextPool em vez de AddDbContext.

Exemplo:

services.AddDbContextPool<BloggingContext>(
    options => options.UseSqlServer(connectionString));

Se o pooling estiver sendo usado, quando uma instância de DbContext é solicitada no controlador, primeiro verifica-se se há uma instância disponível no pool. Uma vez que a solicitação for concluída, qualquer estado na instância será resetado e a instância retornará ao pool.

Conceitualmente, isso funciona da mesma forma que o pooling de conexões no ADO.NET, economizando o custo de inicialização das instâncias de DbContext.

2.2 Compilação Explícita de Consultas

Este é um recurso opcional de melhorias de desempenho, principalmente projetado para fornecer vantagens em cenários de escala massiva.

A API de compilação explícita de consultas já estava disponível em versões anteriores do EF e LINQ to SQL, permitindo que os aplicativos cacheiem a tradução das consultas, para que elas só sejam calculadas uma vez e executadas várias vezes.

Embora o EF Core normalmente compile e cacheie consultas automaticamente com base na representação de hash da expressão de consulta, esse mecanismo possa obter pequenos ganhos de desempenho ao evitar o cálculo de hashes e a busca rápida na memória de cache, permitindo que os aplicativos usem consultas compiladas previamente chamando métodos delegados.

Código exemplo:

// Cria uma consulta compilada explicitamente
private static Func<CustomerContext, int, Customer> _customerById =
    EF.CompileQuery((CustomerContext db, int id) =>
        db.Customers
            .Include(c => c.Address)
            .Single(c => c.Id == id));

// Usa e aplica a consulta compilada
using (var db = new CustomerContext())
{
   var customer = _customerById(db, 147);
}

Consultas

3.1 Melhoria na Tradução de LINQ

Permite que mais consultas sejam executadas com sucesso, trensferindo mais lógica para o SQL no banco de dados em vez de processá-la na memória e recuperando menos dados desnecessários do banco de dados.

3.2 Melhoria na GroupJoin

Trabalha para melhorar a geração de SQL para junções agrupadas.

3.3 Interpolação de String em FromSql e ExecuteSqlCommand

Introduziu a interpolação de string no C# 6, uma funcionalidade que permite incorporar expressões C# diretamente em literais de strings, oferecendo uma maneira boa de construir strings em tempo de execução.

No Entity Framwork Core 2.0, aumentamos o suporte especial para a interpolação de string, aceitando strings SQL brutas em dois principais métodos de API: FromSql e ExecuteSqlCommand.

Esse novo suporte permite usar de forma segura a interpolação de string do C#. Isso ajuda a prevenir ataques de injeção SQL comuns que ocorrem ao construir dinamicamente SQL em tempo de execução.

Exemplo:

var city = "London";
var contactTitle = "Sales Representative";

using (var context = CreateContext())
{
    context.Set<Customer>()
        .FromSql($@"
            SELECT *
            FROM ""Customers""
            WHERE ""City"" = {city} AND
            ""ContactTitle"" = {contactTitle}")
        .ToArray();
}

Gera a seguinte instrução SQL parametrizada:

@p0='London' (Size = 4000)
@p1='Sales Representative' (Size = 4000)

SELECT *
FROM ""Customers""
WHERE ""City"" = @p0
    AND ""ContactTitle"" = @p1

3.4 EF.Functions.Like()

Adicionou a propriedade EF.Functions (notem que isso deve ser expandido para adicionar mais métodos de banco de dados), que permite ao EF Core definir métodos que são mapeados para funções ou operadores de banco de dados, permitindo que esses métodos sejam chamados em consultas LINQ. Um exemplo desses métodos é Like():

var aCustomers =
    from c in context.Customers
    where EF.Functions.Like(c.Name, "a%");
    select c;

É importante notar que o método Like possui uma implementação em memória, facilitando muitas consultas relacionadas à memória tanto no lado do servidor quanto no cliente.

Tags: entity-framework-core ef2.0 new-features database-design

Publicado em 9-24 19:55