如果使用IEnumerable而不是List,为什么fluent nhibernate不缓存结果
本文关键字:为什么 fluent nhibernate 结果 缓存 IEnumerable 如果 List | 更新日期: 2023-09-27 18:20:12
编辑:更改了我的测试,因为测试的运行方式存在缺陷。
最近,我在Fluent Nhibernate上遇到了一些性能问题,我发现了一些我认为非常奇怪的事情。当我制作了一个IEnumerable时,List的性能显著提高。我想弄清楚为什么。这似乎不应该,谷歌也没有发现任何东西。
这是我运行的基本测试:
//Class has various built in type fields, but no references to anything
public class Something
{
public int ID;
public decimal Value;
}
var someRepository = new Repository(uow);
//RUN 1
var start = DateTime.Now;
// Returns a IEnumerable from a session.Linq<SomeAgg> based on the passed in parameters, nothing fancy. Has about 1300 rows that get returned.
var somethings = someRepository.GetABunchOfSomething(various, parameters);
var returnValue = SumAllFunction(somethings);
var timeSpent = DateTime.Now - start; //Takes {00:00:00.3580358} on my box
//RUN2
var start2 = DateTime.Now;
var returnValue = someFunction(somethings);
var timeSpent = DateTime.Now - start2; //Takes {00:00:00.0560000} on my box
public decimal SumAllFunction(IEnumerable<Something> somethings)
{
return somethings.Sum(x => x.Value); //Value is a decimal that's part of the Something class
}
现在,如果我使用相同的代码,只需将行someRepository.GetABunchOfSomethingto和appened.ToList():
//RUN 1
var start = DateTime.Now;
var somethings = someRepository.GetABunchOfSomething(various, parameters).ToList();
var returnValue = SumAllFunction(somethings);
var timeSpent = DateTime.Now - start; //Takes {00:00:00.3580358} on my box
//RUN 2
var start2 = DateTime.Now;
var returnValue = SumAllFunction(somethings);
var timeSpent = DateTime.Now - start2; //Takes {00:00:00.0010000} on my box
其他一切都没有改变。这些结果是非常可重复的。因此,这不仅仅是一个一次性的时间问题。
TLDR版本如下:
当通过一个循环运行同一个IEnumerable两次时,第二次运行所需的时间比在通过两个循环运行IEnumeraable之前使用.ToList()将其更改为List的时间长10-20倍。
我检查了SQL,当它是List时,SQL只运行一次,并且似乎被缓存并再次使用,而不必返回数据库获取结果。
如果它是一个IEnumerable,那么每次访问IEnumeraable的子级时,它都会访问数据库,为它们补水。
我知道你不能向IEnumerable添加/从中删除,但我的理解是,IEnumeraable最初会填充代理对象,然后在需要时对代理对象进行水合。在他们补充水分后,你就不必再回到DB了,但事实并非如此。我显然对此有一个解决办法,但我觉得这很奇怪,我很好奇它为什么会这样。
当您对GetABunchOfSomething
结果调用ToList()
时,此时将执行查询,并将结果放入列表中。如果您不调用ToList()
,那么直到someFunction
运行时才执行查询,并且您的计时器不会考虑这一点。
我想你会发现两者之间的时差就是因为这个原因。
更新
结果虽然可能与你的直觉相悖,但还是有道理的。在迭代之前不运行查询的原因,以及不缓存结果的原因,都是作为一个特性提供的。假设您想在代码中的两个位置调用存储库方法;一个时间由Foo
排序,另一个时间则由Bar
过滤。如果存储库方法返回IQueryable<YourClass>
,则对该对象所做的任何其他修改实际上都会影响发出的SQL,而不是导致在内存中修改集合。例如,如果您运行以下命令:
someRepository
.GetABunchOfSomething(various, parameters)
.Where(s => s.Bar == "SomeValue");
一旦迭代,生成的SQL可能看起来像这样:
select *
from someTable
where Bar = 'SomeValue'
然而,如果你这样做:
someRepository
.GetABunchOfSomething(various, parameters)
.ToList()
.Where(s => s.Bar == "SomeValue");
然后,您将从表中检索所有行,而您的应用程序将过滤结果。