实体框架IQueryable扩展方法不能用作子查询

本文关键字:查询 不能 方法 框架 IQueryable 扩展 实体 | 更新日期: 2023-09-27 18:27:31

我喜欢尽可能使用扩展方法编写查询。因此,以下是一个对我有效的查询:

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无法识别方法"System.LINQ.IQueryable"1[eNotify.Domain.Models.User]IsActive(System.LINQ[IQueryable`1[eNitify.Domain.Models.User])",并且此方法无法转换为存储表达式。

以下是导致该消息的查询:

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发布了一个运行良好的解决方案,但不如最初的问题查询那么好。我向英孚团队提出了这个问题,经过调查,他们想出了一个更优雅的解决方案。我已将其作为已接受的答案发布在下面。

实体框架IQueryable扩展方法不能用作子查询

我最终在GitHub上向实体框架团队提出了这个问题。你可以在这里看到这个线程,并对它发生的原因进行了完整的描述:

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

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

您可以为User模型创建一个分部类,其中包含一个静态类:

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

它没有扩展方法那么优雅,但我认为它应该做到。。。