Estoy tratando de construir una consulta sensata en EF Core que devuelva una colección de cosas que, a su vez, derivan de una colección de cosas. Básicamente, en SQL sin formato uno haría una ÚNETE.
Está en ASP.NET Core, por lo que la colección inicial es la lista de roles en el objeto SecurityPrincipal
:
var roles = User.FindAll(ClaimTypes.Role).Select(r=>r.Value);
Estas funciones se asignan a los grupos en nuestra base de datos, por lo que puedo buscarlos:
var groupsQuery = dbContext.Groups.Where(g=>roles.Any(r=>r==g.GroupName));
var groups = await groupsQuery.ToListAsync();
Esta consulta es bastante feliz y devuelve una colección de grupos como se esperaba. Sin embargo, los grupos tienen acceso a otro recurso, que es lo que realmente quiero y debido a que es una relación de muchos a muchos, existe una tabla puente.
Este soy yo tratando de consultar la tabla de unión de AssetGroup para poder obtener todos los Activos a los que hacen referencia todos los Grupos que se asignan a un Rol en SecurityPrincipal
.
var assetGroupsQuery = dbContext.AssetsGroups.Where(ag => groupsQuery.Any(ag => ag.Id == a.GroupId));
var assetGroups = await assetGroupsQuery.ToListAsync();
Cuando realizo la segunda consulta, recibo una gran cantidad de spam en mi ventana de resultados:
The LINQ expression 'where ([ag].Id == [ag].GroupId)' could not be translated and will be evaluated locally.
The LINQ expression 'Any()' could not be translated and will be evaluated locally.
The LINQ expression 'where {from Group g in __groups_0 where ([ag].Id == [ag].GroupId) select [ag] => Any()}' could not be translated and will be evaluated locally.
The LINQ expression 'where ([ag].Id == [ag].GroupId)' could not be translated and will be evaluated locally.
The LINQ expression 'Any()' could not be translated and will be evaluated locally.
¿Alguna pista sobre cómo se debe formular una consulta anidada como esta para que EF Core pueda componer una sola consulta SQL correctamente?
En general, evite el uso de Any
o cualquier operador que no sea LINQ Contains
en la colección de memoria como sus roles
(que según el código debe ser de tipo IEnumerable<string>
).
En otras palabras, en lugar de
.Where(g => roles.Any(r => r == g.GroupName))
utilizar el equivalente funcional
.Where(g => roles.Contains(g.GroupName))
Lo último está garantizado para ser traducido a SQL IN
, mientras que el primero no lo es.
Es interesante y al mismo tiempo engañoso es que EF Core intenta ser inteligente y traducir el primero de la misma manera que Contains
, y tiene éxito cuando se ejecuta la consulta que contiene, pero no cuando se utiliza como parte de otra consulta.
Podría considerarse un defecto de implementación actual de EF Core. Pero la solución / solución es (como se mencionó al principio) no confiar en ella y usar siempre Contains
.