2013-02-12 4 views
1

Не уверен, что это вполне возможно; Я нашел кое-что о создании выражений и предикатов, но до сих пор ничего не позволяет запускать произвольные запросы, не зная их заранее.Динамически генерировать предикат во время выполнения

В принципе, у меня есть коллекция объектов из большой базы данных SQL, и я создаю веб-страницу (ASP.NET MVC 4), чтобы позволить пользователю отображать и фильтровать эти объекты. Запросы, которые будут вводить пользователи, будут различаться по сложности. Самый простой и опрятный способ позволить им вводить эти запросы - это то, как плагин Visual Studio TFS позволяет вам искать рабочие элементы: таблицу условий, в которой вы можете продолжать добавлять строки. Вы выбираете «и» или «или» условие объединения, а затем выберите поле, введите значение, и выбрать, хотите ли вы то, что делает или не соответствует его:

1. show items where [Field] [is|is not] [value] 
2.   [and|or] [Field] [is|is not] [value] 
3.   [and|or] [Field] [is|is not] [value] 
etc... 

Что это самый простой способ, чтобы включить что во что-то LINQ-ish, что я могу вставить .ToList() на конце? Единственное решение, которое я придумал до сих пор, связано с довольно большим и уродливым блоком переключения с футлярами для соответствия различным полям и привязке к .Where(), но чтобы пользователь мог выбрать «или» для условия, тогда я в конечном итоге делать что-то вроде этого:

  • Хотя условие AND:
    • использовать большой переключатель, чтобы соответствовать поле
    • query = query.Where(ThisField == value);
  • Когда вы нажмете условие, OR:
    • добавить текущие результаты во временный список
    • нового запроса от полного нефильтрованного списка
    • использовать большой переключатель, чтобы соответствовать полю
    • query = fullList.Where(ThisField == value);
    • , как и прежде
  • При запуске из условий , добавьте текущий набор результатов во временный список, который вы использовали все время, и верните этот список.

Это кажется менее элегантным, чем хотелось бы.

+0

Вы хотите запросить базу данных (используя что-то вроде LINQ to SQL) или список в памяти? – svick

+0

База данных, желательно. Я могу загрузить нагрузку в память и работать над ними там, если это проще, но я бы предпочел не потому, что база данных довольно значительна. – anaximander

ответ

6

Вы можете сделать это следующим образом:

class Program 
{ 
    public enum Operator 
    { 
     And, 
     Or 
    } 

    public class Condition 
    { 
     public Operator Operator { get; set; } 
     public string FieldName { get; set; } 
     public object Value { get; set; } 
    } 

    public class DatabaseRow 
    { 
     public int A { get; set; } 
     public string B { get; set; } 
    } 

    static void Main(string[] args) 
    { 
     var conditions = new List<Condition> 
     { 
      new Condition { Operator = Operator.And, FieldName = "A", Value = 1 }, 
      new Condition { Operator = Operator.And, FieldName = "B", Value = "Asger" }, 
      new Condition { Operator = Operator.Or, FieldName = "A", Value = 2 }, 
     }; 

     var parameter = Expression.Parameter(typeof (DatabaseRow), "x"); 
     var currentExpr = MakeExpression(conditions.First(), parameter); 
     foreach (var condition in conditions.Skip(1)) 
     { 
      var nextExpr = MakeExpression(condition, parameter); 
      switch (condition.Operator) 
      { 
       case Operator.And: 
        currentExpr = Expression.And(currentExpr, nextExpr); 
        break; 
       case Operator.Or: 
        currentExpr = Expression.Or(currentExpr, nextExpr); 
        break; 
       default: 
        throw new ArgumentOutOfRangeException(); 
      } 
     } 

     var predicate = Expression.Lambda<Func<DatabaseRow, bool>>(currentExpr, parameter).Compile(); 

     var input = new[] 
     { 
      new DatabaseRow {A = 1, B = "Asger"}, 
      new DatabaseRow {A = 2, B = "Hans"}, 
      new DatabaseRow {A = 3, B = "Grethe"} 
     }; 

     var results = input.Where(predicate).ToList(); 
    } 

    static BinaryExpression MakeExpression(Condition condition, ParameterExpression parameter) 
    { 
     return Expression.Equal(
      Expression.MakeMemberAccess(parameter, typeof (DatabaseRow).GetMember(condition.FieldName)[0]), 
      Expression.Constant(condition.Value)); 
    } 
} 

Это предполагает, что вы есть класс как модель строки базы данных с правильными типами. Затем вы можете проанализировать свои условия в списке типизированного состояния, показанного выше, с помощью регулярного выражения, а предоставленный код может преобразовать его в дерево выражений.Полученное выражение можно либо скомпилировать, либо запустить (как показано), либо преобразовать в SQL (просто набив предикат в IQueryable.Where вместо).

2

Вы можете использовать PredicateBuilder from LINQKit, чтобы сделать это. Используя методы расширения и Or(), вы можете построить для вашего запроса expression tree. Затем вы можете использовать это дерево выражений как условие вашего Where(). Вам также необходимо либо позвонить AsExpandable(), либо ваш query, либо позвонить Expand() на созданное выражение.

+0

Проблема в том, что программное обеспечение, которое я добавляю в это, является рабочим проектом, а добавление материала - это боль ... если возможно, предпочтительнее использовать это, используя только стандартные библиотеки .NET. Если нет ... ну, я всегда могу отправить запрос в более высокие версии. – anaximander

+0

@anaximander Ну, у вас есть полный исходный код на странице - это действительно всего лишь 30 строк кода. Я бы также предложил некоторые изменения - довольно удобно сделать 'True' и' False' метод расширения на 'IQueryable', например, чтобы получить правильный тип с помощью inferrence (так что вы можете ввести' input.True() ' вместо 'PredicateBuilder.True ()'. И, конечно же, это единственный способ использовать анонимные типы в предикате :) – Luaan