代码之家  ›  专栏  ›  技术社区  ›  V0ldek

将属性访问表达式转换为谓词表达式

  •  0
  • V0ldek  · 技术社区  · 6 年前

    我正在尝试创建一个泛型筛选类,它将接收一个属性访问器并检查它是否在允许的值内。所以过滤器的特征是:

    class PropertyFilter<TItem, TProperty>
    {
        PropertyFilter(Expression<Func<TItem, TProperty>> accessor, IEnumerable<TProperty> allowedValues);
    
        IQueryable<TItem> Filter(IQueryable<TItem> items);
    }
    

    和用法

    var filter = new PropertyFilter<Entity, string>(e => e.Name, new [] { "NameA", "NameB" });
    
    await filter.Filter(dbContext.Entities).ToListAsync();
    

    事情一定是这样的 IQueryable 兼容,所以我需要编写一个表达式。从形式的表达 x => x.Property 我需要创建一个表达式 Expression<Func<TItem, bool>> x => allowedValues.Contains(x.Property) . 据我所见,我可以使用Visitors之类的方法来处理表达式的任何基本操作,但是对于一个我不知道将表达式转换为SQL的规则是什么,我不能做什么,或者我破坏了它,而且这个用例似乎太简单了,不足以保证实现我自己的访问者和测试所有这些的代码。有没有一条捷径可以做到这一点,或者也许有一个可靠的图书馆已经找到了答案 并且与.NET Core 3.0和EF Core 3.0预览版兼容 ?

    0 回复  |  直到 6 年前
        1
  •  1
  •   Marc Gravell    6 年前

    未经测试,但这个 工作?

    static Expression<Func<TItem, bool>> Contains<TItem, TProperty>(
        Expression<Func<TItem, TProperty>> accessor,
        IEnumerable<TProperty> allowedValues)
    {
        var wrapped = new { allowedValues };
        var body = Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains),
            new[] { typeof(TProperty) },
            Expression.PropertyOrField(Expression.Constant(wrapped), nameof(allowedValues)),
            accessor.Body);
        return Expression.Lambda<Func<TItem, bool>>(body, accessor.Parameters);
    }
    

    Queryable.Where

    请注意 wrapped

        2
  •  2
  •   Ivan Stoev    6 年前

    首先,EF Core 3.0 都是不可靠的 预览

    所以在这个时候试图避开他们是没有意义的。最好保持在最新稳定的2.x版本,等待3.0的正式发布。

    技术而不是 Expression.Invoke 因为后者是在3.0之前的版本中工作的,但是前者可以与所有查询提供程序一起工作。

    public static partial class ExpressionUtils
    {
        public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
            => new ParameterReplacer { source = source, target = target }.Visit(expression);
    
        class ParameterReplacer : ExpressionVisitor
        {
            public ParameterExpression source;
            public Expression target;
            protected override Expression VisitParameter(ParameterExpression node)
                => node == source ? target : node;
        }
    }
    

    然后是一个像这样的helper表达式合成方法:

    public static partial class ExpressionUtils
    {
        public static Expression<Func<TOuter, TResult>> Select<TOuter, TInner, TResult>(
            this Expression<Func<TOuter, TInner>> innerSelector,
            Expression<Func<TInner, TResult>> resultSelector)
            => Expression.Lambda<Func<TOuter, TResult>>(
                resultSelector.Body.ReplaceParameter(resultSelector.Parameters[0], innerSelector.Body),
                innerSelector.Parameters);
    }
    

    使用这些帮助程序,所涉表达式(幸运的是在3.0预览中工作)的实现将简单:

    return accessor.Select(value => allowedValues.Contains(value));
    

    以这种方式组合表达式的好处是,结果与在编译时所做的完全相同,因此获得支持的机会更大。