¿Cómo realizar una unión grupal en .NET Core 3.0 Entity Framework?

13

Con los cambios a .NET Core 3.0 estoy obteniendo

... NavigationExpandingExpressionVisitor 'falló. Esto puede indicar un error o una limitación en EF Core. Consulte https://go.microsoft.com/fwlink/?linkid=2101433 para obtener información más detallada.) ---> System.InvalidOperationException: Procesando la expresión LINQ 'GroupJoin, ...

Esta es una consulta realmente simple, por lo que debe haber una forma de realizarla en .NET CORE 3.0:

 var queryResults1 = await patients
            .GroupJoin(
                _context.Studies,
                p => p.Id,
                s => s.Patient.Id,
                (p, studies) => new 
                {
                    p.DateOfBirth,
                    p.Id,
                    p.Name,
                    p.Sex,
                   Studies =studies.Select(s1=>s1)
                }
            )
            .AsNoTracking().ToListAsync();

Básicamente, estoy buscando una consulta de Linq (o la sintaxis del método como la anterior) que unirá Estudios en pacientes y establecerá los Estudios en una lista vacía o nula si no hay estudios para el paciente dado.

¿Algunas ideas? Esto funcionaba en .NET Core 2.2. Además, el enlace de MSFT anterior menciona que el cambio de ruptura de clave está relacionado con la evaluación del lado del cliente y evita que la consulta generada lea tablas completas que luego deben unirse o filtrarse del lado del cliente. Sin embargo, con esta simple consulta, la unión debería ser fácilmente factible en el lado del servidor.

shelbypereira
fuente

Respuestas:

11

Como se discutió aquí , está intentando una consulta que no es compatible con la base de datos. EF Core 2 utilizó la evaluación del lado del cliente para hacer que su código funcione, pero EF Core 3 se niega, porque la conveniencia del lado del cliente tiene el costo de problemas de rendimiento difíciles de depurar a medida que aumenta el conjunto de datos.

Puede usar use DefaultIfEmptypara unirse a la izquierda a los estudios de los pacientes y luego agruparlos manualmente ToLookup.

var query =
    from p in db.Patients
    join s in db.Studies on p.Id equals s.PatientId into studies
    from s in studies.DefaultIfEmpty()
    select new { Patient = p, Study = s };

var grouping = query.ToLookup(e => e.Patient); // Grouping done client side

El ejemplo anterior toma las entidades completas de Paciente y Estudio, pero en su lugar puede seleccionar columnas. Si los datos que necesita del Paciente son demasiado grandes para repetirlos en cada Estudio, en la consulta unida, seleccione solo la ID del paciente, consultando el resto de los datos del Paciente en una consulta separada no unida.

Edward Brey
fuente
2
¡La respuesta funciona! Supongo que todavía hay trabajo por hacer en el traductor de consultas. Una consulta simple como esta debería ser traducible. No debería haber problemas de rendimiento para una unión de grupo simple de 2 tablas, ya que el conjunto de datos aumenta asumiendo que FK / índices son correctos. Sospecho que muchas personas tendrán este problema, una unión de grupo de 2 tablas es una consulta bastante estándar y de uso frecuente.
shelbypereira
@ she72 Estoy de acuerdo. Parece que el problema se deriva de la diferencia en cómo LINQ y SQL usan la palabra clave "grupo". EF Core debería traducir el LINQ groupbyen uniones izquierdas donde hacerlo no retire más filas de lo esperado. Publiqué un comentario en consecuencia.
Edward Brey el
Tengo una pregunta de seguimiento, todavía estoy tratando de entender por qué la agrupación para este tipo de consulta debe hacerse del lado del cliente, parece una limitación del nuevo marco LINQ. Para el caso anterior, no veo ningún riesgo de que ralentice la ejecución del lado del cliente de manera inesperada. ¿Puedes aclarar?
shelbypereira
1
Y como seguimiento adicional, la principal preocupación es: en su consulta reformulada, ¿qué grupos del lado del cliente si tengo 1000 estudios por paciente, cargaré a cada paciente 1000 veces desde el DB? ¿Hay alguna alternativa para forzar este trabajo en la base de datos y devolver los resultados agrupados?
shelbypereira
1
@ shev72 La única agrupación que comprende la base de datos involucra agregados, por ejemplo, una consulta de pacientes con un recuento de estudios por paciente. La base de datos siempre devuelve un conjunto de datos rectangular. Una agrupación jerárquica debe ser compuesta por el cliente. Puede verlo como una evaluación del lado del cliente o como parte del ORM . En una agrupación jerárquica, los datos de la entidad principal se repiten, aunque no se vuelven a consultar.
Edward Brey el
0

Tenía exactamente el mismo problema y una gran lucha con él. Resulta que .net Core 3.0 no es compatible con Join o Groupjoin en la sintaxis del método (¿todavía?). Sin embargo, la parte divertida es que funciona en la sintaxis de consulta.

Pruebe esto, es una sintaxis de consulta con un poco de sintaxis de método. Esto se traduce muy bien en la consulta SQL correcta con una buena combinación externa izquierda y se procesa en la base de datos. No tengo sus modelos, así que deben verificar la sintaxis ustedes mismos ...

var queryResults1 = 
    (from p in _context.patients
    from s in _context.Studies.Where(st => st.PatientId == p.Id).DefaultIfEmpty()
    select new
    {
        p.DateOfBirth,
        p.Id,
        p.Name,
        p.Sex,
        Studies = studies.Select(s1 => s1)
    }).ToListAsync();
hwmaat
fuente
Por cierto, la sintaxis Join y GroupJoin with Method funciona con el Framework no básico y EF. Y traduzca a la consulta correcta que está en el lado del servidor
procesado
1
¿Qué es estudios en estudios? Seleccione (s1 => s1)
Ankur Arora
Los modelos no se incluyeron en la pregunta, por lo que no conozco el modelo de estudios. Mi mejor conjetura es que esta es una colección virtual en el modelo.
hwmaat