Ado.net Entity FrameWork的性能问题
当我们需要大批量追加数据,或者一次查询海量数据的时候,必须注意,Ado.net Entity Framework的效率几乎是无法忍受的。简单的例子,我们将中国股市历年的日线,约500万条数据插入数据库,使用Ado.net一般仅需要2分钟左右,而使用EF,则这个时间会变为半小时的样子。当我们要一次性的将所有数据读取到内存的时候,这个效率同样的令人难以忍受。所以,要注意以下要点:
1、使用Ado.net Entity,使用Esql性能略好,因为Linq To entities将首先转换为esql再到sql语句。不过,我们多数的时候仍将使用Linq查询,这样相对来说知识能够复用,而无需急于学习Esql。
2、如果是查询一条记录,用GetObjectByKey
一般情况下,EF每次查询都会从数据库中获取资料,不会使用缓存,这样效率较低。此时可以考虑使用GetObjectByKey,这样,若该记录已经在缓存中则不会再次向数据库发出Sql命令,不过如果该记录
在缓存中不存在,则会引发异常,所以我们通常使用TryGetObjectByKey
using
(QuoteEntities entity =
new
QuoteEntities())
{
object
quote;
EntityKey key =
new
EntityKey(
"Quote"
,
"StockCode"
, "300001");
entity
.TryGetObjectByKey(key,
out
entity);
}
3、如果无需对查询的结果进行更新,则使用Notracking查询,默认情况下系统预设的是MergeOption.AppendOnly
using
(QuoteEntities entity =
new
QuoteEntities())
{
Quote quote= entity.Quotes.Execute(MergeOption.NoTracking).Where(a => a.Stockcode == "300001").FirstOrDefault();
}
4、如果某个查询会多次使用,用预编译查询:我们每次发出Linq To entities查询的时候,系统会将其编译为Esql,然后使用Provider转换为相应数据库的Sql语句,这个过程很长。
我们可以编译好查询,这样EF会缓存编译的结果,这样,后续的查询就无需执行从linq到Esql再到Sql的转换,速度会提高。
private
static
readonly
Func<QuoteEntities, string
,User> CompiledQueryGetByStockCode =
CompiledQuery.Compile<QuoteEntities, string
, User>((entity, stockCode) => entity.Quotes.Where(a=>a.StockCode == stockCode).FirstOrDefault());
然后,执行该查询:
using
(QuoteEntities entity =
new
QuoteEntities())
{
Quote quote= CompiledQueryGetByStockCode.Invoke(entity, "300001");
}
5、可以
使用原生的Ado.net命令,但EF的Context中的Connection并不能直接利用,因为其预设为执行ESQL,那么要直接执行Sql,包括如下三个命令:
ObjectContext.ExecuteStoreCommand(),用来执行非Select的Sql命令。
ObjectContext.ExecuteStoreQuery<T>(),用来执行Select的Sql命令,返回实体集合。
ObjectContext.Translate<T>:这个是用来衔接Ado.net的,当你得到一个DbDataReader的时候,可以将结果转换成实体集合。
但实际上只有查询比较有效。
6、批量insert的时候,只能使用原生Ado.net,自己根据Factory创建DbConnection对象和insert命令。当然,可以为DbCommand建立两个扩展方法,添加参数和为参数赋值。当然,批量insert应该包裹在
事务中才能保证速度,否则一些数据库默认是每insert一条就开启一个事务,性能低下。我们可以每insert数万条记录才提交一次事务,速度会更快些。
7、在以性能为主的应用中,不建议使用EF的实体类,当然伴随而来的,是不建议使用EF:
在使用Sqlite追加数据的时候,在资源管理器中观察文件变化,文件大小每次缓缓的增加4K的样子,但是在不使用EF的实体对象而单纯使用Ado.net的情形下,每次增加1M的样子,要快得多。由此可见即使将数据读入到EF的实体类,然后使用Ado.net直接insert到数据库,性能的下降也在10倍以上,这或许和tracking有关。
而使用原生Ado.net,常见的DbHelper方式和反射到实体类的方式,都有些奇怪。
事实上,建立轻量级的DbHelper,只要为DbCommand命令建立两个扩展方法处理参数问题,实体类实现一个IEntity接口(用于使用序号方式为各属性Get或Set),再实现两个扩展方法,即将DataReader返回的对象,转换成实体类的IEnumerator,将实体对象转换为DbParameter以作为insert、update的输入参数,则数据访问可简单的实现。当然,为了避免写太多Sql语句,为每个实体对象中加入insert、GetByKey之类的支持也是可行的。
从这个角度来说,我们将实体对象和实体集合对象,混合在实体类中当然也是可行的,不过我们可以建立专门的对应类,继承自定义的泛型Repository<实体>来处理,项目结构更为简单,与数据库有关的全部放在Data目录下面。当然,由于实体对象需要创建才能执行其方法,因此涉及到实体对象集合的方法往往写成静态方法更合适。
不过,考虑到软件系统,什么都无所谓,惟快不破,则EF实际上已经淡出视野,并无实际价值。