为什么同样是执行1000次查询,EF要花25秒时间。
ADO.NET 才用了1秒多?这里好像是有了缓存的
详细看补充问题。
就连生成官方的例子,我在调试运行的时候,EF 部分总会有明显的感觉到有一下的停顿。
我运行NopCommerce的时候也明显有这种感觉,是哪里没搞对吗?
最新结果:
EF6.11-执行一千次:11463 毫秒
ADO.NET-执行一行次:6611 毫秒
测试代码:
private static void Main(string[] args) { using (var context = new MainDbContext()) { var objectContext = ((IObjectContextAdapter)context).ObjectContext; var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace); mappingCollection.GenerateViews(new List<EdmSchemaError>()); context.Users.ToList(); } int fromInclusive = 0; int toExclusive = 1000; Stopwatch watch = new Stopwatch(); Console.WriteLine("开始计算 EntityFramework 消耗时间。"); watch.Start(); using (var context = new MainDbContext()) { for (int index = fromInclusive; index < toExclusive; index++) { context.Users.FirstOrDefault(u => u.Id == index); } } watch.Stop(); Console.WriteLine("耗时:{0} 毫秒。", watch.ElapsedMilliseconds); watch = new Stopwatch(); UserRepository userRepository = new UserRepository(); Console.WriteLine("开始计算 ADO.NET 消耗时间。"); watch.Start(); for (int index = fromInclusive; index < toExclusive; index++) { userRepository.GetUser(index); } watch.Stop(); Console.WriteLine("耗时:{0} 毫秒。", watch.ElapsedMilliseconds); Console.Read(); }
ADO.NET
public class UserRepository : RepositoryBase { public User GetUser(int userId) { string sql = "SELECT [Id], [Name], [Email], [Disable] FROM Users WHERE [Id] = " + userId; using (var dataReader = this.DataProvider.SqlQuery(sql)) { User user = null; if (dataReader.Read()) { user = new User(); user.Id = dataReader.GetInt32(0); user.Name = dataReader.GetString(1); user.Email = dataReader.GetString(2); user.Disable = dataReader.GetBoolean(3); } return user; } } }
EntityFramework:
DbContext:
public class MainDbContext : DbContext { public MainDbContext() : base("DefaultConnection") { } public DbSet<User> Users { get; set; } }
User:
/// <summary> /// 用户基本信息。 /// </summary> public class User {
string Id { get; }
/// <summary> /// 用户名称。 /// </summary> string Name { get; } /// <summary> /// 邮箱。 /// </summary> string Email { get; } /// <summary> /// 是否可用。 /// </summary> bool Disable { get; } /// <summary> /// 身份列表。 /// </summary> ICollection<Role> Roles { get; } }
在使用 EF 执行测试之前,我已经执行了一条查询,是不是说初始化过程已经完成了?
我使用EF读取用户信息组装成对象,执行,1000次,传入的用户名是递增的数字,耗时25秒左右。
using (var context = IoC.Resolve<IDbContext>()) { var objectContext = ((IObjectContextAdapter)context).ObjectContext; var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace); mappingCollection.GenerateViews(new List<EdmSchemaError>()); int index = 0; context.Set<User>().FirstOrDefault();
//循环一千次,返回执行时间,单位:毫秒。 var result = PerformanceCalculator.ExpendTime(0, 1000, () => { context.Set<User>().FirstOrDefault(); }); }
使用EF自带的那个执行脚本的在23~24秒
使用ADO.NET 17秒。。。
最新测试的结果是,使用ADO.NET 1134毫秒。
EF 生成的脚本是:
SELECT TOP (1) [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[Email] AS [Email], [Extent1].[Disable] AS [Disable] FROM [dbo].[Users] AS [Extent1] WHERE 0 = [Extent1].[Id]
context.Set<User>().FirstOrDefault(u => u.Id == 0);
EF首次加载时有个预热过程,我写过一篇博客:来,给Entity Framework热热身
感谢分享!还有这里如果使用了using 是会自动释放资源吧?难道只是使用唯一的一个DbContext吗?
请教dudu EF使用的时候要预热这我也知道了。如果不预热第一次查询会很慢, 就我们程序员来说 每次调试都是第一次 第一次预热也要预热几秒,这几秒很难受啊
@cjnmy36723: using会自动释放,这个预热是全局的,与DbContext实例没有关系
@奶茶爽歪歪: 这的确是个问题
@dudu:
就是说预热是直接在Application_Start上使用就可以了。
请问dudu那么为什么我自己测试的查询性能差这么多?
我在使用 EF 的时候,有哪里不对吗?
就是上面的补充问题。
@cjnmy36723: 建议用SQL Profiler看一下是不是有不正常的SQL
@dudu: 生成的脚本已经放到补充问题那里了,生成的脚本没有什么不正常的。。条件什么的都是最简了。
.NET第一次启动的时候在生成二进制代码,是会比较慢一点的。之后运行就很流畅了。
加上调试环境加载的东西多,不像生产环境。
另外你本机的性能和服务器性能有很大差别。
我试了下,使用发布的时候是没在看见有顿卡的问题了。
不过,我使用 EF 里提供的方法来执行 SQL 脚本,与原生的 ADO.NET还是有很大差距。
性能的问题通常要具体问题具体分析的。
真正的性能影响还是在数据库访问上的。使用EF时也要注意数据库访问性能的
是生成的 SQL 脚本来读取数据的时候吗?
@cjnmy36723: 注意是看生成的SQL效率高低
@吴瑞祥: 是生成的SQL脚本的效率低,是会大到上面我补充说明的这种程度吗?
我试了下,一个简单的查询。
自己写的是:
SELECT 字段1, 字段2…… FROM 表名
生成出来的是:
SELECT 字段1, 字段2…… FROM (SELECT 字段1, 字段2…… FROM 表名 )
有没有考虑到Materialization的消耗呢?很多性能测试往往都忽略这个重要的消耗。
给个具体的EF和ADO.net的过程代码,这样方便分析。
已经把源码放到问题上了!
我也是有些怀疑是映射成对象时的消耗问题!
这不是EF性能的问题,是EF功能特性的问题。using (var context = new MainDbContext()) { for (int index = fromInclusive; index < toExclusive; index++) { context.Users.FirstOrDefault(u => u.Id == index); } } 比如这段,EF查询出数据会对每个查询出来的user对象进行做一个“备份”,因为EF上下文需要跟踪这个对象,因此,看是EF查询了一个对象,其实EF还额外new了一个一模一样的对象(我不确定我说的“备份”EF是不是这样做的,也不知道是不是你们所说的缓存,当然,EF肯定还有其他更多的操作维护这么一个强大的功能),所以相对纯ado.net慢是很正常,并且这个慢可不是一般的慢。LZ不妨将context.Users.FirstOrDefault(u => u.Id == index); 改成context.Users.AsNoTracking().FirstOrDefault(u => u.Id == index); 试试。
非常感谢!我先去源码看看,找找原因!
不过可惜这样子改了效果也不大。。
这样比较不科学,EF提供的功能与你ADO.NET的功能,你看来都只是取数据和写数据。但实际上EF做了很多额外的工作,只是你觉得简单的应用中不应该有哪些额外的功能来损失性能。比如你在更新一个实体的时候,你可能会这样做,更新哪个或几个字段,我就写Update set这几个栏位。但是EF会自动跟踪那几个栏位与你上次读出来的版本不一致去更新更改过了的栏位,这样就需要缓存一个副本用于比较。