实体框架 - COUNT而不是SELECT

Jon*_*han 5 c# sql sql-server entity-framework entity-framework-5

如果我GetFoo().Count()从我的数据库类之外的方法调用,实体框架5执行的查询效率相当低,而不是COUNT.从阅读像其他一些问题,这个,我看这是预期的行为.

public IEnumerable<DbItems> GetFoo()
{
    return context.Items.Where(d => d.Foo.equals("bar"));
}
Run Code Online (Sandbox Code Playgroud)

因此我在我的DB类中添加了一个count方法,它正确执行COUNT查询:

public int GetFooCount()
{
    return context.Items.Where(d => d.Foo.equals("bar")).Count();
}
Run Code Online (Sandbox Code Playgroud)

为了避免多次指定查询,我想将其更改为以下内容.然而,这再次执行SELECT,即使它在DB类中.这是为什么 - 我怎么能避免它?

public int GetFooCount()
{
    return this.GetFoo().Count();
}
Run Code Online (Sandbox Code Playgroud)

D S*_*ley 14

因为GetFoo()返回a IEnumerable<DbItems>,查询作为a执行SELECT,然后Count应用于对象集合,而不是投影到SQL.

一种选择是返回IQueryable<DbItems>:

public IQueryable<DbItems> GetFoo()
{
    return context.Items.Where(d => d.Foo.equals("bar"));
}
Run Code Online (Sandbox Code Playgroud)

但是这可能会改变其他调用者的行为,这些调用者期望加载集合(IQueryable它将被延迟加载).特别是添加.Where无法转换为SQL的调用的方法.遗憾的是,您无法在编译时了解这些内容,因此需要进行全面测试.

我会创建一个返回以下内容的方法IQueryable:

public IQueryable<DbItems> GetFooQuery()
{
    return context.Items.Where(d => d.Foo.equals("bar"));
}
Run Code Online (Sandbox Code Playgroud)

所以你现有的用法不会受到影响.如果您想重新使用该代码,可以更改GetFoo为:

public IEnumerable<DbItems> GetFoo()
{
    return GetFooQuery().AsEnumerable();
}
Run Code Online (Sandbox Code Playgroud)


Ser*_*kiy 13

为了理解这种行为,您需要了解IEnumerable<T>IQueryable<T>扩展之间的区别.第一个使用Linq to Objects,它是内存中的查询.此查询未转换为SQL,因为这是简单的.NET代码.所以,如果你有一些IEnumerable<T>值,并且你正在执行Count()它,则调用Enumerable.Count扩展方法,类似于:

public static int Count<TSource>(this IEnumerable<TSource> source)
{   
    int num = 0;
    foreach(var item in source)
        num++;

    return num;
}
Run Code Online (Sandbox Code Playgroud)

但是IQueryable<T>扩展的故事完全不同.这些方法由底层LINQ提供程序(在您的情况下为EF)转换为.NET代码以外的其他方法.例如,SQL.执行查询时会发生此转换.分析所有查询,并生成好的(好吧,并不总是很好)SQL.此SQL在数据库中执行,结果将作为查询执行结果返回给您.

因此,您的方法返回IEnumerable<T>- 这意味着您正在使用Enumerable.Count()应该在内存中执行的方法.因此,以下查询由EF翻译成SQL

context.Items.Where(d => d.Foo.equals("bar")) // translated into SELECT WHERE
Run Code Online (Sandbox Code Playgroud)

执行,然后用上面的方法计算在内存中计算的项目.但是如果你将返回类型更改为IQueryable<T>,那么所有更改

public IQueryable<DbItems> GetFoo()
{
    return context.Items.Where(d => d.Foo.equals("bar"));
}
Run Code Online (Sandbox Code Playgroud)

现在Queryable<T>.Count()执行了.这意味着查询继续构建(嗯,实际上Count()是强制执行查询但是Count()成为此查询的一部分的运算符).和EF翻译

context.Items.Where(d => d.Foo.equals("bar")).Count()
Run Code Online (Sandbox Code Playgroud)

进入在服务器端执行的SQL查询.