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

C# 将属性访问表达式转换为谓词表达式,c#,.net-core,entity-framework-core,.net-core-3.0,C#,.net Core,Entity Framework Core,.net Core 3.0,我正在尝试创建一个通用筛选类,它将接收一个属性访问器并检查它是否在允许的值内。因此,过滤器的签名应为: class PropertyFilter<TItem, TProperty> { PropertyFilter(Expression<Func<TItem, TProperty>> accessor, IEnumerable<TProperty> allowedValues); IQueryable<TItem> F

我正在尝试创建一个通用筛选类,它将接收一个属性访问器并检查它是否在允许的值内。因此,过滤器的签名应为:

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

    IQueryable<TItem> Filter(IQueryable<TItem> items);
}
类属性过滤器
{
PropertyFilter(表达式访问器,IEnumerable allowedValues);
可计量过滤器(可计量项目);
}
用法

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

await filter.Filter(dbContext.Entities).ToListAsync();
var-filter=newpropertyfilter(e=>e.Name,new[]{“NameA”,“NameB”});
等待filter.filter(dbContext.Entities).toListSync();
这个东西必须是兼容的,所以我需要组成一个表达式。从形式为
x=>x.Property
的表达式中,我需要创建一个等价于
x=>allowedValues.Contains(x.Property)
的表达式
expression
。从我所看到的情况来看,我基本上可以使用visitor之类的工具来处理表达式,但我不知道将表达式转换为SQL的规则是什么,我不能做什么,或者我打破了它,而且用例似乎太简单,无法保证实现我自己的访问者并测试所有这些的代码。有没有一个简单的方法可以做到这一点,或者有一个可靠的库已经解决了这个问题,并且与.NET Core 3.0和EF Core 3.0预览兼容?

未经测试,但这应该可以工作

静态表达式包含(
表达式存取器,
IEnumerable允许值)
{
var wrapped=新的{allowedValues};
var body=Expression.Call(typeof(Enumerable)、nameof(Enumerable.Contains),
新[]{typeof(TProperty)},
Expression.PropertyOrField(Expression.Constant(wrapped)、nameof(allowedValues)),
存取器(主体);
返回表达式.Lambda(body,accessor.Parameters);
}
您应该能够将此结果传递给
Queryable.Where


请注意,
wrapped
这里是为了添加接收LINQ查询解析器可能需要的间接层。

首先,EF Core 3.0预览不可靠,因为任何预览(beta)软件都不可靠。此外,目前他们正在重写LINQ查询表达式树转换,因此它非常不稳定,许多事情都不起作用,即使它们在最后一个稳定的EF Core 2.x中工作

因此,在这个时候试图解决这些问题是没有意义的。最好还是停留在上一个稳定的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;
    }
}
然后是一个助手表达式组合方法,如下所示:

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);
}

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

不幸的是,在IQueryable下查询SQLServer数据集时,我从EFCore-
Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.RelationalQueryableMethodTranslationExpressionVisitor.TranslateWhere中引发了一个InvalidOperationException。经过简短的调查,唯一不同于显式编写的表达式
x=>allowedValues.Contains(x.Property)
(有效),解决方案构造的表达式似乎是
allowedValues
包装在匿名lambda捕获对象中。我还能够动态构造一个奇异的
表达式.Equals
表达式,然后通过为每个允许的值链接多个
OrElse
语句来模拟
包含的
调用。然而,这听起来像是在真正的数据库上效率低下。@V0ldek实际上,这是另一种方式-它是编译器生成的一个具有匿名捕获对象的对象-我的版本是直接的,查询生成器不希望:)Graveli对,这就是我的意思,很抱歉不清楚:)虽然这样做有效,我选择马克的答案,仅仅是因为它对我来说更简短、更容易理解。没关系。如果您需要在许多地方进行自己的表达式处理,而从头开始构建表达式是烦人的、不安全的和容易出错的,这一点从用法上可以清楚地看出——这里的一行代码与Mark的代码(最初也是错误的)相比(helper方法不算数,它们只创建一次)。无论如何,如果您计划进行大量表达式处理,那么像这样的助手是必须具备的,尤其是参数替换器。干杯
return accessor.Select(value => allowedValues.Contains(value));