【问题标题】:Query with List.Contains() not cached使用未缓存的 List.Contains() 进行查询
【发布时间】:2014-08-07 21:06:47
【问题描述】:

我正在尝试找到一种“缓存”以下查询的方法,因为它的编译将我的总渲染时间(这在 EF 6.1 的 ASP.NET MVC 5 中使用)从

var revisions = from i in items
                from r in i.Revisions
                where cultures.Contains(r.Culture.CultureCode)
                group r by new { r.ItemId, r.Culture } into g
                select g.OrderByDescending(r => r.DateCreated).FirstOrDefault();

cultures 目前是 List<string>,通常包含 1-3 个项目。

据我了解,IEnumerable.Contains 无法缓存,因为在编译时长度未知,但我也尝试使用没有帮助的数组。

更新:以下查询是错误的,因为它会进行“AND”组合,并且在请求两种文化时不返回任何内容。
目前我使用以下,这给了我

var revisions = items.SelectMany(i => i.Revisions);
foreach (var culture in cultures)
    revisions = revisions.Where(r => r.Culture.CultureCode == culture);

var result = from r in revisions
             group r by new { r.ItemId, r.Culture } into g
             select g.OrderByDescending(r => r.DateCreated).FirstOrDefault();

有没有办法在不“构建自定义查询”的情况下获得相同的结果?

有没有办法编写这个查询以便它被 EF 缓存?

我并不真正关心“第一次点击”大约 700 毫秒,但这个查询几乎在每个页面上执行(因为它加载页面的文本内容) - 有时甚至多次 - 并且会减少整体网站性能显着...

P.S.:我也愿意写完全不同的查询。
我得到一个带有项目的IQueryable<T> 和包含每个项目的文化的列表。然后,我需要文化要求的每个项目的最新版本。

多种文化实际上仅用作“后备” - 所以如果我得到{ "de-AT", "de", "en" },我会先搜索结果以查找带有de-AT的修订版,然后是de,最后是en并返回找到第一个。
如果这已经可以“在数据库中”完成,那将是完美的,但我不知道它如何在 SQL 中工作(“文化树”也保存在数据库中并在其他地方查询/缓存,所以它可能是“加入”)。

【问题讨论】:

  • 为什么要挑出 Contains()?其余的也不缓存。
  • @HenkHolterman:因为这是原因(AFAIK EF 确实在内部缓存了它) - 如果我删除 Contains 它也是
  • 你没有在 MVC 中保持上下文打开是吗?
  • @HenkHolterman:不——DbContext 在它自己的using 块中用于每个请求。但是没有Contains EF 正在缓存“已编译的 LINQ 查询”,但没有...
  • 如果查询 w/o Contains 足够快,然后添加 .ToList().Contains(...)

标签: c# performance linq entity-framework ienumerable


【解决方案1】:

目前我使用 LINQkit 解决了这个问题,但我仍然希望能够使用 Contains:

var revisions = itemQuery.SelectMany(i => i.Revisions
    .Where(r => r.State == ContentRevisionState.Published).OfType<R>());

revisions = revisions.ForList(cultures, 
    (p, c) => p.Or(r => r.CultureCode == c.CultureCode));

revisions = revisions.OrderByDescending(r => r.Culture.Position)
    .ThenByDescending(r => r.DateCreated)
    .Include(r => r.Author).Include(r => r.Culture);

var revision = await revisions.FirstOrDefaultAsync();

和

public static IQueryable<T> ForList<T, K>(this IQueryable<T> items, List<K> list,
    Func<Expression<Func<T, bool>>, K, Expression<Func<T, bool>>> condition)
{
    var predicate = PredicateBuilder.False<T>();
    foreach (var item in list)
    {
        var tmp = item; // local copy for deferred execution
        predicate = condition(predicate, tmp);
    }
    return items.AsExpandable().Where(predicate);
}

【讨论】:

    【解决方案2】:

    编辑:

    您可能想要预编译查询。更多关于预编译查询的信息:

    http://msdn.microsoft.com/en-us/magazine/ee336024.aspx


    您可以将查询存储在静态变量中:

    private static var query = from i in items
                               from r in i.Revisions
                               where cultures.Contains(r.Culture.CultureCode)
                               group r by new { r.ItemId, r.Culture } into g
                               select g.OrderByDescending(r => r.DateCreated);
    

    然后每次都调用它来获取结果而不导致它重新编译:

    var revisions = query.FirstOrDefault();
    

    您可以通过这种机制获得相同的效果:

    private static var query = null;
    
    ...
    ...
    
    if (query == null)
    {
        query = ... // initialize it here
    }
    

    这样你也不需要所有的引用都是静态的。

    【讨论】:

    • 那会更快吗?
    • 如果你强制预编译查询,它肯定不会每次都重新编译。我添加了指向 MSDN 源的链接。这是一篇很长的文章,我不能在这里复制粘贴。
    • 感谢您的输入,但这不起作用:CompiledQuery 不支持我的参数(IQueryable&lt;T&gt; 和 List&lt;string&gt;)并且您的“静态方法”缓存了“确切查询”,但 @ 987654328@ 和 cultures 确实从请求到请求发生变化 - 我只需要缓存“编译的 LINQ”(因为它在没有 Contains 的情况下发生)......
    猜你喜欢
    • 1970-01-01
    • 2012-05-08
    • 1970-01-01
    • 2018-06-24
    • 1970-01-01
    • 2019-04-04
    • 2020-04-03
    • 2010-10-27
    • 1970-01-01
    相关资源
    最近更新 更多