2015-03-13 6 views
0

У меня есть запрос linq, который возвращает последнюю страницу, на которую пользователь смотрел, на основе таблицы обращений к странице. Поля: TimeStamp, UserID и URL, которые регистрируются из активности пользователя. Запрос выглядит так:Что я могу сделать, чтобы улучшить скорость запроса?

public static IQueryable GetUserStatus() 
{ 
    var ctx = new AppEntities(); 
    var currentPageHits = ctx.Pagehits 
     .GroupBy(x => x.UserID) 
     .Select(x => x.Where(y => y.TimeStamp == x.Max(z => z.TimeStamp))) 
     .SelectMany(x => x); 

    return currentPageHits.OrderByDescending(o => o.TimeStamp); 
} 

Запрос работает отлично, но работает медленно. Наш DBA заверяет нас, что таблица имеет индексы во всех правильных местах и ​​что проблема должна быть связана с запросом.

Есть ли что-то по своей сути неправильно или BAD с этим, или есть более эффективный способ получить те же результаты?

+0

Кажется, это довольно простой запрос ... Я не думаю, что он может быть оптимизирован очень. – xanatos

+0

Исправьте меня, если я ошибаюсь, но не является PLINQ (Parallel LINQ) жизнеспособным вариантом здесь? https://msdn.microsoft.com/en-us/library/dd460688%28v=vs.110%29.aspx – cubrr

+0

Вы должны зарегистрировать SQL, который генерируется из запроса, и проанализировать его, чтобы убедиться, что он эффективен. –

ответ

2

Итак, вы пытаетесь реализовать DENSE_RANK() OVER (PARTITION BY UserID ORDER BY TimeStamp DESC) с LINQ? Таким образом, все последние записи для каждой группы пользователей в соответствии с Timestamp. Вы можете попробовать:

public static IQueryable GetUserStatus() 
{ 
    var ctx = new AppEntities(); 
    var currentPageHits = ctx.Pagehits 
     .GroupBy(x => x.UserID) 
     .SelectMany(x => x.GroupBy(y => y.TimeStamp).OrderByDescending(g=> g.Key).FirstOrDefault()) 
     .OrderByDescending(x => x.TimeStamp); 

    return currentPageHits; 
} 

Так это группировка пользователя-группы по TimeStamp, то она занимает последнюю группу (одну или несколько записей в случае связей). SelectMany сглаживает goups до записей. Я думаю, что это более эффективно, чем ваш запрос.

+0

Тим, это определенно быстрее, как минимум 2 секунды сбрил 10-секундный запрос, спасибо. Пришлось изменить '.First()' на '.FirstOrDefault()' хотя –

+0

@GordonCopestake: зачем вам 'FirstOrdefault'? Поскольку вы находитесь в 'user-group', должна быть хотя бы одна запись. Если я группирую это с помощью 'TimeStamp' в какую-то подгруппу, я получаю хотя бы одну запись. –

+0

'First()' дает YSOD с 'Метод 'First' может использоваться только как конечная операция запроса. Рассмотрим вместо этого метод 'FirstOrDefault' в этом случае. ' –

3

Вы можете попробовать:

var currentPageHits2 = ctx.Pagehits 
    .GroupBy(x => x.UserID) 
    .Select(x => x.OrderByDescending(y => y.TimeStamp).First()) 
    .OrderByDescending(x => x.TimeStamp); 

Но скорость должна быть такой же.

Обратите внимание, что существует тонкое различие между этим запросом и вашим ... С вашими, если UserId имеет два «Макс TimeStamp» PageHits с тем же TimeStamp, две «строки» будет будет возвращен, при этом будет возвращен только один.

+0

Я не знаю, поддерживается ли заказ в этом случае, но в Linq-To-Objects это будет еще лучше. 'ctx.Pagehits.OrderByDescending (x => x.TimeStamp) .GroupBy (x => x.UserID). Выберите (g => g.First())' –

+0

@TimSchmelter Да, поскольку в LINQ-to-objects GroupBy гарантированно поддерживает порядок. Но я думаю, что он потерялся «в переводе» – xanatos

+0

@TimSchmelter По MSDN: 'поведение запроса, возникающее в результате выполнения дерева выражений, которое представляет вызов GroupBy (IQueryable , выражение >) ** зависит от реализации типа исходного параметра **. Ожидаемое поведение состоит в том, что он группирует элементы источника по ключевому значению, которое получается путем вызова keySelector для каждого элемента. '* – xanatos

Смежные вопросы