Compartir a través de


Operadores de consulta complejos

Language Integrated Query (LINQ) contiene muchos operadores complejos, que combinan varios orígenes de datos o realiza un procesamiento complejo. No todos los operadores LINQ tienen traducciones adecuadas en el servidor. A veces, una consulta de un formulario se traduce en el servidor, pero si se escribe en otro formulario no se traduce aunque el resultado sea el mismo. En esta página se describen algunos de los operadores complejos y sus variaciones admitidas. En futuras versiones, es posible que reconozcamos más patrones y agreguemos sus traducciones correspondientes. También es importante tener en cuenta que la compatibilidad con la traducción varía entre proveedores. Es posible que una consulta determinada, que se traduce en SqlServer, no funcione para las bases de datos de SQLite.

Sugerencia

Puede ver un ejemplo de este artículo en GitHub.

Unirse

El operador LINQ Join permite conectar dos orígenes de datos basados en el selector de claves de cada origen, generando una tupla de valores cuando la clave coincide. Se traduce de forma natural en INNER JOIN bases de datos relacionales. Aunque la combinación LINQ tiene selectores de claves externos e internos, la base de datos requiere una única condición de combinación. Por lo tanto, EF Core genera una condición de combinación comparando el selector de teclas externa con el selector de claves interno para obtener igualdad.

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on photo.PersonPhotoId equals person.PhotoId
            select new { person, photo };
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON [p0].[PersonPhotoId] = [p].[PhotoId]

Además, si los selectores de claves son tipos anónimos, EF Core genera una condición de combinación para comparar la igualdad de componentes.

var query = from photo in context.Set<PersonPhoto>()
            join person in context.Set<Person>()
                on new { Id = (int?)photo.PersonPhotoId, photo.Caption }
                equals new { Id = person.PhotoId, Caption = "SN" }
            select new { person, photo };
SELECT [p].[PersonId], [p].[Name], [p].[PhotoId], [p0].[PersonPhotoId], [p0].[Caption], [p0].[Photo]
FROM [PersonPhoto] AS [p0]
INNER JOIN [Person] AS [p] ON ([p0].[PersonPhotoId] = [p].[PhotoId] AND ([p0].[Caption] = N'SN'))

GroupJoin

El operador LINQ GroupJoin permite conectar dos orígenes de datos similares a Join, pero crea un grupo de valores internos para buscar elementos externos coincidentes. Ejecutar una consulta como en el ejemplo siguiente genera un resultado de Blog & IEnumerable<Post>. Dado que las bases de datos (especialmente las bases de datos relacionales) no tienen una manera de representar una colección de objetos del lado cliente, GroupJoin no se traduce en el servidor en muchos casos. Requiere que obtenga todos los datos del servidor para realizar GroupJoin sin un selector especial (primera consulta a continuación). Pero si el selector limita la selección de datos, la captura de todos los datos del servidor puede provocar problemas de rendimiento (segunda consulta a continuación). Por eso EF Core no traduce GroupJoin.

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, grouping };
var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            select new { b, Posts = grouping.Where(p => p.Content.Contains("EF")).ToList() };

SelectMany

El operador LINQ SelectMany permite enumerar a través de un selector de recopilación para cada elemento externo y generar tuplas de valores de cada origen de datos. De una manera, es una combinación pero sin ninguna condición, por lo que todos los elementos externos están conectados con un elemento del origen de la colección. En función de cómo esté relacionado el selector de recopilación con el origen de datos externo, SelectMany puede traducirse en varias consultas diferentes en el lado servidor.

El selector de colección no conecta con una referencia externa

Cuando el selector de recopilación no hace referencia a nada desde el origen externo, el resultado es un producto cartesiano de ambos orígenes de datos. Se traduce en CROSS JOIN en bases de datos relacionales.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>()
            select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
CROSS JOIN [Posts] AS [p]

El selector de colección se refiere a un elemento externo en una cláusula where

Cuando el selector de recopilación tiene una cláusula where, que hace referencia al elemento externo, EF Core la traduce a una combinación de base de datos y usa el predicado como condición de combinación. Normalmente, este caso surge cuando se usa la navegación de colecciones en el elemento externo como selector de colecciones. Si la colección está vacía para un elemento externo, no se generaría ningún resultado para ese elemento externo. Pero si DefaultIfEmpty se aplica en el selector de colección, el elemento externo se conectará con un valor predeterminado del elemento interno. Debido a esta distinción, este tipo de consultas se traduce a INNER JOIN en ausencia de DefaultIfEmpty y LEFT JOIN cuando se aplica DefaultIfEmpty.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId)
            select new { b, p };

var query2 = from b in context.Set<Blog>()
             from p in context.Set<Post>().Where(p => b.BlogId == p.BlogId).DefaultIfEmpty()
             select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

El selector de recopilación hace referencia al exterior en un caso distinto de where

Cuando el selector de recopilación hace referencia al elemento externo, que no está en una cláusula where (como en el caso anterior), no se traduce a una combinación de base de datos. Por eso es necesario evaluar el selector de recopilación para cada elemento externo. Se traduce a APPLY operaciones en muchas bases de datos relacionales. Si la colección está vacía para un elemento externo, no se generaría ningún resultado para ese elemento externo. Pero si DefaultIfEmpty se aplica en el selector de colección, el elemento externo se conectará con un valor predeterminado del elemento interno. Debido a esta distinción, este tipo de consultas se traduce a CROSS APPLY en ausencia de DefaultIfEmpty y OUTER APPLY cuando se aplica DefaultIfEmpty. Algunas bases de datos como SQLite no admiten APPLY operadores, por lo que es posible que este tipo de consulta no se traduzca.

var query = from b in context.Set<Blog>()
            from p in context.Set<Post>().Select(p => b.Url + "=>" + p.Title)
            select new { b, p };

var query2 = from b in context.Set<Blog>()
             from p in context.Set<Post>().Select(p => b.Url + "=>" + p.Title).DefaultIfEmpty()
             select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] + N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
CROSS APPLY [Posts] AS [p]

SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], ([b].[Url] + N'=>') + [p].[Title] AS [p]
FROM [Blogs] AS [b]
OUTER APPLY [Posts] AS [p]

AgruparPor

Los operadores groupBy de LINQ crean un resultado de tipo IGrouping<TKey, TElement> donde TKey y TElement podrían ser cualquier tipo arbitrario. Además, IGrouping implementa IEnumerable<TElement>, lo que significa que puede componer sobre él usando cualquier operador LINQ después de la agrupación. Dado que ninguna estructura de base de datos puede representar un IGrouping, los operadores GroupBy no tienen traducción en la mayoría de los casos. Cuando se aplica un operador de agregado a cada grupo, que devuelve un escalar, se puede traducir a SQL GROUP BY en bases de datos relacionales. Sql GROUP BY también es restrictivo. Requiere que solo se agrupe por valores escalares. La proyección solo puede contener columnas de clave de agrupación o cualquier agregado aplicado sobre una columna. EF Core identifica este patrón y lo traduce en el servidor, como en el ejemplo siguiente:

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            select new { g.Key, Count = g.Count() };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]

EF Core también traduce las consultas en las que un operador de agregado de la agrupación aparece en un operador LINQ como Where u OrderBy (u otro operador de ordenación). Se utiliza HAVING como parte de la cláusula WHERE en SQL. La parte de la consulta antes de aplicar el operador GroupBy puede ser cualquier consulta compleja siempre que se pueda traducir al servidor. Además, una vez que aplique operadores de agregado en una consulta de agrupación para quitar agrupaciones del origen resultante, puede componer una nueva consulta sobre ella como cualquier otra consulta.

var query = from p in context.Set<Post>()
            group p by p.AuthorId
            into g
            where g.Count() > 0
            orderby g.Key
            select new { g.Key, Count = g.Count() };
SELECT [p].[AuthorId] AS [Key], COUNT(*) AS [Count]
FROM [Posts] AS [p]
GROUP BY [p].[AuthorId]
HAVING COUNT(*) > 0
ORDER BY [p].[AuthorId]

Los operadores agregados compatibles con EF Core son los siguientes:

.RED SQL
Average(x => x.Property) AVG(Property)
Count() COUNT(*)
LongCount() COUNT(*)
Max(x => x.Property) MAX(Propiedad)
Min(x => x.Property) MIN(Propiedad)
Sum(x => x.Property) SUM(Propiedad)

Se pueden admitir operadores de agregado adicionales. Compruebe los documentos del proveedor para obtener más asignaciones de funciones.

Aunque no hay ninguna estructura de base de datos para representar IGrouping, en algunos casos, EF Core 7.0 y versiones posteriores pueden crear las agrupaciones después de que se devuelvan los resultados de la base de datos. Esto es similar a cómo funciona el Include operador al incluir colecciones relacionadas. La siguiente consulta LINQ usa el operador GroupBy para agrupar los resultados por el valor de su propiedad Price.

var query = context.Books.GroupBy(s => s.Price);
SELECT [b].[Price], [b].[Id], [b].[AuthorId]
FROM [Books] AS [b]
ORDER BY [b].[Price]

En este caso, el operador GroupBy no se traduce directamente en una GROUP BY cláusula de SQL, sino que EF Core crea las agrupaciones después de que se devuelvan los resultados desde el servidor.

Unión a la izquierda

Aunque Left Join no es un operador LINQ, las bases de datos relacionales tienen el concepto de combinación izquierda que se usa con frecuencia en las consultas. Un patrón determinado en las consultas LINQ da el mismo resultado que un LEFT JOIN en el servidor. EF Core identifica estos patrones y genera el equivalente LEFT JOIN en el lado servidor. El patrón implica crear un GroupJoin entre los orígenes de datos y, a continuación, aplanar la agrupación mediante el operador SelectMany con DefaultIfEmpty en el origen de agrupación para que coincida con NULL cuando el elemento interno no tiene un elemento relacionado. En el ejemplo siguiente se muestra cómo es ese patrón y lo que genera.

var query = from b in context.Set<Blog>()
            join p in context.Set<Post>()
                on b.BlogId equals p.BlogId into grouping
            from p in grouping.DefaultIfEmpty()
            select new { b, p };
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url], [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]

El patrón anterior crea una estructura compleja en el árbol de expresiones. Por esta razón, EF Core requiere que desagrupe los resultados del operador GroupJoin en el paso inmediatamente siguiente al operador. Incluso si se usa groupJoin-DefaultIfEmpty-SelectMany pero en un patrón diferente, es posible que no lo identifiquemos como unión izquierda.