一、级联删除在性能上的影响
级联删除:当删除主端时,依赖端数据会被自动删除(含有外键的一端)
对于Required关系,EF默认是打开级联删除的。
那么,级联删除会涉及到下面两种情景:
1.如果依赖端的对象在内存里面,删除主对象时,EF会负责删除掉内存里面的依赖对象,当调用SaveChange时,EF会发送主对象的delete语句到数据库,还有每一个依赖对象的delete语句到数据库。
2.如果依赖端的对象不在内存中,那么只删除内存中的主对象,然后发送一条删除主对象的sql命令到数据库。那些依赖对象由数据库的级联删除功能去负责删除。
对于第一种情况,尽量避免将依赖端的对象加载到内存,因为会发送多条delete语句到数据库。

二、避免从数据库获取不需要的字段
如果一个表有很多字段,但是有的时候只需要一部分字段,有的时候只需要另一部分字段,当将这个表映射到一个实体时,就会浪费资源。可以使用Table splitting功能,将一个表映射到多个实体。

三、优化对Table per Type(TPT)模型的查询性能
TPT是对于继承的模型,每个类型一个表。每个派生类型对应到单独的表,并且都含有对基类表的外键。当查询时,如果知道需要的结果是什么派生类型,可以在查询语句中指明返回结果的类型,这样可以使EF只查询包含指定类型数据的表,否则EF需要先查询所有的表(所有派生类对应的表)。使用dbContext.BaseEntity.OfType<DrivedEntity>().Single(m=>m.Name=”xxx”)来指定返回类型。

四、查询优化,使用DbSet的Find()方法,而不是Linq查询。
当发起linq查询时,总是会去数据库查询需要的数据,即使需要的数据已经被加载到内存中上下文对象中。当查询结束,不在上下文中的对象被加入上下文并被跟踪。默认情况下,如果对象已经在上下文中,则其不会被数据库的最新值覆盖。然而,DbSet对象的Find()方法,它需要实体的主键来查询。它是高效的,总是先查找内存中的上下文对象,如果没找到,则查询数据库,如果还是没找到,则返回NULL。

五、获取实体用于只读访问
当使用AsNoTracking方法时,查询返回的实体(包括include的实体)不会被跟踪。默认情况下,查询返回的实体会被上下文跟踪,这使得更新和删除实体很轻松,但是会有一些cpu和内存的开销。对于那些处理大量对象的应用,例如在电子商务网站浏览产品,使用AsNoTracking可以减少开销,提高应用的性能。

六、构建高效的查询
let带来了更好的可读性,但是生成的sql性能却较差。避免使用let。

var query2 = from reservation in context.Reservations
    let dateMatches = searchDate == null || reservation.ResDate == searchDate
    let nameMatches = searchName == string.Empty || reservation.Name.Contains(searchName)
    where dateMatches && nameMatches
    select reservation;

var query1 = from reservation in context.Reservations
    where (searchDate == null || reservation.ResDate == searchDate)
          && (searchName == string.Empty || reservation.Name.Contains(searchName))
    select reservation;

七、使POCO的变化跟踪更快
为了达到最好的变化跟踪性能,我们需要允许EF使用change-tracking代理类自动包装我们的实体类,这些代理类在属性值变化时会立即通知底层的change-tracking机制。有了这些代理类,EF任何时候都知道你的实体的状态。当创建代理类时,通知事件被添加到每个属性的setter方法,这些方法被Object State Manager处理。当满足下面2个条件时,EF会自动创建代理类:
1.实体的所有属性必须被标记为virtual。
2.任何集合类型的导航属性必须是ICollection<T>类型的。

POCO实体的变化跟踪使用快照或者代理类。对于快照方式,当实体从query或者Attach()方法加载到上下文对象时,EF会给实体的数据拍一张照片。当SaveChange()操作发生时,原始的快照与当前数据比较,来发现哪些数据更新了。使用这种方式,EF为每个对象维护了两个备份,并且比较它们,生成必要的相关更新,插入或者删除sql语句。你也许会觉得这种方式很慢,但是EF在查找不同时很快。
注意:上下文的Add()方法不会调用快照,因为实体时新加的,没必要跟踪单独的值。EF把实体标记为Added状态,然后当SaveChanges()操作发生时发送一个插入sql语句。

第二种方式是使用一个实现了IEntityWithChangeTracking接口的代理对象来封装底层的POCO实体。这个代理负责通知Object State Manage实体值以及关系的变化。当实体满足上述2个条件时,EF自动创建代理。代理避免了潜在的快照复杂对象比较,然而需要一些开销去跟踪变化。
虽然变化跟踪代理立即通知变化跟踪组件关于对象的变化,并且避免了对象比较。事实上,性能优势只在实体很复杂并且/或者大量对象发生了当少量的变化。

八、自动编译LINQ查询
当对数据执行操作时,EF必须将你的强类型的LINQ查询转换成对应的SQL查询,基于数据库提供程序(SQL Server或者Oracle等)。从EF5开始,每个查询转换都默认被缓存,这个操作就是自动缓存。后续的LINQ查询,对应的SQL查询将直接从查询计划中取出来,避免了转换这一步。对于包含参数的查询,改变参数任然会获取同样的查询。有趣的是,这个查询计划被同一个应用程序域(AppDoman)中所有的上下文对象共享,意思是说,一旦缓存,应用程序域中的任何上下文对象都可以访问它。ObjectContext.EnablePlanCaching=false可以关闭缓存。
当你执行LINQ查询时,EF为查询构建一个表达式树对象,然后被转换,或者编译为一个内部的命令树。这个内部的命令树被传递到数据库提供程序,然后被转换成适当的数据库命令(通常是sql)。转换表达式树的开销可以使相对昂贵的,取决于查询的复杂性和底层模型。模型有很深的继承,或者横向分割,会在转换处理中产生足够的复杂性,从而导致编译时间非常接近查询的实际执行时间。然而,EF5中,LINQ查询的自动缓存被引入。为了跟踪每个被编译的查询,EF遍历表达式树的节点并创建哈希值作为这个查询的缓存key。后续的每个查询,EF将试图从缓存定位哈希key,从而避免转换的开销。
当底层查询缓存达到800或者更多查询计划时,一个缓存驱逐进程被自动启动。每分钟,一个清扫进程会根据LFRU算法移除缓存的查询。
对于次数多,包含参数的查询,编译查询很有帮助。

九、返回部分填充的实体
有的实体的属性很少被读取和更新,由于他的大小导致这个属性的读取和更新和昂贵,为了提供性能,你可以有选择的加载这个属性。通过使用上下文的SqlQuery()方法执行sql语句 ,我们可以避免加载实体的一个或者多个属性。

十、将昂贵的属性抽取出来放到单独的实体(将表映射到多个实体)
将昂贵并且使用很少的属性单独放到实体,这样有助于提高性能。

十一、避免Include

 

Include方法是一个强大的,并且通常是高效的渴望加载实体的方法。然而,它确实有一些性能方面的缺点。虽然使用Include方法减少了与数据库单的交互次数,但是单次的查询很复杂,可能还比不上多次简单的查询。另一面,如果大量简单的查询,也比不上一次复杂的查询。需要权衡。

十二、避免加载不必要的表

本文的知识整理自《Entity Framework 6 Recipes》

发表评论

电子邮件地址不会被公开。 必填项已用*标注