【问题标题】:How to call Expression<Func<T, bool>> parameterized function wtih Linq Skip and Take如何使用 Linq Skip and Take 调用 Expression<Func<T, bool>> 参数化函数
【发布时间】:2021-05-08 14:30:32
【问题描述】:

我有一个如下所示的函数。它有 Expression&lt;Func&lt;T, bool&gt;&gt; 参数,其中 T 是名为“语言”的实体。

public async Task<List<T>> MyFilterAsync(Expression<Func<T, bool>> filter)
{
    return await context.Set<T>().Where(filter).ToListAsync();
}

我想从我的 razor 页面调用这个函数,这样我就可以只获取从 10 到 15 的记录(而不是每条记录)。所以这个方法中有Expression&lt;Func&lt;T, bool&gt;&gt;类型的“filter”参数。我想利用它。

所以来自我剃须刀页面上的 C# 代码。我可以这样称呼它,如下所示:

Expression<Func<Languages, bool>> filter = m => m.Name == "ABC";

上面的代码将为我提供名称为“ABC”的语言。现在是修改部分。

我只想要 10 到 15 条记录,所以我需要修改它,包括 Skip(skip).Take(pageSize) 用于 Linq 表达式的 where 子句。问题是——这可以做到吗,那怎么做?

context.Set&lt;T&gt;() 是一个语言列表,所以我们可以跳过并采取,对吧?

我希望我能够正确地解释这个问题。

【问题讨论】:

    标签: c# linq asp.net-core


    【解决方案1】:

    奇怪的是你不能修改你的MyFilterAsync,你确定吗?因为排序、跳过和获取的要求需要更多的参数,而不仅仅是filter。因此,最好为 MyFilterAsync 编写更多重载以接受更多参数并编写与其他用户建议的代码类似的代码。

    但是在这里,我试图让您的MyFilterAsync 保持不变,但您仍然可以加入订购、跳过和获取的逻辑。这一点都不神奇,但您仍然需要编写其他代码:您自己的扩展方法来替换默认的Where。这取决于编译器如何选择扩展方法重载。对于实体类型,默认具有最通用的 TEntity 类型。您只需在类型上使您的扩展方法重载更具体,例如:您的示例中的 Languages 类型。它可以是您的基本实体类型。当它不太通用(更具体)时,编译器将使用您的扩展重载而不是默认重载。

    您可以通过以下方式使其发挥作用:

    //put this in the same namespace with the default 
    //extension methods defined in System.Linq.Queryable
    public static class YaQueryableExtensions
    {
        static readonly AsyncLocal<int> _skip = new AsyncLocal<int>();
        static readonly AsyncLocal<int> _take = new AsyncLocal<int>();
        static class ExpressionBuffers<TEntity>
        {
            public static readonly AsyncLocal<Expression<Func<TEntity, object>>> OrderBy =
                                   new AsyncLocal<Expression<Func<TEntity, object>>>();
        }
    
        //here is your own extension method for Where
        //targeting the specific type of Languages
        //which can be any base entity type (if you want it to apply on a broader scope)
        public static IQueryable<Languages> Where(this IQueryable<Languages> source,
                                                  Expression<Func<Languages, bool>> filter)
        {
            return source.WhereWithExpressionBuffers(filter);
        }
    
        //the generic helper method which can be used on a specific closed type
        //of T (this method can be made private)
        public static IQueryable<T> WhereWithExpressionBuffers<T>(this IQueryable<T> source,
            Expression<Func<T, bool>> filter)
        {
            source = Queryable.Where(source, filter);
            //check for order-by (which should be chained first if any)
            var orderBy = ExpressionBuffers<T>.OrderBy.Value;
            if(orderBy != null)
            {
                source = source.OrderBy(orderBy);
                ExpressionBuffers<T>.OrderBy.Value = null;
            }
            //check for skip
            var skip = _skip.Value;
            if (skip > 0)
            {
                source = source.Skip(_skip.Value);
                _skip.Value = 0;
            }
            //check for take
            var take = _take.Value;
            if (take > 0)
            {
                source = source.Take(take);
                _take.Value = 0;
            }
            return source;
        }
    
        public static Expression<Func<T, bool>> Skip<T>(this Expression<Func<T, bool>> filter, int skip)
        {
            _skip.Value = skip;
            return filter;
        }
        public static Expression<Func<T, bool>> Take<T>(this Expression<Func<T, bool>> filter, int take)
        {
            _take.Value = take;
            return filter;
        }
        public static Expression<Func<TEntity, bool>> OrderBy<TEntity>(this Expression<Func<TEntity, bool>> filter, 
            Expression<Func<TEntity,object>> orderBy)
        {
            ExpressionBuffers<TEntity>.OrderBy.Value = orderBy;
            return filter;
        }
    }
    

    现在是你如何使用它:

    var result = await MyFilterAsync(filter.OrderBy(e => e.Name).Skip(skip).Take(pageSize));
    

    OrderBySkipTake 被链接在 filter 上(使用我们的扩展方法),以便它们可以被缓冲以供以后在我们自己的 Where 扩展方法中使用,我们可以在其中读取缓冲表达式以按照我们想要的方式正确构建Where)。

    注意:您应该将您的扩展类与Queryable(即System.Linq)放在同一个命名空间中,以便您的扩展方法可以自动可用(当然会使用默认扩展方法)。

    【讨论】:

      【解决方案2】:

      是的,只需在对项目进行排序后执行此操作,您可能需要为Name 属性实现一个接口,以使 orderby 属性与泛型一起使用。

      public interface IHasName
      {
          string Name { get; set; }
      }
      
      public async Task<List<T>> MyFilterAsync(Expression<Func<T, bool>> filter, int skip, int take)
      where T : class, IHasName
      {
      return await context.Set<T>()
                          .Where(filter)
                          .OrderBy(x=> x.Name)
                          .Skip(skip)
                          .Take(take)
                          .ToListAsync();
      }
      

      【讨论】:

      • 谢谢。实际上我希望我的函数MyFilterAsync() 保持不变。我想从剃刀页面以某种方式将跳过和条件传递到 where() 内部。
      • 我认为不修改函数是不可能的! Where 子句中的表达式对返回项的数量没有影响,它将返回所有匹配记录的列表。
      • 如果你不能修改函数,你可以在得到结果列表后做skip/return,但这是不推荐,因为它会对a进行不必要的fetch很多物品没有使用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-17
      相关资源
      最近更新 更多