C#中 EF(EntityFramework) 性能优化

现在工作中很少使用原生的sql了,大多数的时候都在使用EF。刚开始的时候,只是在注重功能的实现,最近一段时间在做服务端接口开发。开发的时候也是像之前一样,键盘噼里啪啦的一顿敲,接口秒秒钟上线,但是到联调测试的时候就悲剧了。。。。那叫一个慢啊,客户端有种“千年等一回的赶脚” 。由于访问量和数量都提升了一个数量级,之前没有 考虑过的问题,都在此时暴露了,根据自己百度、google的经历实践,整理了这一些优化点。欢迎各位大神批评指正!

1.使用AsNoTracking(),无跟踪查询,查询出的数据不可以修改,但是可以提高查询速度

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

2.合理使用延迟加载。

如果用不到导航属性中的数据,那么使用懒加载就行了,不会加载不需要的数据到内存中。但是,如果会在 foreach 中使用导航属性中的数据,那么最好是禁用懒加载,通过Include()方法,一次加载全部数据,防止在 foreach 多次和数据库进行交互。当然,一般情况下是这样的,具体的还是要根据当时的业务情况而定。

3.判断List中是否含有数据的时候,最好使用Any(),避免使用Count()>0,这么Low的方式,真是慢的一逼啊。

4.在where子句中进行中文字符模糊匹配值的时候,记得加上使用DbFunctions.AsNonUnicode("要匹配的字符")。

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

//修改后:
var query = db.User.Where(a=>a.Name== DbFunctions.AsNonUnicode("makmong")).ToList();

5.错误的使用OrderBy() 导致的错误排序。

要对多个字段进行先后组合排序的时候,正确的是Orderby().ThenBy();切记不要这样啊:OrderBy().OrderBy(),这样达不到你想要的效果,只会按照最后的那个排序字段进行排序。

6.真假分页的问题

  • line1就是加分页,直接从DB中取出全部的数据,然后在内存中进行分页;line2才是真正分页。没有ToList()的时候,返回值类型还是IQueryable,执行了ToList(),之后直接查询数据库了,返回值类型直接是IEnumerable.
line1:query.ToList().Skip((PageIndex - 1) * PageSize).Take(PageSize);

line2: query.Skip((PageIndex - 1) * PageSize).Take(PageSize).ToList();

7.按需加载部分列。

  • 通过在Select子句中使用select(t=>{t.id,t.name}),只加载需要的列来提升速度。
//建议使用ViewModel代替实体Model.接着新建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()

8.使用扩展库Entity Framework Extendeds 进行批量Insert和delete操作,避免生成大量的sql语句和数据库进行多次交互。

  • Nuget****安装 PM> Install-Package EntityFramework.Extended

9.这些做了,性能还是不行?那SqlQuery(),或者通过储存过程,将多条sql语句作为一个提交单元,也可以减少与数据库的交互次数,从而提高性能。

对于EF首次启动慢的问题,可通过下面的措施进行优化:
- EF的预热问题,在应用程序进行初始化的时候就事先进行预热。比如,mvc应用程序可在global文件中添加如下操作。

protected void Application_Start()
{
    using (var ctx = new mcccEntities())
    {
        var objectContext = ((IObjectContextAdapter)ctx).ObjectContext;
        var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace);
        mappingCollection.GenerateViews(new List<EdmSchemaError>());
    }

//记得,连接了几个DB就要写几个using啦。

}

10.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
}

11.EF使用SQL分库操作

-当数据库的表及数据达到一定规模后我们想到的优化就有分库,分表之类的优化操作。对于之前的ADO.NET来说分库是一件很普通的操作。比如下面的非跨数据库查询语句:
````sql
SELECT Name FROM dbo.User WHERE ID=1

##### 我们知道EF的DbContext中已经指定了连接字符串
```xml
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查询反而不如分开多次查询来得快,且由于在同一个上下文中,不用太过于关心由数据多次连接开关而产生影响,凡事有利弊总得有个最优是吧),因此我们可以把一些独立的容易过期的数据表给移植到单独的数据库,利于管理同时也利于优化查询。
THE END
分享
二维码
< <上一篇
下一篇>>