Удалить OrderBy из IQueryable <T>
У меня есть пейджинговый API, который возвращает строки, запрашиваемые пользователем, но только так много за один раз, а не всю коллекцию. API работает так, как планировалось, но мне нужно рассчитать общее количество доступных записей (для правильных расчётов страниц). В API я использую Linq2Sql, и я много работаю с IQueryable, прежде чем я, наконец, сделаю свои запросы. Когда я иду, чтобы получить счет, я вызываю что-то вроде: totalRecordCount = queryable.Count();
Получающийся SQL интересен тем не менее, но он также добавляет ненужный порядок, который делает запрос очень дорогостоящим.
exec sp_executesql N'SELECT COUNT(*) AS [value]
FROM (
SELECT TOP (1) NULL AS [EMPTY]
FROM [dbo].[JournalEventsView] AS [t0]
WHERE [t0].[DataOwnerID] = @p0
ORDER BY [t0].[DataTimeStamp] DESC
) AS [t1]',N'@p0 int',@p0=1
Поскольку я использую IQueryable, я могу манипулировать IQueryable перед тем, как сделать это на сервере SQL.
Мой вопрос: если у меня уже есть IQueryable с OrderBy в нем, можно ли удалить этот OrderBy, прежде чем я вызову Count()?
like: totalRecordCount = queryable. NoOrder.Count();
Если нет, не biggie. Я вижу много вопросов, как OrderBy, но не связанных с удалением OrderBy из выражения Linq.
Спасибо!
Ответы
Ответ 1
Существует не только ненужный ORDER BY, но и ложный TOP (1).
SELECT TOP (1) NULL AS [EMPTY] ...
Этот подзабор будет возвращать только 0 или 1 строки. Фактически без TOP там не было бы законным иметь ORDER BY в подзапросе.
Предложение ORDER BY недействительно в представлениях, встроенных функциях, производных таблицах, подзапросах и общих табличных выражениях, если не указано значение TOP или FOR XML.: SELECT COUNT (*) FROM (SELECT * FROM Table1 ORDER BY foo)
sqlfiddle
Я думаю, что вы, вероятно, сделали что-то не так в своей LINQ. Вы уверены, что не написали .Take(1)
или что-то подобное в своем запросе, прежде чем называть .Count()
?
Это неправильно:
IQueryable<Foo> foo = (...).OrderBy(x => x.Foo).Take(1);
int count = foo.Count();
Вместо этого вы должны сделать это:
IQueryable<Foo> foo = (...);
Iqueryable<Foo> topOne = foo.OrderBy(x => x.Foo).Take(1);
int count = foo.Count();
Ответ 2
Таким образом, приведенный ниже код является всплеском против массива в памяти. Там могут быть некоторые препятствия, чтобы получить эту работу с Entity Framework (или какой-либо другой произвольной реализацией IQueryProvider). В принципе, мы собираемся посетить дерево выражений и искать любой вызов метода Ordering и просто удалить его из дерева. Надеюсь, это указывает на то, что вы в правильном направлении.
class Program
{
static void Main(string[] args)
{
var seq = new[] { 1, 3, 5, 7, 9, 2, 4, 6, 8 };
var query = seq.OrderBy(x => x);
Console.WriteLine("Print out in reverse order.");
foreach (var item in query)
{
Console.WriteLine(item);
}
Console.WriteLine("Prints out in original order");
var queryExpression = seq.AsQueryable().OrderBy(x => x).ThenByDescending(x => x).Expression;
var queryDelegate = Expression.Lambda<Func<IEnumerable<int>>>(new OrderByRemover().Visit(queryExpression)).Compile();
foreach (var item in queryDelegate())
{
Console.WriteLine(item);
}
Console.ReadLine();
}
}
public class OrderByRemover : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType != typeof(Enumerable) && node.Method.DeclaringType != typeof(Queryable))
return base.VisitMethodCall(node);
if (node.Method.Name != "OrderBy" && node.Method.Name != "OrderByDescending" && node.Method.Name != "ThenBy" && node.Method.Name != "ThenByDescending")
return base.VisitMethodCall(node);
//eliminate the method call from the expression tree by returning the object of the call.
return base.Visit(node.Arguments[0]);
}
}
Ответ 3
Если вы не можете устранить основную причину, вот обходной путь:
totalRecordCount = queryable.OrderBy(x => 0).Count();
Оптимизатор запросов SQL Server удалит этот бесполезный порядок. Он не будет иметь затраты времени исполнения.
Ответ 4
Я боюсь, что нет простого способа удалить оператор OrderBy
из запроса.
Однако вы можете воссоздать IQueryable
на основе нового выражения, полученного при перезаписи queryable.Expression
(см. здесь), опустив вызов OrderBy
.
Ответ 5
Я думаю, что вы неправильно использовали код подкачки. Вам действительно нужно дважды запрашивать базу данных, один раз для постраничного источника данных и один раз для подсчета общей строки. Вот как должна выглядеть установка.
public IList<MyObj> GetPagedData(string filter, string sort, int skip, int take)
{
using(var db = new DataContext())
{
var q = GetDataInternal(db);
if(!String.IsNullOrEmpty(filter))
q = q.Where(filter); //Using Dynamic linq
if(!String.IsNullOrEmpty(sort))
q = q.OrderBy(sort); //And here
return q.Skip(skip).Take(take).ToList();
}
}
public int GetTotalCount(string filter)
{
using(var db = new DataContext())
{
var q = GetDataInternal(db);
if(!String.IsNullOrEmpty(filter))
q = q.Where(filter); //Using Dynamic linq
return q.Count(); //Without ordering and paging.
}
}
private static IQuerable<MyObj> GetDataInternal(DataContext db)
{
return
from x in db.JournalEventsView
where ...
select new ...;
}
Фильтрация и сортировка выполняются с помощью динамической библиотеки linq
Ответ 6
Я знаю, что это не совсем то, что вы ищете, но индекс на [DataOwnerID] с включением DataTimeStamp может сделать ваш запрос менее дорогостоящим.