【问题标题】:Complexity limits of Linq queriesLinq 查询的复杂性限制
【发布时间】:2014-03-24 08:13:15
【问题描述】:

我是 Linq 的忠实粉丝,我一直非常享受表达式树等的强大功能。但我发现,每当我试图对我的查询变得过于聪明时,我都会在框架中遇到某种限制:虽然查询在数据库上运行可能需要很短的时间(如性能分析器所示),但结果需要 ages 才能实现。当这种情况发生时,我知道我太花哨了,我开始将查询分解成更小的、一口大小的块 - 所以我有一个解决方案,尽管它可能并不总是最佳的。

但我想了解:

  • 是什么让 Linq 框架在实现查询结果方面超越了边缘?
  • 我在哪里可以了解具体化查询结果的机制?
  • Linq 查询是否有一定的可衡量复杂性限制,应该避免?
  • 已知哪些设计模式会导致此问题,哪些模式可以解决此问题?

编辑:根据 cmets 的要求,这是一个查询示例,我测量了它可以在几秒钟内在 SQL Server 上运行,但需要将近 2 分钟才能实现。我不会尝试在上下文中解释所有内容。它在这里只是为了让您可以查看构造并查看我正在谈论的示例:

Expression<Func<Staff, TeacherInfo>> teacherInfo =
    st => new TeacherInfo
    {
        ID = st.ID,
        Name = st.FirstName + " " + st.LastName,
        Email = st.Email,
        Phone = st.TelMobile,
    };

var step1 =
    currentReportCards.AsExpandable()
        .GroupJoin(db.ScholarReportCards,
                             current =>
                             new { current.ScholarID, current.AcademicTerm.AcademicYearID },
                             past => new { past.ScholarID, past.AcademicTerm.AcademicYearID },
                             (current, past) => new
                             {
                                 Current = current,
                                 PastCards =
                                 past.Where(
                                     rc =>
                                     rc.AcademicTerm.StartDate <
                                     current.AcademicTerm.StartDate &&
                                     rc.AcademicTerm.Grade == current.AcademicTerm.Grade &&
                                     rc.AcademicTerm.SchoolID == current.AcademicTerm.SchoolID)
                             });
// This materialization is what takes a long time:
var subjects = step1.SelectMany(x => from key in x.Current.Subjects
                            .Select(s => new { s.Subject.SubjectID, s.Subject.SubjectCategoryID })
                            .Union(x.PastCards.SelectMany(c => c.Subjects)
                                            .Select(
                                                s => new { s.Subject.SubjectID, s.Subject.SubjectCategoryID }))
         join cur in x.Current.Subjects on key equals
             new { cur.Subject.SubjectID, cur.Subject.SubjectCategoryID } into jcur
         from cur in jcur.DefaultIfEmpty()
         join past in x.PastCards.SelectMany(p => p.Subjects) on key equals
             new { past.Subject.SubjectID, past.Subject.SubjectCategoryID } into past
         select new
         {
             x.Current.ScholarID,
             IncludeInContactSection =
                 // ReSharper disable ConstantNullCoalescingCondition
                (bool?)cur.Subject.IncludeInContactSection ?? false,
             IncludeGrades = (bool?)cur.Subject.IncludeGrades ?? true,
             // ReSharper restore ConstantNullCoalescingCondition
             SubjectName =
                cur.Subject.Subject.Name ?? past.FirstOrDefault().Subject.Subject.Name,
             SubjectCategoryName = cur.Subject.SubjectCategory.Description,
             ClassInfo = (from ce in myDb.ClassEnrollments
                             .Where(
                                 ce =>
                                 ce.Class.SubjectID == cur.Subject.SubjectID
                                 && ce.ScholarID == x.Current.ScholarID)
                             .Where(enrollmentExpr)
                             .OrderByDescending(ce => ce.TerminationDate ?? DateTime.Today)
                     let teacher = ce.Class.Teacher
                     let secTeachers = ce.Class.SecondaryTeachers
                     select new
                     {
                         ce.Class.Nickname,
                         Primary = teacherInfo.Invoke(teacher),
                         Secondaries = secTeachers.AsQueryable().AsExpandable()
                            .Select(ti => teacherInfo.Invoke(ti))
                     })
                .FirstOrDefault(),
             Comments = cur.Comments
                 .Select(cc => new
                 {
                     Staff = cc.Staff.FirstName + " "
                                     + cc.Staff.LastName,
                     Comment = cc.CommentTemplate.Text ??
                                         cc.CommentFreeText
                 }),
             // ReSharper disable ConstantNullCoalescingCondition
             DisplayOrder = (byte?)cur.Subject.DisplayOrder ?? (byte)99,
             // ReSharper restore ConstantNullCoalescingCondition
             cur.Percentile,
             cur.Score,
             cur.Symbol,
             cur.MasteryLevel,
             PastScores = past.Select(p => new
                {
                    p.Score,
                    p.Symbol,
                    p.MasteryLevel,
                    p.ScholarReportCard
                     .AcademicTermID
                }),
             Assessments = cur.Assessments
                 .Select(a => new
                 {
                     a.ScholarAssessment.AssessmentID,
                     a.ScholarAssessment.Assessment.Description,
                     a.ScholarAssessment.Assessment.Type.Nickname,
                     a.ScholarAssessment.AssessmentDate,
                     a.ScoreDesc,
                     a.ScorePerc,
                     a.MasteryLevel,
                     a.ScholarAssessment.Assessment.Type.AssessmentFormat,
                     a.ScholarAssessment.PublishedStatus,
                     a.ScholarAssessment.FPScore,
                     a.ScholarAssessment.TotalScore,
                     a.ScholarAssessment.Assessment.Type.ScoreType,
                     a.ScholarAssessment.Assessment.Type.OverrideBelowLabel,
                     a.ScholarAssessment.Assessment.Type.OverrideApproachingLabel,
                     a.ScholarAssessment.Assessment.Type.OverrideMeetingLabel,
                     a.ScholarAssessment.Assessment.Type.OverrideExceedingLabel,
                 })
            })
        .ToList();

【问题讨论】:

  • 我经常认为会发生这种情况,但永远无法真正确定它是 Linq 问题。您是否有机会提供这些复杂查询之一的示例?

标签: c# linq entity-framework linq-to-entities


【解决方案1】:

Linq 对某些任务使用延迟执行,例如在迭代 IEnumerable&lt;&gt; 时,因此您所说的实现包括一些实际的数据获取。

var reportCards = db.ScholarReportCards.Where(cr => ...); // this prepares the query 
foreach (var rc in reportCards) {} // this executes your query and calls the DB

我认为,如果您在 SQL 服务器上跟踪/计时查询,您可能会在“实现”步骤中看到一些查询。这个问题甚至可能会因诸如 the "Select N+1" problem 之类的反模式而加剧:例如,您的请求中似乎没有包含 AcademicTerm 对象;如果您不解决这些将导致选择 N+1,即对于每个 ScholarReportCard,都会调用数据库以延迟解析附加的 AcademicTerm

如果我们专注于 Linq to DB 方面,至少尽量不要:

  • 选择n+1:Include你需要的相关数据表
  • 选择的数据过多:仅在选择中包含您需要的列(Include 在您需要的表格上)

【讨论】:

  • 我对“选择 N+1”模式很熟悉,并且非常确信这里不会发生这种情况。 SQL 性能分析器显示只有一个查询正在执行;如果它是“选择 N+1”,我会看到一百个子查询正在执行。
  • 我不太确定,因为您没有包含 DataContext 初始化,所以我认为提及它会有所帮助。实际上,选择 N+1 会导致在主查询之后出现许多子查询
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-08
  • 2014-02-23
  • 1970-01-01
相关资源
最近更新 更多