Вызов лямбда-выражений в деревьях выражений

#c# #lambda #expression-trees #linq-expressions

#c# #лямбда #деревья выражений #linq-выражения

Вопрос:

У меня есть класс SelectionCriteria, который я использую для построения выражений запросов Entity Framework на основе PredicateBuilder. В своих пределах он работает нормально. Я хотел бы расширить его, чтобы он мог запрашивать, содержит ли поле подстроку. Моя проблема в том, что я не вижу, как создать необходимый объект выражения.

Мой фактический класс поддерживает and , or и not, но они не имеют отношения к моему вопросу. Итак, я упростил свой пример кода для обработки только одной двоичной операции:

 public class SelectionCriteria
{
    public SelectionComparison selectionComparison { get; set; }
    public string fieldName { get; set; }
    public object fieldValue { get; set; }

    public Expression<Func<T, bool>> constructSinglePredicate<T>()
    {
        var type = typeof(T);

        if (type.GetProperty(this.fieldName) == null amp;amp; type.GetField(this.fieldName) == null)
            throw new MissingMemberException(type.Name, this.fieldName);

        ExpressionType operation;
        if (!operationMap.TryGetValue(this.selectionComparison, out operation))
            throw new ArgumentOutOfRangeException("selectionComparison", this.selectionComparison, "Invalid filter operation");

        var parameter = Expression.Parameter(type);
        var member = Expression.PropertyOrField(parameter, this.fieldName);
        var value = (this.fieldValue == null) ? Expression.Constant(null) : Expression.Constant(this.fieldValue, this.fieldValue.GetType());

        try
        {
            var converted = (value.Type != member.Type)
                ? (Expression) Expression.Convert(value, member.Type)
                : (Expression) value;

            var comparison = Expression.MakeBinary(operation, member, converted);

            var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);

            return lambda;
        }
        catch (Exception)
        {
            throw new InvalidOperationException(
                String.Format("Cannot convert value "{0}" of type "{1}" to field "{2}" of type "{3}"", this.fieldValue,
                    value.Type, this.fieldName, member.Type));
        }
    }

    private static Dictionary<SelectionComparison, ExpressionType> operationMap =
        new Dictionary<SelectionComparison, ExpressionType>
        {
            { SelectionComparison.Equal, ExpressionType.Equal },
            { SelectionComparison.GreaterThan, ExpressionType.GreaterThan },
        };
}

public enum SelectionComparison
{
    Equal,
    GreaterThan,
    Contains,
};
 

Использование простое:

 var criteria = new SelectionCriteria
{
    selectionComparison = SelectionComparison.GreaterThan,
    fieldName = "worktobegindate",
    fieldValue = DateTime.Now.AddDays(-2)
};

var predicate = criteria .constructPredicate<job>();
var jobs = myDbContext.jobs.Where(predicate);
 

Итак, моя проблема — мне нужен метод, подобный моему constructSinglePredictate() выше, который возвращает выражение> и применяет a .Содержит(

Тривиально применить Contains() к строке, учитывая строку для ее сравнения, но мне трудно понять, как сделать то же самое в выражении.

Идеи?

Ответ №1:

Как обычно, я думал о вещах неправильно. Мне не нужен лямбда, который вызывает метод в строке, мне нужно выражение MethodExpression (здесь извлечено из словаря methodMap):

 public Expression<Func<T, bool>> constructMethodCallPredicate<T>()
{
    var type = typeof(T);

    if (type.GetProperty(this.fieldName) == null amp;amp; type.GetField(this.fieldName) == null)
        throw new MissingMemberException(type.Name, this.fieldName);

    MethodInfo method;
    if (!methodMap.TryGetValue(this.selectionComparison, out method))
        throw new ArgumentOutOfRangeException("selectionComparison", this.selectionComparison, "Invalid filter operation");

    var parameter = Expression.Parameter(type);
    var member = Expression.PropertyOrField(parameter, this.fieldName);
    var value = (this.fieldValue == null) ? Expression.Constant(null) : Expression.Constant(this.fieldValue, this.fieldValue.GetType());

    try
    {
        var converted = (value.Type != member.Type)
            ? (Expression)Expression.Convert(value, member.Type)
            : (Expression)value;

        var methodExpression = Expression.Call(member, method, converted);

        var lambda = Expression.Lambda<Func<T, bool>>(methodExpression, parameter);

        return lambda;
    }
    catch (Exception)
    {
        throw new InvalidOperationException(
            String.Format("Cannot convert value "{0}" of type "{1}" to field "{2}" of type "{3}"", this.fieldValue,
                value.Type, this.fieldName, member.Type));
    }
}

private static readonly Dictionary<SelectionComparison, MethodInfo> methodMap =
    new Dictionary<SelectionComparison, MethodInfo>
    {
        { SelectionComparison.Contains, typeof(string).GetMethod("Contains", new[] { typeof(string) }) },
        { SelectionComparison.StartsWith, typeof(string).GetMethod("StartsWith", new[] { typeof(string) }) },
        { SelectionComparison.EndsWith, typeof(string).GetMethod("EndsWith", new[] { typeof(string) }) },
    };

public enum SelectionComparison
{
    Equal,
    NotEqual,
    LessThan,
    LessThanOrEqual,
    GreaterThan,
    GreaterThanOrEqual,
    Contains,
    StartsWith,
    EndsWith,
};
 

Получение фактического сравнения «Как» для работы, используя SqlFunctions .PatIndex немного сложнее, PatIndex() возвращает значение int, и мне нужно обернуть его в выражение> 0, но оно также отлично работает.