Consejos para trabajar con EntityFramework Core

Hola.



Recientemente descubrí que no todos los que trabajan con EF saben cómo cocinarlo. Además, no están ansiosos por comprender. Enfrentando problemas en las primeras etapas: instalación.

Incluso después de una configuración exitosa, existen problemas con las solicitudes de datos. No porque la gente no conozca LINQ, sino porque no todo se puede mapear, desde objetos hasta modelos relacionales. Porque cuando se trabaja con un enlace, la gente piensa en tablas. Dibuje consultas SQL e intente traducirlas a LINQ.



Esto y, quizás, algo más de lo que quiero hablar en el artículo.



Configurar



Una persona vino al proyecto, vio a EF allí por primera vez, se maravilló de tal milagro, aprendió a usarlo y decidió usarlo para fines personales. Creé un nuevo proyecto, ... ¿Y luego qué hacer?



Empiezas a buscar en Google, prueba, error. Para simplemente conectar EF a un proyecto, el desarrollador se enfrenta a problemas incomprensibles.



1. ¿Y cómo configurarlo para que funcione con el DBMS requerido?

2. ¿Y cómo configurar el trabajo de las migraciones?



Estoy seguro de que hay más problemas, pero probablemente estos sean los más frecuentes. Hablemos de todo en orden.



1. Bueno, aquí está Google. :) Su tarea es encontrar un proveedor EntityFramework Core para su DBMS.



También en la descripción del proveedor encontrará instrucciones para la configuración.



Para PostgreSQL, por ejemplo, debe instalar el paquete Nuget Npgsql.EntityFrameworkCore.PostgreSQL

Cree su propio contexto aceptando DbContextOptions y cree todo así



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

var ctx = new MyDbContext(opts.Options);




Si tiene una aplicación ASP .NET Core, el contexto se registra en el contenedor de manera diferente. Pero ya puedes ver esto en tu proyecto de trabajo. O google.



2. Si no hay niñeras en su empresa, lo más probable es que sepa cómo instalar las herramientas necesarias para trabajar con migraciones. Ya sea para un administrador de paquetes o NET Core CLI.



Pero lo que quizás no sepa es que el proyecto de inicio seleccionado (--startup-project) se inicia al trabajar con migraciones. Esto significa que si el proyecto inicial para las migraciones es el mismo proyecto que lanza su aplicación y cuando comienza por alguna razón pasa las migraciones a través de ctx.Database.Migrate (), entonces cuando intente crear, elimine una migración creada previamente o cree otra, la última migración creada rodará sobre la base. ¡SORPRESA!



Pero, quizás, al intentar crear la primera migración, capte algo como esto



No se ha configurado ningún proveedor de base de datos para este DbContext. Se puede configurar un proveedor anulando el método DbContext.OnConfiguring o utilizando AddDbContext en el proveedor de servicios de la aplicación. Si se usa AddDbContext, asegúrese también de que su tipo DbContext acepte un objeto DbContextOptions <TContext> en su constructor y lo pase al constructor base para DbContext.


Esto se debe a que la herramienta que trabaja con las migraciones las crea en función de su contexto, pero para ello necesita una instancia. Y para proporcionarlo, debe implementar la interfaz IDesignTimeDbContextFactory en el proyecto de inicio. Esto no es difícil, solo hay un método que debe devolver una instancia de su contexto.



Configurar modelos



Ya ha creado su primera migración, pero por alguna razón está vacía. Aunque tienes modelos.



El punto es que no le ha enseñado a su contexto para traducir sus modelos a su base de datos.



Para que EF cree una migración para crear una tabla para su modelo en la base de datos, debe al menos crear una propiedad del tipo DbSet <MyEntity> en su contexto.



por ejemplo, con esta propiedad

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



La tabla MyEntity se creará con campos correspondientes a las propiedades de la entidad MyEntity.



Si no desea tener un DbSet, o si desea influir en las reglas de creación de tablas predeterminadas para una entidad, debe anular

protected override void OnModelCreating(ModelBuilder modelBuilder)

el método de su contexto.



Cómo hacer esto, probablemente lo sepa. Además, probablemente esté familiarizado con los atributos que le permiten controlar las reglas de asignación. Pero esta es la cuestión. Los atributos no dan un control completo sobre la asignación, lo que significa que tendrá mejoras en OnModelCreating. Es decir, tendrá reglas para mapear entidades tanto en forma de anotaciones como en forma de api fluida. Vaya y busque por qué el campo tiene el nombre incorrecto, el que esperaba, o las restricciones incorrectas.



- Bueno, entonces, todo es simple, - dices - Configuraré todo a través de OnModelCreating



Y luego, después de configurar la entidad número 20 a partir de 10 campos, tus ojos comienzan a ondular. Está tratando de encontrar la configuración de campo para alguna entidad, pero todo flota ante sus ojos desde un bloque uniforme de 200-500 líneas de largo.



Sí, puede dividir este bloque en 20 métodos y será un poco más fácil. Pero es útil saber que existe una interfaz IEntityTypeConfiguration <TEntity>, al implementar la cual para una entidad específica, puede describir en esta implementación las reglas de mapeo para una entidad específica. Bueno, para que el contexto lo recoja, en OnModelCreating necesitas escribir

modelBuilder.ApplyConfigurationsFromAssembly (assemblyWithConfigurations);



Y, por supuesto, es posible transferir el comportamiento general al mismo OnModelCreating. Por ejemplo, si tiene una regla general para identificadores de todas o muchas entidades, puede configurarla así



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));
}


Construyendo consultas



Bueno, pusimos las cosas en orden, se volvió un poco más agradable.



Ahora queda rehacer las consultas de nuestro proyecto de casa de SQL a Linq. Ya escribí solicitudes en el trabajo, es tan fácil como

pelar peras ctx.MyEntities.Where (condición) .Select (mapa) .GroupBy (expresión) .OrderBy (expresión)… ligereza.



Entonces, ¿cuál es nuestra solicitud?



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


Si



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


He estado usando Linq y sus métodos de extensión mucho antes de conocer EF. Y nunca tuve una pregunta, pero ¿dónde está RIGHT JOIN, LEFT JOIN en él ...



Qué es LEFT JOIN en términos de objetos?



eso




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


Ni siquiera es magia. Solo tenemos una determinada entidad que se refiere a una lista de otras entidades, pero es posible que no tenga ninguna.



Eso es esto



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


¿Qué es RIGHT JOIN? Esta es una LEFT JOIN invertida, lo que significa que solo comenzamos con una entidad diferente.



Pero cualquier cosa sucede, a veces necesita controlar cada paquete como una entidad separada, incluso si la entidad izquierda no está asociada con ninguna derecha (la derecha es NULL). Por lo tanto, explícitamente LEFT JOIN se puede hacer así



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


Esto nos da control sobre la vinculación, como en el modelo relacional. ¿Por qué podrías necesitarlo? Digamos que un cliente necesita mostrar datos en forma de tabla, y se necesita ordenar y / o paginar.



Tampoco me gusta esta idea, pero todos tienen sus propias peculiaridades y un amor infinito en Excel. Entonces cubriremos el tapete con una tos, maldiciendo en un puño, y seguiremos trabajando. ¿Qué sigue para nosotros?



FULL JOIN


Entonces, bueno, ya entendí, no pensamos con SQL, pensamos con objetos, así, y ahora así ... maldita sea. ¿Cómo retratar esto?



Sin tuplas, en ninguna parte.



En general, cuando tenemos que trabajar con tuplas, perdemos comprensión sobre el tipo de conexión (1-1, 1-n, nn) ​​y en general, teóricamente, podemos cruzar moscas y elefantes. Esto es magia negra.

¡Vamos a hacerlo!



Tomemos como base muchos a muchos.



Por lo tanto, tenemos 3 tipos de entidad



LeftEntity

RightEntity

LeftRight (para organizar la conexión)



LeftRight que crearé con una clave compuesta. No necesito acceso directo a esta entidad, no se requieren referencias externas a ella, por lo que no produciré campos e índices innecesarios.



Como resultado de la consulta, quiero obtener un conjunto de tuplas, que contendrá la entidad izquierda y la entidad derecha. Además, si la entidad virgen no tiene nada que ver con la correcta, entonces el objeto correcto será nulo. Lo mismo ocurre con las entidades de la derecha.



Resulta que LeftRight no nos conviene como salida, sus limitaciones no nos permitirán crear un paquete sin una de las entidades. Si practica DDD, detectará inconsistencias.

Incluso si no practicas, no deberías. Creemos un nuevo tipo para la salida

LeftRightFull, que aún contiene una referencia a las entidades izquierda y derecha.



Por lo tanto, tenemos



Izquierda

L1

L2



derecha

R1

R2



Left Right

L2 R2



Queremos la salida

L1 N

L2 R2

n R1



Comencemos con la conexión izquierda



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




Ahora, ya tenemos

L1 n

L2 R2



¿Qué sigue? ¿Stick RightEntity a través de SelectMany? Bueno, entonces obtenemos

L1 n R1 n

L1 n R2 L2

L2 R2 R1 n

L2 R2 R2 L2



Podemos filtrar lo innecesario (L2 R2 R1 n y L1 n R2 L2) por condición, sin embargo, no está claro cómo transformar L1 n R1 n en

L1 n

n R1



Quizás las excavaciones nos han llevado a la estepa equivocada. Simplemente conectemos estas consultas similares con combinaciones izquierdas para entidades izquierda y derecha a través de Union.

L1 n

L2 R2

UNION

L2 R2

n R1



Aquí puede que tenga preguntas sobre el rendimiento. No les responderé, porque todo dependerá del DBMS específico y de la meticulosidad de su optimizador.



Alternativamente, puede crear una Vista con su consulta deseada. Union no funciona en EF Core 2, así que tuve que crear una vista. Sin embargo, recomiendo no olvidarlo. De repente, decide implementar la eliminación suave de entidades a través de la marca IsDeleted. En la vista, debe respaldar esto. Si lo olvida, no se sabe cuándo recibirá un informe de error.



Por cierto, ¿cómo implementar la eliminación suave sin reescribir todo el código? Hablaré de esto la próxima vez. Quizás otra cosa.



Adios a todos



All Articles