【问题标题】:IQueryable not generating the desired query in entity framework coreIQueryable 未在实体框架核心中生成所需的查询
【发布时间】:2021-05-08 12:52:23
【问题描述】:

我有一些类似于以下的实体:

public class Teacher
{
    public int TeacherId { get; set; }
    public string TeacherName { get; set; }
    public int Age { get; set; }
    public string CurrentGrade { get; set; }
    public int YearsTeaching { get; set; }
    pubilc ICollection<StudentFeedback> StudentFeedback { get; set; }
}

public class StudentFeedback
    {
        public int StudentFeedBackId { get; set; }
        public int TeacherId { get; set; }
        public int StudentId { get; set; }
        public string Feedback { get; set; }

        public Teacher Teacher { get; set; }
        public Student Student { get; set; }
    } 
    
public class Student
{
    public int StudentId { get; set; }
    public string StudentName { get; set; }
    public int Age { get; set; }
    public string CurrentGrade { get; set; }
}

我有一个存储库,其中我想要返回一个教师或教师列表,返回的 StudentFeedback 属于正在查看它的学生(studentId 存储在令牌中)。

所以,假设我有一个教师 (teacherId) 和一个学生 (userId) 正在访问 API 端点。我目前有以下:

int teacherId = 2;
int userId = 20; // This is the currently logged in user, extracted from the token.

var query = _context.Teachers.AsQueryable();/* _context is the DataContext*/
query = query.Where(t => p.TeacherId == teacherId);
query = query.Where(u => u.StudentFeedback.Any(x => x.StudentId == userId));

但是,这仍然会返回所有学生的所有 StudentFeedback,只要 userId(学生)有为相关教师提供的反馈。我查看了执行的查询,问题是 studentId 谓词在错误的位置。查询的一个非常粗略的版本是:

SELECT      *
FROM        (   SELECT  t.*
                FROM    dbo.Teachers t
                WHERE   (t.TeacherId = 2)
                        AND EXISTS (   SELECT   1
                                       FROM     dbo.StudentFeedback t0
                                       WHERE    (t.TeacherId = t0.TeacherId)
                                                AND (t0.StudentId = 20))) p
LEFT JOIN   dbo.StudentFeedback sf ON p.TeacherId = sf.TeacherId

应该是这样的

SELECT      *
FROM        (   SELECT  t.*
                FROM    dbo.Teachers t
                WHERE   (t.TeacherId = 2)) p
LEFT JOIN   dbo.StudentFeedback sf ON p.TeacherId = sf.TeacherId
                                      AND   sf.StudentId = 20

但我不知道如何做到这一点。我设置的 IQueryable 谓词是否有问题,或者我错过了数据上下文中模型构建器中的一些逻辑? 谢谢。

编辑:我正在使用 Entity Framework Core 5.0.2,我也在使用 Automapper,代码如下:

query.ProjectTo<TeacherDTO>(_mapper.ConfigurationProvider).AsNoTracking()

这是我目前返回的内容:

[
        {
            "teacherid": 2,
            "teacherName": "Jane Smith",
            "age": 35,
            "currentGrade": "One",
            "yearsTeaching": 12,
            "studentFeedback": [
                {
                    "studentFeedBackId": 12,
                    "teacherId": 6,
                    "studentId": 20,
                    "feedback": "Ms Smith is my favorite teacher"
                } ,
                {
                    "studentFeedBackId": 16,
                    "teacherId": 6,
                    "studentId": 43,
                    "feedback": "Ms Smith was so kind to me"
                } ,
                {
                    "studentFeedBackId": 21,
                    "teacherId": 6,
                    "studentId": 89,
                    "feedback": "Thank you Mrs Smith for being my teacher. I learned a lot."
                } 
            ]
        }
    ]

这是我想要回来的:

[
    {
        "teacherid": 2,
        "teacherName": "Jane Smith",
        "age": 35,
        "currentGrade": "One",
        "yearsTeaching": 12,
        "studentFeedback": [
            {
                "studentFeedBackId": 12,
                "teacherId": 6,
                "studentId": 20,
                "feedback": "Ms Smith is my favorite teacher"
            } 
        ]
    }
]

【问题讨论】:

  • 我添加了EF版本。 “返回所有用户的所有 StudentFeedback”是指每个学生的 StudentFeedback。我将更新问题中的措辞。第二个查询绝对是我想要的。我已经直接在数据库上运行查询以获得我想要的结果。
  • 我已经提供了我要返回的内容与我想要返回的内容的示例
  • 嗨格特。我尝试了query = query.Include(u =&gt; u.StudentFeedback.Where(x =&gt; x.StudentId == userId));,但得到了完全相同的结果,但查询略有不同。在这种情况下,根本没有 EXISTS 子句。

标签: api .net-core entity-framework-core automapper iqueryable


【解决方案1】:

如果你想要一个连接,你应该在你的 linq 语句中使用 Join 方法。见https://www.tutorialsteacher.com/linq/linq-joining-operator-join。你得到的正是你在查询中写的。 Where(u =&gt; u.StudentFeedback.Any(x =&gt; x.StudentId == userId)); .Any 翻译为存在。

【讨论】:

  • 如果你想加入。也许第一个问题应该是:你真的想要这个加入吗? join 语句应该很少用在 LINQ-to-Entities 查询中。导航属性的存在是有原因的。 如果 查询应该返回StudentFeedback 数据(这似乎与问题状态相反)应该使用Include
  • Include 不需要 ProjectTo
【解决方案2】:

如果您使用 Net5 EF,您只需将 Students 属性添加到 Teacher 类:

public class Teacher
{
    .....
    pubilc ICollection<Student> Students { get; set; }
    pubilc ICollection<StudentFeedback> StudentFeedbacks { get; set; }
}

您可以这样使用查询:

var query = _context.Teachers.Include(i=> i.StudentFeedbacks)
.Where(t => 
t.TeacherId == teacherId 
&&  t.StudentFeedbacks.Any(x => x.StudentId == userId))
.ToArray();

【讨论】:

  • 您可以在没有任何Include 的情况下实现相同的目的,只需在映射本身中添加where
  • 嗨@LucianBargaoanu 您如何将userId 是变量的映射添加到哪里?
【解决方案3】:

感谢@LucianBargaoanu 通过说在映射本身中包含where 为我指明了正确的方向。解决方法是在使用Automapper时使用Parameterization

此页面中的代码显示了一个示例:

string currentUserName = null;
cfg.CreateMap<Course, CourseModel>()
    .ForMember(m => m.CurrentUserName, opt => opt.MapFrom(src => currentUserName));

然后

dbContext.Courses.ProjectTo<CourseModel>(Config, new { currentUserName = Request.User.Name });

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-20
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    • 2012-03-09
    • 1970-01-01
    • 2021-07-14
    相关资源
    最近更新 更多