一对多的LINQ查询重复执行

6
我正在将LINQ to SQL的结果投影到强类型类Parent和Child中。这两个查询之间的性能差异很大:
慢查询 - 从DataContext记录可以看出,每个父项都会进行一次单独的数据库调用。
var q = from p in parenttable
        select new Parent()
        {
            id = p.id,
            Children = (from c in childtable
                        where c.parentid = p.id
                        select c).ToList()
        }
return q.ToList()  //SLOW

快速查询 - 从数据上下文的日志中可以看到只有一个数据库查询,返回所有所需数据

var q = from p in parenttable
        select new Parent()
        {
            id = p.id,
            Children = from c in childtable
                        where c.parentid = p.id
                        select c
        }
return q.ToList()  //FAST

我想强制LINQ使用第二个例子的单查询风格,但直接将Parent类填充为其Children对象。否则,Children属性是一个IQuerierable,必须查询才能暴露Child对象。
所引用的问题似乎没有解决我的情况。使用db.LoadOptions无法工作。也许它需要类型是已在DataContext中注册的TEntity。
   DataLoadOptions options = new DataLoadOptions();
   options.LoadWith<Parent>(p => p.Children);
   db.LoadOptions = options;

请注意:Parent和Child是简单类型,不是Table类型。Parent和Child之间没有上下文关系。子查询是即席查询。
问题的核心:在第二个LINQ示例中,我实现了IQueryable语句,并且没有调用ToList()函数,但由于某种原因LINQ知道如何生成一个可以检索所有所需数据的单个查询。我该如何像第一个查询那样填充我的即席投影以获得实际数据?如果有人能帮我更好地表达我的问题,我将不胜感激。

“Child” 怎么可能是一个“简单类型”?它应该是一个映射类型。而 “parenttable” 是一个“Table”还是之前的 Linq 查询结果呢?一些 Linq 语句会导致 L2S 从连接转换为 N+1。 - Gert Arnold
1
重复标记:您知道实体框架和 LINQ to SQL 之间的区别吗? - Gert Arnold
@GertArnold 在EF/L2S的LINQ部分中,虽然常常会使类似的问题在功能上重复,但在这种情况下,我认为您是完全正确的;它们不是重复的。重新开放。 - Andrew Barber
4个回答

7
请记住,LINQ查询依赖于延迟执行。在第二个查询中,你实际上没有获取任何有关子项的信息。你已经创建了查询,但尚未执行它们以获取这些查询的结果。如果您对列表进行迭代,然后对每个项的Children集合进行迭代,您会发现它需要与第一个查询一样多的时间。
您的查询本质上也非常低效。您正在使用嵌套查询来表示Join关系。如果您改用Join,则查询将能够通过查询提供程序和数据库进行适当优化,以更快地执行。您可能还需要调整数据库上的索引以提高性能。以下是加入的示例:
var q = from p in parenttable
        join child in childtable
        on p.id equals child.parentid into children
        select new Parent()
        {
            id = p.id,
            Children = children.ToList(),
        }
return q.ToList()  //SLOW

我知道这个问题涉及到LINQ to SQL,但我想提一下,如果你正在使用Entity Framework,children上不需要使用ToList() - Brian Cauthon

1
我发现完成这个任务最快的方法是查询所有结果,然后对所有结果进行分组。请确保在第一个查询上执行.ToList(),以便第二个查询不会进行多次调用。
在这里,r 应该只使用单个数据库查询来实现您想要的功能。
            var q = from p in parenttable
                    join c in childtable on p.id equals c.parentid
                    select c).ToList();
            var r = q.GroupBy(x => x.parentid).Select(x => new { id = x.Key, Children=x });

1
不要先进行连接再进行分组,直接使用GroupJoin。 - Servy

0

您必须为数据加载设置正确的选项。

options.LoadWith<Document>(d => d.Metadata);

看看 this

P.S. 仅适用于 LINQToEntityInclude


这看起来应该是答案,但对我没有用。我尝试了dlo.LoadWith<Parent>(p => p.Children),仍然有多个数据库查询。 - Paul
@Paul,你不需要使用parenttable。你是否通过LINQ2SQL生成了Parent实体?那么当你从上下文请求Parent时,你的实体已经包含了Children。 - Hamlet Hakobyan

0
第二个查询速度快,正是因为没有填充Children。
而第一个查询速度慢,只是因为正在填充Children。
选择最适合您需求的那个,您不能同时拥有它们的功能!
编辑:
正如@Servy所说:

在第二个查询中,您实际上没有获取有关子项的任何信息。您已经创建了查询,但尚未执行这些查询以获取查询结果。如果您遍历列表,然后遍历每个项目的Children集合,您会发现它需要与第一个查询一样多的时间。


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