Entity framework IQueryable 扩展方法不能用作子查询

Entity framework IQueryable extension methods do not work as a sub query

我喜欢尽可能使用扩展方法来编写查询。所以以下是对我有用的查询:

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

扩展方法如下:

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

但是,当我在子查询中使用扩展方法时,我收到以下消息:

LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[eNotify.Domain.Models.User] IsActive(System.Linq.IQueryable`1[eNotify.Domain.Models.User])' method, and this method cannot be translated into a store expression.

这是导致该消息的查询:

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

我做错了什么?

更新: Alexander Derck 发布了一个运行良好但不如原始问题查询好用的解决方案。我向 EF 团队提出了这个问题,在调查之后他们想出了一个更优雅的解决方法。我已将其作为已接受的答案发布在下方。

您可以为您的 User 模型制作部分 class,其中包含静态 class:

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

那么您的查询将如下所示:

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

它不像扩展方法那么优雅,但我认为它应该可以解决问题...

我最终在 GitHub 上向 Entity Framework 团队提出了这个问题。您可以在此处查看该主题,并完整描述了它发生的原因:

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

它似乎已作为包含在 EF 6.2 中的建议提出,但在此之前,提出了一个非常优雅的解决方法。您可以在线程中阅读它,但我已将其复制到此处以供快速参考。

这是原始查询(由于在子查询中使用了 IQueryable 扩展方法而发生错误):

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

下面是如何写才不会出错:

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

我可以确认这种样式仍然只会导致 1 次数据库往返。将查询分解为多个语句实际上也提高了很多地方的可读性。