如何优化这个linq查询?

3

我有以下的linq查询:

var allnews = from a in db.News
                              where !(from c in db.NewsViews
                                      where c.UserGuid == thisUser.UserGuid
                                      select c.NewsGuid).Contains(a.NewsGuid)
                              orderby a.Date descending
                              select a;

我想知道最佳的优化方式是什么?或者查询分析器会为我完成这个任务吗?
编辑:目的是获取用户尚未看过的所有新闻项目。因此,一旦用户查看了一个项目,我就会将该项目存储在NewsViews中。而新闻本身则存储在News中。

实际上并不慢,只是现在表格中几乎没有数据。只是觉得这不是最好的方法,肯定有一种更快的“更好”的解决方案。 - rksprst
3
之后再担心速度吧,等到它真的很重要的时候。 - corymathews
6个回答

4
子查询似乎没有使用 a,因此
      //untested
      var allnews = from a in db.News
                    let excluders = from c in db.NewsViews
                                    where c.UserGuid == thisUser.UserGuid
                                    select c.NewsGuid   
                          where !excluders.Contains(a.NewsGuid)
                          orderby a.Date descending
                          select a;

请注意,您现在是通过LINQ进行SQL优化(顺便问一下,这是L2S还是EF?)。


普通的SQL优化已经够困难了。必须使用真实数据进行测量和分析。很可能@Joachim使用多个内连接子查询的方法更好。


+1 表示很难估计这个的性能 - 要测试一下! - Eamon Nerbonne
顺便说一下,将子查询放入let子句中不会改变原始查询的语义,因此如果这样做可以改善性能,那将是令人惊讶的 - 尽管你永远不知道... - Eamon Nerbonne
@Earnon:我想这可能是一些缓存,但你说得对,在IEnumerable中并不明显。 但是这是一个IQueryable,我依靠SQL服务器。 - H H
是的,LINQ 会生成什么样的 SQL,内部执行计划 SQL 又会生成什么样的 SQL 并不明显 - 测试确实是最好的方法。 - Eamon Nerbonne

2

不要使用 contains,你可以将以下内容添加到内部查询的 where 语句中:

... 并且 c.newsguid == a.newsguid

并在内部查询中使用 .Any()

var allnews = from a in db.News
                  where !(from c in db.NewsViews
                          where c.UserGuid == thisUser.UserGuid
                            and c.NewsGuid == a.NewsGuid).Any()
                   orderby a.Date descending
                   select a;

是的,它返回集合/查询中是否有任何项。在子查询中包含等式条件具有完全相同的结果,只是子查询中的集合更小,并且没有使用'in'语句。 - Joachim VR
好的,所以:new[]{false,false}.Any() 是 true,因为集合不是空的 - 事实上,布尔值是否为假与此无关。 - Eamon Nerbonne
我不确定你具体指的是哪一点,但是为了明确起见,你目前的表述并不等同于OP的表述——对于已查看任何新闻项目的用户,它将不返回任何新闻项目。 - Eamon Nerbonne
非常抱歉,我明白你的观点。我的错,选择应该是“and”,正如我在解释中所说的那样。这只是一个复制错误。 - Joachim VR
从语法上讲,您需要执行类似于以下内容的操作... where !(from c in db.NewsViews where c.UserGuid == thisUser.UserGuid && c.NewsGuid == a.NewsGuid select 0).Any() - Eamon Nerbonne
没错,C#需要使用select语句,而不是VB.NET。已经有一段时间了。 - Joachim VR

1
我假设目标是按日期降序检索NewsViews:
db.News.OrderByDescending(a => a.Date).NewsViews;

当然,这假定您已经在模型中设置了新闻和新闻视图实体之间的关联。通过提前设置关联,子查询变得不必要。

更新:

我已经使用LINQ-to-SQL约18个月了,并且我一直在使用与您所示相同的结构来进行NOT IN查询。正如我之前所述,如果您提前在模型中设置关联并在数据库本身中使用索引,则可能会获得一些性能提升,但从LINQ的角度来看,我认为您已经尽可能地优化了,而无需诉诸于一个不必要的加密查询语句。


1

这里有一个替代的表述:

from newsitem in db.News
join viewing in (
       from viewing in db.NewsViews
       where viewing.UserGuid == thisUser.UserGuid
       select viewing
) on newsitem.NewsGuid equals viewing.NewsGuid into usersviewings
where !usersviewings.Any()
orderby newsitem.Date descending
select newsitem;

但是关于这是否更快 - 这取决于任何人的猜测; 试试看。基本上,您正在执行一个左连接,其中左侧部分被过滤并且不得返回任何结果 - 这不会很好地索引,据我所知。执行引擎将需要扫描新闻集中的所有行,如果您由SQL支持,则表扫描不是您的朋友。话虽如此,除非您实际上希望这是一个巨大的表,否则可能并不重要,特别是如果您只报告前N个命中...


0

在这里你可以做出最好的优化决策,那就是允许从NewsViews导航到News... 但由于不存在这样的导航,我不得不在优化方面做一些小手脚。

db.News.Join(db.News.Select(n => n.NewsGuid)
    .Except(db.NewsViews
        .Where(c => c.UserGuid == thisUser.UserGuid)
        .Select(c => c.NewsGuid)
    ), n1 => n1.NewsGuid, n2 => n2, (n1, n2) => new { n1 = n1, n2 = n2 })
    .Select(anon => anon.n1);

当您尝试查询一个列表不包含另一个列表时,异常将生成最佳执行SQL。由于从NewsView到News没有导航,因此我们必须使用内部连接来欺骗返回新闻。

另一种方法是使用GroupJoin。

db.News
    .GroupJoin(db.NewsViews, n => n.NewsGuid, nv => nv.NewsGuid, (n, nv) => new { News = n, NewsViewList = nv })
    .Where(anon => anon.NewsViewList != null) // I don't remember the best test here, either it's not null, or the count > 0 :-)
    .OrderByDescending(anon => anon.News.Date)
    .Select(anon => anon.News);

至少我会这样做。


0
也许这是我对 Linq 知识的不足,但是在 NewsViews 中一个列为空的情况下进行左连接,这似乎比制作子查询并比较两者更好。

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