简体   繁体   English

使用现有的表达式/函数来构建另一个表达式/函数

[英]Using an existing expression/func to build out another expression/func

I want to be able to use a generic method to select a property and pass that into the Any() method.我希望能够使用通用方法来选择一个属性并将其传递给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)
{
  // ....
}

I'm not sure how to take the propertySelector and use it with the Any() on a List<TModel> .我不确定如何获取propertySelector并将其与List<TModel>上的Any()一起使用。

This is close, but I'm missing something:这很接近,但我错过了一些东西:

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

Operator '==' cannot be applied to operands of type 'TProperty' and 'TProperty'运算符“==”不能应用于“TProperty”和“TProperty”类型的操作数

What am I missing here?我在这里缺少什么?

The question is more of a contrived example, as the Expression will ultimately be consumed by to build a query.这个问题更像是一个人为的例子,因为表达式最终将由来构建查询。

If the question is how to build Expression<Func<TModel, bool>> from Expression<Func<TModel, TProperty>> and TModel representing equal predicate, it could be done like this:如果问题是如何从Expression<Func<TModel, TProperty>>和表示相等谓词的TModel构建Expression<Func<TModel, bool>> ,则可以这样做:

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

The essential parts are the Expression.Equal (the expression equivalent of == operator) and the expression for invoking the property selector on the passed object instance.基本部分是Expression.Equal (相当于==运算符的表达式)和用于在传递的对象实例上调用属性选择器的表达式。

In case the query provider does not support invocation expressions, it could be replaced with如果查询提供程序不支持调用表达式,则可以将其替换为

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

where ReplaceParameter is the usual ExpressionVisitor based helper for replacing ParameterExpression with another arbitrary expression (pretty much like string.Replace , but with expressions):其中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;
    }
} 

The Any methods in your question look like they were intended to be extension methods on a source sequence but they were missing the parameter for the target object.您问题中的Any方法看起来像是源序列上的扩展方法,但它们缺少目标对象的参数。

Conceptually, this is what I believe you were looking to do:从概念上讲,这就是我相信您想要做的:

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

Now, that'll work on objects in memory, but since you have the tag, we'll need something that deals with expressions.现在,这将适用于内存中的对象,但由于您有标签,我们需要一些处理表达式的东西。

To do that, we must first evaluate the property to get the value against which we'll be comparing each entity's selected property.要做到这一点,我们必须首先评估属性以获得我们将比较每个实体的选定属性的值。 In memory, we just call selector(other) and we're done.在内存中,我们只需调用selector(other)就完成了。 Since we have to deal with an expression now, we need to compile the expression first.既然我们现在要处理一个表达式,我们需要先编译这个表达式。 That's fairly simple.这相当简单。

The next step is to build an expression that represents selector(item) == otherValue .下一步是构建一个表示selector(item) == otherValue Fortunately, we don't need to replace the parameter since we're not trying to fold two separate lambdas into the same expression.幸运的是,我们不需要替换参数,因为我们不会尝试将两个单独的 lambda 折叠到同一个表达式中。 We can simply reuse selector 's first parameter.我们可以简单地重用selector的第一个参数。 We then call Expression.Invoke with the selector and its parameter, which will be translated into the column reference in the SQL.然后我们使用选择器及其参数调用Expression.Invoke ,它将被转换为 SQL 中的列引用。

Finally, we build the lambda that we can pass along to the built-in Any method.最后,我们构建了可以传递给内置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);
}

In my own testing, using SQL Profiler to confirm, I was able to get the condition to evaluate in the SQL query.在我自己的测试中,使用 SQL Profiler 进行确认,我能够在 SQL 查询中获得要评估的条件。 After changing the property selection expression, the query changed accordingly.更改属性选择表达式后,查询也相应更改。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM