将 属性 访问表达式变成谓词表达式
Turn property access Expression into a predicate Expression
我正在尝试进行通用过滤 class,它将接收 属性 访问器并检查它是否在允许的值范围内。所以过滤器的签名是:
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
形式的表达式中,我需要创建一个等同于 x => allowedValues.Contains(x.Property)
的表达式 Expression<Func<TItem, bool>>
。从我所看到的情况来看,我基本上可以使用 Visitors 和诸如此类的东西对表达式做任何事情,但是对于一个人来说,我不知道将表达式转换为 SQL 的规则是什么,以及我不能做什么或我破坏了什么它,而且用例似乎太简单了,无法保证有那么多代码来实现我自己的访问者并测试所有这些。是否有一个简短的方法可以做到这一点,或者是否有一个可靠的库已经解决了 并且与 .NET Core 3.0 和 EF Core 3.0 预览版兼容?
未经测试,但这应该有效吗?
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
此处是添加接收 LINQ 查询解析器可能需要的间接层。
首先,EF Core 3.0 previews 与任何 preview(测试版)软件一样不可靠。此外,目前他们正在重写 LINQ 查询表达式树翻译,因此它非常不稳定,很多东西都不起作用,即使它们在最后一个稳定的 EF Core 2.x.
因此,此时尝试解决它们没有意义。最好留在 last stable 2.x 并等待官方 3.0 发布。
无论如何,你问的是表达式组合,有很多post你可以怎么做,包括我的一些。重要的是使用 参数替换 技术而不是 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);
}
有了这些助手,所讨论的表达式(幸运的是在 3.0 预览版中有效)的实现将很简单:
return accessor.Select(value => allowedValues.Contains(value));
以这种方式编写表达式的好处是,结果与您在编译时这样做的结果完全相同,因此更有可能得到支持。
我正在尝试进行通用过滤 class,它将接收 属性 访问器并检查它是否在允许的值范围内。所以过滤器的签名是:
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
形式的表达式中,我需要创建一个等同于 x => allowedValues.Contains(x.Property)
的表达式 Expression<Func<TItem, bool>>
。从我所看到的情况来看,我基本上可以使用 Visitors 和诸如此类的东西对表达式做任何事情,但是对于一个人来说,我不知道将表达式转换为 SQL 的规则是什么,以及我不能做什么或我破坏了什么它,而且用例似乎太简单了,无法保证有那么多代码来实现我自己的访问者并测试所有这些。是否有一个简短的方法可以做到这一点,或者是否有一个可靠的库已经解决了 并且与 .NET Core 3.0 和 EF Core 3.0 预览版兼容?
未经测试,但这应该有效吗?
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
此处是添加接收 LINQ 查询解析器可能需要的间接层。
首先,EF Core 3.0 previews 与任何 preview(测试版)软件一样不可靠。此外,目前他们正在重写 LINQ 查询表达式树翻译,因此它非常不稳定,很多东西都不起作用,即使它们在最后一个稳定的 EF Core 2.x.
因此,此时尝试解决它们没有意义。最好留在 last stable 2.x 并等待官方 3.0 发布。
无论如何,你问的是表达式组合,有很多post你可以怎么做,包括我的一些。重要的是使用 参数替换 技术而不是 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);
}
有了这些助手,所讨论的表达式(幸运的是在 3.0 预览版中有效)的实现将很简单:
return accessor.Select(value => allowedValues.Contains(value));
以这种方式编写表达式的好处是,结果与您在编译时这样做的结果完全相同,因此更有可能得到支持。