删除隐式转换:
public IQueryable<Item> GetItemsFromRepository()
{
var setA = from a in this.dataContext.TableA
where /* criteria */
select a.Prop;
return DoSubquery(setA);
}
private IQueryable<Item> DoSubQuery(IQueryable<DateTimeOffset> set)
{
return from item in set
where /* criteria */
select item;
}
IQueryable<Item>
从to的隐式转换IEnumerable<Item>
与调用AsEnumerable()
您的IQueryable<Item>
. 当然,有时您需要这样做,但您应该将其保留为IQueryable
默认值,以便可以在数据库上执行整个查询,而不仅仅是GetItemsFromRepository()
在内存中完成其余部分。
次要问题:
1) System.Linq.Table instace 何时通过方法导致查询执行?
当某事需要最终结果时,例如Max()
,ToList()
等既不是可查询对象,也不是加载时可枚举的对象。
但是请注意,虽然AsEnumerable()
不会导致查询执行,但这确实意味着当执行确实发生在AsEnumerable()
对源数据源执行之前,这将产生一个按需的内存数据源,其余的将被执行.
2) 何时在另一个查询中使用 System.Linq.Table 会导致执行?
和上面一样。Table<T>
实现IQueryable<T>
。例如,如果您将其中两个连接在一起,那还不会导致执行任何操作。
3) 对 System.Linq.Table 将参数传递给方法的操作类型(Take、First、Last、order by 等)是否有限制?
由 定义的那些IQueryable<T>
。
编辑:对 和 之间的异同进行一些IEnumerable
澄清IQueryable
。
几乎可以在 an 上执行的任何操作,IQueryable
反之亦然IEnumerable
,但执行方式会有所不同。
任何给定IQueryable
的实现都可以在 linq 查询中使用,并且将具有所有 linqy 扩展方法,例如,Take()
等。Select()
GroupBy
只是如何做到这一点,取决于实施。例如,System.Linq.Data.Table
通过将查询转换为 SQL 查询来实现这些方法,其结果在加载时转换为对象。所以如果mySource
是一张桌子,那么:
var filtered = from item in mySource
where item.ID < 23
select new{item.ID, item.Name};
foreach(var i in filtered)
Console.WriteLine(i.Name);
变成如下 SQL:
select id, name from mySourceTable where id < 23
然后从中创建一个枚举器,以便在每次调用MoveNext()
另一行时从结果中读取,并从中创建一个新的匿名对象。
另一方面,如果mySource
where aList
或 a HashSet
,或其他任何实现IEnumerable<T>
但没有自己的查询引擎的东西,那么 linq-to-objects 代码会将其变成如下内容:
foreach(var item in mySource)
if(item.ID < 23)
yield return new {item.ID, item.Name};
这与该代码可以在内存中完成的效率差不多。结果将是相同的,但获得它们的方式会有所不同:
现在,由于所有IQueryable<T>
都可以转换为等价物IEnumerable<T>
,如果我们愿意,我们可以采用第一个mySource
(在数据库中执行的地方)并执行以下操作:
var filtered = from item in mySource.AsEnumerable()
where item.ID < 23
select new{item.ID, item.Name};
在这里,虽然在我们迭代结果或调用检查所有这些结果的东西之前,仍然没有对数据库执行任何操作,但一旦我们这样做,就好像我们将执行分为两个单独的步骤:
var asEnum = mySource.AsEnumerable();
var filtered = from item in asEnum
where item.ID < 23
select new{item.ID, item.Name};
第一行的实现是执行 SQL SELECT * FROM mySourceTable
,其余的执行就像上面的 linq-to-objects 示例。
不难看出,如果数据库包含 10 个 id < 23 的项目,以及 50,000 个 id 更高的项目,那么现在的性能要低得多。
除了提供显式AsEnumerable()
方法外,所有IQueryable<T>
都可以隐式转换为IEnumerable<T>
. 这让我们可以foreach
对它们进行处理并将它们与任何其他处理 的现有代码一起使用IEnumerable<T>
,但是如果我们在不适当的时间不小心这样做了,我们可以使查询变得更慢,这就是当你DoSubQuery
被定义为获取IEnumerable<DateTimeOffset>
和返回时发生的情况一个IEnumerable<Item>
; 它隐式地调用AsEnumerable()
了你IQueryable<DateTimeOffset>
和你的IQueryable<Item>
,并导致可能在数据库上执行的操作在内存中执行。
出于这个原因,在 99% 的情况下,我们都希望继续进行交易,IQueryable
直到最后一刻。
作为一个相反的例子,只是为了指出这一点AsEnumerable()
以及演员IEnumerable<T>
们并不是出于疯狂,我们应该考虑两件事。第一个是IEnumerable<T>
让我们做一些其他方式无法完成的事情,比如加入两个完全不同但彼此不了解的来源(例如两个不同的数据库、一个数据库和一个 XML 文件等)
另一个是有时IEnumerable<T>
实际上也更有效。考虑:
IQueryable<IGrouping<string, int>> groupingQuery = from item in mySource select item.ID group by item.Name;
var list1 = groupingQuery.Select(grp => new {Name=grp.Key, Count=grp.Count()}).ToList();//fine
foreach(var grp in groupingQuery)//disaster!
Console.WriteLine(grp.Count());
这里groupingQuery
被设置为一个可查询的,它进行一些分组,但无论如何都没有执行。当我们创建 list1 时,首先我们IQueryable
基于它创建一个新的,查询引擎最好找出最适合它的 SQL 是什么,并提出如下内容:
select name, count(id) from mySourceTable group by name
这是非常有效的执行。然后将行转换为对象,然后将其放入列表中。
另一方面,对于第二个查询,对于不对group by
所有非分组项执行聚合方法的 a 没有自然的 SQL 转换,因此查询引擎能想到的最好的方法是首先做:
select distinct name from mySourceTable,
然后对于它收到的每个名称,执行:
select id from mySourceTable where name = '{name found in last query goes here}'
依此类推,这是否意味着 2 个 SQL 查询,还是 200,000 个。
在这种情况下,我们的工作要好得多,mySource.AsEnumerable()
因为在这里首先将整个表抓取到内存中效率更高。(更好的是继续工作,mySource.Select(item => new {item.ID, item.Name}).AsEnumerable()
因为那时我们仍然只从数据库中检索我们关心的列,然后切换到内存中)。
The last bit is worth remembering because it breaks our rule that we should stay with IQueryable<T>
as long as possible. It isn't something to worry about much, but it is worth keeping an eye on if you do grouping and find yourself with a very slow query.