Dicas para trabalhar com EntityFramework Core

Olá.



Recentemente, descobri que nem todo mundo que trabalha com a EF sabe cozinhá-lo. Além disso, eles não estão ansiosos para entender. Enfrentando problemas nos estágios iniciais - instalação.

Mesmo após uma configuração bem-sucedida, existem problemas com solicitações de dados. Não porque as pessoas não conheçam o LINQ, mas porque nem tudo pode ser mapeado de objetos a modelos relacionais. Porque quando se trabalha com link, as pessoas pensam em tabelas. Desenhe consultas SQL e tente traduzi-las em LINQ.



Isso e, talvez, algo mais sobre o qual gostaria de falar no artigo.



Configurando



Uma pessoa veio ao projeto, viu EF ali pela primeira vez, maravilhou-se com tal milagre, aprendeu a usá-lo, decidiu usá-lo para fins pessoais. Criei um novo projeto, ... E depois o que fazer?



Você começa a pesquisar no Google, tentativa, erro. Para simplesmente conectar o EF a um projeto, o desenvolvedor se depara com tipos de problemas incompreensíveis.



1. E como configurá-lo para funcionar com o DBMS necessário?

2. E como configurar o trabalho de migrações?



Tenho certeza de que há mais problemas, mas esses são provavelmente os mais frequentes. Vamos conversar sobre tudo em ordem.



1. Bem, aqui está para o google. :) Sua tarefa é encontrar um provedor EntityFramework Core para seu DBMS.



Também na descrição do provedor você encontrará instruções para a configuração.



Para PostgreSQL, por exemplo, você precisa instalar o pacote Nuget Npgsql.EntityFrameworkCore.PostgreSQL

Crie seu próprio contexto aceitando DbContextOptions e crie tudo assim



var opts = new DbContextOptionsBuilder<MyDbContext>()
        .UseNpgsql(constring);

var ctx = new MyDbContext(opts.Options);




Se você tiver um aplicativo ASP .NET Core, o contexto será registrado no contêiner de maneira diferente. Mas você já pode ver isso em seu projeto de trabalho. Ou google.



2. Se não houver babás em sua empresa, provavelmente você sabe como instalar as ferramentas necessárias para trabalhar com migrações. Seja para um gerenciador de pacotes ou NET Core CLI.



Mas o que você pode não saber é que o projeto de inicialização selecionado (--startup-project) é iniciado ao trabalhar com migrações. Isso significa que se o projeto inicial para migrações é o mesmo projeto que inicia seu aplicativo e quando você inicia por algum motivo, você rola as migrações por meio de ctx.Database.Migrate (), então, ao tentar criar, exclua uma migração criada anteriormente ou crie outra, a última migração criada será transferida para a base. SURPRESA!



Mas, talvez, ao tentar criar a primeira migração, você irá pegar algo assim



Nenhum provedor de banco de dados foi configurado para este DbContext. Um provedor pode ser configurado substituindo o método DbContext.OnConfiguring ou usando AddDbContext no provedor de serviços de aplicativo. Se AddDbContext for usado, então também certifique-se de que seu tipo DbContext aceite um objeto DbContextOptions <TContext> em seu construtor e o transmita para o construtor base para DbContext.


Isso porque a ferramenta que trabalha com migrações as cria com base no seu contexto, mas para isso precisa de uma instância. E para fornecê-lo, você precisa implementar a interface IDesignTimeDbContextFactory no projeto inicial.Isso não é difícil, há apenas um método que deve retornar uma instância do seu contexto.



Configurando modelos



Você já criou sua primeira migração, mas por algum motivo ela está vazia. Embora você tenha modelos.



A questão é que você não ensinou seu contexto para traduzir seus modelos em seu banco de dados.



Para que o EF crie uma migração para criar uma tabela para seu modelo no banco de dados, você deve pelo menos criar uma propriedade do tipo DbSet <MyEntity> em seu contexto.



por exemplo, por esta propriedade

public DbSet <MyEntity> MyEntities {get; conjunto; }



A tabela MyEntities será criada com campos correspondentes às propriedades da entidade MyEntity.



Se você não deseja ter um DbSet ou deseja influenciar as regras de criação de tabela padrão para uma entidade, você precisa substituir

protected override void OnModelCreating(ModelBuilder modelBuilder)

o método do seu contexto.



Como fazer isso, você provavelmente sabe. Além disso, você provavelmente está familiarizado com os atributos que permitem controlar as regras de mapeamento. Mas é o seguinte. Os atributos não fornecem controle completo sobre o mapeamento, o que significa que você terá refinamentos em OnModelCreating. Ou seja, você terá regras para mapear entidades na forma de anotações e na forma de API fluente. Vá e procure por que o campo tem o nome errado, o que você esperava, ou as restrições erradas.



- Pois então, tudo é simples, - você diz - vou configurar tudo através do OnModelCreating



E aí, depois de configurar a 20ª entidade de 10 campos, seus olhos começam a se agitar. Você está tentando encontrar a configuração de campo para alguma entidade, mas tudo flutua diante de seus olhos em um bloco uniforme de 200-500 linhas.



Sim, você pode dividir este bloco em 20 métodos e isso se tornará um pouco mais fácil. Mas é útil saber que existe uma interface IEntityTypeConfiguration <TEntity>, ao implementar a qual para uma entidade específica, você pode descrever nesta implementação as regras de mapeamento para uma entidade específica. Bem, para que o contexto seja capturado, em OnModelCreating você precisa escrever

modelBuilder.ApplyConfigurationsFromAssembly (assemblyWithConfigurations);



E, claro, é possível transferir o comportamento geral para o mesmo OnModelCreating. Por exemplo, se você tem uma regra geral para identificadores de todas ou muitas entidades, você pode configurá-la assim



foreach (var idEntity in modelBuilder.Model.GetEntityTypes()
    .Where(x => typeof(BaseIdEntity).IsAssignableFrom(x.ClrType))
    .Select(x => modelBuilder.Entity(x.ClrType)))
{
    idEntity.HasKey(nameof(BaseIdEntity.Id));
}


Criação de consultas



Bem, a gente arrumava as coisas, ficou um pouco mais gostoso.



Agora resta refazer as consultas de nosso projeto inicial de SQL para Linq. Já escrevi solicitações no trabalho, é tão fácil quanto

descascar peras ctx.MyEntities.Where (condição) .Select (map) .GroupBy (expression) .OrderBy (expression) ... fácil.



Então, qual é o nosso pedido?



SELECT bla bla bla FROM table
RIGHT JOIN....... 


sim



ctx.LeftEntities.RightJoin(.... f@#$


Eu tenho usado o Linq e seus métodos de extensão muito antes de conhecer o EF. E eu nunca tive uma pergunta, mas onde está RIGHT JOIN, LEFT JOIN nisso ...



O que é LEFT JOIN em termos de objetos?



isto




class LeftEntity 
{
    public List<RightEntity> RightEntities { get; set; }
}


Não é nem mágico. Temos apenas uma determinada entidade que se refere a uma lista de outras entidades, mas pode não ter nenhuma.



Ou seja, este



ctx.LeftEntities.Include(x => x.RightEntities)


O que é RIGHT JOIN? Este é um LEFT JOIN invertido, o que significa que apenas começamos com uma entidade diferente.



Mas tudo acontece, às vezes você precisa de controle sobre cada pacote, como uma entidade separada, mesmo se a entidade esquerda não estiver associada a uma única entidade direita (a direita é NULL). Portanto, LEFT JOIN explicitamente pode ser feito assim



ctx.LeftEntities.SelectMany(x => x.RightEntities.DefaultIfEmpty(), (l, r) => new { Left = l, Right = r })


Isso nos dá controle sobre a vinculação, como no modelo relacional. Por que você pode precisar? Digamos que um cliente precise exibir dados na forma de uma tabela e a classificação e / ou paginação seja necessária.



Eu também não gosto dessa ideia, mas todo mundo tem suas peculiaridades e um amor infinito no Excel. Então, vamos cobrir o tapete com uma tosse, xingando em punho, e continuar trabalhando. O que vem a seguir para nós?



FULL JOIN


Então, bom, eu já entendi, não pensamos com SQL, pensamos com objetos, assim, e agora assim ... droga. Como retratar isso?



Sem tuplas, em lugar nenhum.



Em geral, quando temos que trabalhar com tuplas, perdemos o entendimento sobre o tipo de conexão (1-1, 1-n, nn) ​​e em geral, teoricamente, podemos cruzar moscas e elefantes. Isso é magia negra.

Vamos fazer isso!



Vamos tomar muitos para muitos como base.



Portanto, temos 3 tipos de entidade



LeftEntity

RightEntity

LeftRight (para organizar a conexão)



LeftRight que criarei com uma chave composta. Não preciso de acesso direto a esta entidade, referências externas a ela não são obrigatórias, portanto, não irei produzir campos e índices desnecessários.



Como resultado da consulta, desejo obter um conjunto de tuplas, que conterá a entidade esquerda e a entidade direita. Além disso, se a entidade virgem não tem nada a ver com a certa, então o objeto certo será nulo. O mesmo vale para as entidades do lado direito.



Acontece que LeftRight não é adequado para nós como uma saída, suas limitações não nos permitem criar um pacote sem uma das entidades. Se você praticar DDD, descobrirá inconsistência.

Mesmo se você não praticar, você não deve. Vamos criar um novo tipo para a saída

LeftRightFull, que ainda contém uma referência às entidades esquerda e direita.



Então, nós temos



Esquerda

L1

L2



direito

R1

R2



leftright

L2 R2



Queremos a saída

L1 n

L2 R2

n R1



Vamos começar com a conexão esquerda



var query = ctx.LeftEntities
    .SelectMany(x => x.RightLinks.DefaultIfEmpty(), (l, lr) => new LeftRightFull
    {
        LeftEntity = l,
        RightEntity = lr.RightEntity
    })




Agora, já temos

L1 n

L2 R2



O que vem a seguir? Manter RightEntity via SelectMany? Bem, então temos

L1 n R1 n

L1 n R2 L2

L2 R2 R1 n

L2 R2 R2 R2 L2



Podemos filtrar desnecessariamente (L2 R2 R1 n e L1 n R2 L2) por condição, no entanto, não está claro como transformar L1 n R1 n em

L1 n

n R1



Talvez as escavações nos tenham levado à estepe errada. Vamos apenas combinar essas consultas semelhantes com junções à esquerda para entidades esquerda e direita via Union.

L1 n

L2 R2

UNION

L2 R2

n R1



Aqui você pode ter perguntas sobre o desempenho. Não vou respondê-las, pois tudo dependerá do DBMS específico e da meticulosidade de seu otimizador.



Como alternativa, você pode criar uma Visualização com a consulta desejada. O Union não funciona no EF Core 2, então tive que criar um View. No entanto, recomendo não se esquecer disso. De repente, você decide implementar a exclusão reversível de entidades por meio do sinalizador IsDeleted. No modo de exibição, você precisa apoiar isso. Se você se esquecer, não se sabe quando receberá um relatório de bug.



A propósito, como implementar a exclusão suave sem reescrever o código inteiro? Falarei sobre isso na próxima vez. Talvez outra coisa.



Adeus a todos



All Articles