简体   繁体   中英

Query with List.Contains() not cached

I am trying to find a way to "cache" the following query, as the compilation of it increases my total render time (this is used in ASP.NET MVC 5 with EF 6.1) from <50ms to ~700ms:

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 is a List<string> at the moment and contains normally 1-3 items.

As far as I understood the IEnumerable.Contains can't be cached as the length is not known at compile time, but I also tried using an array which did not help.

UPDATE: The following query is wrong, as it makes "AND"-combination and returns nothing as soon as two cultures are requested.
At the moment I use the following, which gives me my <50ms render time back, but seems "hacky" to me:

 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(); 

Is there a way to achive the same result without "building the custom query"?

Is there a way to write this query so it is cached by EF?

I do not really care about the ~700ms "on the first hit", but this query is executed on nearly every page (as it loads the text content of a page) - sometimes even multiple times - and would decrease the whole sites performance dramatically...

PS: I am also open to write the query totaly different.
I get an IQueryable<T> with the items and the list with the cultures to get for each item. I then need the newest revision of each item which the culture requested.

The multiple cultures are actualy only used as a "fall-back" - so if I get { "de-AT", "de", "en" } I search the result first for a revision with de-AT , then de and finally en and return the first one found.
If this can already be done "in the database" it would be perfect, but I have no idea how that could work in SQL (the "culture tree" is also saved in the DB and queried/cached elsewhere, so it could be "joined").

For now I solved it using LINQkit, but I would still prefer beeing able to use 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();

and

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);
}

EDIT :

You may want to precompile the query. More about precompiling queries here:

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


You can just store the query in a static variable:

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);

And then invoke it every time to get the result without causing it to recompile:

var revisions = query.FirstOrDefault();

You can obtain the same with this mechanism:

private static var query = null;

...
...

if (query == null)
{
    query = ... // initialize it here
}

This way you don't need all references to be static, too.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM