2015-10-13 2 views
1

Рассмотрим этот код:Linq запрос занимает слишком много времени при использовании Func & OrderByDescending

public List<Clients> GetFilteredClients(DateTime? FromDate = null, 
             DateTime? ToDate = null,  
             int? fromLocationType = null, 
             int? toLocationType = null) 
{ 
    Func<Clients, bool> fromDateFilter = f => true; 
    if (FromDate.HasValue) 
    { 
     fromDateFilter = z => z.Insert_Date.Value.Date >= FromDate.Value.Date; 
    } 

    Func<Clients, bool> toDateFilter = f => true; 
    if (ToDate.HasValue) 
    { 
     toDateFilter = z => z.Insert_Date.Value.Date <= ToDate.Value.Date; 
    } 

    Func<Clients, bool> fromLocationTypeFilter = f => true; 
    if (fromLocationType.HasValue) 
    { 
     fromOrgFilter = z => z.LocationTypeId >= fromLocationType.Value; 
    } 

    Func<Clients, bool> toLocationTypeFilter = f => true; 
    if (toLocationType.HasValue) 
    { 
     toLocationTypeFilter = z => z.LocationTypeId <= toLocationType.Value; 
    } 

    var filtered = DB_Context.Clients 
     .Where(fromDateFilter) 
     .Where(toDateFilter) 
     .Where(fromLocationTypeFilter) 
     .Where(toLocationTypeFilter) 
     .OrderByDescending(k => k.Id) 
     .Take(1000) 
     .ToList(); 
    return filtered; 
} 

Я что-то вроде 100K записей в БД, мне нужно только верхнюю 1000, которые отвечают требованиям:

.Where(fromDateFilter) 
.Where(toDateFilter) 
.Where(fromLocationTypeFilter) 
.Where(toLocationTypeFilter) 

Однако время выполнения все равно занимает примерно 10 секунд.

Любая идея, почему?

+0

Почему не запрашивать базу данных специально для топ-1000? –

+0

@ Юваль Ицчаков: Сначала мне нужно найти записи, которые соответствуют предложению 'where'. После этого возьмите верхнюю 1000. – ron

+0

@ron Ваша БД не поддерживает предложения where? –

ответ

3

Вы должны использовать Expression<Func<...>>, а не Func<...>. Когда вы используете Func, в запросе можно использовать только перечислимые методы, которые в этом случае означают, что вы сначала загружаете все в память, а затем выполняете фильтрацию. Если вы перейдете на Expression<...>, O/RM будет выполнять фильтрацию на сервере БД, а не в вашем приложении.

Кроме того, есть лучшие способы сделать то, что вы делаете. Например, вы можете создать условия, например, так:

var query = DB_Context.Clients.AsQueryable(); 

if (FromDate.HasValue) query = query.Where(...); 
if (ToDate.HasValue) query = query.Where(...); 

... 

return query.OrderByDescending(k => k.Id).Take(1000).ToList(); 

Конечно, это означает, что любой поставщик DB вы используете должен быть в состоянии поддерживать вид фильтрации вы пытаетесь сделать - вы будете необходимо проконсультироваться с документацией.

+0

Можете ли вы поделиться своим кодом после использования выражения, пожалуйста. Спасибо – aguetat

+1

@aguetat Самый простой способ - просто использовать 'Expression >' вместо 'Func ' - то есть, никаких других изменений.Другая альтернатива уже в моем ответе - просто используйте 'Where', чтобы добавить дополнительные фильтры к запросу, и вы избегаете наличия этих локальных переменных, которые в первую очередь вызывают проблемы. – Luaan

1

Вы используете делегаты вместо выражений LINQ. Это приводит к обработке данных вашим приложением, а не SQL Server.

Выражения LINQ выглядят как выражения лямбда благодаря синтаксису, но они не то же самое. Компилятор принимает решение о том, что создавать (делегаты или выражения LINQ) в зависимости от ситуации.

Если объект реализует интерфейс IQueriable, то компилятор использует Queryable класс и генерирует дерева выражений LINQ, которые впоследствии могут быть переведены в запрос SQL или другую форму конкретного IQueryProvider.

В противном случае компилятор использует расширения из класса Enumerable, которые создают итераторы по исходной коллекции (все записи из таблицы в вашем случае).

В качестве примера. Следующий код будет скомпонован в выражения LINQ.

// Source code 
IQueryable<Clients> source = null; 
IQueryable<Clients> result = source.Where(c => c.LocationTypeId >= 1); 

// Compiller generated code 
IQueryable<Clients> source = null; 

Expression parameterC = Expression.Parameter(typeof(Clients), "c"); 

IQueryable<Clients> result = Queryable.Where<Clients>(
    source, 
    Expression.Lambda<Func<Clients, bool>>(
     Expression.LessThanOrEqual(
      Expression.Property(
       parameterC , 
       typeof(Clients).GetProperty("LocationTypeId").GetGetMethod() 
       ), 
      Expression.Constant(1, typeof(int)) 
      ), 
    new ParameterExpression[] 
     { 
      parameterC 
     } 
    ); 

И этот код использует делегаты:

// Source code 
IQueryable<Clients> source = null; 
Func<Clients, bool> filter = c => c.LocationTypeId >= 1; 

IEnumerable<Clients> result = source.Where(filter); 

// Compiller generated code 
IQueryable<Clients> source = null; 
Func<Clients, bool> filter = c => c.LocationTypeId >= 1; 

IEnumerable<Clients> result = Enumerable.Where(source, filter); 

Таким образом, чтобы решить проблему вам использовать Expression<Func<Clients, bool>> вместо Func<Clients, bool>:

IQueryable<Clients> result = DB_Context.Clients; 

if (someFilter.HasValue) 
    result = result.Where(c => c.SomeProperty == someFilter.Value); 

// other filters 

return query 
    .OrderByDescending(k => k.Id) 
    .Take(1000) 
    .ToList(); 
Смежные вопросы