我在使用通用方法进行数据聚合时遇到了Entity Framework的性能问题。当使用泛型方法查询具有几十万行的表的(索引的)Id列的最大值时,我的性能下降很大。我使用代码生成的int键而不是sql身份,此代码用于获取下一个新ID。
这是示例说明。 MaxTyped
使用Max
上的db.Posts
(DbSet<Post>
),而MaxGeneric
使用通用方法max
来执行此操作。
static int MaxTyped()
{
using (var db = new BloggingContext())
{
return db.Posts.Max(p => p.PostId);
}
}
static int MaxGeneric()
{
using (var db = new BloggingContext())
{
return max(db.Posts, p => p.PostId);
}
}
static int max<T>(DbSet<T> set, Func<T, int> func) where T : class
{
// intellisense says its IEnumerable.Max
return set.Max(func);
}
在我不太老的家用台式机上使用相当快的硬盘,100k行的MaxTyped
运行0.5秒,而MaxGeneric
大约6.5s。这慢了一个数量级。
在我办公室旧的测试服务器上,我们有几秒钟的时间,差不多10分钟。
我发现问题的唯一痕迹是两种情况下Max方法的Intellisense输出的差异:在MaxTyped
中,它将方法识别为IQueryable.Max
,而在{{1它表示它的MaxGeneric
,这可能意味着EF在所有加载的实体上而不是在数据库中执行Max。我尝试将IEnumerable.Max
投射到set
,但没有任何改变。为什么会有这样的差异?如何解决?
答案 0 :(得分:3)
因为MaxTyped
执行“服务器端”,所以只返回一行,而MaxGeneric
执行“客户端”,因此返回表的所有行,“反序列化“到int
和”max(ed)“(最后一步是最快的一步)
实际成本在于SQL Server和.NET应用程序之间的数据传递:必须传递的数据越少,就越好。
现在尝试
static int max<T>(DbSet<T> set, Expression<Func<T, int>> func) where T : class
{
// intellisense says its IEnumerable.Max
return set.Max(func);
}
并查看set.Max
是否正在使用IEnumerable<T>.Max()
或IQueriable<T>.Max()
。如果它正在使用第二个,它的速度应该是大约0.5秒。
不同之处在于IQueriable<T>.Max()
仅接受Expression<Func<T, int>>
,而IEnumerable<T>.Max()
接受Func<T, int>
,而当您撰写p => p.PostId
时,这可能是Expression<Func<>>
或者Func<>
,取决于它所放入的变量的类型。