EF Core 使用编译查询提高性能

今天,我将向您展示这些EF Core中一个很酷的功能,通过使用显式编译的查询,提高查询性能。

不过在介绍具体内容之前,需要说明一点,EF Core已经对表达式的编译使用了缓存;当您的代码需要重用以前执行的查询时,EF Core将使用哈希查找并从缓存中返回已编译的查询。

不过,您可能希望直接对查询进行编译,跳过哈希的计算和缓存查找。我们可以通过在EF静态类中下面两个方法来实现:

  • EF.CompileQuery()
  • EF.CompileAsyncQuery()

这些方法允许您定义一个已编译的查询,然后通过调用一个委托调用它。

为了避免因为数据库查询产生测试结果的差异,我们这里使用内存数据库,它开销更小,同时也可以避免数据库优化执行计划以及缓存所带来的问题。

实体定义以前数据库DbContext

定义实体

在这我们定义一个Category实体类型,非常简单,只有两个属性。

public class Category
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

数据库DbContext

FillCategories方法中,将内存数据库中增加三条记录。

public class TestDbContext : DbContext
{
    public TestDbContext(DbContextOptions<TestDbContext> options) : base(options)
    {
    }
    public DbSet<Category> Categories { get; set; }
    public void FillCategories()
    {
        var foodCategory = new Category {
            Id = Guid.NewGuid(),
            Name = "Food"
        };
        Categories.AddRange(foodCategory, new Category {
            Id = Guid.NewGuid(),
            Name = "Drinks"
        }, new Category {
            Id = Guid.NewGuid(),
            Name = "Clothing"
        }, new Category {
            Id = Guid.NewGuid(),
            Name = "Electronis"
        });
        SaveChanges(true);
    }
}

测试代码

public class CompileQueryTest
{
    private   Func<TestDbContext, Guid, Category> _getCategory =
        EF.CompileQuery((TestDbContext context, Guid id) => context.Categories.FirstOrDefault(c => c.Id == id));
    private readonly TestDbContext _dbContext;
    public CompileQueryTest()
    {
        var options = new DbContextOptionsBuilder<TestDbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;
        var context = new TestDbContext(options);
        context.FillCategories();
        _dbContext = context;
    }
    private readonly Guid _queryId = Guid.NewGuid();
    [Benchmark]
    public void CompiledQuery()
    {
        _ = _getCategory(_dbContext, _queryId);
    }
    [Benchmark]
    public void UnCompiledQuery()
    {
        _ = _dbContext.Categories.FirstOrDefault(c => c.Id == _queryId);
    }
}

为了更加接近测试结果,我们在构造函数中创建TestDbContext对象以及填充数据库。

测试结果

我们使用Benchmark.Net进行基准测试,测试结果如下:

Method        	  Mean	     Error	     StdDev
CompiledQuery	10.59 us	0.0580 us	0.0543 us
UnCompiledQuery	79.55 us	0.7860 us	0.7353 us

经过编译的查询比未编译过的查询存在接近8倍的差距。如果您对这个功能感兴趣,不防自己测试一下。


EF core的原生SQL查询以及用EF core进行分页查询遇到的问题

在用.net core进行数据库访问,需要处理一些比较复杂的查询,就不得不用原生的SQL查询了,然而EF Core 和EF6 的原生sql查询存在很大的差异。

在EF6中我们用SqlQueryExecuteSqlCommand进行sql语句的执行,而在EF Core中我们则使用FromSqlExecuteSqlCommand

一.ExecuteSqlCommand(这两者没什么太大的区别)

Company08Entities db = new Company08Entities();
string sql = string.Format("update Cars set IsPub='是',PubTime='{1}' where Id in ({0})",ids,DateTime.Now);
int res = db.Database.ExecuteSqlCommand(sql);    //返回受影响的行数
if (res>)
{
    return Json(new UIResult(true,"发布成功!"));
}
else
{
    return Json(new UIResult(false,"发布失败,请重试!"));
}

二.数据库查询语句两者的差别就太大了,这里我会详细举例说明

1.在EF6中使用SqlQuery进行查询以及联和Linq进行分页

Company08Entities db = new Company08Entities();
string sql = "select c.* from Cars c join ResCommend r on c.Id=r.ResId where r.Posld=2 and DeadLine>GETDATE() and c.IsPub='是'";
var res = db.Database.SqlQuery<Cars>(sql);
var list = res.Skip((pn - ) * pz).Take(pz).ToList();    //其中pn为页码,pz为页大小

2.在EF Core中我们使用FromSql

private Company08Context db = null;
public ProductController(Company08Context context)
{
   this.db = context;
}

String sql =string.Format($"select c.* from Cars c join ResCommend r on c.Id=r.ResId where r.Posld=2 and DeadLine>GETDATE() and c.IsPub='是'");
 
var res = db.Cars.FromSql(sql);
 
var list = res.Skip((pn-)*pz).Take(pz).ToList();

这中使用 LINQ 运算符在初始的原始 SQL 查询基础上进行组合会出现以下这种问题
在这里插入图片描述
这是因为使用 LINQ 运算符在初始的原始 SQL 查询基础上进行组合。 EF Core 将其视为子查询,并在数据库中对其进行组合,导致查询出错

解决方案就是阻止查询运算操作的组合,在 FromSql 方法之后立即使用 AsEnumerableAsAsyncEnumerable 方法,确保 EF Core 不会尝试对存储过程进行组合。

String sql =string.Format($"select c.* from Cars c join ResCommend r on c.Id=r.ResId where r.Posld=2 and DeadLine>GETDATE() and c.IsPub='是'");
var res = db.Cars.FromSql(sql).AsEnumerable();
var list = res.Skip((pn-)*pz).Take(pz).ToList();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值