给定以下示例,使用 LINQ 查询相关表的最佳做法是什么?

What is the best practice for querying related tables using LINQ given the following example?

假设我正在从几个相关表中生成一个视图模型。这样查询的advantages/differences/disadvantages是什么:

var enrollment = db.enrollment
.Include(d => d.cohort.OldCourses.OldCourseSections.Select(f => f.OldCoursePages))
            .Include(d => d.OldProgress)
            .FirstOrDefault(b => b.UserID == currentuser && b.cohort.OldCourses.Titleabbrev == courseabbrev);
var viewModel = new OldSectionViewModel();

        viewModel.OldCourseTitle = enrollment.cohort.OldCourses.OldCourseTitle;
        viewModel.OldCourseSec_title = enrollment.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCourseSec_title;
        viewModel.Meta = enrollment.cohort.OldCourses.Meta;
        viewModel.Titleabbrev = enrollment.cohort.OldCourses.Titleabbrev;
        viewModel.OldCourseSec_abbrev = enrollment.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCourseSec_abbrev;
        viewModel.progress = currentprogress;
        viewModel.pages = enrollment.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCourse_Page_Total;
        viewModel.EnrollmentID = enrollment.EnrollmentID;
        viewModel.OldCourseSectionID = enrollment.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCourseSectionID;

        viewModel.OldCoursePage_title = enrollment.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCoursePages.FirstOrDefault(g => g.OldCoursePage_Order ==
        currentprogress).OldCoursePage_title;

        viewModel.OldCoursePage_HTML = enrollment.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCoursePages.FirstOrDefault(g => g.OldCoursePage_Order ==
        currentprogress).OldCoursePage_HTML;

        viewModel.OldCoursePage_Order = enrollment.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCoursePages.FirstOrDefault(g => g.OldCoursePage_Order ==
        currentprogress).OldCoursePage_Order;

或者这样:

var viewModel = db.enrollment
        .Where(b => b.UserID == currentuser && b.cohort.OldCourses.Titleabbrev == courseabbrev)

        .Select(x => new OldSectionViewModel
        {
            OldCourseTitle = x.cohort.OldCourses.OldCourseTitle,
            OldCourseSec_title = x.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCourseSec_title,
            Meta = x.cohort.OldCourses.Meta,
            Titleabbrev = x.cohort.OldCourses.Titleabbrev,
            OldCourseSec_abbrev = x.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCourseSec_abbrev,
            progress = currentprogress,
            pages = x.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCourse_Page_Total,
            EnrollmentID = x.EnrollmentID,
            OldCourseSectionID = x.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCourseSectionID,

            OldCoursePage_title = x.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCoursePages.FirstOrDefault(g => g.OldCoursePage_Order ==
            currentprogress).OldCoursePage_title,

            OldCoursePage_HTML = x.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCoursePages.FirstOrDefault(g => g.OldCoursePage_Order ==
            currentprogress).OldCoursePage_HTML,

            OldCoursePage_Order = x.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev).OldCoursePages.FirstOrDefault(g => g.OldCoursePage_Order ==
            currentprogress).OldCoursePage_Order,

        }).Single();

假设'currentprogress'是一个在别处设置的整数。在我看来,第二个查询会稍微有效一些,因为我加载了我需要的特定字段,而第一个查询我加载了整个表。然而,我对这些事情的理解非常基础,我很想知道究竟哪一个更有效,为什么来自真正知道他们在说什么的人?

这两个查询都符合预先加载的条件吗?

简单地说,在第一种方法中,您将在单个查询中包含所有相关表。在第二种方法中,您将生成 N+1 个查询。

虽然一般来说您应该避免 N+1 查询情况,但这也很大程度上 取决于数据的情况。一次查询如此多的相关表会带来大量数据,而执行这些连接的性质意味着您将要减少 所有 相关项目。例如,如果您实际上只需要每个项目的第一个相关项目,那么通过执行一个大查询,您将返回大量您永远不会使用的数据。

执行 N+1 个查询可能意味着向您的服务器发送大量查询,但 "a lot" 的定义是可变的。如果它最终是 10-20 个相对较小的查询,并且您有一个具有大量资源的良好数据库服务器实例,那么它可能无关紧要,而且您最好只选择您实际需要的数据.

此外,您可以使用内存缓存,这样您可能只需要每小时 运行 这些查询一次或最多一次。您只需要研究任一选项的情况并确定最终最有效/最有意义的选项。

var viewModel = db.enrollment
    .Where(b => b.UserID == currentuser && b.cohort.OldCourses.Titleabbrev == courseabbrev).ToList()
    .Select(x => 
        {
           var oldCourseSection = x.cohort.OldCourses.OldCourseSections.FirstOrDefault(f => f.OldCourseSec_abbrev == secabbrev);
           var oldCourseSectionPage = oldCourseSection.OldCoursePages.FirstOrDefault(g => g.OldCoursePage_Order == currentprogress);

           return new OldSectionViewModel()
            {
                OldCourseTitle = x.cohort.OldCourses.OldCourseTitle,
                OldCourseSec_title = oldCourseSection.OldCourseSec_title,
                Meta = x.cohort.OldCourses.Meta,
                Titleabbrev = x.cohort.OldCourses.Titleabbrev,
                OldCourseSec_abbrev = oldCourseSection.OldCourseSec_abbrev,
                progress = currentprogress,
                pages = oldCourseSection.OldCourse_Page_Total,
                EnrollmentID = x.EnrollmentID,
                OldCourseSectionID = oldCourseSection.OldCourseSectionID,
                OldCoursePage_title = oldCourseSectionPage.OldCoursePage_title,
                OldCoursePage_HTML = oldCourseSectionPage.OldCoursePage_HTML,
                OldCoursePage_Order = oldCourseSectionPage.OldCoursePage_Order,

            };}).Single();

重点是在 select 中加载变量,您需要使用方括号加上 return 关键字。这是一个关于如何做到这一点的小例子。它允许您在匿名或 class 对象的 return 之前 运行 编码(两者都有效)

 public class Item { public int Price { get; set; } = 0; public string Name { get; set; } = ""; }

 static void Main(string[] args)
    {           
        var Collection = new List<Item>();

        var itemPrices = Collection.Select(item =>
        {
            var x = 10;
            var y = item.Price;
            return new { ItemName = item.Name, ItemPrice = x * y };
        }).ToList();

        itemPrices.ForEach(itemData =>
        {
            Console.WriteLine(itemData.ItemName + " " + itemData.ItemPrice.ToString());
        });
    }