如果我从我的数据库类之外的方法调用GetFoo().Count()
,则实体框架5执行的查询效率相当低,而不是COUNT。通过阅读其他一些问题,例如this,我发现这是预期的行为。
public IEnumerable<DbItems> GetFoo()
{
return context.Items.Where(d => d.Foo.equals("bar"));
}
因此我在我的数据库类中添加了一个count方法,它正确执行COUNT查询:
public int GetFooCount()
{
return context.Items.Where(d => d.Foo.equals("bar")).Count();
}
为了避免多次指定查询,我想将其更改为以下内容。然而,这再次执行SELECT,即使它在DB类中。这是为什么 - 我怎么能避免它呢?
public int GetFooCount()
{
return this.GetFoo().Count();
}
答案 0 :(得分:14)
由于GetFoo()
返回IEnumerable<DbItems>
,查询将作为SELECT
执行,然后Count
将应用于对象集合,而不会投影到SQL。
一个选项是返回IQueryable<DbItems>
:
public IQueryable<DbItems> GetFoo()
{
return context.Items.Where(d => d.Foo.equals("bar"));
}
但是这可能会改变其他调用者的行为,这些调用者期望加载集合(IQueryable
它将被延迟加载)。特别是添加无法转换为SQL的.Where
调用的方法。遗憾的是,您无法在编译时了解这些内容,因此需要进行全面测试。
我会创建一个返回IQueryable
的 new 方法:
public IQueryable<DbItems> GetFooQuery()
{
return context.Items.Where(d => d.Foo.equals("bar"));
}
因此您的现有使用不会受到影响。如果您想重新使用该代码,可以将GetFoo
更改为:
public IEnumerable<DbItems> GetFoo()
{
return GetFooQuery().AsEnumerable();
}
答案 1 :(得分: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;
}
但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
执行,然后使用上述方法计算内存中计算的项目数。但是,如果您将返回类型更改为IQueryable<T>
,则所有更改
public IQueryable<DbItems> GetFoo()
{
return context.Items.Where(d => d.Foo.equals("bar"));
}
现在执行Queryable<T>.Count()
。这意味着查询继续构建(实际上,Count()
是强制查询执行的运算符,但Count()
成为此查询的一部分)。和EF翻译
context.Items.Where(d => d.Foo.equals("bar")).Count()
进入在服务器端执行的SQL查询。