Linq Lambda表达式中的布尔短路求值

7

我有以下的Linq Lambda表达式:

private IEnumerable<SubjectSelectorSubjectGroup> GetSubjectList()
{
    User user = db.Users.Find(WebSecurity.CurrentUserId);
    return db.RequiredSubjects.Where(r => !r.Subject.Name.Contains("Home"))
                              .GroupBy(r => r.Subject)
                              .OrderByDescending(r => r.Count())
                              .Select(r => new SubjectSelectorSubjectGroup()
                              {
                                  SubjectId = r.Key.SubjectId,
                                  SubjectName = r.Key.Name,
                                  IsInFavourites = HttpContext.Current.Request.IsAuthenticated &&
                                                  (user.Elective1 != null && user.Elective1.SubjectId == r.Key.SubjectId ||
                                                   user.Elective2 != null && user.Elective2.SubjectId == r.Key.SubjectId ||
                                                   user.Elective3 != null && user.Elective3.SubjectId == r.Key.SubjectId),
                                  Occurrences = r.Count()
                              });
}

当用户未登录时,此函数中的user变量为null。这本不应该成为问题,因为短路布尔运算应该解决这个问题。问题在于,它并没有!相反,抛出了一个System.NullReferenceException
当用户为null时,HttpContext.Current.Request.IsAuthenticated返回false。我通过注释引用user变量的括号部分来检查这一点,然后表达式正确地计算出来。
有人知道为什么在这种情况下Linq to Sql会尝试去取消引用user变量,而实际上这是不必要的吗?有人对此问题有解决方法吗?

当引用为空时,在内存中抛出“NullReferenceException”。这表示查询的某个部分未被转换为SQL,而是在内存中评估。您能检查发出的SQL查询吗?罪魁祸首应该是未被转换为SQL的部分。 - Gert Arnold
嗨,Gert。我已经通过创建List<string>解决了这个问题。但是,作为一个有趣的问题,我该如何检查发出的SQL查询?我还是Linq的新手,仍在努力弄清楚这些东西。感谢您的评论。 - BruceHill
你可以使用 DataContext.Log 或者 Sql Profiler。 - Gert Arnold
3个回答

6
整个表达式被翻译成SQL并进行计算,这意味着&&运算符不像预期那样短路。
您可以通过构建所需搜索的ElectiveX.SubjectId的列表或数组,然后在查询中使用tmpList.Contains(r.Key.SubjectId)来解决问题。这将被翻译为一个WHERE IN (...)的SQL表达式。

2
感谢您的解释和建议使用列表。像您建议的那样,我已经使用列表来解决了问题。我会在另一个回答中放置我的解决方案的代码,但我会将您的回答标记为正确的。感谢您的帮助。 - BruceHill
2
我还观察到短路计算并没有按预期工作。我认为,在幕后将C#表达式转换为SQL并不意味着Microsoft可以摆脱遵守C#语言规范的义务,参见7.12条件逻辑运算符。 - R. Schreurs

3

我使用 Anders Abel 给出的建议解决了这个问题。

private IEnumerable<SubjectSelectorSubjectGroup> GetSubjectList()
{
    List<string> userSubjects = new List<string>();
    if (HttpContext.Current.Request.IsAuthenticated)
    {
        User user = db.Users.Find(WebSecurity.CurrentUserId);
        if (user.Elective1 != null) { userSubjects.Add(user.Elective1.SubjectId); }
        if (user.Elective2 != null) { userSubjects.Add(user.Elective2.SubjectId); }
        if (user.Elective3 != null) { userSubjects.Add(user.Elective3.SubjectId); }
    }

    return db.RequiredSubjects.Where(r => !r.Subject.Name.Contains("Home"))
                              .GroupBy(r => r.Subject)
                              .OrderByDescending(r => r.Count())
                              .Select(r => new SubjectSelectorSubjectGroup()
                              {
                                   SubjectId = r.Key.SubjectId,
                                   SubjectName = r.Key.Name,
                                   IsInFavourites = userSubjects.Contains(r.Key.SubjectId),
                                   Occurrences = r.Count()
                              });
}

1
我能想到这种行为的唯一原因是你的查询正在被翻译成 SQL(因为它是 LINQ to SQL),并且对于 user.Elective,它正在尝试生成一个 CASE 语句。这就是为什么你会收到错误信息的原因。

感谢Habib解释我的代码为什么会抛出空指针异常。 - BruceHill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接