您当前的位置: 首页 > 

寒冰屋

暂无认证

  • 3浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

3种重构EF Linq查询的方法而不扼杀性能

寒冰屋 发布时间:2019-09-20 11:59:21 ,浏览量:3

目录

枚举

返回解决方案[0]

但等等,还有更多

或是LinqKit?

总结

从实体框架LINQ查询中提取方法会悄然扼杀性能。这里有三个简单的解决方案,包括:表达式,扩展方法和LinqKit。

枚举

上周,我惊讶地发现通过提取方法重构实体框架LINQ查询的可读性或可重用性会悄悄地将查询从SQL交换到内存中处理并扼杀性能。

这是我的问题的简化版本。

private async Task GetUsersMatching(IMainFilterDto filter, string prefix)
{
   var usersQuery = Users.Where(u =>
      (filter.StartDate == null || u.CreationTime > filter.StartDate) &&
      (filter.EndDate == null || u.CreationTime  i.Roles).Where(u =>
              (filter.StartDate == null || u.CreationTime > filter.StartDate) &&
              (filter.EndDate == null || u.CreationTime =更换>,我要找出所有与该代码一样的地方。我很想提取它:

private bool ApplyMainFilter(IMainFilterDto filter, User u)
{
       return (filter.StartDate == null || u.CreationTime > filter.StartDate) &&
              (filter.EndDate == null || u.CreationTime 
        ApplyMainFilter(filter, u) &&
        u.Name.StartsWith(prefix));

这当然读得更好。当我测试它时,它返回完全相同的结果。遗憾的是,当我通过LINQPad运行它时,原始查询(其中过滤器具有非null开始日期但是null结束日期)由以下:

SELECT [stuff]
FROM [Users] AS [u]
WHERE ([u].[CreationTime] > @__filter_StartDate_0) AND (([u].[Name] LIKE @__prefix_1 + N'%' _
AND (LEFT([u].[Name], LEN(@__prefix_1)) = @__prefix_1)) OR (@__prefix_1 = N''))

变成:

SELECT [stuff]
FROM [Users] AS [u]WHERE ([u].[Name] LIKE @__prefix_1 + N'%' AND _
(LEFT([u].[Name], LEN(@__prefix_1)) = @__prefix_1)) OR (@__prefix_1 = N'')

它删除了ApplyMainFilter()中的所有代码!在这个简单的例子中,这可能看起来并不可怕,但想象一下更复杂的场景。这可能会导致很多更多的记录从数据库中返回。它可能会造成网络瓶颈或对中间件造成过大压力。

最糟糕的是,它可能会阻止数据库执行它最擅长的操作:使用索引来优化查询执行。这可能意味着绕过现有索引,阻止使用未来索引进行查询优化,或者通过完全隐藏数据库中的问题来降低Azure SQL数据库中性能建议的有效性。

顺便提一下,如果你想看一个问题和解决方案的视频,请查看Code Hour的第22集:https://youtu.be/hYry3i5Nvzw

返回解决方案[0]

一旦发现问题,解决方案就变得相当容易了。了解实体框架如何在内部是有帮助的。这是关于表达树的所有内容,我之前已经写过了(好吧,我在11年前写过,但它所描述的基本原理仍然是可靠的)。

预测所有可能的方式,有人可能会将任意C#语言传递给一个where子句并将其全部转换为SQL是一个难题。我需要提供实体框架。一种方法是返回一个完全可解析的表达式树,Expression而不仅仅是bool或Func。它看起来像这样:

private Expression GetMainFilterQuery(IMainFilterDto filter)
{
    return u => (filter.StartDate == null || u.CreationTime > filter.StartDate) &&
        (filter.EndDate == null || u.CreationTime  u.Name.StartsWith(prefix));

这不是一个美学上令人愉悦的解决方案吗?它可重用,读取良好,并转换为SQL。

但等等,还有更多

但是,如果你想进一步阅读,我想我会提出一个更有趣的选择。如果您使用的是流式API,那么扩展方法方法可能是完美的:

public static class QueryUtils
{
    public static IQueryable AppendMainFilterQuery(
        this IQueryable existingQuery, IMainFilterDto filter)
    {
        return existingQuery.Where(u => (
            filter.StartDate == null ||  u.CreationTime > filter.StartDate) &&
            (filter.EndDate == null || u.CreationTime  u.Name.StartsWith(prefix))
        .AppendMainFilterQuery(filter);

这读得很好,是可重用的,就像第一个解决方案一样,保持SQL的最初状态。

或是LinqKit?

我是由一位聪明的同事来管理这一切的,他建议我查看LinqKit以防万一我需要做更复杂的事情。除此之外,LinqKit允许您跨多个方法构建表达式。例如,如果我需要一个OR子句而不是一个AND子句,它可能看起来像这样:

private ExpressionStarter GetMainFilterPredicate(IMainFilterDto filter)
{
    var predicate = PredicateBuilder.New().Start(u => 
        (filter.StartDate == null || u.CreationTime > filter.StartDate) &&
        (filter.EndDate == null || u.CreationTime  u.Name.StartsWith(prefix));
    return Users.Where(predicate).ToListAsync();
}

很漂亮。

总结

如果我不需要任何更复杂的东西,我喜欢第一种方法,但无论如何,确定如何不重构LINQ查询是重要的部分。如果您有任何其他创意解决方案,请分享以下评论。

关注
打赏
1665926880
查看更多评论
0.0573s