working related query not method framework first context code c# entity-framework linq linq-to-sql entity-framework-6

c# - query - load related entities entity framework core



Los métodos de extensión IQueryable de Entity Framework no funcionan como una sub consulta (2)

Me gusta escribir mis consultas utilizando métodos de extensión cuando sea posible. Entonces la siguiente es una consulta que funciona para mí:

int studentId = ( from u in db.Users .FromOrganisation(org.Id) .IsStudent() .IsActive() where u.ExtId == dto.StudentExtId select u.Id ).FirstOrDefault();

Los métodos de extensión son los siguientes:

public static IQueryable<User> IsStudent(this IQueryable<User> u) { return u.Where(x => x.Type == (int)UserTypes.Student); }

Sin embargo, cuando uso métodos de extensión en una sub consulta, recibo el siguiente mensaje:

LINQ to Entities no reconoce el método ''System.Linq.IQueryable`1 [eNotify.Domain.Models.User] IsActive (System.Linq.IQueryable`1 [eNotify.Domain.Models.User])'' método, y este método no se puede traducir a una expresión de tienda.

Aquí está la consulta que causa ese mensaje:

var vm = from o in db.Organisations select new StaffStudentVm { StudentId = ( from u in db.Users .FromOrganisation(org.Id) .IsStudent() .IsActive() where u.ExtId == dto.StudentExtId select u.Id ).FirstOrDefault(), StaffId = ( from u in db.Users .FromOrganisation(org.Id) .IsStaff() .IsActive() where u.ExtId == dto.StaffExtId select u.Id ).FirstOrDefault() }; return vm.FirstOrDefault();

¿Qué estoy haciendo mal?

Actualización: Alexander Derck publicó una solución que funcionó bien, pero no fue tan buena como la consulta original del problema. Lo planteé con el equipo de EF, y después de investigar dieron con una solución más elegante. Lo publiqué a continuación como la respuesta aceptada.


Eventualmente planteé esto con el equipo de Entity Framework en GitHub. Puede ver el hilo aquí, con una descripción completa de por qué sucede:

https://github.com/aspnet/EntityFramework6/issues/98

Parece haber sido planteada como una sugerencia para su inclusión en EF 6.2, pero hasta entonces, se sugirió una solución muy elegante. Puedes leerlo en el hilo, pero lo he copiado aquí para una referencia rápida.

Aquí está la consulta original (donde ocurre un error debido a que se usa un método de extensión IQueryable en una sub consulta):

var vm = from o in db.Organisations select new StaffStudentVm { StudentId = ( from u in db.Users .FromOrganisation(org.Id) .IsStudent() .IsActive() where u.ExtId == dto.StudentExtId select u.Id ).FirstOrDefault(), StaffId = ( from u in db.Users .FromOrganisation(org.Id) .IsStaff() .IsActive() where u.ExtId == dto.StaffExtId select u.Id ).FirstOrDefault() }; return vm.FirstOrDefault();

Y aquí está cómo escribirlo para que no ocurra ningún error:

var stuList = db.Users.FromOrganisation(org.Id).IsStudent().IsActive(); var staffList = db.Users.FromOrganisation(org.Id).IsStaff().IsActive(); var vm = from o in db.Organisations select new StaffStudentVm { StudentId = ( from u in stuList where u.ExtId == dto.StudentExtId select u.Id ).FirstOrDefault(), StaffId = ( from u in staffList where u.ExtId == dto.StaffExtId select u.Id ).FirstOrDefault() }; return vm.FirstOrDefault();

Puedo confirmar que este estilo todavía solo da como resultado 1 viaje de ida y vuelta a la base de datos. Al dividir la consulta en varias declaraciones, en realidad mejora la legibilidad en muchos lugares también.


Puede hacer una clase parcial para su modelo de User con una clase estática dentro:

partial class User { public static class Q { public static Expression<Func<User,bool>> IsStudent { return x => x.Type == (int)UserTypes.Student; } } }

Entonces su consulta se vería así:

var vm = from o in db.Organisations select new StaffStudentVm { StudentId = ( from u in db.Users .FromOrganisation(org.Id) .Where(User.Q.IsStudent) .IsActive() where u.ExtId == dto.StudentExtId select u.Id ).FirstOrDefault(), StaffId = ( from u in db.Users .FromOrganisation(org.Id) .IsStaff() .IsActive() where u.ExtId == dto.StaffExtId select u.Id ).FirstOrDefault() };

No es tan elegante como los métodos de extensión, pero debería funcionar, creo ...