Как перенести viewmodel в Expression <Func <T, bool >>? - PullRequest
0 голосов
/ 15 мая 2018

Совершение очень похожего вопроса ...

Мне нужно сгенерировать выражение из ViewModel для передачи в качестве предиката поиска для IQueryable.Where. Мне нужно иметь возможность включать / исключать параметры запроса на основе того, что предоставлено пользователем. Пример:

public class StoresFilter
{
    public int[] Ids { get; set; }

    [StringLength(150)]
    public string Name { get; set; }

    [StringLength(5)]
    public string Abbreviation { get; set; }

    [Display(Name = "Show all")]
    public bool ShowAll { get; set; } = true;

    public Expression<Func<Store, bool>> ToExpression()
    {
        List<Expression<Func<Store, bool>>> expressions = new List<Expression<Func<Store, bool>>>();

        if (Ids != null && Ids.Length > 0)
        {
            expressions.Add(x => Ids.Contains(x.Id));
        }
        if (Name.HasValue())
        {
            expressions.Add(x => x.Name.Contains(Name));
        }
        if (Abbreviation.HasValue())
        {
            expressions.Add(x => x.Abbreviation.Contains(Abbreviation));
        }
        if (!ShowAll)
        {
            expressions.Add(x => x.Enabled == true);
        }
        if (expressions.Count == 0)
        {
            return x => true;
        }

        // how to combine list of expressions into composite expression???
        return compositeExpression;
    }
}

Есть ли простой способ построить составное выражение из списка выражений? Или мне нужно пройти через процесс ручного построения выражения, используя ParameterExpression, Expression.AndAlso, ExpressionVisitor и т. Д.?

Ответы [ 2 ]

0 голосов
/ 15 мая 2018

Вы не должны строить и комбинировать Expression s, но вместо этого вы должны делать это через IQuerable<Store> через .Where цепочку.Кроме того, source.Expression будет содержать желаемое выражение:

public IQueryable<Store> ApplyFilter(IQueryable<Store> source)
{
    if (Ids != null && Ids.Length > 0)  
        source = source.Where(x => Ids.Contains(x.Id)); 

    if (Name.HasValue())    
        source = source.Where(x => x.Name.Contains(Name));  

    if (Abbreviation.HasValue())    
        source = source.Where(x => x.Abbreviation.Contains(Abbreviation));  

    if (!ShowAll)   
        source = source.Where(x => x.Enabled == true);      

    //or return source.Expression as you wanted
    return source;
}

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

var filter = new StoresFilter { Name = "Market" };
var filteredStores = filter.ApplyFilter(context.Stores).ToList();
0 голосов
/ 15 мая 2018
void Main()
{
    var store = new Store
    {
      Id = 1,
      Abbreviation = "ABC",
      Enabled = true,
      Name = "DEF"
    };

   var filter =  new Filter<Store>
   {
    Ids = new HashSet<int>(new [] {1,2,3,4}),
    Abbreviation = "GFABC",
    Enabled = true,
    Name = "SDEFGH",
    ShowAll = false
   }

   var expression = filter.ToExpression(store);

   var parameterType = Expression.Parameter(typeof(Store), "obj");

   // Generate Func from the Expression Tree
   Func<Store,bool> func = Expression.Lambda<Func<Store,bool>>(expression,parameterType).Compile();
}

public class Store
{
    public int Id {get; set;}

    public string Name {get; set;}

    public string Abbreviation { get; set; }

    public bool Enabled { get; set; }   
}

public class Filter<T> where T : Store
{
    public HashSet<int> Ids { get; set; }

    public string Name { get; set; }

    public string Abbreviation { get; set; }

    public bool Enabled {get; set;}

    public bool ShowAll { get; set; } = true;

    public Expression ToExpression(T data)
    {
        var parameterType = Expression.Parameter(typeof(T), "obj");

        var expressionList = new List<Expression>();

        if (Ids != null && Ids.Count > 0)
        {
            MemberExpression idExpressionColumn = Expression.Property(parameterType, "Id");

            ConstantExpression idConstantExpression = Expression.Constant(data.Id, typeof(int));

            MethodInfo filtersMethodInfo = typeof(HashsetExtensions).GetMethod("Contains", new[] { typeof(HashSet<int>), typeof(int) });

            var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);

            expressionList.Add(methodCallExpression);
        }
        if (!string.IsNullOrEmpty(Name))
        {
            MemberExpression idExpressionColumn = Expression.Property(parameterType, "Name");

            ConstantExpression idConstantExpression = Expression.Constant(data.Name, typeof(string));

            MethodInfo filtersMethodInfo = typeof(StringExtensions).GetMethod("Contains", new[] { typeof(string), typeof(string) });

            var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);

            expressionList.Add(methodCallExpression);
        }
        if (!string.IsNullOrEmpty(Abbreviation))
        {
            MemberExpression idExpressionColumn = Expression.Property(parameterType, "Abbreviation");

            ConstantExpression idConstantExpression = Expression.Constant(data.Abbreviation, typeof(string));

            MethodInfo filtersMethodInfo = typeof(StringExtensions).GetMethod("Contains", new[] { typeof(string), typeof(string) });

            var methodCallExpression = Expression.Call(null, filtersMethodInfo, idExpressionColumn, idConstantExpression);

            expressionList.Add(methodCallExpression);
        }
        if (!ShowAll)
        {
            MemberExpression idExpressionColumn = Expression.Property(parameterType, "Enabled");

            var binaryExpression = Expression.Equal(idExpressionColumn, Expression.Constant(true, typeof(bool)));

            expressionList.Add(binaryExpression);
        }

        if (expressionList.Count == 0)
        {
            expressionList.Add(BinaryExpression.Constant(true));
        }

        // Aggregate List<Expression> data into single Expression

        var returnExpression = expressionList.Skip(1).Aggregate(expressionList.First(), (expr1,expr2) => Expression.And(expr1,expr2));      

        return returnExpression;

        // Generate Func<T,bool> - Expression.Lambda<Func<T,bool>>(returnExpression,parameterType).Compile();
    }

}

public static class StringExtensions
{
    public static bool Contains(this string source, string subString)
    {
        return source?.IndexOf(subString, StringComparison.OrdinalIgnoreCase) >= 0;
    }
}

public static class HashsetExtensions
{
    public static bool Contains(this HashSet<string> source, string subString)
    {
        return source.Contains(subString,StringComparer.OrdinalIgnoreCase);
    }
}

Как это работает?

  • Только в простых случаях равенства вы можете использовать BinaryExpression как Expression.Equal, Expression.GreaterThan, что показано для свойства как "ShowAll"
  • Для других случаев, таких как string / Array / List Contains, вам нужен метод расширения, который может принимать два типа и предоставлять результат. Отдельный Contains для строки, чтобы сделать его регистр нейтральным. Также для коллекции Hashset есть лучший выбор, он имеет O (1) сложность по времени, в отличие от O (N) для массива
  • Мы используем MethodCallExpression для вызова методов расширения
  • Наконец, мы объединяем все выражения, которые можно скомпилировать для создания Func<T,bool>
  • Если вам нужно что-то вроде x => true, тогда достаточно BinaryExpression.Constant(true)
  • Я предоставил пример реализации, используя класс Store, который вы определили
...