EntityFramework 优化建议

原文地址 http://blog.jd-in.com/947.html

Entity Framework目前最新版本是6.1.3,当然Entity Framework 7 目前还是预览版,并不能投入正式生产环境,估计正式版16年第一季度会出来,了解过EF7的部分新特性后,还是狠狠期待一下滴。

EF性能问题一直为开发者所诟病,最让人纠结的也是这块,所以此次我也来谈谈EF的性能优及建议。既然是把优化点列举出来,可能有些地方关于底层的知识就不会介绍的太深刻,权当抛砖引玉吧。

先说说EF性能优化工具MiniProfiler,(不过也可以直接用Sqlserver profiler)MiniProfiler是StackOverFlow团队设计的一款对.net的性能分析小程序。

在这里我们可以使用MiniProfiler嵌入页面查看页面处理的周期和Sql语句执行的周期及Sql语句。可以通过Nuget下载MiniProfiler和MiniProfiler.EF然后进行安装与配置(具体操作暂不细说)。

因为作为宇宙级的开发工具VS2015已经提供了一个更为直接明了的方式,那就是“诊断工具”,具体打开的位置

此工具能更为直观的将EF操作数据库的SQL语句所列举出来。如我要查询角色表数据

EntityDB db = new EntityDB();

db.Role.Where(a => a.Id > 1).Select(a => a.Id).ToList();

查看工具显示

查看“执行Reader”可以看到SQL语句

方便你根据查询语句修改你的查询表达式及显示model.

以下为此次目录列表:

下面开始一一介绍

1.使用最新版的EF

使用最新版的EF正式版本代替老的版本(除旧迎新哈哈),毕竟EF是微软所重视的主流数据操作库,每次升级版本优化效果都挺明显的。

2. 禁用延迟加载

若使用延迟加载遍历单个Model下的某一集合属性,如下面的例子:

var user = db.Person.Single(a => a.Id == 1);

foreach (var role in user.Roles)
{
    Console.WriteLine(role.Name);
}

每次我们需要访问属性Role.Name的时候都会访问数据,这样累加起来的开销是很大的。

EF默认使用延迟加载获取导航属性关联的数据。

作为默认配置的延迟加载,需要满足以下几个条件:

  1. context.Configuration.ProxyCreationEnabled = true;
  2. context.Configuration.LazyLoadingEnabled = true;
  3. 导航属性被标记为virtual

这三个条见缺一不可。因此可以选择性禁用全局延迟加载或者是某一属性的延迟加载.

3.使用贪婪加载(又叫预加载就是数据库的多表查询)

这点其实也跟上面的一样响应了一个原则:尽量的减少数据库的访问次数,

var user = db.Person.Include(a=>a.Roles);

一次查询将UserProfile与其Role表数据查询出来

4.了解 IQueryable,IEnumerable的区别

IQueryable返回的是查询表达式,也就是说生成了SQL查询语句但是却还没有与数据库进行交互。

IEnumerable则是已经执行查询数据库的操作且数据保存在了内存中

所以在进行条件拼接的时候一定要在IQueryable类型后面追加Where条件语句,而不是等到ToList之后再开始写条件

错误的写法:

db.Person.ToList().Where(a => a.IsDeleted == false);

正确的写法:

db.Person.Where(a => a.IsDeleted == false).ToList();

这些写法的意思就是把数据条件拼凑好,再访问数据库。否则从数据库获取全部数据后再过滤,假如数据很庞大几十万,那后果可想而知!

5.优化操作AsNoTracking()与Attach

对于只读操作,强烈建议使用AsNoTracking进行数据获取,这样省去了访问EF Context的时间,会大大降低数据获取所需的时间。

同时由于没有受到上下文的跟踪缓存,因此取得的数据也是及时最新的,更利于某些对数据及时性要求高的数据查询。

db.Person.Where(a => a.IsDeleted == false).AsNoTracking().ToList();

下面是本人编写关于更改AsNoTracking数据Update的两种方式测试与总结:

EntityDB db = new EntityDB();
var users = db.User.AsNoTracking().ToList();
foreach (var user in users)
{
    db.Set<User>().Attach(user);
}
foreach (var user in users)
{
    user.IsDeleted = true;
    //db.Entry(user).State=EntityState.Modified;
}
db.SaveChanges();

以上代码我将未跟踪的数据做Attach后赋值SaveChanges生成的SQL语句如下:

而采用直接赋值后Entry修改State状态为Modified

 EntityDB db = new EntityDB();
 var users = db.User.AsNoTracking().ToList();
/* foreach (var user in users)
 {
     db.Set<User>().Attach(user);
 }*/
 foreach (var user in users)
 {
     user.IsDeleted = false;
     db.Entry(user).State=EntityState.Modified;
 }
 db.SaveChanges();

生成的SQL语句如下:

对比我们得出结论第一种采用Attach后赋值的方法是执行的按需更新,也就是说更新哪个字段就update它,而第二种则是不管更新了哪个字段,生成的SQL语句都是更新全部。

为什么第一种方法中我Attach后仅仅只是给对象赋值且没有修改State为Modified,但EF却能帮我修改数据值,那是因为

当SaveChanges时,将会自动调用DetectChanges方法,此方法将扫描上下文中所有实体,

并比较当前属性值和存储在快照中的原始属性值。如果被找到的属性值发生了改变,

此时EF将会与数据库进行交互,进行数据更新,所以不用设置State为Modified。

对于删除操作则需要在Attach后设置 db.Entry(user).State = EntityState.Deleted;

借鉴于此,我又封装了一个独立的AttachList方法,此方法仅仅只是将由AsNoTracking 取得的数据附加到上下文中,因为不用关注之后的操作是Update或者Delete所以只用了Attach。

以下截图代码是直接从我的项目中摘取出来展示:

其中最关键的是性能上的提高(就是上述文字标记的地方),当查询大量数据时,使用此方法比不使用而将其附加到上下文容器中,性能提升不是一点点。

6.EF使用SqlQuery

对于某些特殊业务,我们也可以使用sql语句查询实体,以下只是一个简单的事例操作

SqlParameter[] parameter = { };
var user = db.Database.SqlQuery<User>("select * from user", parameter).ToList();

此方法获得的实体查询是在数据库(Database)上,实体不会被上下文跟踪。

SqlParameter[] parameter = { };
var user = db.Set<User>().SqlQuery("select * from user", parameter).ToList();

此方法获得的实体查询是被上下文跟踪,所以能直接赋值后SaveChanges()。

var user = db.Set<User>().SqlQuery("select * from user").ToList();
user.Last().Name = "makmong";
db.SaveChanges();

当然同样支持带参数的查询与存储过程操作,我就不一一列出了此处只做点出即可。

7.关于AsNonUnicode

我们执行如下语句

var query = db.User.Where(a=>a.Name=="makmong").ToList();

生成的SQL语句

再试一个语句

var query = db.User.Where(a=>a.Name== DbFunctions.AsNonUnicode("makmong")).ToList();

生成的SQL语句

其中生成的SQL语句区别了,一个加了N,一个未加N,N是将字符串作为Unicode格式进行存储。

因为.Net字符串是Unicode格式,在上述SQL的Where子句中当一侧有N型而另一侧没有N型时,此时会进行数据转换,也就是说如果你在表中建立了索引此时会失效代替的是造成全表扫描。

用 DbFunctions.AsNonUnicode 方法来告诉.Net将其作为一个非Unicode来对待,此时生成的SQL语句两侧都没有N型,就不会进行更多的数据转换,也就是说不会造成更多的全表扫描。

所以当有大量数据时如果不进行转换会造成意想不到的结果。

因此在进行字符串查找或者比较时建议用AsNonUnicode()方法来提高查询性能。

8.建议使用ViewModel代替实体Model

大家可能都会碰到这种情况就是Model实体拥有多个字段,但是查询数据到页面展示的时候可能只需要显示那么几个字段,这个时候建议使用ViewModel查询,

也就是说需要哪些字段就查询哪些,而不是 “select *”将全部字段加载出来。此操作即出于安全考虑 (不应该将实体Model直接传递到View上面),同时查询的字段减少 (可能就几个) 对查询性能也有所提升。

例:

var query = db.User.ToList();

对应的查询语句为:

接着新建ViewModel

public class UserViewModel
{
  public int Id { get; set; }
  public string Name { get; set; }
}

开始查询:

var query = db.User.Select(a=>new UserViewModel()
{
    Id = a.Id,
    Name = a.Name
}).ToList();

对应的查询语句为:

9.建议Model实体中枚举使用byte类型

我们先来了解下Sqlserver中tinyint, smallint, int, bigint的区别

  • bigint:从-263(-9223372036854775808)到263-1(9223372036854775807)的整型数据,存储大小为 8 个字节。一个字节就是8位,那么bigint就有64位
  • int:从-231(-2,147,483,648)到231-1(2,147,483,647)的整型数据,存储大小为 4 个字节。int类型,最大可以存储32位的数据
  • smallint:从-215(-32,768)到215-1(32,767)的整数数据,存储大小为 2 个字节。smallint就是有16位

tinyint:从0到255的整数数据,存储大小为 1 字节。tinyint就有8位。

所以对于有些范围比较短的数值长度,例如枚举类型值,完全可以使用byte类型替换int类型,对应生成数据库tinyint类型以节省数据存储。

如:

public CouponType CouponType { get; set; }
public enum CouponType : byte
{
    RedBag = 0,
    Experience = 1,
    Cash = 2,
    JiaXiQuan = 3
}

对应的数据库类型:

此时的CouponType字段对应数据库就是一个tinyint类型

10.Model实体使用DateTime2替换DateTime控制内容值精度

我们先看下 SQL Server中DateTime与DateTime2的区别

  • DateTime字段类型对应的时间格式是 yyyy-MM-dd HH:mm:ss.fff ,3个f,精确到1毫秒(ms),示例 2014-12-03 17:06:15.433 。
  • DateTime2字段类型对应的时间格式是 yyyy-MM-dd HH:mm:ss.fffffff ,7个f,精确到0.1微秒(μs),示例 2014-12-03 17:23:19.2880929 。

我们知道EF Model的DateTime对应的SQL类型是DateTime

例:

public DateTime CreateDateTime { get; set; }

对应的数据库实体类型:

但是在业务操作中很多时间值我们仅仅只需要精确到秒就够了(特殊业务除外),

那多余的毫秒数既无用又占数据库存储(逼死处女座),既然是优化操作那么我们是否可以去除毫秒数而只存储到秒呢?例:2014-12-03 17:06:15

So我们可以使用特性Attribute及抽象类PrimitivePropertyAttributeConfigurationConvention来达到这一目的。

不多说直接上代码:

[AttributeUsage(AttributeTargets.Property)]
public sealed class DateTime2PrecisionAttribute : Attribute
{
    public DateTime2PrecisionAttribute(byte precision = 0)
    {
        Precision = precision;
    }
    public byte Precision { get; set; }
}
public class DateTime2PrecisionAttributeConvention: PrimitivePropertyAttributeConfigurationConvention<DateTime2PrecisionAttribute>
{
    public override void Apply(ConventionPrimitivePropertyConfiguration configuration,
        DateTime2PrecisionAttribute attribute)
    {
        if (attribute.Precision > 7)
        {
            throw new InvalidOperationException("Precision must be between 0 and 7.");
        }
        configuration.HasPrecision(attribute.Precision);
        configuration.HasColumnType("datetime2");
    }
}

理解一下代码,第一句中的AttributeTargets.Property表示可以对属性(Property)应用特性(Attribute)

而构造函数DateTime2PrecisionAttribute则指定了要应用的datetime的精度值。

而最后两句

configuration.HasPrecision(attribute.Precision);
configuration.HasColumnType("datetime2");

则是将我们所定义的类型精度与对应声明数据类型附加给要标记的实体类型。

最后还需要将DateTime2PrecisionAttributeConvention方法注册到我们的DbContext中

public virtual DbSet<User> User { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Add(new DateTime2PrecisionAttributeConvention());
}

现在我们再使用此特性在上面的属性CreateDateTime中看下效果吧

结果图:

是不是感觉不错。当然基于此拓展,我们也可以扩展我们想要的Model数据类型,如:控制decimal的精度(2位或4位小数),改边nvarchar(max)为我们想要的长度类型(具体情况看业务再优化吧)。

11.合理使用EF扩展库

1.EF实现指定字段的更新

在以往的数据更新操作中我们使用EF的修改都是先查询一次数据附加到上下文中,然后给需要修改的属性赋值,虽说EF能够自动跟踪实体做到按需更新,但更新前查询不仅没有必要,而且增加了额外的开销。EF删除和修改数据只能先从数据库取出,然后再进行删除.

当进行如下操作时:

delete from user where Id>5;

update user set Name=”10”;

我们需要这样操作

var t1 = db.User.Where(t => t.Id > 5).ToList();
foreach (var t in t1)
{
    db.User.Remove(t);
}
db.SaveChanges();
var t2 = db.User.ToList();
foreach (var t in t1)
{
    t.Name = "ceshi";
}
db.SaveChanges();

有没办法做到一条语句操作的更改呢?如“update user set name=’张三’where id=1”。

此时就需要使用EF的扩展库EntityFramework.Extended了。

在github中提供了一个EF扩展库https://github.com/loresoft/EntityFramework.Extended

在VS可以直接通过NuGet安装

安装完成后试验下:

当然需要先引用:

using EntityFramework.Extensions;

编写代码测试及查看结果:

EntityDB db = new EntityDB();
db.User.Where(a => true).Update(a => new User() {Name = "ceshi"});

EntityDB db = new EntityDB();
db.User.Where(a => true).Delete();

嗯,至于具体选择怎么用,看业务分析哈。

2.批量查询功能

例如:在分页查询的时候,需要查询结果数,和结果集

EF做法:查询两次

var q = db.User.Where(u => u.Name.StartsWith("a"));
var count = q.Count();
var data = q.Skip(10).Take(10).ToList();

EF扩展库的做法:一次查询

var q = db.User.Where(t => t.Name.StartsWith("a"));
var q1 = q.FutureCount();
var q2 = q.Skip(10).Take(10).Future();
var data = q2.ToList();
var count = q1.Value;

3.查询缓存功能

我们现在的后台项目权限管理模块,所有的菜单项都是写进数据库里,不同的角色用户所获取展示的菜单项各不相同。

项目导航菜单就是频繁的访问数据库导致性能低下(一开始得到1级菜单,然后通过1级获取2级菜单,2级获取3级)

解决方法就是第一次查询后把数据给缓存起来设定缓存时间,然后一段时间继续查询此数据(譬如整个页面刷新)则直接在缓存中获取,从而减少与数据库的交互。

代码如下:

var users = db.User.Where(u => u.Id > 5).FromCache(CachePolicy.WithDurationExpiration(TimeSpan.FromSeconds(30)));

如果在30秒内重复查询,则会从缓存中读取,不会查询数据库

我们再提出二个问题那就是,

1:第一次查询缓存数据修改后(如:保存到数据库)紧接着继续查询一次,由于缓存时间没有失效,此时在缓存中查询的数据是刚刚修改的最新的吗?

2:在不同的上下文中缓存获取结果是一样的吗?

写代码测试看下:

上图中我在第一个上下文中获得数据缓存,然后给Name赋值”sss”,当然此处为了测试缓存是否更新所以我没有做SaveChanges()的操作,然后接着从缓存中获取数据,由结果可知此缓存值也相应的更改了。

因此在一段时间内即使操作修改了数据值也只需要在更改的时候操作一次数据库,减少了与数据库的交互。

另外需要注意的是更改的时候可以根据操作结果选择是否继续缓存,例如数据更改失败但是缓存却改动了,下次取值数据就会不一致,所以当我们在更新数据库失败时就可以选择移除缓存调用RemoveCache()方法。

12.EF使用SQL分库操作

当数据库的表及数据达到一定规模后我们想到的优化就有分库,分表之类的优化操作。

对于之前的ADO.NET来说分库是一件很普通的操作。

比如下面的非跨数据库查询语句:

SELECT Name FROM dbo.User WHERE ID=1

跨数据库查询语句:

SELECT Name FROM MaiMangAdb.dbo.blog_PostBody WHERE ID=1

我们知道EF的DbContext中已经指定了连接字符串

public EntityDB() : base("DefaultConnection")
<connectionStrings>
  <add name="DefaultConnection" connectionString="Data Source=.;Initial Catalog=EFStudy;Integrated Security=True;" providerName="System.Data.SqlClient" />
</connectionStrings>

也就是说所有的上下文操作都是基于这个数据库来操作的,那我们就不能用ADO.NET那套,多个查询配多个链接去操作数据库。

当然大神们也给出了一套方法,而且也是简单明了。那我也就直接将其移植过来记录一下吧。

方法就是给数据库添加SYNONYM 同义词,我在此演示下

创建2张Model表User和Role

public class User
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsDeleted { get; set; }
    [DateTime2Precision]
    public DateTime CreateDateTime { get; set; }
}
public class Role
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
}

并添加一条语句:

EntityDB db = new EntityDB();
db.User.Add(new User { Id = 1, Name = "ddd" ,CreateDateTime = DateTime.Now});
db.Role.Add(new Role() {Id = 1, Name = "admin"});
db.SaveChanges();

运行查看数据库:

 

现在数据库表及内容都有了。然后我要把User表及内容移植到另一个数据库中,且不影响当前的EF操作。

创建新的数据库EFSYNONYM并添加User表,表结构和EFStudy中的User一致。

然后在EFStudy中删除表User且创建同义词

CREATE SYNONYM [dbo].[Users] FOR  [EFSYNONYM].[dbo].[Users]

效果如图

此时的User和Role已经分别存在于不同的数据库里面,我们来插入查询数据操作下

至此分库成功。当然此方法也有个缺点就是分库表和主表间由同义词关联而无法建立主外键关系(其实当数据量达到一定级别后联合join查询反而不如分开多次查询来得快,

且由于在同一个上下文中,不用太过于关心由数据多次连接开关而产生影响,凡事有利弊总得有个最优是吧),因此我们可以把一些独立的容易过期的数据表给移植到单独的数据库,利于管理同时也利于优化查询。

时间: 2024-11-03 00:07:55

EntityFramework 优化建议的相关文章

雅虎35条性能优化建议

雅虎35条性能优化建议分7类,共35条: [内容]尽量减少HTTP请求数 [服务器]使用CDN(Content Delivery Network) [服务器]添上Expires或者Cache-Control HTTP头 [服务器]Gzip组件 [css]把样式表放在顶部 [js]把脚本放在底部 [css]避免使用CSS表达式 [js, css]把JavaScript和CSS放到外面 [内容]减少DNS查找 [js, css]压缩JavaScript和CSS [内容]避免重定向 [js]去除重复脚

对Android开发者有益的40条优化建议

下面是开始Android编程的好方法: 找一些与你想做事情类似的代码 调整它,尝试让它做你像做的事情 经历问题 使用StackOverflow解决问题 对每个你像添加的特征重复上述过程.这种方法能够激励你,因为你在保持不断迭代,不经意中你学到了很多.然而,当你发布应用时你还要做一些更深入的事情. 从一些可正常工作的代码到一个可怕的应用程序是一个巨大的跳跃,相比iOS平台Android更是如此 .当在iOS上发布应用时只是在一个设备上跳跃–你的手机–对很多设备而言都很相似–同样大小的屏幕,都有很好

简单梳理memcached工作原理/工作流程/优化建议

一.memcached工作原理基本概念:slab,page,chunk.slab,是一个逻辑概念.它是在启动memcached实例的时候预处理好的,每个slab对应一个chunk size,也就是说不同slab有不同的chunk size.具体分配多少个slab由参数 -f (增长因子)和 -n (chunk最小尺寸)决定的.page,可以理解为内存页.大小固定为1m.slab会在存储请求时向系统申请page,并将page按chunk size进行切割.chunk,是保存用户数据的最小单位.用户

Jquery学习--性能优化建议

一.选择器性能优化建议 1. 总是从#id选择器来继承 这是jQuery选择器的一条黄金法则.jQuery选择一个元素最快的方法就是用ID来选择了. 1 $('#content').hide(); 或者从ID选择器继承来选择多个元素: 1 $('#content p').hide(); 2. 在class前面使用tag jQuery中第二快的选择器就是tag选择器(如$(‘head’)),因为它和直接来自于原生的Javascript方法getElementByTagName().所以最好总是用t

Yahoo! 35条网站性能优化建议

Yahoo! 35条网站性能优化建议 分类: 网站性能优化2014-03-08 17:18 212人阅读 评论(0) 收藏 举报 网站性能优化 Yahoo!的 Exceptional Performance团队为改善 Web性能带来最佳实践.他们为此进行了一系列的实验.开发了各种工具.写了大量的文章和博客并在各种会议上参与探讨.最佳实践的核心就是旨在提高网站性能.原版猛戳:Best Practices for Speeding Up Your Web Site, Excetional Perfo

网站大规模并发访问的优化建议

一.服务器配置优化 我们需要根据应用服务器的性能和并发访问量的大小来规划应用服务器的数量.有一个使用原则是:单台应用服务器的性能不一定要求最好,但是数量一定要足够, 最好能有一定的冗余来保障服务器故障.特别是,在高并发访问峰期间,适当增加某些关键应用的服务器数量.比如在某些高峰查询业务上,可以使用多台服务器, 以满足用户每小时上百万次的点击量. 二.使用负载均衡技术 负载均衡技术是解决集中并发访问的核心技术,也是一种较为有效的解决网站大规模并发访问的方法.实现负载均衡技术的主要设备是负载均衡器服

查看Oracle自动优化建议

Oracle运行一段时间后,系统会自动给出一些调优建议,放在dba_advisor_actions视图中,视图的几个主要字段见下: task_name       --优化任务名 execution_name  --执行名 object_id       --对象ID command         --使用的命令 attr1           --具体命令:如,alter table xxx  shrink space attr2           --具体命令:如,alter table

Unity 几种优化建议

转: http://user.qzone.qq.com/289422269/blog/1453815561?ptlang=2052 最简单的优化建议: 1.PC平台的话保持场景中显示的顶点数少于200K~3M,移动设备的话少于10W,一切取决于你的目标GPU与CPU.2.如果你用U3D自带的SHADER,在表现不差的情况下选择Mobile或Unlit目录下的.它们更高效.3.尽可能共用材质.4.将不需要移动的物体设为Static,让引擎可以进行其批处理.5.尽可能不用灯光.6.动态灯光更加不要了

Unity开发-你必须知道的优化建议

转自:http://blog.csdn.net/leonwei/article/details/18042603 最近研究U3D开发,个人认为,精通一种新的技术,最快最好的方法就是看它的document,而且个人习惯不喜欢看中文的资料,原汁原味的东西是最正确的,一翻译过来很多东西就都不那么准确了.于是通读了unity的官方manuel,最后面几章都是精华,里面给了非常非常多的官方的优化建议,尤其是做移动平台的开发,这些建议就是非常重要的.我将官方manuel advanced后面的那几个章节的东