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.