【问题标题】:Using an existing expression/func to build out another expression/func使用现有的表达式/函数来构建另一个表达式/函数
【发布时间】:2019-09-10 23:36:11
【问题描述】:

我希望能够使用通用方法来选择一个属性并将其传递给Any() 方法。

private List<TModel> _models;

public bool Any<TModel, TProperty>(
  Expression<Func<TModel, TProperty>> propertySelector,
  TModel model)
{
  // ....
}
// OR
public bool Any<TModel, TProperty>(
  Func<TModel, TProperty> propertySelector,
  TModel model)
{
  // ....
}

我不确定如何使用 propertySelector 并将其与 Any() 上的 List&lt;TModel&gt; 一起使用。

这很接近,但我错过了一些东西:

_models.Any(m => propertySelector(m) == propertySeletor(model));

运算符“==”不能应用于“TProperty”和“TProperty”类型的操作数

我在这里错过了什么?

这个问题更像是一个人为的例子,因为表达式最终会被 使用来构建查询。

【问题讨论】:

  • 我猜propertySelector(model)_models 的迭代过程中不会改变。你能做一些将m =&gt; propertySelector(m) 作为表达式,并将propertySelector(model) 的结果作为TProperty 的东西吗?我正在考虑在EqualExpression 中将前者表达式与后者组合为ConstantExpression 并从中创建一个委托。
  • 您在propertySelector(model) 上是正确的。我不太确定你的第二句话是什么意思,也不知道为什么 TProperty 的结果会解决问题(尤其是基于异常消息)。
  • 我想我也是。如果我指定类型(字符串,int,long),它似乎可以工作,而不是使 TProperty 通用。我认为这是我必须为每种值类型构建一个的情况之一。
  • 嘿,它对你有用,太好了。感谢您回来并接受它。
  • @madreflection 我讨厌开放式问题...

标签: entity-framework-core c# generics lambda entity-framework-core expression


【解决方案1】:

如果问题是如何从代表相等谓词的Expression&lt;Func&lt;TModel, TProperty&gt;&gt;TModel 构建Expression&lt;Func&lt;TModel, bool&gt;&gt;,可以这样完成:

// Expression<Func<TModel, TProperty>> propertySelector
// TModel model
var parameter = propertySelector.Parameters[0];
var left = propertySelector.Body;
var right = Expression.Invoke(propertySelector, Expression.Constant(model));
var body = Expression.Equal(left, right);
var predicate = Expression.Lambda<Func<TModel, bool>>(body, parameter);

基本部分是Expression.Equal(等效于== 运算符的表达式)和用于在传递的对象实例上调用属性选择器的表达式。

如果查询提供程序不支持调用表达式,则可以将其替换为

var right = propertySelector.Body.ReplaceParameter(
    propertySelector.Parameters[0],
    Expression.Constant(model));

其中ReplaceParameter 是通常的基于ExpressionVisitor 的助手,用于将ParameterExpression 替换为另一个任意表达式(非常类似于string.Replace,但带有表达式):

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

【讨论】:

    【解决方案2】:

    您问题中的 Any 方法看起来像是源序列上的扩展方法,但它们缺少目标对象的参数。

    从概念上讲,这就是我相信您想要做的事情:

    public static bool Any<TEntity, TProperty>(this IEnumerable<TEntity> source, Func<TEntity, TProperty> selector, TEntity other)
    {
        if (source is null)
            throw new ArgumentNullException(nameof(source));
        if (selector is null)
            throw new ArgumentNullException(nameof(selector));
        if (other == null)
            throw new ArgumentNullException(nameof(other));
    
        TProperty otherProperty = selector(other);
    
        return source.Any(item => EqualityComparer<TProperty>.Default.Equals(selector(item), otherProperty));
    }
    

    现在,这将适用于内存中的对象,但由于您有 标记,我们需要一些处理表达式的东西。

    为此,我们必须首先评估属性以获取我们将比较每个实体的选定属性的值。在内存中,我们只需调用selector(other) 就完成了。由于我们现在必须处理表达式,因此我们需要先编译表达式。这很简单。

    下一步是构建一个表示selector(item) == otherValue 的表达式。幸运的是,我们不需要替换参数,因为我们没有尝试将两个单独的 lambda 折叠到同一个表达式中。我们可以简单地重用selector 的第一个参数。然后我们使用选择器及其参数调用Expression.Invoke,这些参数将被转换为SQL 中的列引用。

    最后,我们构建了可以传递给内置 Any 方法的 lambda。

    public static bool Any<TEntity, TProperty>(this IQueryable<TEntity> source, Expression<Func<TEntity, TProperty>> selectorExpression, TEntity other)
    {
        if (source is null)
            throw new ArgumentNullException(nameof(source));
        if (selectorExpression is null)
            throw new ArgumentNullException(nameof(selectorExpression));
        if (other == null)
            throw new ArgumentNullException(nameof(other));
    
        ParameterExpression itemParameter = selectorExpression.Parameters[0];
        ConstantExpression otherValue = Expression.Constant(selectorExpression.Compile()(other), typeof(TProperty));
    
        BinaryExpression equalExpression = Expression.Equal(Expression.Invoke(selectorExpression, itemParameter), otherValue);
    
        Expression<Func<TEntity, bool>> predicate = Expression.Lambda<Func<TEntity, bool>>(equalExpression, itemParameter);
    
        return source.Any(predicate);
    }
    

    在我自己的测试中,使用 SQL Profiler 进行确认,我能够在 SQL 查询中获得要评估的条件。更改属性选择表达式后,查询也随之改变。

    【讨论】:

    • 这个的第二部分看起来不错......我想测试这个太糟糕了。
    • 第二部分是我知道在所有讨论之后你想要的。第一部分只是让其他读者了解这个概念的简单版本,然后再用好东西打他们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-27
    • 1970-01-01
    • 1970-01-01
    • 2017-09-02
    相关资源
    最近更新 更多