简体   繁体   English

使用Func和OrderByDescending时Linq查询花费的时间太长

[英]Linq query takes too long when using Func & OrderByDescending

Consider this code: 考虑以下代码:

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

I have something like 100K records in the DB, I need only the top 1000 that answer to the requirements of: 我在数据库中有100K条记录,我只需要满足以下要求的前1000条记录:

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

However the execution time still takes something like 10 seconds. 但是执行时间仍然需要大约10秒。

Any idea why? 知道为什么吗?

You must use Expression<Func<...>> rather than Func<...> . 您必须使用Expression<Func<...>>而不是Func<...> When you use Func , only the enumerable methods can be used on the queryable, which in this case means you first download everything to memory, and then do the filtering. 使用Func ,仅可枚举方法可用于查询对象,在这种情况下,这意味着您首先将所有内容下载到内存中,然后进行过滤。 If you switch over to Expression<...> , the O/RM will do the filtering on the DB server, rather than in your application. 如果切换到Expression<...> ,则O / RM将在数据库服务器上而不是在应用程序中进行过滤。

Also, there's better ways to do what you're doing. 此外,还有更好的方法来做您正在做的事情。 For example, you can build the conditions like so: 例如,您可以建立如下条件:

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

Of course, this means that whatever DB provider you're using must be able to support the kind of filtering you're trying to do - you'll need to consult the documentation. 当然,这意味着您正在使用的任何数据库提供程序都必须能够支持您尝试执行的过滤类型-您需要查阅文档。

You are using delegates instead LINQ expressions. 您正在使用委托而不是LINQ表达式。 That leads to processing a data by your application and not by SQL Server. 这导致您的应用程序而不是SQL Server处理数据。

LINQ expressions look like lambda expressions thanks for the syntax, but they are not same thing. 感谢语法,LINQ表达式看起来像lambda表达式,但是它们不是同一回事。 The compiler takes a decision what to create (delegates or LINQ expressions) depending on the situation. 编译器根据情况决定要创建的内容(代理或LINQ表达式)。

If an object implements the IQueriable interface, then the compiler uses the Queryable class and generates LINQ expression trees, which later can be translated into a SQL query or other form by the specific IQueryProvider . 如果一个对象实现了IQueriable接口,则编译器将使用Queryable类并生成LINQ表达式树,稍后可通过特定的IQueryProvider将其转换为SQL查询或其他形式。

Otherwise, the compiler uses extensions from the Enumerable class, which create iterators over source collection (all records from the table in your case). 否则,编译器将使用Enumerable类的扩展,这些扩展将在源集合(您情况下的表中的所有记录)上创建迭代器。

As an example. 举个例子。 The code bellow will be compilled into LINQ expressions. 下面的代码将被编译为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 
        }
    );

And this code uses delegates: 此代码使用委托:

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

So, to solve you problem use Expression<Func<Clients, bool>> instead of Func<Clients, bool> : 因此,要解决您的问题,请使用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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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